All pages

Errors and limits

Status codes, error bodies, rate limits and quota. Which to retry.

Every error has the same shape, and the code is stable enough to branch on.

{
  "error": {
    "code": "quota_exhausted",
    "message": "This month's allowance is used up. It renews on your billing date, or you can upgrade now."
  }
}

Status codes

StatusCodeRetry?
401missing_api_keynoNo key on the request.
401invalid_api_keynoNot recognised. Revoked, or from the other environment.
402quota_exhaustednoOut of credits. Wait for the renewal or upgrade.
422nothing_to_checkno/signup with none of name, email or bio.
422ai_unavailableno"ai": true on /name, /email or /url. No model reads those, and accepting the flag and ignoring it would be a model call you were priced for and never got. Send false or leave it out.
422unknown_rulenoA name inside rules that is not a category, subject, lead type or term list. The message names it and lists what was expected, because a misspelled line acts on nothing and looks exactly like a rule that works.
400invalid_idempotency_keynoThe key is longer than 255 characters.
409idempotency_in_flightyesYour earlier call with this key is still running.
422idempotency_key_reusednoThat key was used for a different body. Details.
422-noValidation failed. The body lists the fields.
422image_too_largenoAn inline picture over the size this endpoint accepts. Send a smaller one, or a URL.
422unknown_projectnoYou named a project this organization does not have. Nothing was judged.
422unknown_policynoYou named a policy this account does not have. Nothing was judged.
404record_not_foundnoNo verdict with that id on this account.
404batch_not_foundnoNo batch with that id on this account.
404key_not_foundnoPOST /keys/{id}/revoke for a key this account does not have.
429too_many_attemptsyesToo many requests with a key we do not recognise, counted by address. Fix the key.
429rate_limitedyesToo many requests. Back off and retry.

402 and 429 are not the same thing

429 means come back in a minute. 402 means come back with a bigger plan. A client that retries both will hammer a 402 forever and never succeed.

This is the one distinction worth getting right in your client, because the correct behaviour is opposite in each case. Retry 429 with backoff. On 402, stop calling and alert somebody: no amount of retrying will produce credits.

A 402 body carries your credit state too, so you can log how long the wait is:

{
  "error": { "code": "quota_exhausted", "message": "..." },
  "credits": {
    "remaining": 5,
    "required": 10,
    "renews_at": "2026-09-30T00:00:00+00:00"
  }
}

required is what the call you just made is estimated to cost: the check, plus what the model would probably use if it reads it. For a batch it is the whole list added up. It is why a 402 can arrive while credits remain: the check prices this call, not one credit. An account with five left cannot start an image call estimated at ten, but the same account can still moderate text without the model, and the message says so. Retrying with "ai": false is often the right move.

You can see all of it, at any time and without spending anything, at GET /api/v1/usage. That endpoint is outside the quota check, so it answers even at zero.

Rate limits

KeyLimit
tf_live_…600 requests per minute
tf_test_…60 requests per minute
No key20 per minute, by IP, and then a 401 anyway
/batch60 per minute (10 on a test key, 5 with no key), in its own bucket
/ping, /usage, /records, /batches, /keys120 per minute, in their own bucket

Each in its own bucket, so a backfill cannot spend the allowance your live traffic needs, and polling a batch or the review queue costs nothing against either.

Counted per key, not per IP. Every call from one customer arrives from the same handful of addresses, so an IP limit either throttles a whole customer at once or is set so high it stops nobody.

Quota and what a call costs

Credits are checked before the work and charged after it, and only for what actually ran. Before the call, its price is estimated, and a call your credits cannot cover is refused without being made. After it, you are charged what it really used. Charging up front would mean billing for a model reading on every request, including the ones the cheap detectors settled, which is most of them.

What ranCredits
The instant detectors1
The model read it as well1, plus the tokens it used, rounded up to the next credit
A repeat answered from cache1

The model's tokens are billed at its rate in credits per million tokens: gpt-5.6-luna is 2,000 in and 12,000 out. A comment comes to about 8 credits in all, a picture to about 10, and longer text costs more.

used_ai in every successful response tells you whether the model read it, and credits.charged says what it actually cost. Test keys run the free checks, never the model, are charged nothing, and are never refused for want of credits.

Content this service has judged before is answered from cache and billed as one check whatever it originally cost, so repeats are cheap. A call that failed is never billed at all, and a retry of one that timed out need not be either, if you send an Idempotency-Key.