Skip to content

GraphQL

Read your career site, jobs, one job, filters and the sitemap in one request, with the schema, examples, errors and limits.

Reference6 min read

GraphQL reads the same jobs as the delivery API, with the same public keys. Use it when one page needs several things at once, such as the site, the job list and the filters, in a single request.

Production
Address
https://api.bifrostorbit.com/careers/graphql
Staging
Address
https://api-stage.bifrostorbit.com/careers/graphql

https://app.bifrostorbit.com/careers/api/cdn/graphql is the same endpoint.

GraphQL only reads. Applications, talent pool sign-ups and the candidate portal stay in the delivery API, and the management API has no GraphQL version.

Requests

Send a public key as Authorization: Bearer <key> or x-api-key: <key>, and POST a JSON body:

query
Type
string
Means
The GraphQL document. Required.
variables
Type
object
Means
Values for the query's variables
operationName
Type
string
Means
Which operation to run when the document has several
Shell
curl 'https://api.bifrostorbit.com/careers/graphql' \  -H 'Authorization: Bearer careers_pk_…' \  -H 'Content-Type: application/json' \  -d '{"query":"query ($slug: String) { job(slug: $slug, locale: \"de\") { title url } }","variables":{"slug":"mountain-guide"}}'

GET works too, with query, variables (as JSON text) and operationName in the query string. URL-encode them:

Shell
curl -G 'https://api.bifrostorbit.com/careers/graphql' \  -H 'Authorization: Bearer careers_pk_…' \  --data-urlencode 'query={ jobs { total items { slug title } } }'

Answers are JSON with data, and errors when something went wrong:

JSON
{ "data": { "job": { "title": "Bergführer", "url": "https://northwilds.com/de/careers/mountain-guide/" } } }

Browsers may call it like the delivery API: from any website, unless you list allowed websites under Career site › Address and website.

Queries

Every query that reads text takes locale, such as de. Leave it out for your main language. Text missing in that language comes in your main language, field by field, as in REST.

site
Arguments
locale
Returns
Site!. Name, introduction, logo, colours, languages, hosted address and the current privacy notice.
jobs
Arguments
locale, department, location, remote, type, q, page: Int, perPage: Int
Returns
JobList!. Live jobs, newest first. The filters work as in GET/jobs. perPage defaults to 50, at most 100.
job
Arguments
slug: String or id: String, locale
Returns
Job. One live job with its description, form, privacy notice and JobPosting data.
filters
Arguments
locale
Returns
Filters!. Departments, locations, job types and workplaces that have live jobs, with counts.
sitemap
Returns
[SitemapEntry!]!. Every live job with its address in each language.

A key that reads internal jobs sees them in jobs, job and filters, as in REST. When a trial or plan has ended and its grace period is over, the queries answer with no jobs, as the REST endpoints do.

Types

JSON holds data without a fixed shape, such as jobPosting and the form's fields, and cannot be selected into. DateTime is an ISO 8601 timestamp.

Site
Fields
name, slug, intro, logoUrl, colours { brand accent }, locale, languages, defaultLanguage, contactEmail, websiteUrl, hostedUrl, privacy { version text }, insights { site }, texting { sms whatsapp }
JobList
Fields
items: [JobSummary!]!, locale, total, page, perPage, hasMore
JobSummary
Fields
id, slug, url, title, summary, department { id name }, locations, employmentType, employmentTypeLabel, remote, remoteLabel, salary, publishedAt, closesAt, internal
Job
Fields
Everything in JobSummary, and locale, locales, fallback, description, seo { title description }, cmsEntryId, form { fields questions }, privacy, jobPosting
Location
Fields
id, name, street, postalCode, city, region, country, remote
Salary
Fields
min, max, currency, period, text
Question
Fields
id, kind, label, help, required, options { id label }
Filters
Fields
departments, locations (with city and country), employmentTypes, remote, each { value label count }
SitemapEntry
Fields
url, slug, updatedAt, alternates

Job lists leave the description out on purpose. Ask job for one.

Examples

A job list with its filters

graphql
query Jobs($locale: String, $department: String) {  site(locale: $locale) {    name    intro    logoUrl  }  jobs(locale: $locale, department: $department, perPage: 20) {    items {      slug      title      url      department {        name      }      locations {        city      }    }    total    hasMore  }  filters(locale: $locale) {    departments {      value      label      count    }  }}
JSON
{ "locale": "de" }

A job page

graphql
query Job($slug: String!) {  job(slug: $slug, locale: "de") {    title    description    seo {      title      description    }    form {      fields      questions {        id        kind        label        required        options {          id          label        }      }    }    privacy {      version      text    }    jobPosting  }}

Put jobPosting in a <script type="application/ld+json"> for Google for Jobs, and send privacy.version with the application.

Errors

Errors come in errors, each with a message, a code under extensions, and the path and locations of the field that failed when there is one. The codes are the same as in REST:

JSON
{  "data": { "job": null },  "errors": [    {      "message": "Job was not found",      "locations": [{ "line": 1, "column": 3 }],      "path": ["job"],      "extensions": { "code": "not_found" }    }  ]}
400
When
No query, variables that are not a JSON object, a query that does not parse or does not match the schema (bad_request), one over the limits (query_too_deep, query_too_complex), or job without a slug or id (invalid)
401
When
No key, or a key that is wrong, revoked or expired (unauthorized)
403
When
A browser call from a website not on your allowed list (forbidden)
404
When
No such job, or it isn't live (not_found)
413
When
A query longer than 20,000 characters (query_too_large)
429
When
Too many requests (rate_limited). data is left out.
422
When
An argument with a wrong value (invalid)
500
When
Something failed on our side (server_error). Try again.
200
When
Success
  • When job fails, it is null and the other fields still answer.
  • site, jobs, filters and sitemap never answer null, so when one of them fails the whole data is null.
  • When several fields fail, the HTTP status comes from the first error.

Limits

  • Each request counts once against the same rate limit as the delivery API's reads, per key and network.
  • A query may be at most 20,000 characters long, nest at most 10 levels and ask for at most 300 fields. Each alias and each use of a fragment counts.
  • Introspection is on and needs no key, so GraphQL tools can read the schema from the address. Introspection fields do not count towards the limits.
  • Persisted queries are not supported. Send the full query each time.

Caching

Every GraphQL answer has Cache-Control: no-store and Vary: Authorization, so nothing between your website and the API keeps it. Cache in your website instead, and refresh on the website webhook. For pages a CDN should cache, the REST endpoints carry cache headers.

Was this page helpful?

Read next