Skip to content
Catalog API

Reference

Catalog API

Call Shopify's Global Catalog from your own backend for product search, with an agent profile you publish and an optional API key for higher rate limits.

Shop MCP’s catalog tools search Shopify’s Global Catalog. You can also call the Catalog yourself, at https://catalog.shopify.com/api/ucp/mcp, when an app only needs product search and has no use for a Shop account, carts or checkout. It takes the same three tools and the same catalog arguments as Shop MCP, so Find products applies to both.

Shop MCP Catalog
Address https://mcp.shop.com/ https://catalog.shopify.com/api/ucp/mcp
Tools All 17 search_catalog, lookup_catalog and get_product
Agent profile Shop MCP sends its own You send yours with every call
Headers The MCP headers and _meta from the quickstart Content-Type and Accept
Credentials A Shop account token, optional for search An API key, optional, for higher rate limits
Results structuredContent, content and isError structuredContent only
A limit over 50 Refused with a -32602 error Lowered to 50, with a pagination_limit_clamped message

Shop MCP adds its own profile to every catalog call, and its catalog tools take only catalog, so don’t send meta to Shop MCP.

This finds rain jackets for a shopper in Canada, priced up to 80 CAD:

Shell
curl -sS https://catalog.shopify.com/api/ucp/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "search_catalog",
      "arguments": {
        "meta": {
          "ucp-agent": {
            "profile": "https://shopify.dev/ucp/agent-profiles/examples/2026-08-25/valid-with-capabilities.json"
          }
        },
        "catalog": {
          "query": "rain jacket",
          "context": { "address_country": "CA", "currency": "CAD" },
          "filters": { "price": { "max": 8000 } },
          "pagination": { "limit": 5 }
        }
      }
    }
  }'

meta.ucp-agent.profile is the URL of your agent profile, which tells the Catalog which capabilities your app supports. The one here is Shopify’s sample, which is fine for trying the API. Before you go live, publish your own.

A call without a profile gets HTTP 422:

JSON
{
  "error": {
    "code": -32001,
    "message": "UCP discovery failed",
    "data": { "code": "invalid_profile_url", "content": "Unable to fetch agent profile: Missing profile uri" }
  }
}

Read the result

The result has only structuredContent. Its ucp object lists the capabilities the Catalog agreed to use with your profile, and status says whether the call worked:

JSON
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "structuredContent": {
      "ucp": {
        "version": "2026-08-25",
        "capabilities": {
          "dev.ucp.shopping.catalog.search": [{ "version": "2026-08-25" }],
          "dev.ucp.shopping.catalog.lookup": [{ "version": "2026-08-25" }],
          "dev.shopify.catalog.global": [{ "version": "2026-08-25" }]
        },
        "status": "success"
      },
      "products": [
        { "id": "gid://shopify/p/33aRQ8AfwUkvYQ0wdHUCr1", "title": "Craven Mens Waterproof Packaway Jacket - Black" }
      ],
      "messages": [
        {
          "type": "info",
          "code": "price_filter_applied",
          "path": "$.filters.price",
          "content": "Price filtering was applied on a USD basis; returned prices are contextualized to the buyer's market and may differ."
        }
      ],
      "pagination": { "has_next_page": true, "total_count": 368, "cursor": "eyJvZmZzZXQiOjUsInRvdGFsX2NvdW50IjozNjgsInJldHVybmVkIjo1fQ" }
    }
  }
}

The capabilities and products above are cut down. Read the result in the quickstart goes through a product’s main fields.

An error from the Catalog also comes back with HTTP 200. ucp.status is "error", and messages says what went wrong, such as not_found for a product ID that doesn’t exist. Check the HTTP status, the JSON-RPC error and ucp.status before you use the data:

JavaScript
async function readCatalogResult(response) {
  if (!response.ok) throw new Error(`Catalog HTTP ${response.status}`);
  const body = await response.json();
  if (body.error) throw new Error(body.error.message);
  const data = body.result?.structuredContent;
  if (!data) throw new Error("Catalog response has no structuredContent");
  if (data.ucp?.status === "error") {
    throw new Error(data.messages?.map((m) => m.content).join("; ") || "Catalog error");
  }
  return data;
}

