Error Handling
The Merchant Data API returns errors using the standard GraphQL error format. This guide explains the error envelope, error categories, and how to build a resilient integration with retry logic.
The Merchant Data API returns errors in the standard GraphQL errors array. Each error
carries a human-readable message and an extensions object with a deterministic,
stable code and a retryable boolean.
Error shape
{
"errors": [
{
"message": "The date range you requested is wider than the maximum allowed window.",
"extensions": {
"code": "DATE_WINDOW_EXCEEDED",
"retryable": false,
"traceId": "00-3f9a...-01"
}
}
]
}| Field | Meaning |
|---|---|
message | A human-readable description of what went wrong. |
extensions.code | A deterministic, stable error code you can branch on. |
extensions.retryable | true if the request may be safely retried without changes. |
extensions.detail | Optional extra context, such as which filter was invalid. |
extensions.traceId | A trace identifier to quote when contacting support. |
HTTP status codes
Most errors are returned with HTTP 200, in the GraphQL errors array. Two cases
differ:
- HTTP 401 — credential failures (
UNAUTHORIZED). - HTTP 400 — a request body that cannot be parsed as a valid GraphQL request.
Always inspect the errors array even on an HTTP 200 response.
Handling errors in code
Branch on extensions.code, and retry only when extensions.retryable is true.
Python
import time
import requests
RETRYABLE_MAX = 3
def run_query(query, variables=None):
for attempt in range(RETRYABLE_MAX):
resp = requests.post(
"https://api.payretailers.com/data-api/graphql",
auth=("YOUR_SHOP_ID", "YOUR_API_TOKEN"),
headers={"Content-Type": "application/json"},
json={"query": query, "variables": variables or {}},
timeout=30,
)
body = resp.json()
errors = body.get("errors")
if not errors:
return body["data"]
err = errors[0]
code = err["extensions"]["code"]
retryable = err["extensions"].get("retryable", False)
if retryable and attempt < RETRYABLE_MAX - 1:
time.sleep(2 ** attempt) # exponential backoff
continue
raise RuntimeError(f"{code}: {err['message']}")
raise RuntimeError("Exhausted retries")Retryable vs non-retryable
Retryable errors are transient. Wait briefly (ideally with exponential backoff) and
retry the same request:
MERCHANT_CONCURRENCY_LIMIT_EXCEEDEDGLOBAL_CONCURRENCY_LIMIT_EXCEEDEDQUERY_TIMEOUTSERVICE_UNAVAILABLEDATABRICKS_ERRORTOO_MANY_REQUESTS
Non-retryable errors require a change to your request or credentials. Retrying
without changes will fail again.
Error codes
Governance
| Code | Retryable | What to do |
|---|---|---|
PAGE_SIZE_EXCEEDED | No | Lower first to 5000 or fewer and page with after. |
DATE_WINDOW_EXCEEDED | No | Narrow your date range (90 days max; 31 for movement balances). |
INVALID_DATE_RANGE | No | Ensure the "from" date is not after the "to" date. |
QUERY_DEPTH_EXCEEDED | No | Reduce query nesting to 10 levels or fewer. |
Pagination cursors
| Code | Retryable | What to do |
|---|---|---|
CURSOR_EXPIRED | No | Cursors last 4 hours. Restart from the first page. |
INVALID_CURSOR | No | Pass the exact endCursor value; do not modify cursors. |
Authentication and access
| Code | Retryable | What to do |
|---|---|---|
UNAUTHORIZED | No | Send valid Basic auth (Shop ID + API token). Also HTTP 401. |
MERCHANT_ID_MISSING | No | Confirm your credentials belong to an active merchant account. |
FORBIDDEN | No | You requested data outside your account. Queries are auto-scoped. |
Concurrency
| Code | Retryable | What to do |
|---|---|---|
MERCHANT_CONCURRENCY_LIMIT_EXCEEDED | Yes | Reduce parallel requests (limit 10). Retry. |
GLOBAL_CONCURRENCY_LIMIT_EXCEEDED | Yes | Platform-wide; back off and retry. |
TOO_MANY_REQUESTS | Yes | Slow your request rate; retry after a delay. |
Timeouts and infrastructure
| Code | Retryable | What to do |
|---|---|---|
QUERY_TIMEOUT | Yes | Make the query more selective, then retry (15s limit). |
DATABRICKS_ERROR | Yes | Temporary data-platform problem. Back off and retry. |
SERVICE_UNAVAILABLE | Yes | Service temporarily unavailable. Back off and retry. |
INTERNAL_ERROR | No | Not caused by your request. Contact support with the trace ID. |
Validation
| Code | Retryable | What to do |
|---|---|---|
INVALID_FILTER | No | Check filter fields/values. Balance datasets cannot be aggregated. |
VALIDATION_ERROR | No | Review query arguments and filter values, then resubmit. |
NOT_FOUND | No | Verify the identifier is correct and within any date range. |
CONFLICT | No | Review request parameters. If unexpected, contact support. |
Getting support
When you contact support about an error, quote the extensions.traceId value. It lets
the team correlate the request on their side.
_review_needed (from Stage 3): Error messages here are documentation-friendly
paraphrases written from the merchant's perspective. The exact wire text of each
message is not fixed in the source and may differ. Confirm final message wording with
the team before publication.
Next steps
- Troubleshooting — symptom-based debugging.
Updated 23 days ago