Pagination & errors

The API uses one list envelope, one cursor-pagination scheme, and one error shape everywhere. Fields are snake_case and dates are ISO-8601 UTC.

The list envelope

Every list endpoint responds with the same shape:

Response
{
  "data": [ ... ],
  "has_more": true,
  "next_cursor": "eyJzb3J0X3ZhbHVlIjoi...",
  "total": 1284
}
  • data — the page of items.
  • has_more — whether another page exists.
  • next_cursor — opaque cursor for the next page; null on the last page.
  • total — total item count, included where it is cheap to compute.

Cursor pagination walkthrough

Lists take ?limit= (default 100, max 500) and ?cursor=. Fetch the first page:

Terminal
curl "https://api.zutrix.com/v1/projects/PROJECT_ID/keywords?limit=200" \
  -H "Authorization: Bearer $ZUTRIX_API_KEY"

If has_more is true, pass next_cursor back verbatim to get the next page:

Terminal
curl "https://api.zutrix.com/v1/projects/PROJECT_ID/keywords?limit=200&cursor=eyJzb3J0X3ZhbHVlIjoi..." \
  -H "Authorization: Bearer $ZUTRIX_API_KEY"

Repeat until has_more is false. Cursors are opaque — do not parse or construct them, and keep the other query parameters identical between pages.

The error envelope

All errors are JSON with the same fields:

Error response
{
  "error": "Analytics is not connected for this project",
  "code": "NO_DATA",
  "request_id": "req_01j8f2c9...",
  "hint": "Connect Google Analytics in the Zutrix dashboard, then retry.",
  "setup_url": "https://zutrix.com/dashboard/project/..."
}
  • error — human-readable message.
  • code — stable machine-readable code (table below). Match on this, not on the message.
  • request_id — unique id for the request, also sent on every response (success or error) as the X-Request-Id header. Include it when contacting support.
  • hint — optional, what to do about it.
  • setup_url — optional, the dashboard page where the missing setup can be completed.
  • details — optional structured context, e.g. per-field entries on VALIDATION_ERROR or { current_usage, limit } on quota errors.

Codes are frozen

Error codes are a stable contract: new codes may be added, but existing ones are never renamed or removed. Build your error handling on code.

Error codes

CodeStatusMeaning
INVALID_INPUT400A parameter or body field is malformed or unsupported.
VALIDATION_ERROR422Request failed schema validation; details[] lists each offending field.
UNAUTHORIZED401Missing, invalid, or revoked API key.
FORBIDDEN403Your key is valid but lacks permission for this resource or action.
NOT_FOUND404The resource does not exist (or is not visible to you).
CONFLICT409The request conflicts with current state (e.g. duplicate resource).
FEATURE_NOT_ENABLED400The feature is not set up for this project; setup_url points at the dashboard page to enable it.
NO_DATA404The feature is available but has no data yet (e.g. analytics not connected, no tracked keywords).
NO_RESULT404The upstream data source has no results for this request.
RATE_LIMITED429Per-account requests/minute exceeded; retry after Retry-After seconds.
QUOTA_EXCEEDED402A plan quota dimension is used up; details holds current_usage and limit.
PLAN_UPGRADE_REQUIRED402Your plan does not include this feature; upgrading unlocks it.
UPSTREAM_TIMEOUT504A data provider took too long; safe to retry shortly.
UPSTREAM_RATE_LIMITED429A data provider is throttling this operation; retry shortly.
UPSTREAM_ERROR502A data provider failed; retry shortly.
INTERNAL500Unexpected server error; report it with the request_id.

Validation errors

VALIDATION_ERROR (422) includes a details array naming each invalid field:

422 Unprocessable Entity
{
  "error": "Request validation failed",
  "code": "VALIDATION_ERROR",
  "request_id": "req_01j8f2c9...",
  "hint": "Fix the fields listed in details and retry.",
  "details": [
    { "path": "limit", "message": "Number must be less than or equal to 500" }
  ]
}