search_catalog and lookup_catalog return products, and get_product returns product.

To get the next page, send the same arguments with pagination.cursor set to the returned cursor. Pages stop at 1,000 results, so has_next_page turns false there however many products match.

Look up products and get details

Use the same request with another tool name and catalog object:

Tool Example catalog
lookup_catalog { "ids": ["gid://shopify/p/33aRQ8AfwUkvYQ0wdHUCr1"], "context": { "address_country": "CA", "currency": "CAD" } }
get_product { "id": "gid://shopify/p/33aRQ8AfwUkvYQ0wdHUCr1", "selected": [{ "name": "Size", "label": "M" }], "context": { "address_country": "CA", "currency": "CAD" } }

Use the IDs and option labels the Catalog returned. Look up known items and Choose a size or color explain the arguments.

Publish a profile

An agent profile is a JSON document at a public HTTPS URL you control, such as https://your-app.example/profiles/agent.json. It declares the UCP version and the capabilities your app supports. It holds no secret, and it doesn’t sign anyone in.

For the Catalog, declare the two base capabilities and Shopify’s extension. This is the catalog part of Shopify’s sample profile:

JSON
{
  "dev.ucp.shopping.catalog.search": [
    {
      "version": "2026-08-25",
      "spec": "https://ucp.dev/2026-08-25/specification/catalog/search",
      "schema": "https://ucp.dev/2026-08-25/schemas/shopping/catalog_search.json"
    }
  ],
  "dev.ucp.shopping.catalog.lookup": [
    {
      "version": "2026-08-25",
      "spec": "https://ucp.dev/2026-08-25/specification/catalog/lookup",
      "schema": "https://ucp.dev/2026-08-25/schemas/shopping/catalog_lookup.json"
    }
  ],
  "dev.shopify.catalog.global": [
    {
      "version": "2026-08-25",
      "spec": "https://shopify.dev/docs/agents/catalog/global-catalog",
      "schema": "https://shopify.dev/ucp/schemas/2026-08-25/shopify_catalog_global.json",
      "extends": ["dev.ucp.shopping.catalog.lookup", "dev.ucp.shopping.catalog.search"]
    }
  ]
}

The base capabilities give you the tools, and the extension adds Shopify’s fields, such as each variant’s seller. Without the extension, results come back without those fields. An extension whose base capability is missing is dropped. Start from the full sample, take out what your app doesn’t use, and send your profile’s URL in place of the sample’s. Profiles on shopify.dev covers the format.

Get an API key

Calls without credentials get the Catalog’s lowest rate limits. A token from an API key gets the highest. The key identifies your app to Shopify. It doesn’t sign a shopper in, and it isn’t a Shop account token.

  1. In the Dev Dashboard, go to Catalogs and click Get an API key.
  2. Keep the client ID and client secret on your server.
  3. Exchange them for a token:
JavaScript
async function getCatalogToken() {
  const response = await fetch("https://api.shopify.com/auth/access_token", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      client_id: process.env.CLIENT_ID,
      client_secret: process.env.CLIENT_SECRET,
      grant_type: "client_credentials",
    }),
    signal: AbortSignal.timeout(15000),
  });
  if (!response.ok) throw new Error(`Token HTTP ${response.status}`);
  const { access_token } = await response.json();
  if (!access_token) throw new Error("Token response has no access_token");
  return access_token;
}

Send the token in an Authorization: Bearer header on each Catalog call, and keep sending the profile in the body. The token lasts 60 minutes, so get a new one before then. Keep it in server memory, and keep the key and token out of browser code and logs.

Shopify also has a middle tier for apps that sign their requests instead. Auth and rate limiting on shopify.dev covers all three.

Build on it

  • Call the Catalog from your backend, with a timeout on each request.
  • On HTTP 429, wait before you try again, for as long as Retry-After says when the response has one.
  • Don’t cache results. They reflect each merchant’s current pricing, availability and presentation.
  • Show product images from the URLs the Catalog returns, next to the product they belong to. Don’t download them to your servers.
  • Some fields are inferred by Shopify’s AI, so they can be missing or less accurate. The Catalog reference marks them as inferred.

Shopify’s Catalog usage guidelines has the full rules.