Skip to main content
The BotShield API reports most failures inside an HTTP 200 response. If your client checks only the HTTP status, it will treat a rejected request as a success. This page lists the four shapes a failure can take, the codes each operation returns, and a TypeScript helper that turns all of them into one typed error.

The four shapes

HTTP 200 with data.error

data.error sits directly under data on every operation, including the two whose success payload is not double-nested (GET /verification/status and POST /sdk/logout).

HTTP 400: input validation

The request did not match the operation’s input schema: a required field is missing, a value has the wrong type, a string is too long, or a request_id is not a UUID. The operation did not run.
Fix the request. Retrying the same body returns the same error.
Some business rules also use 400, but as data.error.statusCode inside an HTTP 200 response: for example a gate key that does not exist, or a ttl_seconds outside the allowed range. Check both places.

HTTP 403: rate limited

The API answers 403, not 429, and does not send a Retry-After header. Detect this case with errors[0].code === "RATE_LIMITED" and wait _rateLimit.primary.resetIn seconds. See Rate limits.
A 403 inside data.error.statusCode (HTTP 200) is a different error: an origin that is not allowed for a site key, a gate the key may not use, or an action category your agent is not registered for. Only a real HTTP 403 with RATE_LIMITED is a rate limit.

HTTP 500: unhandled failure

Something failed outside the operation’s own error handling. The body may be empty or may carry a message. Retry with backoff. If it persists, contact support with the time of the request and the request_id if you have one.

Status codes by operation

All codes in this table arrive as data.error.statusCode in an HTTP 200 response. Every operation can also return HTTP 400 (input validation), HTTP 403 RATE_LIMITED, and HTTP 500 as described above.

BotShield Gate

Two Gate operations report problems in their normal payload instead of data.error:

The 409 conflict

POST /sdk/create-verification-link answers statusCode 409 in one situation only: the same user has a request for the same gate that is still pending and has not reached its expires_at.
  • The same user means the partner_user_ref you send on create-verification-link. When you send none, it is the partner_user_id you sent to POST /sdk/create-session.
  • Only pending requests block. A request that completed, failed, or expired never blocks a new one, so a user who has just verified can verify again straight away.
  • Requests without a user are never blocked. A call that carries neither value is not checked, and neither is a call that names no scope.
  • The widget is not affected. A browser request made with a site key and no partner_user_id on the session is not checked, so a page visitor never meets a 409.
To clear the conflict, call POST /sdk/revoke-verification with your API key, the same scope, and the user’s reference as partner_user_id. It ends exactly the pending requests that would block a new one and leaves completed and failed requests alone. The response reports how many it ended:
A revoked_count of 0 is a success. It means nothing was pending. Then create the request again. A rejected call does not use up the grant token, so the same bss_… token works for the second attempt until its five minutes run out. A revoked request reads expired on GET /verification/status.

Agents Ask

Cancelling a proposal that already has an outcome is not an error. The response carries the current status and already_resolved: true.

Machine codes

These are the only values of code a partner can receive: For every other error, branch on statusCode. Authentication failures in particular carry only message and statusCode: 401, with no code. The message tells you which check failed (missing header, wrong key format, unknown or revoked agent, wrong environment), so log it.
A 401 on every call usually means the Authorization header is missing the Bearer prefix. The API reads the credential only from Authorization: Bearer <credential>.

What to retry

A typed unwrap helper

This helper calls any operation, handles all four shapes, knows which two operations are not double-nested, and throws one error type.
Use it like this:
The TypeScript SDK throws its own error classes for the HTTP 400, 403, and 500 cases, and returns data.error to you as a value.

Next steps

Rate limits

Limits by key type and a backoff example.

API overview

Credentials, the envelope, and the list of operations.

TypeScript SDK

Error classes thrown by the SDK.

Result states

Verified and Unavailable are results, not errors.