Skip to content

Reference

Delivery API

Every endpoint websites read content from, with parameters, example answers, keys, fallback, caching, rate limits, limits, timeouts and errors.

Reference26 min read

The delivery API is how a website reads published content. It is read only. Every endpoint answers GET requests with JSON.

Production
Address
https://api.bifrostorbit.com/cms/v1
Staging
Address
https://api-stage.bifrostorbit.com/cms/v1
GraphQL
Address
https://api.bifrostorbit.com/cms/graphql, see GraphQL

Try any endpoint in Configuration › API playground. Paste a key, pick an endpoint and run it.

Endpoints

Each endpoint takes the common parameters listed in the last column, and its own parameters in its section. Errors are the same everywhere: see Errors.

Answers with
What an address is: a page, a redirect, gone or not found
Common parameters
region, locale, strict, draft, include=references
Answers with
One page by its address
Common parameters
region, locale, strict, draft, include=references
Answers with
Entries of one content type
Common parameters
region, locale, draft, include=fields,references
Answers with
A global entry, such as the footer
Common parameters
region, locale, strict, draft, include=references
Answers with
A menu
Common parameters
region, locale, draft
Answers with
The regions and languages the key may read
Common parameters
None
Answers with
A taxonomy and its terms
Common parameters
locale
Answers with
Content types and components with their fields
Common parameters
None
Answers with
Banners live now
Common parameters
region, locale, draft
Answers with
Popups live now
Common parameters
region, locale, draft
Answers with
Shop products with prices and stock
Common parameters
region, locale, draft
Answers with
The sitemap index, or one region's sitemap
Common parameters
region, locale
Answers with
The robots.txt
Common parameters
None

Leave the trailing slash off endpoint addresses. /cms/v1/content/ answers 308 with a redirect to /cms/api/cdn/v1/content, the same endpoint under its internal address, which costs a second request.

Authentication

Send a delivery key with every request, in either header:

HTTP
Authorization: Bearer cms_pk_…x-api-key: cms_pk_…

When both are sent, Authorization wins. A request with no key, an unknown key, a deleted key or an expired key gets 401.

Shell
curl 'https://api.bifrostorbit.com/cms/v1/content?url=/products/hiking-boots&region=de' \  -H 'Authorization: Bearer cms_pk_…'

From a browser

Any website may call the API from browser code with a public key: every answer allows all origins, and preflight requests are answered. Use it for a static site, a widget or anything that reads published content in the browser.

A secret key only works from a server. Sent from another website's browser, it is refused with 401, because a key in browser code can be read by anyone. Keep drafts and preview on your server.

Keys and environments

Create keys in Configuration › API keys.

Public
Starts with
cms_pk_
Reads
Published content only
Secret
Starts with
cms_sk_
Reads
Published content, and drafts when you add draft=true. Keep it on the server.
  • Each key belongs to one environment and only ever sees that environment's content. Use a staging key against a staging environment and a production key against production.
  • A secret key cannot be created for a production environment. Drafts are read from staging environments only.
  • A key can be limited to some regions and languages. Asking for any other region or language gets 401, and /regions lists only the ones the key may read.
  • A key can have an expiry date. After it, the key gets 401.

See Security and access.

Common parameters

Most endpoints take these query parameters. The endpoints table says which apply to each. This is the only table of them; the sections below list only each endpoint's own parameters.

region
Type
string
Default
The space's first region
Means
Region key or its short code, such as germany or de. Not case sensitive.
locale
Type
string
Default
The region's default language
Means
Language code, such as de-DE. Not case sensitive.
strict
Type
true
Default
off
Means
Refuse every fallback: the page must exist in the language and region asked for.
draft
Type
true
Default
off
Means
Read drafts instead of published content. Needs a secret key.
include
Type
list
Default
nothing
Means
Extra content to add to the answer, separated by commas: fields and references. See Lists and References.

Any value other than true counts as off for strict and draft. A region or language that does not exist, or anything other than fields or references in include, is an error.

Pages

GET/resolve

