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 arequest_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
Retry-After header. Detect this case with errors[0].code === "RATE_LIMITED" and wait _rateLimit.primary.resetIn seconds. See Rate limits.
HTTP 500: unhandled failure
Something failed outside the operation’s own error handling. The body may be empty or may carry amessage. 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 asdata.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_refyou send on create-verification-link. When you send none, it is thepartner_user_idyou sent toPOST /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_idon the session is not checked, so a page visitor never meets a 409.
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:
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 ofcode 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.
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.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.
