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.
https://api.bifrostorbit.com/careers/graphqlhttps://api-stage.bifrostorbit.com/careers/graphql| Address | |
|---|---|
| Production | https://api.bifrostorbit.com/careers/graphql |
| Staging | 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:
queryvariablesoperationName| Property | Type | Means |
|---|---|---|
query | string | The GraphQL document. Required. |
variables | object | Values for the query's variables |
operationName | string | Which operation to run when the document has several |
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:
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:
{ "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.
sitelocaleSite!. Name, introduction, logo, colours, languages, hosted address and the current privacy notice.jobslocale, department, location, remote, type, q, page: Int, perPage: IntJobList!. Live jobs, newest first. The filters work as in GET/jobs. perPage defaults to 50, at most 100.jobslug: String or id: String, localeJob. One live job with its description, form, privacy notice and JobPosting data.filterslocaleFilters!. Departments, locations, job types and workplaces that have live jobs, with counts.sitemap[SitemapEntry!]!. Every live job with its address in each language.| Query | Arguments | Returns |
|---|---|---|
site | locale | Site!. Name, introduction, logo, colours, languages, hosted address and the current privacy notice. |
jobs | locale, department, location, remote, type, q, page: Int, perPage: Int | JobList!. Live jobs, newest first. The filters work as in GET/jobs. perPage defaults to 50, at most 100. |
job | slug: String or id: String, locale | Job. One live job with its description, form, privacy notice and JobPosting data. |
filters | locale | Filters!. Departments, locations, job types and workplaces that have live jobs, with counts. |
sitemap | [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.
Sitename, slug, intro, logoUrl, colours { brand accent }, locale, languages, defaultLanguage, contactEmail, websiteUrl, hostedUrl, privacy { version text }, insights { site }, texting { sms whatsapp }JobListitems: [JobSummary!]!, locale, total, page, perPage, hasMoreJobSummaryid, slug, url, title, summary, department { id name }, locations, employmentType, employmentTypeLabel, remote, remoteLabel, salary, publishedAt, closesAt, internalJobJobSummary, and locale, locales, fallback, description, seo { title description }, cmsEntryId, form { fields questions }, privacy, jobPostingLocationid, name, street, postalCode, city, region, country, remoteSalarymin, max, currency, period, textQuestionid, kind, label, help, required, options { id label }Filtersdepartments, locations (with city and country), employmentTypes, remote, each { value label count }SitemapEntryurl, slug, updatedAt, alternates| Type | Fields |
|---|---|
Site | name, slug, intro, logoUrl, colours { brand accent }, locale, languages, defaultLanguage, contactEmail, websiteUrl, hostedUrl, privacy { version text }, insights { site }, texting { sms whatsapp } |
JobList | items: [JobSummary!]!, locale, total, page, perPage, hasMore |
JobSummary | id, slug, url, title, summary, department { id name }, locations, employmentType, employmentTypeLabel, remote, remoteLabel, salary, publishedAt, closesAt, internal |
Job | Everything in JobSummary, and locale, locales, fallback, description, seo { title description }, cmsEntryId, form { fields questions }, privacy, jobPosting |
Location | id, name, street, postalCode, city, region, country, remote |
Salary | min, max, currency, period, text |
Question | id, kind, label, help, required, options { id label } |
Filters | departments, locations (with city and country), employmentTypes, remote, each { value label count } |
SitemapEntry | url, slug, updatedAt, alternates |
Job lists leave the description out on purpose. Ask job for one.
Examples
A job list with its filters
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 } }}{ "locale": "de" }A job page
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:
{ "data": { "job": null }, "errors": [ { "message": "Job was not found", "locations": [{ "line": 1, "column": 3 }], "path": ["job"], "extensions": { "code": "not_found" } } ]}400query, 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)401unauthorized)403forbidden)404not_found)413query_too_large)429rate_limited). data is left out.422invalid)500server_error). Try again.200| Status | When |
|---|---|
400 | 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 | No key, or a key that is wrong, revoked or expired (unauthorized) |
403 | A browser call from a website not on your allowed list (forbidden) |
404 | No such job, or it isn't live (not_found) |
413 | A query longer than 20,000 characters (query_too_large) |
429 | Too many requests (rate_limited). data is left out. |
422 | An argument with a wrong value (invalid) |
500 | Something failed on our side (server_error). Try again. |
200 | Success |
- When
jobfails, it isnulland the other fields still answer. site,jobs,filtersandsitemapnever answernull, so when one of them fails the wholedataisnull.- 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?