{"title":"Content API","slug":"content-api","url":"https://support.storeconnect.com/articles/content-api","url_markdown":"https://support.storeconnect.com/articles/content-api.md","subtitle":null,"summary":"The endpoints an agent or integration uses to read and build a store\u0026#39;s catalog and content. Covers where to find the live OpenAPI contract, how to authenticate, which scopes allow which actions, and how to page through and sync records.","type":"Developer_Documentation","video_url":"","keywords":"content api, api/v1/content, openapi, contract, agent platform, mcp, cursor pagination, updated_since, incremental sync, sync token, changes endpoint, deleted records, api token, bearer, scopes, store role, content changes, staging","last_modified":"2026-11-04T00:53:28+0000","body_markdown":"The content API is the set of endpoints an agent or integration uses to read and build a store's catalog and content: products, categories, pages, articles, content blocks, menus, media and themes. The MCP connector calls the same endpoints.\n\nThe endpoints sit under `/api/v1/content`. Every write goes onto a draft rather than the live store, as described in [Content staging](content-staging).\n\n## Find the endpoint contract\n\nThe store publishes the full endpoint contract as an OpenAPI document:\n\n```text\n\nhttps://\u003cyour-store-domain\u003e/api/v1/openapi.json\n```\n\nThe document is public and needs no credential, so you can read it before you have a key. Use it for paths, parameters, request bodies and response shapes. This article covers behavior the document does not describe.\n\n:::note\nThe content API and the OpenAPI document are served at the root of the domain, not under a store path. For a store at `https://shop.example.com/au`, use `https://shop.example.com/api/v1/openapi.json`. The MCP endpoint and the MCP discovery documents are different: they do use the store path. See [Connect an AI agent to your store](connect-an-ai-agent-to-your-store).\n:::\n\n## Authenticating\n\nSend one of two credentials as a bearer token:\n\n- **A store credential**, in three colon-separated parts, `org_id:store_sfid:api_token`. The credential names the store, so it reaches that store whichever address it is sent to.\n- **An OAuth 2.1 access token**, obtained through the agent sign-in flow. The token is bound to the store it was issued for, and is refused by any other store.\n\nFor a store served at a path, an OAuth token is refused on `/api/v1`, because the token is bound to the store path and the API is served at the domain root. Use a store credential for that store, or connect to the store at the root of the domain.\n\nBoth credentials carry the scopes of the Salesforce user behind them, taken from the user's **Store Role**. A request that needs a scope the user does not have is refused with a `403`, so a user whose role grants no scopes can authenticate but cannot read or write any content.\n\nFor example, to list the first 100 products changed since the start of March:\n\n```text\n\ncurl \"https://shop.example.com/api/v1/content/products?limit=100\u0026updated_since=2026-03-01T00:00:00Z\" \\\n  -H \"Authorization: Bearer 00D5g000004XyZ1EAA:a1B5g00000AbCdEFGH:example-api-token\"\n```\n\n:::warning\nSetting the `api.disable_path_access` store variable refuses authenticated API requests made on the store's own domain, including this API and the MCP endpoint. Do not set it on a store where anyone connects an agent. See [Store variables](store-variables).\n:::\n\n## Scopes\n\nEach resource group has a read scope and a write scope, such as `content.read` and `content.write`, so you can give a credential only the access it needs. The authoring scopes are `content.write`, `catalog.write`, `themes.write` and `publish`.\n\nThese rules decide who can create, push and discard a draft:\n\n- **A read scope never allows publishing.** A credential that can read content cannot push a draft to Salesforce.\n- **Any authoring scope can push a draft the credential owns.**\n- **Creating a draft through the REST API needs `content.write`.** Every other REST write names a draft the credential already owns, so a credential with only `catalog.write` cannot start one over REST. Through MCP, the first write opens a draft automatically, so a credential with only `catalog.write` can stage changes and push them.\n- **Discarding a draft needs `content.write` through MCP**, or any authoring scope through the REST API.\n\n## Paging through results\n\nCollections are paginated with a cursor rather than an offset. Records change while a draft is being edited, and an offset would skip or repeat rows.\n\nEach response includes a `pagination` object. When `pagination.has_more` is `true`, pass the value of `pagination.cursor` as the `cursor` parameter to fetch the next page. When `has_more` is `false`, you have the last page.\n\nSet the page size with `limit`. The default is 50 and the maximum is 200.\n\n## Fetching only what changed\n\nPass `updated_since` to a list request to fetch only records changed after a timestamp. Results are ordered by when each record was last changed, then by id, so records that share a timestamp still page in a stable order.\n\n:::warning\n`updated_since` must be an ISO 8601 timestamp, such as `2026-03-01T00:00:00Z`. A value that cannot be parsed is ignored, and the full collection is returned instead of the changes.\n:::\n\n## Keeping a copy in sync\n\n`updated_since` does not report deleted records. To keep a local copy in sync, including deletions, use the `changes` endpoint for the resource, such as `GET /api/v1/content/products/changes`. Pages, articles, categories, products, menus, themes, media, content blocks and the trait resources each have one. See the OpenAPI document for the exact paths.\n\n1.  Send the first request without a `since` parameter.\n2.  Store the `sync_token` from the response. The response also contains the changed records in `data`, and the ids of deleted records in `deleted`.\n3.  While `has_more` is `true`, send the request again with `since` set to the latest `sync_token`.\n4.  On the next sync, send `since` with the last stored `sync_token`.\n\nA sync token expires 90 days after it is issued. A request with an expired token is refused with a `422`. Start again from step 1."}