Says what an address is: a page, a redirect, gone or not found. Call it before rendering a page, so redirects and removed pages behave correctly.

url
Type
string
Default
/
Means
The page address, such as /products/hiking-boots. A missing leading slash is added and a trailing slash is removed.

Optional header: x-cms-referrer with the address of the page that linked here. It is recorded with addresses that are not found, so editors can see where broken links come from.

Shell
curl 'https://api.bifrostorbit.com/cms/v1/resolve?url=/products/hiking-boots&region=de' \  -H 'Authorization: Bearer cms_pk_…'

The HTTP status follows the answer's kind:

page
Status
200
Means
page holds the same page as /content
redirect
Status
200
Means
Send the visitor to destination with statusCode
gone
Status
410
Means
The page was published once and has been removed, or a campaign page has ended
notFound
Status
404
Means
Nothing was ever published here

A page. page is the same object /content answers with:

JSON
{ "data": { "kind": "page", "page": { "url": "/products/hiking-boots", "name": "Hiking boots", "…": "…" } } }

A redirect. permanent is true for 301 and 308, and hops counts the redirects that were followed to reach the final address:

JSON
{  "data": {    "kind": "redirect",    "destination": "/products/hiking-boots",    "statusCode": 301,    "permanent": true,    "hops": 1  }}

Gone and not found carry only the kind:

JSON
{ "data": { "kind": "notFound" } }

GET/content

One page by its address. Use it when you only need the page and handle redirects yourself.

url
Type
string
Default
/
Means
The page address, read as in /resolve
Shell
curl 'https://api.bifrostorbit.com/cms/v1/content?url=/products/hiking-boots&region=de&locale=de-DE' \  -H 'Authorization: Bearer cms_pk_…'
JSON
{  "data": {    "id": "6650c0f2a1b2c3d4e5f60718",    "url": "/products/hiking-boots",    "name": "Hiking boots",    "contentType": "page",    "locale": "de-DE",    "region": "germany",    "requestedLocale": "de-DE",    "requestedRegion": "germany",    "fallbackApplied": false,    "regionFallbackApplied": false,    "draft": false,    "publishedAt": "2026-10-01T09:30:00.000Z",    "version": 7,    "fields": {      "title": "Find a shop",      "heroImage": { "id": "6650c1…", "url": "https://…/hero.jpg", "mimeType": "image/jpeg", "width": 1600, "height": 900, "alt": "A pair of hiking boots", "title": null, "subtitles": null }    },    "components": [      {        "id": "c1",        "type": "hero",        "schemaVersion": 1,        "fields": { "heading": "Free boot fitting" },        "children": {},        "variant": null      }    ],    "seo": {      "title": "Hiking boots | Acme Outdoors",      "description": "Find a shop near you.",      "canonical": "https://www.example.com/de/products/hiking-boots",      "robots": { "index": true, "follow": true },      "openGraph": { "type": "website", "title": "Hiking boots", "description": "Find a shop near you.", "url": "https://www.example.com/de/products/hiking-boots", "siteName": "Acme Outdoors", "locale": "de_DE", "image": null },      "twitter": { "card": "summary_large_image", "site": null, "title": "Hiking boots", "description": "Find a shop near you.", "image": null },      "verification": { "google": null, "bing": null },      "alternates": [{ "hreflang": "de-DE", "href": "https://www.example.com/de/products/hiking-boots" }],      "breadcrumbs": [{ "name": "Products", "url": "https://www.example.com/de/products" }],      "jsonLd": []    },    "insights": null  }}
id
Means
The entry's id in this region and language, the same as in the management API
url, name, contentType
Means
The page's address, its name in the CMS and its content type key
locale, region
Means
The language and region that answered
requestedLocale, requestedRegion
Means
What was asked for, or the defaults when nothing was
fallbackApplied
Means
true when another language answered. See Fallback.
regionFallbackApplied
Means
true when another region answered
draft
Means
true for a draft
publishedAt
Means
When it was last published, or null if never
version
Means
The published version number, or the draft version for a draft
fields
Means
The content type's fields. Images become objects with url, size and alt; links carry the target page's current address. See Field types.
components
Means
The page body: a tree of components with id, type, schemaVersion, fields, children (lists by slot) and variant. Blocks in a slot arrive both in children and on the slot's field. See Components and slots in Field types. Shared sections arrive already in place: their blocks appear where the section was placed, and the sharedSection block itself is never delivered.
seo
Means
Ready-made metadata: title, description, canonical, robots, Open Graph, X card, hreflang alternates, breadcrumbs and JSON-LD
insights
Means
{ "site": "…" } when the space is connected to Insights Orbit, with the site key the script counts visits with; otherwise null. The key is public, like the script tag it goes in. See Insights Orbit.
expired
Means
Only on a campaign page past its end date that is set to stay visible: { "message": "This offer has ended." }

