Errors
Every status code, what causes it, whether you were charged, and whether retrying helps.
Two kinds of failure, and they are not the same thing: the request failed, or the agent failed. Only one of them is a non-200.
- The request failed — bad key, bad JSON, no such agent, no credits.
Non-200, an
errorstring, and nothing was charged. - The agent failed — it ran and did not produce a result. HTTP
200,success: false, and your credits are already back in your balance.
{
"success": false,
"error": "The model refused this request.",
"credits_used": 0,
"credits_remaining": 497
}{
"error": "Not enough credits. This run costs 3 credits.",
"credits_remaining": 1,
"credits_required": 3
}The one-line rule
if (!res.ok || !body.success) — both, every time. Checking only the status
code makes a failed agent look like a success and hands your users an empty
result.
Every status code
| Code | What it means | What to do |
|---|---|---|
200 | Handled. Read success | Branch on success, not on the status |
400 | Invalid JSON, missing agent, missing input, or a required field absent | Fix the payload. Retrying the same body fails identically |
401 | No Authorization header, wrong format, or a key that is invalid or revoked | Send Bearer amnt_sk_.... Check for a trailing newline in the env var |
402 | Not enough credits for this run | Top up. credits_required says how many are needed |
404 | That agent does not exist, or is no longer live | Confirm handle/slug via the list endpoint |
422 | A value broke a rule — too long, not an allowed choice, or a bad image URL | The message names the field. Nothing was charged |
429 | More than 60 requests in a minute on this key | Sleep for Retry-After seconds, then retry. Safe to retry |
500 | Misconfigured agent, or a fault on our side | Retry once. Nothing was charged |
503 | The agent's owner could not be verified, so it refuses to run | Retry in a few minutes. This fails closed on purpose rather than paying the wrong account |
What is safe to retry
| Codes | |
|---|---|
| ✅ Yes | 429, 503, one 500, and network errors before a response arrives |
| 🚫 No | 400, 401, 404, 422 — the same request fails the same way |
| ⚠️ Careful | success: false. Usually the input was wrong. Retrying costs credits again and often fails again — change something first |
There is no idempotency key yet
If a request times out client-side after the run started, the agent still
finished and the charge stands — a retry is a second run and a second
charge. Log credits_remaining from every response so you can tell a double
charge from a lost one.
Error messages are for people
Messages are written in plain words and are safe to show to your users. They
are not stable identifiers — never match on the text. Branch on the
status code and on success.
const res = await fetch(url, options);
const body = await res.json().catch(() => ({}));
switch (res.status) {
case 200:
if (!body.success) throw new AgentFailed(body.error); // refunded already
return body.output;
case 402:
throw new OutOfCredits(body.credits_required, body.credits_remaining);
case 429:
await sleep(Number(res.headers.get("retry-after") ?? 60) * 1000);
return retry();
case 401:
throw new BadKey("Check AMNT_API_KEY");
default:
throw new Error(body.error ?? `amnt returned ${res.status}`);
}