Every Tracksuit API error comes back in the same JSON shape, with the same fields, on every endpoint. Once you handle that shape once, you handle errors everywhere. This guide shows you the envelope, the status codes you'll see, and the stable error_code values you can branch on in code.
Try it in the browser first. Our interactive API documentation lets you paste in your key and run live requests against every endpoint — including the ones that fail — so you can see real error responses before you write any code.
The error format
Whenever a request fails, the body is a JSON object with three fields:
{ "code": "400", "message": "Pagination cursor has expired", "error_code": "cursor_expired" }Field | Type | Use it for |
| string | The HTTP status code, as a string. Matches the response's status. |
| string | A human-readable explanation. Good for logs — don't match on its exact text, it can change. |
| string or null | A stable, machine-readable identifier for the specific problem. This is the field to branch on in code. |
Branch on error_code, not message. error_code is a contract — the values below are stable and safe to write if statements against. message is for humans and may be reworded at any time. error_code is null only for a few generic errors that carry nothing beyond the HTTP status.
HTTP status codes at a glance
Status | Meaning | Should you retry? |
| Bad request — something in your parameters or pagination cursor is wrong. | No. Fix the request first. |
| Authentication required — missing, invalid, or expired API key. | No. Check your |
| Forbidden — your key can't access this resource or feature, or the key has been revoked. | No. See the access notes below. |
| Not found — the resource (e.g. a category view ID) doesn't exist or isn't visible to your key. | No. |
| Validation failed — the request was syntactically valid but semantically wrong. | No. Fix the flagged fields. |
| Rate limit exceeded — too many requests. | Yes — wait, then retry. See rate limits. |
| Internal server error — something went wrong on our side. | Yes — retry with backoff. |
| Service unavailable / gateway timeout — an upstream dependency was slow or down. | Yes — retry with backoff. |
Rule of thumb: 4xx means you need to change something (don't retry the same request); 5xx and 429 are transient (retry with backoff). The one exception is pagination 400s — those are recoverable by restarting the pull, covered below.
400 vs 422 — a quick example. A 400 means we couldn't parse your request — e.g. filters=["Age 18-24"] with no : separator returns invalid_filter_string, or smoothing=13mo returns invalid_smoothing_value. A 422 means we parsed it fine but a value is wrong — e.g. a missing start_period, or page_size=5000 (above the max), returns validation_failed with one details[] entry per bad field.
The error_code catalog
These are the stable codes the API can return. Group your handling by what you'd do about each one.
Request errors (400)
| What happened | What to do |
| The request is malformed in a general way. | Check the |
| A | Fix the filter syntax. See How to: Filter by demographics. |
| The smoothing parameter isn't a valid | Use a supported smoothing value. |
| The pagination token is malformed or was modified. | Pass the token back exactly as received; don't edit it. |
| The pagination token is older than 30 minutes. | Restart pagination from the first page. |
| A filter or query parameter changed mid-pagination. | Keep parameters identical across pages, or start fresh. |
| The token was issued by a different endpoint. | Use a token only against the endpoint that returned it. |
| The token uses an older, retired format. | Restart pagination to get a current token. |
All cursor errors have one simple recovery: restart the pull from page 1 with your original parameters. Full detail in How to handle pagination.
Access errors (403 / 404)
| Status | What happened | What to do |
|
| The endpoint or feature isn't enabled for this category view. | This data isn't part of your subscription for that view — contact your Brand Champion. |
|
| The resource doesn't exist, or your key can't see it. | Check the ID. Remember a key only sees category views its user has dashboard access to. |
Revoked keys return 403, not 401. A key whose signature is still valid but that has been revoked or deactivated on our side is denied at the gateway — a 403 with no error_code (same bare shape as a 401). If a key that used to work suddenly returns 403 on every endpoint, it has likely been revoked; re-authenticate with a fresh key.
Validation errors (422)
A 422 means the request was understood but failed validation. The body is the same error format, plus a details array pinpointing each bad field:
{ "code": "422", "message": "Validation failed", "error_code": "validation_failed", "details": [ { "field": "start_period", "message": "Field required" }, { "field": "page_size", "message": "Input should be less than or equal to 1000" } ] }
| What happened | What to do |
| One or more fields are missing or invalid. | Read |
| A dimension you supplied doesn't match any available filter data. | Check the dimension name against the view's available filters. |
Rate limiting (429)
A 429 means you've exceeded your rate limit. Unlike the other errors, its body doesn't use the standard format — there's no code or error_code, just a message .
{ "message": "Too Many Requests" }
Back off and retry. Unlike some APIs, there's no retry_after to read, so wait using exponential backoff (e.g. 1s, 2s, 4s, …) before retrying, and pace bulk pulls rather than firing requests as fast as you can. Branch on the 429 status code, not the body. See Tracksuit API rate limits for the current limits.
Server errors (500 / 503)
| Status | What happened | What to do |
|
| An unexpected error on our side. | Retry with backoff. |
|
| A dependency the API relies on failed. | Retry with backoff. |
|
| The upstream data service was unreachable. | Retry with backoff. |
A resilient error-handling pattern
Most integrations need only three branches:
2xx— success, process the body.Retryable (
429,500,503,504) — wait and retry with exponential backoff. Give up after a few attempts.Everything else (
4xx) — don't retry blindly. Logerror_codeand fix the request. Cursor400s are the one recoverable case: restart pagination from page 1.
Log error_code and code together. When something breaks in production, those two fields tell you exactly what happened and whether it's your side or ours — far more useful than the human message alone.
Common pitfalls
Matching on message text. The wording can change without notice. Branch on error_code (or the HTTP status); use message only for logs and humans.
Retrying 4xx errors unchanged. A 400, 403, 404 or 422 won't fix itself — retrying the identical request just wastes calls against your rate limit. Fix the request instead.
Retrying a 429 immediately. There's no retry_after to tell you how long to wait, so hammering the API again straight away just wastes calls against your limit. Back off exponentially before retrying.