Nothing at the address is a 404 error.

Lists

GET/list

Entries of one content type, such as products, articles or FAQ entries. Items are summaries without a body; ask /content for a full page. Add include=fields to get each entry's fields too, which is how you read entries that have no address of their own, such as FAQ entries.

type
Type
string
Limits and notes
Content type key, such as product. Required in practice: without it the list is empty.
page
Type
integer
Default
1
Limits and notes
1 or more
perPage
Type
integer
Default
20
Limits and notes
1 to 100. Larger values are capped at 100.
sort
Type
string
Default
sortOrder
Limits and notes
filter[<field>]
Type
string
Limits and notes
Exact match on a field. Repeat for several fields; all must match.
tag
Type
string
Limits and notes
Entries whose tags field contains this value
category
Type
string
Limits and notes
Entries whose category field equals this value
q
Type
string
Limits and notes
Entries whose name contains this text, ignoring case
include
Type
list
Limits and notes
fields adds each entry's fields. references also replaces the references in them with the entries they point at, and implies fields.

Lists do not fall back to another language.

Shell
curl 'https://api.bifrostorbit.com/cms/v1/list?type=product&region=de&sort=-publishedAt&perPage=10&filter[activity]=hiking' \  -H 'Authorization: Bearer cms_pk_…'
JSON
{  "data": {    "items": [      {        "id": "66f1c0ffee0000000000a001",        "url": "/products/hiking-boots",        "name": "Hiking boots",        "contentType": "product",        "publishedAt": "2026-09-14T11:02:00.000Z",        "seo": { "title": "Hiking boots", "description": "…" }      }    ],    "total": 42,    "page": 1,    "hasMore": true  }}

An entry without its own address has url and seo set to null. Global entries never appear in lists. id is the entry's id, the same value a Reference field holds.

Entries with their fields

Shell
curl 'https://api.bifrostorbit.com/cms/v1/list?type=faqEntry&region=de&category=pricing&include=fields' \  -H 'Authorization: Bearer cms_pk_…'
JSON
{  "data": {    "items": [      {        "id": "66f1c0ffee0000000000b007",        "url": null,        "name": "Can I cancel at any time?",        "contentType": "faqEntry",        "publishedAt": "2026-09-30T08:00:00.000Z",        "seo": null,        "fields": {          "question": "Can I cancel at any time?",          "answer": "<p>Yes. Your plan ends at the close of the month.</p>",          "category": "pricing"        }      }    ],    "total": 1,    "page": 1,    "hasMore": false  }}

fields are delivered as on a page: images become objects and links carry the target page's address. Without include=fields the items look exactly as before, so existing websites are not affected.

Pagination

Ask for page=1, then the next page while hasMore is true. total is the number of matching entries across all pages.

Sorting

sortOrder
Order
The order editors set by hand, then by name. The default.
name, -name
Order
By name, A to Z or Z to A
publishedAt, -publishedAt
Order
Oldest or newest published first, then by name
updatedAt, -updatedAt
Order
Least or most recently changed first, then by name

Any other value is a 400 error.

