Integrating your job platform with Taloo
What the API can do, how the calls fit together, what to build, and what we need from you to go live.
Overview
Recruiters publish vacancies in Taloo. Your platform shows them, collects applications, and reports back how many people looked at each job. Concretely you can:
- Show vacancies and keep them in sync when they change or go offline.
- Accept applications with CV files, delivered straight to the recruiter.
- Report reach: page views and application-form opens show up in the recruiter's statistics.
- Preview drafts in your layout before the recruiter publishes.
Steps 1–4 are required for every integration. Without step 3 the recruiter's statistics for your platform are empty. Step 5 is recommended.
Build against demo.api.taloo.ch with seeded vacancies and a shared demo key. Nothing to set up on our side.
Send us your channel settings. We create your channel on demo for testing, then on production.
How it fits together
Your platform is a channel in Taloo. Three lanes: the visitor's or recruiter's browser, your server, and Taloo. Every call into Taloo comes from your server with your API key. The only exception is the preview, where the token is the credential. Click a step to pause.
Stage 1 · Explore on demo
| Environment | API | Use for |
|---|---|---|
| Demo | https://demo.api.taloo.ch | Exploring and building. Seeded vacancies, shared demo key. Resets every night at 01:00 CET: vacancies, applications and events are recreated, keys and channels stay. |
| Production | https://api.taloo.ch | Go-live only, with your own key. |
The Example Platform on demo is a complete reference integration: job list, job page with tracking, application form and live preview. Compare your implementation against it.
Rules for every call
- The API key stays on your server. Never ship it in browser JavaScript or an app bundle. Browser events go through your own backend.
- Serve pages from your own database. Each key has a per-minute budget; over it you get
429withRetry-After. Don't call the API per page view. - Reference: GraphQL Playground (vacancies, preview) and Swagger (tracking, applications).
Kick-start with an AI coding agent
Paste this into Claude Code, Cursor or whatever you use. It points the agent at this guide and the machine-readable specs, and lists the steps and rules in the order we'd build them. Fill in the placeholders in angle brackets first.
Show the prompt
I'm integrating our job platform with the Taloo Public API. Taloo is an applicant tracking system: recruiters publish vacancies there, our platform shows them, collects applications and reports views back.
Read these first:
- Integration guide (what to build, in which order, the rules): https://demo.api.taloo.ch/guide
- OpenAPI spec for the REST endpoints (track-action, job-application): https://demo.api.taloo.ch/openapi.json
- GraphQL endpoint (vacancies, preview; introspection is open): https://demo.api.taloo.ch/v1/graphql
- Reference implementation to compare against: https://demo.debug.taloo.ch/example-platform
Our setup:
- Stack: <e.g. Laravel 12 + MySQL, or Next.js + Postgres>
- Job pages: <https://our-site.ch/jobs/:publicUuid>
- Environment variables: TALOO_API_URL=https://demo.api.taloo.ch and TALOO_API_KEY=<demo key from the Example Platform settings bar>. The key must only ever be used server-side.
Build in this order and stop after each step so I can review:
1. Taloo client module. Sends the x-channel-api-key header on every call, server-side only. Handles 429 by honouring Retry-After.
2. Vacancy sync into our own database (pages are rendered from our DB, never from live API calls).
- Initial import paging through vacancies(first: 100, after: endCursor).
- Webhook endpoint receiving { "event": "publish" | "unpublish", "channelUuid", "publicVacancyUuid" }. Require our secret header, respond 2xx immediately, process in the background: publish = fetch vacancy(publicUuid) and upsert; unpublish = take offline.
- Scheduled job every 5 minutes using vacancies(changedSince: <last run>, sortBy: LAST_PUBLICATION_CHANGE_AT, sortOrder: ASC), plus a daily full import that takes offline anything missing from the list.
- Store every public id of a vacancy (the uuid field is a list, first entry is current). Old ids redirect to the current job URL.
3. Job pages. Sanitize descriptions[].text (HTML) with an allowlist. Show only location, zipCode and jobLocationAddress to visitors; use searchZipCode, geoLocation, simpleLocation(s) and mongoLocation for search and filtering only.
4. Tracking via POST /v1/track-action, server-side, fire and forget:
- VACANCY_VIEWED on every job-page view.
- VACANCY_APPLICATION_FORM_OPENED once per page view, on first interaction with the application form (browser notifies our backend, backend forwards).
- Always send webOrigin = the visitor's full browser URL with all utm_* parameters intact (persist the entry URL's UTM parameters across the session), referrer = document.referrer, and the visitor's user agent.
5. Application form posting to our backend. Run CAPTCHA or honeypot there, then forward as multipart/form-data to POST /v1/job-application with up to 5 files (10 MB each), plus webOrigin and referrer. Required fields: firstName, lastName, sex, email.
6. Preview page at <https://our-site.ch/preview/:previewToken> using the previewVacancy(previewToken) query (no API key, may run in the browser) and the previewVacancyUpdates subscription for live refresh. noindex, not in listings, no tracking events.
Verify each step against the demo environment. Finish with the go-live checklist from the guide: tick what you verified, list what you couldn't.
1Authenticate Required
Send the key as x-channel-api-key on every request: GraphQL, REST and the GraphQL WebSocket. No logins, no token refresh.
curl -H "x-channel-api-key: <key>" -H "Content-Type: application/json" \
-d '{"query":"{ vacancies(first: 1) { totalCount } }"}' https://demo.api.taloo.ch/v1/graphql
totalCount equals the number of vacancies published on the channel.2Sync vacancies Required
Three mechanisms, used together:
a) Initial import
query Vacancies($after: String) {
vacancies(first: 100, after: $after) {
pageInfo { hasNextPage endCursor }
edges { node {
uuid # list; first entry is the current public id
title language jobPostingDate
location zipCode jobLocationAddress { street zip location }
workloadText employmentType salaryText
descriptions { type title text }
contactPerson { firstName lastName email phone }
contactCompany { name }
searchZipCode geoLocation { lat lon }
} }
}
}
Repeat with after: endCursor while hasNextPage. Max page size 100. All fields: Playground.
b) Webhooks
Taloo POSTs JSON to your publish and unpublish URLs:
// new or updated: fetch vacancy(publicUuid) and upsert
{ "event": "publish", "channelUuid": "…", "publicVacancyUuid": "…" }
// take offline
{ "event": "unpublish", "channelUuid": "…", "publicVacancyUuid": "…" }
Respond 2xx quickly and do the work afterwards. Failures are retried 3 times over a few minutes.
Authorization: Bearer …); we send it with every call, you reject calls without it.c) Periodic sync
vacancies(first: 100, changedSince: "<start of previous run>",
sortBy: LAST_PUBLICATION_CHANGE_AT, sortOrder: ASC)
Run every few minutes to catch missed webhooks. changedSince only returns vacancies that are still live, so once a day do a full import and take offline whatever is missing from it.
Public ids
uuid is a list; the first entry is the current public id. Use it in your job URL. A republish creates a new id: you get unpublish for the old and publish for the new. vacancy(publicUuid) still resolves old ids, so redirect old URLs instead of returning 404.
Rendering
descriptions[].textis HTML. Sanitize with an allowlist.- Recruiters can hide the exact address. Show
location,zipCode,jobLocationAddress(empty when hidden). UsesearchZipCode,geoLocation,simpleLocation(s),mongoLocationfor search and filters only, never on the page. They are always filled.
3Track views and form opens Required
Two events, sent from your backend to POST /v1/track-action:
VACANCY_VIEWED | Every job-page view |
VACANCY_APPLICATION_FORM_OPENED | Once per page view, on first interaction with the form (e.g. focus) |
POST https://demo.api.taloo.ch/v1/track-action
{
"action": "VACANCY_VIEWED",
"vacancyPublicUuid": "<public id>",
"webOrigin": "https://your-site.ch/jobs/abc?utm_source=linkedin&utm_medium=social",
"referrer": "https://www.linkedin.com/",
"userAgent": "Mozilla/5.0 …"
}
webOrigin: the visitor's full browser URL with allutm_*parameters intact. This is how Taloo attributes the visit to LinkedIn, a newsletter or your own site. If visitors land on a list page first, keep the entry URL's UTM parameters (e.g. in the session) and send those.referrer:document.referrer.userAgent: the visitor's, not your server's; used for bot filtering.- Fire and forget; never let it delay the page. Browser-side events (form focus) call your backend, which forwards them.
Submitted applications are counted by step 4; no extra event needed.
?utm_source=test-guide and focus the form. Ask us to confirm both events arrived with source test-guide.4Accept applications Required
Your form posts to your backend; your backend runs spam protection (CAPTCHA, honeypot) and forwards to POST /v1/job-application as multipart/form-data. Taloo trusts what your server sends.
curl -X POST https://demo.api.taloo.ch/v1/job-application -H "x-channel-api-key: <key>" \
-F firstName=Anna -F lastName=Muster -F sex=FEMALE -F email=anna@example.com \
-F phone="+41 79 123 45 67" -F language=DE \
-F vacancyPublicUuid=<public id> \
-F webOrigin="https://your-site.ch/jobs/abc?utm_source=linkedin" \
-F referrer="https://www.linkedin.com/" \
-F files=@cv.pdf -F files=@letter.pdf
- Required:
firstName,lastName,sex,email. Up to 5 files, 10 MB each. webOriginandreferrer: same rules as step 3.- Spontaneous application: omit
vacancyPublicUuid. - Response
{ "success": true, "applicationId": "…" }; processing is asynchronous.
5Preview drafts Recommended
Taloo opens your preview URL with the draft's previewToken. The token is the credential, so this runs without an API key, in the browser if you like.
query { previewVacancy(previewToken: "…") { title location descriptions { type title text } } }
# optional live refresh: emits on every edit, then re-run the query
subscription { previewVacancyUpdates(previewToken: "…") }
Mark preview pages noindex, keep them out of listings, and don't send tracking events for them.
Stage 2 · Your channel settings
Send us these and we create your channel, first on demo, then on production. Placeholders are replaced by Taloo when building links. On demo your channel is part of the nightly seed, so it survives the reset with the same key and settings.
| Setting | Example | Used for |
|---|---|---|
| Platform name | KMU Jobs | Channel name recruiters see in Taloo |
| Website URL | https://your-site.ch | Link to your platform in Taloo |
| Icon URL | https://your-site.ch/favicon.svg | Small square icon (favicon-size, SVG or PNG) next to the channel name |
| Logo URL | https://your-site.ch/logo.svg | Full-size logo in larger views |
| Job page URL | https://your-site.ch/jobs/:publicUuid | Recruiters open and share the published job |
| Apply page URL | https://your-site.ch/jobs/:publicUuid/apply | Optional. Direct "apply" link if separate from the job page |
| Preview URL | https://your-site.ch/preview/:previewToken | Step 5 |
| Webhook URLs | https://your-site.ch/api/taloo/webhook | Step 2b. One URL for publish and unpublish, or two |
| Webhook secret header | Authorization: Bearer … | Sent with every webhook call |
| Technical contact | dev@your-site.ch | Who we reach when webhooks fail or the key rotates |
All URLs must be publicly reachable over HTTPS. The icon and logo are loaded directly by recruiters' browsers.
You get back
- Your channel's API key for demo, later for production
- Your
channelUuid, as it appears in webhook payloads - Your UTM source slug (e.g.
kmu-jobs): the name your platform gets in the recruiter's statistics
Go-live checklist
- ☐ API key only used server-side
- ☐ Import, webhooks and periodic sync work; unpublished vacancies disappear; old ids redirect
- ☐ Visitors see display location fields only; description HTML sanitized
- ☐ Both tracking events arrive with UTM parameters intact
- ☐ Test application with files arrives; form has spam protection
- ☐ Preview opens from Taloo
Once this passes on demo, we hand you the production key and switch the channel over.