Skip to main content
The API answers with conventional HTTP status codes and one JSON error shape. A status tells you the class of problem. The code inside the body tells you exactly what happened.

The error envelope

Every error response has the same body:
string
required
The stable machine value. Branch on this.
string
required
A sentence for people. The wording can change, so never match on it.
object | array
Extra data for some codes: the fields that failed validation, the id of the resource that already exists, the numbers behind a plan limit. Its shape depends on code. Treat it as optional, even on a code that normally carries it.
string
required
The id of this request. It equals the X-Request-Id response header.
A status is not a code. Several codes share one status, and 409 alone covers many different situations. Always branch on error.code.

Request ids

Every response carries an X-Request-Id header, whether it succeeded or failed. On an error, the same value is request_id in the body. Log the request id with every call you make. When you contact support, quote it. It lets us find the exact request.

Validation errors

400 validation_error means a query parameter, a header or a body field failed validation. details is a list with one entry per problem:
string
The field that failed. Nested fields are joined with dots, as in competitors.0.domain. For a header it is the header’s name, as in Idempotency-Key. It can be empty when the problem is the body as a whole, and it names the pair from/to for invalid_date. Branch on code; use path for display.
string
What was wrong with it.
string
The same, as a sentence.
These detail codes are stable: Other checks report a generic code for the kind of rule that failed, such as too_big for a limit over the maximum. Webhook endpoint and subscription requests add field-level codes of their own, such as url_not_public. Rely on path and on the codes above. Pagination and date windows covers the three window codes.

When part of a write happened

Two responses mean that some of the write was applied. Handle them before anything else.Provisioning a property. POST /orgs/{orgId}/properties creates the own brand first, then links each competitor in turn. A 422 competitor_cap_exceeded, or a 409 whose details carries brand_id, means the brand was created and one or more competitors were not linked. Do not provision again. Read details, then link the missing competitors with POST /orgs/{orgId}/brands/{brandId}/competitors. See Provision a property.Syncing prompts. PUT /orgs/{orgId}/brands/{brandId}/prompts applies rows one at a time. A row that is refused comes back as "action": "failed" with its own error, inside a 200. The rest of the request was still applied. Always check summary.failed. See Sync prompts.
On the partial provisioning responses, details has three fields:
string
The id of the own brand that was created.
object
The created property, in the same shape as data in the 201 response.
array
One entry per competitor domain you sent, each with name, domain and a status: applied, failed or not_attempted. An applied entry carries competitor_id and reused. A failed entry carries error with its own code and message. not_attempted means an earlier competitor hit the plan limit, so this one was never tried.
The other refusals on that endpoint leave nothing behind: 409 brand_exists, 409 external_ref_exists, 422 country_cap_exceeded and 422 brand_cap_exceeded.

Error codes by status

400 Bad Request

401 Unauthorized

402 Payment Required

403 Forbidden

404 Not Found

405 Method Not Allowed

409 Conflict

Inside a prompt sync, prompt_writes_busy is reported per row, as one failed result in the 200.

413 Payload Too Large

422 Unprocessable Entity

A plan limit refused the write. details can carry tier, cap and attempted. Every one of those fields is optional. Read usage on GET /orgs/{orgId} to see what is used against the prompt, own-brand and country limits, and the per-brand competitor limit, before you write. Only active prompts count, so deactivating a prompt frees its place. Plans and limits lists the limits by plan.

429 Too Many Requests

Both codes carry Retry-After. See Rate limits and quotas.

500 Internal Server Error

503 Service Unavailable

Every 503 carries Retry-After.

Which errors to retry

One 400 has an automatic remedy: on invalid_cursor, start the walk again from the first page. A 5xx on a write does not tell you whether the write was applied. Provisioning a property and syncing prompts apply their rows one at a time, so a failure part-way leaves the earlier rows in place. Send an Idempotency-Key with every write, and after a timeout or a 5xx retry with the same key. A create that did land the first time then answers 409 with the id of the existing resource. See Idempotent requests.

Request bodies

Writes take a JSON body of at most 1 MB. A larger body answers 413 payload_too_large, malformed JSON answers 400 invalid_json, and a body on a GET answers 400 body_not_allowed. Unknown body fields are ignored, with two documented exceptions. text on PATCH /orgs/{orgId}/prompts/{promptId} answers 400 text_immutable. The bodies of POST /webhook-endpoints and POST /webhook-endpoints/{endpointId}/subscriptions are strict, so an unknown field there answers 400 validation_error. A request body must arrive in full within 10 seconds. After that the connection is closed without a response.

Handling errors in code

Read the envelope once, keep the code, the details and the request id, and branch on the code.