Skip to content

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

GET/jobs
Returns
Live jobs, newest first: { data: Job[], meta: { locale, total, page, perPage } }
GET/jobs/{slug}
Returns
One live job with its description, form, privacy notice and JobPosting data: { data: JobDetail }
POST/jobs/{slug}/applications
Returns
Sends an application: { data: { applicationId, updated } }. 201 when new, 200 when it updated an earlier one
POST/jobs/{slug}/track
Returns
Counts a view or an apply start for the reports. 204
GET/filters
Returns
Departments, locations, job types and workplaces that have live jobs, with counts
GET/site
Returns
Name, introduction, logo, colours, languages, contact email, hosted address, the current privacy notice, the Insights Orbit site key and the texting channels
GET/sitemap
Returns
Every live job with its address in each language
POST/talent-pool
Returns
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 with firstName, lastName, email, the standard fields the form asks for (phone, coverLetter, linkedin, location, salaryExpectation, startDate), answers keyed by question id, privacyVersion (from the job), privacyAccepted: true, and optionally talentPool, texting (["sms"], ["whatsapp"] or both), locale, source (such as northwilds.com), utm ({ source, medium, campaign }), referrer and referral (the ref from a colleague's referral link)
  • cv: the CV, a PDF or Word file up to 10 MB
  • file:<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.

Shell
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.

POST/portal/code
What it does
{ email, locale }. Emails a six-digit code if the address has applied. Always answers { data: { sent: true } }
POST/portal/session
What it does
{ email, code }. Answers { data: { token, expiresIn } }; the token lasts two hours
DELETE/portal/session
What it does
Signs out
GET/portal/me
What it does
The candidate's applications, offers, talent pool and requests, or { data: null } when signed out
POST/portal/applications/{id}/withdraw
What it does
{ reason }. Withdraws an open application
POST/portal/applications/{id}/documents
What it does
Multipart with file: a PDF or Word document up to 10 MB, at most 20 files an application
POST/portal/offers
What it does
Notes that the candidate has seen their open offers
POST/portal/offers/{id}/answer
What it does
{ action: "accept", name, consent: true, documentHash } signs an offer; { action: "decline", reason } declines it
GET/portal/offers/{id}/pdf
What it does
The offer as a PDF, signed once accepted
GET/portal/export
What it does
The candidate's data as a JSON download, with links to their files that work for 15 minutes
POST/portal/requests
What it does
{ kind: "erasure" } or { kind: "correction", detail }. Opens a data request
POST/portal/talent-pool/stay, /leave
What it does
Renews or withdraws talent pool consent
POST/portal/texting/stop
What it does
{ 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:

400
Code
invalid
When
A field is wrong, a file is too large or not a PDF or Word file, or the privacy notice changed
401
Code
unauthorized
When
No key, or a key that is wrong, revoked or expired; or a portal session that is missing or has expired
403
Code
forbidden
When
A browser call from a website not on your allowed list
404
Code
not_found
When
No such job, or it isn't live
402
Code
subscription_inactive
When
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
Code
invalid
When
A required field is missing, with the details in issues
429
Code
rate_limited
When
Too many requests from one network in a short time
500
Code
server_error
When
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?

Read next