Skip to main content

How to handle API errors

A guide to understanding and handling errors from the Tracksuit API.

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

code

string

The HTTP status code, as a string. Matches the response's status.

message

string

A human-readable explanation. Good for logs — don't match on its exact text, it can change.

error_code

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?

400

Bad request — something in your parameters or pagination cursor is wrong.

No. Fix the request first.

401

Authentication required — missing, invalid, or expired API key.

No. Check your Authorization header.

403

Forbidden — your key can't access this resource or feature, or the key has been revoked.

No. See the access notes below.

404

Not found — the resource (e.g. a category view ID) doesn't exist or isn't visible to your key.

No.

422

Validation failed — the request was syntactically valid but semantically wrong.

No. Fix the flagged fields.

429

Rate limit exceeded — too many requests.

Yes — wait, then retry. See rate limits.

500

Internal server error — something went wrong on our side.

Yes — retry with backoff.

503 / 504

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)

error_code

What happened

What to do

bad_request

The request is malformed in a general way.

Check the message and fix the request.

invalid_filter_string

A filters value isn't in one of the accepted forms.

Fix the filter syntax. See How to: Filter by demographics.

invalid_smoothing_value

The smoothing parameter isn't a valid Nmo / Ny value.

Use a supported smoothing value.

invalid_cursor

The pagination token is malformed or was modified.

Pass the token back exactly as received; don't edit it.

cursor_expired

The pagination token is older than 30 minutes.

Restart pagination from the first page.

cursor_filters_changed

A filter or query parameter changed mid-pagination.

Keep parameters identical across pages, or start fresh.

cursor_endpoint_mismatch

The token was issued by a different endpoint.

Use a token only against the endpoint that returned it.

cursor_version_unsupported

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)

error_code

Status

What happened

What to do

feature_not_enabled

403

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.

not_found

404

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" }   ] }

error_code

What happened

What to do

validation_failed

One or more fields are missing or invalid.

Read details[] — each entry names the field and the problem.

unknown_dimension

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)

error_code

Status

What happened

What to do

internal_error

500

An unexpected error on our side.

Retry with backoff.

service_unavailable

503

A dependency the API relies on failed.

Retry with backoff.

upstream_unavailable

503

The upstream data service was unreachable.

Retry with backoff.


A resilient error-handling pattern

Most integrations need only three branches:

  1. 2xx — success, process the body.

  2. Retryable (429, 500, 503, 504) — wait and retry with exponential backoff. Give up after a few attempts.

  3. Everything else (4xx) — don't retry blindly. Log error_code and fix the request. Cursor 400s 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.

Did this answer your question?