GraphQL
Read the shop, products and collections in one request, with the schema, examples, errors and limits. Carts and checkout stay in the storefront API.
Reference5 min read
GraphQL reads the same shop, products and collections as the storefront API. Use it when one page needs several things at once, such as the shop's currencies, a product and a collection menu, in a single request.
https://app.bifrostorbit.com/commerce/api/cdn/graphqlGraphQL only reads. Carts, checkout and orders stay in the storefront API.
Requests
GraphQL has one address for every shop, so it needs to know whose products to read. Either:
- add
?organisation=and the shop address from Settings, such as?organisation=northwilds. This needs no key. - or send an API key as
Authorization: Bearer <key>orX-Api-Key: <key>. The key says which shop it belongs to, and a secret key (com_sk_) adds draft products.
With both, the key must belong to that shop.
To read as a signed-in customer, also send their token as X-Customer-Token: cst_…. Prices and stock are then theirs, and customer returns who they are. Only the shop's allowed websites, or your server, may send a customer token. See Customer accounts and personal prices.
POST a JSON body with query, and optionally variables (an object) and operationName:
curl 'https://app.bifrostorbit.com/commerce/api/cdn/graphql?organisation=northwilds' \ -H 'Content-Type: application/json' \ -d '{"query":"query ($slug: String!) { product(slug: $slug, locale: \"nb\", currency: \"NOK\") { title price { min currency } available } }","variables":{"slug":"tind-2-tent"}}'GET works too, with query, variables (as JSON text) and operationName in the query string, URL-encoded:
curl -G 'https://app.bifrostorbit.com/commerce/api/cdn/graphql' \ --data-urlencode 'organisation=northwilds' \ --data-urlencode 'query={ products(tag: "winter", limit: 12) { slug title price { min currency } } }'Answers are JSON with data, and errors when something went wrong. Any website may call it from a browser. A secret key works only from a server; from another website it gets 403. Answers aren't cached by us: cache them on your side.
Queries
storeStore!: name, logo, colour, currencies, languages, whether prices include VAT and the hosted shop addresscustomerCustomer: the signed-in customer, or null without a customer tokenproductsids, slugs, tag, collection, query, limit, locale, currency[Product!]!: active products, as in the REST listproductslug: String! (address or id), locale, currencyProduct, or null when there is no such active productcollectionsids, locale[Collection!]!: active collectionscollectionslug: String! (address or id), locale, currency, limitCollection with its products, or null| Query | Arguments | Returns |
|---|---|---|
store | Store!: name, logo, colour, currencies, languages, whether prices include VAT and the hosted shop address | |
customer | Customer: the signed-in customer, or null without a customer token | |
products | ids, slugs, tag, collection, query, limit, locale, currency | [Product!]!: active products, as in the REST list |
product | slug: String! (address or id), locale, currency | Product, or null when there is no such active product |
collections | ids, locale | [Collection!]!: active collections |
collection | slug: String! (address or id), locale, currency, limit | Collection with its products, or null |
locale and currency work as in REST. limit is 100 unless you say, at most 250 for products.
Types
Storeslug, name, logoUrl, colour, currency, currencies, locales, defaultLocale, pricesIncludeTax, shopUrl, termsUrl, returnsDaysProductid, slug, title, summary, description (HTML), vendor, productType, tags, options { name values }, variants, images { url alt }, specs { label value }, price, available, status, seo { title description }, url, locale, updatedAt, stockSyncedAt, pricing { source account checkedAt }, metafields (JSON, by key), bundle { components { productId variantId quantity } }Variantid, title, options, sku, price, compareAtPrice, available, quantity (null when stock isn't tracked), listPrice, priceSource, branches { code name quantity yours }, requiresShipping, digital, plans { id name interval intervalCount price }, metafields (JSON)Customeremail, name, company { name terms termsDays }, account { number status onStop creditLimit balance branch branchName }, priceListPriceRangemin, max, currency, compareAtCollectionid, slug, title, description, image { url alt }, productCount, url, products (only on collection)| Type | Fields |
|---|---|
Store | slug, name, logoUrl, colour, currency, currencies, locales, defaultLocale, pricesIncludeTax, shopUrl, termsUrl, returnsDays |
Product | id, slug, title, summary, description (HTML), vendor, productType, tags, options { name values }, variants, images { url alt }, specs { label value }, price, available, status, seo { title description }, url, locale, updatedAt, stockSyncedAt, pricing { source account checkedAt }, metafields (JSON, by key), bundle { components { productId variantId quantity } } |
Variant | id, title, options, sku, price, compareAtPrice, available, quantity (null when stock isn't tracked), listPrice, priceSource, branches { code name quantity yours }, requiresShipping, digital, plans { id name interval intervalCount price }, metafields (JSON) |
Customer | email, name, company { name terms termsDays }, account { number status onStop creditLimit balance branch branchName }, priceList |
PriceRange | min, max, currency, compareAt |
Collection | id, slug, title, description, image { url alt }, productCount, url, products (only on collection) |
Amounts are whole minor units, as in REST: 38900 with EUR is €389.00. price is null on a variant with no price in the chosen currency.
Examples
A product page
query ProductPage($slug: String!, $locale: String, $currency: String) { store { name currencies pricesIncludeTax } product(slug: $slug, locale: $locale, currency: $currency) { title description images { url alt } options { name values } variants { id title price compareAtPrice available quantity } specs { label value } }}{ "slug": "fjell-gtx-boot", "locale": "nb", "currency": "NOK" }A collection with the menu
{ collection(slug: "tents", limit: 12) { title products { slug title price { min currency } available } } collections { slug title productCount }}Errors
Errors come in errors, each with a message, a code under extensions, and the path and locations of the field when there is one.
400query, variables that aren't a JSON object, a query that doesn't parse or match the schema (bad_request), or one over the limits (query_too_deep, query_too_complex)401?organisation=, or a key that is wrong, revoked, expired or for another shop (unauthorized)403forbidden)402subscription_inactive)404not_found)413query_too_large)429rate_limited). Wait a minute.500server_error). Try again.| Status | When |
|---|---|
400 | No query, variables that aren't a JSON object, a query that doesn't parse or match the schema (bad_request), or one over the limits (query_too_deep, query_too_complex) |
401 | Neither a key nor ?organisation=, or a key that is wrong, revoked, expired or for another shop (unauthorized) |
403 | A secret key sent from a browser on another website (forbidden) |
402 | The shop is paused because its Commerce Orbit trial or plan ended (subscription_inactive) |
404 | No shop with that address (not_found) |
413 | A query longer than 20,000 characters (query_too_large) |
429 | Too many requests in a short time (rate_limited). Wait a minute. |
500 | Something failed on our side (server_error). Try again. |
product and collection answer null when nothing matches, without an error.
Limits
- 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.
- Requests share the catalogue limit of the storefront API: 120 a minute per IP address and shop.
Was this page helpful?