Filtering

  • filter[<field>]=<value> matches the field's key as set on the content type, such as filter[activity]=hiking. A key must start with a letter and contain only letters, digits and underscores, otherwise the request is a 400 error.
  • Values are compared as text and must match exactly. A list field matches when it contains the value.
  • filter, tag, category and q combine: an entry must match all of them.

References

Reference and References fields hold entry ids. Add include=references to /resolve, /content, /global or /list and each id is replaced by the entry it points at, wherever the field is: on the entry itself or in any component of its body.

Shell
curl 'https://api.bifrostorbit.com/cms/v1/resolve?url=/pricing&region=de&include=references' \  -H 'Authorization: Bearer cms_pk_…'

A component with a References field called questions then arrives like this:

JSON
{  "id": "c4",  "type": "faqList",  "schemaVersion": 1,  "fields": {    "heading": "Questions about plans",    "questions": [      {        "id": "66f1c0ffee0000000000b007",        "url": null,        "name": "Can I cancel at any time?",        "contentType": "faqEntry",        "publishedAt": "2026-09-30T08:00:00.000Z",        "fields": { "question": "Can I cancel at any time?", "answer": "<p>Yes.</p>", "category": "pricing" }      }    ]  },  "children": {},  "variant": null}
  • Each entry has its id, url (or null without an address), name, contentType, publishedAt and its fields, delivered as on a page. Its body is not included; ask for its page when it has one.
  • One level only: references inside the referenced entry stay ids.
  • The entry is read in the same region, and in the language of the request, following the same language fallback as the page. An entry that is translated answers in the visitor's language even though editors picked it in another one, and a page promoted from staging points at production's copy of each entry.
  • Published content shows published entries only. An entry that is unpublished, deleted, missing in the region or in a language the key may not read is left out of a References list, and a Reference becomes null.
  • With draft=true, the referenced entries' drafts are shown too.
  • Without include=references references stay ids, as before.

To refresh pages when an entry they show is published, see Refresh the website when content changes. The SDK does this for you.

Site-wide content

GET/global

A global entry, such as the footer or the site header, by its key.

key
Type
string
Means
The global entry's key. Required.

It follows the language fallback, but not the region fallback.

Shell
curl 'https://api.bifrostorbit.com/cms/v1/global?key=footer&region=de' \  -H 'Authorization: Bearer cms_pk_…'
JSON
{  "data": {    "key": "footer",    "name": "Footer",    "contentType": "footer",    "locale": "de-DE",    "fields": { "copyright": "© Acme Outdoors" },    "components": []  }}

locale is the language that answered, which may be a fallback.

GET/navigation

A menu, by its key. A menu can be shared by all regions or belong to one.

key
Type
string
Means
The menu key, such as main. Required.
Shell
curl 'https://api.bifrostorbit.com/cms/v1/navigation?key=main&region=de' \  -H 'Authorization: Bearer cms_pk_…'
JSON
{  "data": {    "key": "main",    "name": "Main menu",    "items": [      {        "label": "Services",        "href": "/services",        "target": null,        "icon": null,        "badge": null,        "description": null,        "children": [          { "label": "Hiking boots", "href": "/products/hiking-boots", "target": null, "icon": null, "badge": "New", "description": "Fitted by people who hike", "children": [] }        ]      }    ]  }}

Items that link to a page carry its current address, so renaming a page never breaks the menu. Hidden items, and items linking to a page that is not published in this region and language, are left out.

GET/regions

The regions the key may read, with their languages. No parameters.

JSON
{  "data": [    {      "key": "germany",      "name": "Germany",      "locales": [        { "code": "de-DE", "name": "German (Germany)", "isDefault": true },        { "code": "en-DE", "name": "English", "isDefault": false }      ]    }  ]}

GET/taxonomies/{key}

A taxonomy and its terms, in their set order.

locale
Type
string
Means
Language for the term labels. Terms without a label in that language use their default label.
JSON
{  "data": {    "key": "activity",    "name": "Activity",    "terms": [{ "value": "hiking", "label": "Hiking", "sortOrder": 0 }]  }}

GET/schema

