Commerce MCP
Limits and errors
How much you can call, what the errors mean, and how fresh the data is.
Rate limits#
| Caller | Limit | Counted per |
|---|---|---|
| API keys | 600 requests / minute | Consumer, across all its keys |
| Sandbox consumers | 60 requests / minute | Consumer, across all its keys |
| Directory endpoints (anonymous) | 60 requests / minute | Client IP address, per platform |
| Directory endpoints (anonymous) | 3000 requests / minute | All anonymous callers of one platform together |
| OAuth sign-in | 120 requests / minute | Signed-in user |
| Console playground | 30 requests / minute | Consumer |
A consumer can also have a daily cap covering its keyed and anonymous calls. Your console Overview shows the limits that apply to you. Over any limit, the response is HTTP 429 with Retry-After in seconds.
HTTP errors#
{ "error": "…" }.| Status | error | When | Headers |
|---|---|---|---|
| 401 | authentication_required | No credential was sent to an endpoint that needs one: /api/mcp without a key, or a directory endpoint that is not open to anonymous calls. | WWW-Authenticate: Bearer resource_metadata="…" |
| 401 | invalid_token | The API key is malformed, unknown, revoked or expired, or the OAuth access token is invalid or no longer honored (client, membership or consumer changed). | WWW-Authenticate: Bearer resource_metadata="…", error="invalid_token" |
| 403 | consumer_suspended | The key is valid but its consumer is not active (pending, suspended or revoked). | |
| 403 | consumer_mismatch | The key belongs to another consumer than the /api/mcp/p/{slug} path it was sent to. Send keys to /api/mcp. | |
| 403 | insufficient_scope | An OAuth call used a tool whose scope your consumer has but the token lacks. The body's error_description is "Allow this app to access this data on Aiptimise to use this tool"; re-authorize with the scopes named in the challenge. | WWW-Authenticate: Bearer resource_metadata="…", error="insufficient_scope", scope="…" |
| 404 | unknown_endpoint | There is no directory endpoint /api/mcp/p/{slug} with that slug. | |
| 429 | rate_limit_exceeded | A rate limit was reached. Wait the number of seconds in Retry-After, then retry. | Retry-After: <seconds> |
| 503 | mcp_unavailable | The service is switched off by Aiptimise, or rate limiting is unavailable (the service fails closed rather than serve unmetered). Retry later. |
HTTP 401
content-type: application/json
www-authenticate: Bearer resource_metadata="https://app.aiptimise.com/.well-known/oauth-protected-resource/api/mcp", error="invalid_token"
x-request-id: <uuid>
{
"error": "invalid_token"
}resource_metadata in WWW-Authenticate points OAuth clients to the sign-in metadata (see Authentication and access). ChatGPT only offers sign-in when the challenge arrives in a tool result, so a call carrying ChatGPT's openai/* request _meta gets the same challenge that way instead (HTTP 200):
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"isError": true,
"content": [
{
"type": "text",
"text": "Allow this app to access this data on Aiptimise to use this tool."
}
],
"_meta": {
"mcp/www_authenticate": [
"Bearer resource_metadata=\"https://app.aiptimise.com/.well-known/oauth-protected-resource/api/mcp/p/openai\", error=\"insufficient_scope\", error_description=\"Allow this app to access this data on Aiptimise to use this tool\", scope=\"catalog:read offers:read\""
]
}
}
}No tool requires sign-in today, so the sign-in form of this challenge (“Sign in to Aiptimise to use this tool”) is not sent; only OAuth callers can get the step-up form above.
Protocol errors#
| Case | HTTP | JSON-RPC code |
|---|---|---|
| Body is not valid JSON | 400 | -32700 Parse error: Invalid JSON |
| accept header lacks application/json or text/event-stream | 406 | -32000 Not Acceptable: Client must accept both application/json and text/event-stream |
| Unknown method (for example resources/list) | 200 | -32601 Method not found |
Tool errors#
result.isError: true and the message in content[0].text. Show the shopper a plain explanation; do not retry unchanged.| Case | Message |
|---|---|
| The store or product does not exist, is not served to you, belongs to another store, or is in a restricted category. The answer is the same in every case, so IDs cannot be probed. | The requested store or product is not available. |
| A tool refused its input; the message says what to change. | This product has 4 variants; pass variant_id. Valid IDs: demo-summit-rain-jacket:demo-variant-1-1, demo-summit-rain-jacket:demo-variant-1-2, demo-summit-rain-jacket:demo-variant-1-3, demo-summit-rain-jacket:demo-variant-1-4 |
| Arguments do not match the input schema. | MCP error -32602: Input validation error: Invalid arguments for tool get_store_info: Invalid UUID at store_id |
| The tool does not exist, or your scopes do not include it. | MCP error -32602: Tool place_order not found |
An unexpected failure answers “The request could not be completed. Please try again later.” Retry later, and quote the response's X-Request-Id if it persists.
Freshness: as_of, source and valid_until#
- Search results and variants report price and availability from the last catalog sync; each search result has its
as_oftime. get_live_price_and_inventoryreturnssource:livewhen it read the merchant's platform just now, orsnapshotwhen it used the last catalog sync. Live reads are supported for Shopify stores; if one takes longer than 700 ms, the snapshot is returned instead.- A live offer has
as_ofandvalid_until, 30 seconds later. The same offer is served from cache until then. Do not show it aftervalid_until; call again. - Show prices with their
as_oftime. The data-use terms set how long you may cache catalog content.