Response format, errors & rate limits

These conventions hold across every endpoint.

The response envelope

Successful responses are wrapped in a consistent envelope:

{
  "code": 200,
  "message": "success",
  "data": { }
}
  • code: the HTTP status code, mirrored in the body.
  • message: a short human-readable status.
  • data: the payload (an object, or null).

Lists

List endpoints return the page in results, alongside the total and the window you asked for:

{
  "code": 200,
  "message": "success",
  "data": {
    "results": [ ],
    "count": 128,
    "limit": 10,
    "offset": 0
  }
}

Errors

Errors use the same envelope, reduced to code and message. A retryable error adds error_code and retry_after_s (see 503 below):

{
  "code": 400,
  "message": "Invalid request body"
}

Build error handling around code and message, and route on error_code when one is present.

A request refused because your account doesn’t include a capability it needs answers 403. When that refusal carries an error_code, it is capability_required, or a more specific code documented on that feature’s page. The message says what is missing.

Common status codes: 400 invalid request · 401 authentication failed (check you’re using Authorization: API_KEY, not Bearer) · 402 your account can’t afford this comparison (see Metering & quotas below) · 403 insufficient permissions or API access not enabled · 404 not found · 409 already exists · 413 the request body is larger than the endpoint accepts · 415 the request body was sent compressed; send it uncompressed, with no Content-Encoding header · 422 the request was understood but the file isn’t in a state that can answer it, or a follow-up’s conversation has grown too large to answer (the message says what’s needed) · 429 rate limited, your unit budget is spent, or a follow-up on the same conversation is still being answered · 500 server error · 503 something on our side is temporarily unavailable, so we couldn’t answer about your file. Retry it.

503 is the one to single out: it is the only status here that says the request was fine and the fault is ours. Treat it as retryable, not as a verdict on what you sent. An upload can answer it when we can’t classify the file because a service we depend on is down. A quick search, on a file or a comparison, can also answer it while a capture it needs is still being prepared for querying: that response carries error_code: "capture_preparing", a retry_after_s field and a matching Retry-After header. It is not charged, and the preparation keeps running, so ask again after that many seconds. A quick search also answers 503 with error_code: "semantic_search_unavailable", retry_after_s and Retry-After while semantic search is temporarily unavailable; it is not charged, so ask again after that many seconds. On an installation that has semantic search turned off, the code is semantic_search_disabled and there is no wait to honour: retrying will not change the answer.

413 applies to the JSON endpoints, not to uploads: a call that sends a short document, like creating a comparison, has a size ceiling well above any real request, so hitting it usually means a client is sending something other than the payload it intended. Uploading a file is a different route with a much larger limit.

Pagination

List endpoints page with query parameters:

?limit=<n>&offset=<n>

limit caps the page size (default 10, maximum 100); offset skips ahead. Use the count in the list envelope to know when you’ve read everything.

Rate limits

  • Global: 60 requests per second per IP across the API.
  • Sign-in endpoints: a few requests per minute per IP. These aren’t part of the /api/v1 API you call with a key.

Poll on a sensible interval (see Analysis lifecycle & polling) rather than tight-looping, and back off on 429. A 429 can also come from heavy overall load on the service rather than from your own rate, so retry after a short wait either way.

Metering & quotas

Beyond rate limits, the AI work run on your files is metered against your account in investigation units. Different operations draw down different amounts.

A search or comparison you can’t afford is refused before it runs, so those calls are never billed for work you didn’t have the budget for. Single-file searches are refused with a 429, the same status as a rate limit; comparison endpoints are refused with a 402. Read the message in either case: it tells you what you’ve used, your total, how many units the call needs, and your reset date. That also distinguishes a rate limit, which clears on its own and is worth retrying, from an exhausted budget, which isn’t.

The analysis that runs automatically on an upload draws down units as well. That one is charged after it runs rather than refused up front, so it can take an account past its budget. Check your remaining balance before a bulk upload if that matters to you.

How the budget works and when it resets is covered in Investigation units & quotas.

Next steps