The space's content types and components, with their fields. The SDK's type generator uses it. No parameters. Deprecated components and hidden fields are left out.

JSON
{  "data": {    "contentTypes": [      { "key": "page", "name": "Page", "description": null, "isGlobal": false, "fields": [] }    ],    "components": [      { "key": "hero", "name": "Hero", "description": null, "group": "Layout", "allowedChildren": "*", "fields": [] }    ]  }}

Each field has key, name, type, required, description, options, allowedComponents, allowedContentTypes, taxonomy and validation.

Campaigns

Banners and popups are worked out for the moment of the request. Both answers include serverTime, so countdowns can tick from the CMS clock rather than the visitor's.

GET/banners

Banners live now for a page, pinned first and then by priority.

path
Type
string
Default
/
Means
The page being shown. Only banners targeting it are returned.
everyPage
Type
true
Default
off
Means
Return the banners live on any page instead, each with the paths it targets, so a website can read them once for all its pages and pick per page itself. path is then ignored.
loggedIn
Type
true
Default
off
Means
Whether the visitor is signed in. Blocks meant only for guests or only for signed-in visitors are left out accordingly.
Shell
curl 'https://api.bifrostorbit.com/cms/v1/banners?path=/products/hiking-boots&region=de' \  -H 'Authorization: Bearer cms_pk_…'
JSON
{  "data": {    "serverTime": "2026-10-17T10:00:00.000Z",    "items": [      {        "id": "66f1a2…",        "blocks": [          { "id": "b1", "type": "message", "text": "Free boot fitting this week", "segments": [{ "text": "Free boot fitting", "highlight": true }, { "text": " this week", "highlight": false }] }        ],        "text": "Free boot fitting this week",        "segments": [{ "text": "Free boot fitting", "highlight": true }, { "text": " this week", "highlight": false }],        "link": null,        "background": "#1f2937",        "foreground": "#ffffff",        "placement": "aboveHeader",        "paths": ["/services/*"],        "pinned": false,        "priority": 0,        "trackingLabel": "consult-week",        "countdown": null,        "endsAt": "2026-10-20T22:59:00.000Z",        "next": null      }    ]  }}

