# Errors

Adspy API · https://www.adspy.sh/docs/api/errors · Updated October 24, 2026

Every error is a problem details object with a stable code and the request id.

## The format

Errors answer with Content-Type: application/problem+json (RFC 9457) and the HTTP status of their type. The type is the URL of the type's page below; code is the same type as a short string, for your code to switch on.

A 400 answer:

```json
{
  "type": "https://www.adspy.sh/docs/api/errors/invalid-request",
  "title": "Invalid request",
  "status": 400,
  "detail": "Invalid query: query.limit: Too big: expected number to be <=100",
  "code": "invalid_request",
  "request_id": "a91c4e07-2b6d-4f38-8e15-c7d09b3f62a4",
  "instance": "/api/v1/creatives",
  "errors": [{ "path": "query.limit", "message": "Too big: expected number to be <=100" }]
}
```

| Field | Meaning |
| --- | --- |
| type | The URL of the error type's documentation page. |
| title | The type's short title, the same for every occurrence. |
| status | The HTTP status. |
| detail | What went wrong with this request, in English. |
| code | The type as a stable string, such as invalid_request. |
| request_id | The request's id, also in the X-Request-Id header. Quote it to support. |
| instance | The path that was requested. |
| errors | On invalid_request only: each field problem, with its path (query.limit, body.brand_keys.0) and message. |

## Handling errors

- Switch on code, not on detail: detail is for people and can change.
- New codes may be added; treat an unknown code by its HTTP status.
- 5xx answers never carry internal messages, and nothing was charged. A 401 never says whether a key exists.
- Retry only the types marked retryable, after Retry-After when it is sent, with the same Idempotency-Key for a POST.

## Error types

