Delivery API
Read published jobs, filters, your career site and a sitemap, and send applications, talent pool sign-ups, job views and candidate portal calls, with a public key.
Reference7 min read
The delivery API gives your website or app everything your hosted career pages show. The Careers SDK and the SDKs for PHP, Swift and Kotlin wrap it, but any language can call it. It is in every plan.
Base address: https://api.bifrostorbit.com/careers/v1/, which is the same as https://app.bifrostorbit.com/careers/api/cdn/v1/. Staging is https://api-stage.bifrostorbit.com/careers/v1/.
GraphQL: the reads below are also one GraphQL endpoint, with the same keys.
Key: every call needs a public key as Authorization: Bearer careers_pk_..., X-Api-Key: careers_pk_... or ?key=careers_pk_....
Language: add locale=de (or lang=de) to any read. Text missing in that language comes in your main language, field by field. Responses say which language they used, and a job lists the fields that fell back in fallback.
Endpoints
/jobs{ data: Job[], meta: { locale, total, page, perPage } }/jobs/{slug}{ data: JobDetail }/jobs/{slug}/applications{ data: { applicationId, updated } }. 201 when new, 200 when it updated an earlier one/jobs/{slug}/track/filters/site/sitemap/talent-pool{ data: { joined: true } }, 201| Method and path | Returns |
|---|---|
GET/jobs | Live jobs, newest first: { data: Job[], meta: { locale, total, page, perPage } } |
GET/jobs/{slug} | One live job with its description, form, privacy notice and JobPosting data: { data: JobDetail } |
POST/jobs/{slug}/applications | Sends an application: { data: { applicationId, updated } }. 201 when new, 200 when it updated an earlier one |
POST/jobs/{slug}/track | Counts a view or an apply start for the reports. 204 |
GET/filters | Departments, locations, job types and workplaces that have live jobs, with counts |
GET/site | Name, introduction, logo, colours, languages, contact email, hosted address, the current privacy notice, the Insights Orbit site key and the texting channels |
GET/sitemap | Every live job with its address in each language |
POST/talent-pool | Keeps someone's details for future jobs: { data: { joined: true } }, 201 |
A key with Read internal-only jobs also sees internal jobs in /jobs, /jobs/{slug} and /filters.
Filtering jobs
GET/jobs takes department (a department id), location (a location id), remote (onsite, hybrid or remote), type (fullTime, partTime, contract, temporary, internship, apprenticeship or workingStudent), q (words in the title, summary, department or location), page and perPage (50 by default, up to 100). The ids come from GET/filters.
A job
Each job has id, slug, url (on your website if you set job addresses, otherwise on your hosted pages), title, summary, department, locations (name, street, postal code, city, region, country and workplace), employmentType with a label, remote with a label, salary (only when you chose to show it, with a ready-made text such as "42.000 NOK – 48.000 NOK pro Monat"), publishedAt, closesAt and internal.
GET/jobs/{slug} adds locale, locales, fallback, description (cleaned HTML), seo, form (the standard fields and the questions to ask, without any screening rules), privacy (version and text) and jobPosting, the schema.org data to put in a <script type="application/ld+json"> for Google for Jobs. A closed, scheduled or unpublished job is 404.
Sending an application
Send multipart/form-data with:
data: JSON withfirstName,lastName,email, the standard fields the form asks for (phone,coverLetter,linkedin,location,salaryExpectation,startDate),answerskeyed by question id,privacyVersion(from the job),privacyAccepted: true, and optionallytalentPool,texting(["sms"],["whatsapp"]or both),locale,source(such asnorthwilds.com),utm({ source, medium, campaign }),referrerandreferral(thereffrom a colleague's referral link)cv: the CV, a PDF or Word file up to 10 MBfile:<questionId>: a file for each file question
Without files, a JSON body works too. Choice answers are option ids; yes or no answers are "yes" or "no". A second application to the same job from the same email updates the first.
curl -X POST "https://api.bifrostorbit.com/careers/v1/jobs/mountain-guide-lofoten/applications" \ -H "Authorization: Bearer careers_pk_..." \ -F 'data={"firstName":"Kari","lastName":"Nordmann","email":"kari@northwilds.com","privacyVersion":3,"privacyAccepted":true,"source":"northwilds.com"}' \ -F "cv=@kari-nordmann.pdf"If the privacy notice changed since the page was opened, the call fails with a request to reload, so nobody agrees to text they didn't read.
Talent pool and job alerts
POST/talent-pool takes JSON with firstName, lastName (optional), email, privacyVersion, privacyAccepted: true, locale, jobAlerts (true to get an email when a new job opens) and departmentIds (up to 30, to hear only about jobs in those departments).
Counting views
POST/jobs/{slug}/track takes { "kind": "view" } when a job page shows, or { "kind": "start" } when someone starts the form, with an optional source and utmSource. It stores no cookies and nothing about the person, and ignores bots. The SDKs' trackJob calls it.
Candidate portal
The portal calls take the public key too, and the candidate's session in X-Careers-Session. They are never cached.
/portal/code{ email, locale }. Emails a six-digit code if the address has applied. Always answers { data: { sent: true } }/portal/session{ email, code }. Answers { data: { token, expiresIn } }; the token lasts two hours/portal/session/portal/me{ data: null } when signed out/portal/applications/{id}/withdraw{ reason }. Withdraws an open application/portal/applications/{id}/documentsfile: a PDF or Word document up to 10 MB, at most 20 files an application/portal/offers/portal/offers/{id}/answer{ action: "accept", name, consent: true, documentHash } signs an offer; { action: "decline", reason } declines it/portal/offers/{id}/pdf/portal/export/portal/requests/portal/talent-pool/stay, /leave/portal/texting/stop{ channel: "sms" } or "whatsapp". Stops text messages on that channel| Call | What it does |
|---|---|
POST/portal/code | { email, locale }. Emails a six-digit code if the address has applied. Always answers { data: { sent: true } } |
POST/portal/session | { email, code }. Answers { data: { token, expiresIn } }; the token lasts two hours |
DELETE/portal/session | Signs out |
GET/portal/me | The candidate's applications, offers, talent pool and requests, or { data: null } when signed out |
POST/portal/applications/{id}/withdraw | { reason }. Withdraws an open application |
POST/portal/applications/{id}/documents | Multipart with file: a PDF or Word document up to 10 MB, at most 20 files an application |
POST/portal/offers | Notes that the candidate has seen their open offers |
POST/portal/offers/{id}/answer | { action: "accept", name, consent: true, documentHash } signs an offer; { action: "decline", reason } declines it |
GET/portal/offers/{id}/pdf | The offer as a PDF, signed once accepted |
GET/portal/export | The candidate's data as a JSON download, with links to their files that work for 15 minutes |
POST/portal/requests | { kind: "erasure" } or { kind: "correction", detail }. Opens a data request |
POST/portal/talent-pool/stay, /leave | Renews or withdraws talent pool consent |
POST/portal/texting/stop | { channel: "sms" } or "whatsapp". Stops text messages on that channel |
Calls that change something answer with the whole portal again, as GET/portal/me does. A code lasts ten minutes and allows five tries, and a new one can be sent every 45 seconds. Sign-in calls have their own, tighter rate limits per network.
Errors
Errors come as { error: { code, message } }, with messages candidates can read in the language of the request:
invalidunauthorizedforbiddennot_foundsubscription_inactiveinvalidissuesrate_limitedserver_error| Status | Code | When |
|---|---|---|
| 400 | invalid | A field is wrong, a file is too large or not a PDF or Word file, or the privacy notice changed |
| 401 | unauthorized | No key, or a key that is wrong, revoked or expired; or a portal session that is missing or has expired |
| 403 | forbidden | A browser call from a website not on your allowed list |
| 404 | not_found | No such job, or it isn't live |
| 402 | subscription_inactive | Careers Orbit is paused because the trial or plan ended. Every call except the candidate portal gets it; show your own "temporarily unavailable" message |
| 422 | invalid | A required field is missing, with the details in issues |
| 429 | rate_limited | Too many requests from one network in a short time |
| 500 | server_error | Something failed on our side. Try again |
See Troubleshooting for each message and its fix.
Limits
- Reads: about 120 requests a minute for each key and network.
- Applications and talent pool sign-ups: a burst of six, then two a minute, for each network.
- Views and starts: 30 a minute for each network.
Browsers and caching
Any website may call the API from a browser unless you list allowed websites under Career site › Address and website; calls from servers are always allowed. Reads carry Cache-Control: public, max-age=60, stale-while-revalidate=300. Set the website webhook to refresh sooner.
Was this page helpful?