foreground is the text colour the editor chose, or white or dark, whichever reads best on background. placement is where the editor wants it: aboveHeader or belowHeader. paths are the pages it targets, with /* at the end for a whole section, and empty for every page.

next is the banner that takes over when this one ends, with its startsAt, so the website can switch without asking again. See Render banners, popups and countdowns.

GET/popups

Popups live now for a page and the visitor's sign-in state, highest priority first.

path
Type
string
Default
/
Means
The page being shown
loggedIn
Type
true
Default
off
Means
Whether the visitor is signed in. Popups for guests only or signed-in visitors only are filtered by it.

Each item has id, name, layout, style, minimisedText, blocks, priority, hideForDays (default 30), maxViews, trackingLabel, startsAt and endsAt.

Shop

GET/products

Shop products with live prices and stock, in their set order.

page
Type
integer
Default
1
Limits and notes
1 or more
perPage
Type
integer
Default
24
Limits and notes
1 to 100. Larger values are capped at 100.
filter
Type
string
Limits and notes
A taxonomy term value. Products tagged with it in any taxonomy field match.

The answer has items, total, page and hasMore, like /list. Each product has slug, url, name, catalogId, size, price, currency, wasPrice, inStock, onSale, badges, benefits, directions, ingredients, terms, images, filters (with labels), relatedSlugs, seo and publishedAt. catalogId links the product to your shop catalogue, which supplies price and inStock. Both are null when the product is not linked or the catalogue cannot be reached, and currency is then the region's currency.

Search engines

GET/sitemap

Without region, the index of every region and language. With region, that region's entries.

region
Type
string
Means
Region key or code. Leave out for the index.
locale
Type
string
Default
The region's default language
Means
Language of the sitemap
page
Type
integer
Default
1
Means
Sitemap file number. Each holds up to 50,000 addresses.

The index:

JSON
{  "data": {    "indexable": true,    "sitemaps": [{ "region": "germany", "locale": "de-DE", "base": "https://www.example.com/de", "pages": 312, "files": 1 }]  }}

A region's sitemap:

JSON
{  "data": {    "region": "germany",    "locale": "de-DE",    "page": 1,    "totalPages": 1,    "entries": [      {        "loc": "https://www.example.com/de/products/hiking-boots",        "lastmod": "2026-10-01T09:30:00.000Z",        "changefreq": "monthly",        "priority": null,        "images": [],        "alternates": [{ "hreflang": "de-DE", "href": "https://www.example.com/de/products/hiking-boots" }]      }    ]  }}

Only published pages with an address are listed. Pages set to no-index or left out of the sitemap are skipped. Sitemaps always show published content.

GET/robots

The robots.txt for the environment. No parameters.

JSON
{  "data": {    "indexable": true,    "content": "User-agent: *\nAllow: /\nSitemap: https://www.example.com/de/sitemap.xml\n"  }}

An environment that is not indexable, such as staging, always gets User-agent: * and Disallow: /.

Fallback

When a page is missing in the language or region asked for, the API can answer with another one rather than 404. Every page answer says what happened.

  1. Language fallback. Each language can name a fallback language in the same region. The API tries the language asked for, then its fallback, then that language's fallback, and so on. The first one with published content answers. A space can turn this off with Allow locale fallback in its settings, and then every request behaves as if it sent strict=true.
  2. Region fallback. If no language in the region has the page, and the space is set to show another region's page when one is missing, the API tries that region in its default language. See Space settings.
  3. strict=true turns both off.
Pages (/content, /resolve)
Language fallback
Yes
Region fallback
Yes
/global
Language fallback
Yes
Region fallback
No
Everything else
Language fallback
No
Region fallback
No

On a page, compare the fields to see what answered:

requestedRegion, requestedLocale
Example
germany, en-DE
Means
What was asked for
region, locale
Example
germany, de-DE
Means
What answered
fallbackApplied
Example
true
Means
Another language answered
regionFallbackApplied
Example
true
Means
Another region answered

The SDK copies these into page.meta, so a website can show a notice such as "This page is not available in English yet." A key limited to some languages never falls back to a language it may not read.

Drafts

Add draft=true with a secret key to read the latest saved draft instead of the published version. Use it for previews only.

  • A public key asking for drafts gets 401. See Errors.
  • Drafts are never cached.
  • Pages that were never published are found, and campaign pages show outside their start and end dates, so editors can preview ahead of time.
  • /regions, /schema, /sitemap, /robots and /taxonomies/{key} always answer with published content.

See Set up the live preview.

Caching

Successful answers tell caches how long they may keep them. Every answer also has Vary: Authorization.

/content, /resolve, /list, /banners, /popups, /products, /taxonomies/{key}
Cache-Control
public, s-maxage=60, stale-while-revalidate=300, stale-if-error=86400
/global, /navigation, /schema, /sitemap, /robots
Cache-Control
public, s-maxage=300, stale-while-revalidate=1500, stale-if-error=86400
/regions
Cache-Control
public, s-maxage=3600, stale-while-revalidate=18000, stale-if-error=86400
Any request with draft=true
Cache-Control
private, no-store
/resolve answering notFound
Cache-Control
private, no-store
Errors, including 429
Cache-Control
no-store

In words: a shared cache, such as a CDN in front of your website, may keep a published answer for 60 seconds (5 minutes for menus, globals and sitemaps, an hour for regions). After that it may serve the old answer for five times as long while it fetches a new one, and for a day if the API is failing. A newly created page is found as soon as it is published, because not found is never cached.

The API does not send ETag or Last-Modified, so conditional requests are not supported.

To show a change at once rather than within these times, listen for publish webhooks and refresh only what changed. See Refresh the website when content changes.

Rate limits

Each key and each client IP address may make 15,000 requests a minute, counted separately. A request counts against both, and the stricter one applies. GraphQL requests count too, against the same limit. Windows are fixed calendar minutes, so the count resets at the start of each minute.

Going over gets 429:

HTTP
HTTP/1.1 429 Too Many RequestsCache-Control: no-storeRetry-After: 23X-RateLimit-Limit: 15000X-RateLimit-Remaining: 0X-RateLimit-Reset: 23
JSON
{ "error": { "code": "rate_limited", "message": "Too many requests; try again shortly" } }

Every answer from an endpoint, not only a 429, says where you stand:

X-RateLimit-Limit
Meaning
The requests allowed this minute, from the stricter of the key and the IP address
X-RateLimit-Remaining
Meaning
The requests left this minute, counting this one
X-RateLimit-Reset
Meaning
Whole seconds until the minute ends and the count starts again, from 1 to 60
Retry-After
Meaning
On a 429 only: the seconds to wait, the same as X-RateLimit-Reset

Browser code can read all four. An answer served from a cache in front of the API, such as your own CDN, keeps the values it had when it was cached.

Cache answers in your website and you will stay well below the limit.

Limits and timeouts

Requests
Value
15,000 a minute per key and per IP address. See Rate limits.
perPage in /list and /products
Value
At most 100. Larger values are capped at 100.
Addresses in one sitemap file
Value
50,000
References
Value
One level. See References.
GraphQL query
Value
At most 20,000 characters, 10 levels deep and 300 fields. See GraphQL.

The API sets no limit of its own on the length of the address or query string, the length of q, or the number of filter[…] parameters. Keep addresses short all the same: very long ones can be refused by the network in front of the API before they reach it.

Timeouts:

  • The SDKs: In the JavaScript, PHP, Swift and Kotlin SDKs each attempt times out after 5 seconds. Network errors, timeouts and server errors are retried twice, after 250 milliseconds and then 750 milliseconds. A 429 is not retried, and GraphQL requests from the JavaScript SDK are tried once. Each SDK lets you change the timeout and the number of retries; see the SDK.
  • The API: It sets no time limit of its own on a request.
  • While the API restarts: During a release the gateway holds a request for up to 30 seconds while it waits for the API to come back, so a request can take that long without failing.

Errors

Errors from the API come as JSON with the HTTP status, Cache-Control: no-store and the same browser headers as every other answer:

JSON
{ "error": { "code": "not_found", "message": "Content not found" } }
400
code
bad_request
When
An unknown sort, a filter[…] name that is not a valid field key, or anything other than fields or references in include
401
code
unauthorized
When
No key, an unknown, deleted or expired key, a public key asking for drafts, a secret key sent from a browser on another website, or a region or language the key may not read
402
code
plan_ended
When
The organisation's CMS Orbit plan has ended. An owner can choose a plan under Billing in Bifröst Orbit.
404
code
not_found
When
Nothing at the address, or no menu, global entry, taxonomy, region or language with that key. The message names which, such as Region not found.
429
code
rate_limited
When
Too many requests. See Rate limits.
500
code
internal_error
When
Something failed on our side. Try again; the message is always The request failed.

/resolve is different: not found and gone are normal answers, { "data": { "kind": "notFound" } } with 404 and { "data": { "kind": "gone" } } with 410, not errors.

GraphQL errors have their own shape. See GraphQL.

Answers that do not come from an endpoint

A few answers come from before a request reaches an endpoint. Check the status before reading the body.

An address outside the API, such as https://api.bifrostorbit.com/cms/v2/content, or an endpoint that does not exist under /cms/v1/, such as /cms/v1/pages
Answer
404 in the usual shape, with a link to these docs: { "error": { "code": "not_found", "message": "…" }, "docs": "…" }
An endpoint with a trailing slash, such as /cms/v1/content/
Answer
308 redirect. See Endpoints.
Any request while the API cannot be reached for more than 30 seconds
Answer
A 5xx status, such as 502, without a JSON body

The JavaScript SDK handles all of these: it follows the redirect, a 404 throws CmsNotFoundError, and a 5xx is retried and then throws CmsUnavailableError.

Was this page helpful?

Read next