| Status | Code | Meaning | What to do | Retry |
| --- | --- | --- | --- | --- |
| 400 | `invalid_request` | A path parameter, query parameter or body field is missing, unknown or out of range, or the body is not valid JSON. | Read `detail` and `errors` (each with the field's `path`), fix the request and send it again. Unknown parameters are refused rather than ignored. | No |
| 400 | `invalid_cursor` | The `cursor` was not issued by Adspy, was altered, expired after 24 hours, belongs to another organization or another list, or was sent with different filters than the request that returned it. | Send the cursor with exactly the filters of the request that returned it, or start again from the first page without a cursor. | No |
| 400 | `idempotency_key_required` | Every POST needs an `Idempotency-Key` header. | Send a unique key of 8 to 160 visible ASCII characters (a UUID works) with each new POST, and the same key when you retry it. | No |
| 401 | `unauthorized` | The `Authorization` header is missing, or the key is unknown, expired or revoked. | Send `Authorization: Bearer <your API key>`. Create or rotate keys in the Adspy console (API page). | No |
| 402 | `payment_required` | The organization's contract does not cover this request, or a spending limit an organization admin set in Adspy stops it. | An organization owner or admin can check the limits in Adspy (Settings, then Usage), or write to info@adspy.sh. Nothing was charged. | No |
| 402 | `partner_key_cap_reached` | Partner keys only: the credits this key's requests settled this calendar month (UTC), plus the most this request can settle, pass the key's monthly cap. | Stop the metered requests until the 1st of next month (UTC), or ask the partner to raise the key's cap. Nothing was charged; free requests keep working. | No |
| 402 | `partner_tenant_cap_reached` | Partner keys only: the request named a trial cap in `X-Partner-Credit-Cap`, and the credits this tenant settled since it was created, plus the most this request can settle, pass it. The answer carries `cap` and `used`. | Send the request again without the trial cap once the partner's customer has an active plan. Nothing was charged; free requests keep working. | No |
| 403 | `enterprise_required` | The REST API is part of Adspy Enterprise, and this organization has no active Enterprise contract. | Write to info@adspy.sh to enable Enterprise for the organization. | No |
| 403 | `insufficient_scope` | The key lacks the permission this route needs (read, actions or export). | Use a key with that permission, or ask an organization owner or admin to create one. | No |
| 403 | `forbidden` | The credential is recognized but may not make this request, for example a partner key that names another tenant than the one it is pinned to. | Read `detail`, then use a key that may make the request. | No |
| 403 | `not_offered_to_partner` | Partner keys only: exports and usage are not offered to partner keys. Exports settle in the background, after the answer that would bill them; a partner's usage is its own feed. | Page through the lists to build files, and read the key's usage at /api/partner/usage. | No |
| 404 | `not_found` | The route does not exist, or the brand, creative, post, collection, fetch or export is unknown to the key's workspace. | Check the path and the id. Collections, fetches and exports belong to the workspace that created them: use a key of that workspace. | No |
| 405 | `method_not_allowed` | The route exists but not with this HTTP method. | Use one of the methods listed in the `Allow` header. | No |
| 409 | `idempotency_conflict` | This `Idempotency-Key` was already used for a different request, or for a request that failed without a charge. | Use a new key for a new request. Reuse a key only to retry exactly the same request. | No |
| 409 | `operation_in_progress` | A request with this `Idempotency-Key` is still running. | Retry the same request with the same key after `Retry-After` seconds: it returns the first request's result once it is done. | Yes |
| 409 | `conflict` | The resource changed while the request ran (a fetch replaced, a report expired). | Read the resource again, then retry with a new idempotency key. | Yes |
| 413 | `payload_too_large` | The body, the query string or the result passes its size limit. | Send fewer ids per request, or narrow the request. Bodies are limited to 4 MB and query strings to 8 KB. | No |
| 415 | `unsupported_media_type` | A POST or PATCH body was not sent as JSON. | Send `Content-Type: application/json`. | No |
| 422 | `instagram_not_in_api` | The request names Instagram: a platform, a post id, an account, a profile link or an export of Instagram data. Instagram data is not offered through the API. | Use tiktok or youtube. Meta and Google ads stay available, including ads Meta shows on Instagram placements. | No |
| 429 | `rate_limited` | The key sent more requests a minute than its contract allows, or the organization more research reads than its limit (120 reads a minute, or the contract's per-key rate when that is higher), shared by its keys and its MCP connections. | Wait `Retry-After` seconds. The `RateLimit-*` headers on every response show the window. | Yes |
| 429 | `fetch_quota_exceeded` | The organization started its contract's number of new brand or account collections this calendar month (UTC; 200 by default), counted across its API keys and its MCP connections. | Wait for the 1st of next month (UTC), or ask Adspy to raise the contract's limit. Brands and accounts Adspy already has stay readable and are never refused. | Yes |
| 429 | `partner_fair_use` | Partner keys of a client with a fair use policy only: this tenant settled its rolling 48-hour research allowance (`scope` research), or started its rolling 30-day number of new brand or account collections (`scope` collections). The answer carries `scope` and `retry_at`, the exact time the request fits again. | Wait until `retry_at` (also in `Retry-After`, in seconds). Nothing was charged; free requests, and brands or accounts already collected, keep working. | Yes |
| 429 | `fetch_limited` | The organization has three fetches running, or this advertiser was requested in the last hour. | Follow the running fetches with GET /v1/fetches/{id} and retry after `Retry-After` seconds. | Yes |
| 500 | `internal_error` | Adspy failed to answer. Nothing was charged. | Retry with the same idempotency key. If it persists, write to info@adspy.sh with the `request_id`. | Yes |
| 503 | `service_unavailable` | A dependency (the creative library, collection or exports) is temporarily unavailable. Nothing was charged. | Retry after `Retry-After` seconds with the same idempotency key. | Yes |
| 504 | `timeout` | The library query exceeded its time limit. Nothing was charged. | Retry, or narrow the request (fewer filters on free text, a brand, a shorter range). | Yes |