Skip to main content

Error response format

When an error occurs, data is null and the errors array contains one or more error objects:
{
  "data": null,
  "meta": {
    "request_id": "req_abc123",
    "api_version": "v2"
  },
  "errors": [
    {
      "code": "RATE_LIMIT_EXCEEDED",
      "message": "Rate limit exceeded. Retry after 42 seconds.",
      "field": null,
      "retryable": true,
      "retry_after_seconds": 42
    }
  ]
}

Error codes

CodeHTTPDescriptionRetryable
AUTHENTICATION_REQUIRED401Missing or invalid API keyNo
INSUFFICIENT_SCOPE403Key lacks the required scopeNo
MERCHANT_NOT_IN_PORTFOLIO403Merchant exists but belongs to a different orgNo
RESOURCE_NOT_FOUND404Merchant, statement, or export not foundNo
VALIDATION_ERROR422Invalid query parameters (bad date, pagination, etc.)No
RATE_LIMIT_EXCEEDED429Over the request limit for your keyYes
UPSTREAM_UNAVAILABLE503Data source temporarily downYes
SERVICE_UNAVAILABLE503Service-level issueYes
INTERNAL_ERROR500Unexpected server errorYes

Retry strategy

For errors with "retryable": true, use exponential backoff:
import time
import requests

def api_request(url, headers, max_retries=3):
    for attempt in range(max_retries):
        resp = requests.get(url, headers=headers)

        if resp.status_code == 429:
            retry_after = resp.json()["errors"][0].get("retry_after_seconds", 60)
            time.sleep(retry_after)
            continue

        if resp.status_code >= 500:
            time.sleep(2 ** attempt)
            continue

        return resp.json()

    raise Exception("Max retries exceeded")

Validation errors

VALIDATION_ERROR responses include a field property indicating which parameter failed:
{
  "errors": [
    {
      "code": "VALIDATION_ERROR",
      "message": "page_size must be between 1 and 200",
      "field": "page_size",
      "retryable": false
    }
  ]
}

Tips

  • Always check errors.length > 0 before accessing data
  • Use meta.request_id when contacting support — it uniquely identifies the request
  • For 429 responses, prefer the retry_after_seconds value over a fixed delay
  • For 503 responses, retry with exponential backoff (1s, 2s, 4s)