# Errors

> Every Anytime Capital Payments API error has one JSON shape: a stable code to branch on and a message for people. Status codes, codes and retries.

Source: https://anytime-capital.com/docs/errors · Markdown: https://anytime-capital.com/docs/errors.md · All docs: https://anytime-capital.com/docs/llms-full.txt

Successful requests answer `200`, or `201` when something was created. Every error, on every endpoint, has the same shape: a stable `code` to branch on and a `message` written for people. Validation messages name the field that needs fixing.

**Error response**

```json
{
  "error": {
    "code": "INVALID_INPUT",
    "message": "amountCents: The smallest link is $1.00"
  }
}
```

## Status codes

| Status | Codes | What happened |
| --- | --- | --- |
| 400 | `INVALID_INPUT`, `INVALID_JSON`, `NOT_EDITABLE`, `INVALID_IDEMPOTENCY_KEY` | The request is malformed, or a field is invalid or can't be changed. The message names the field. |
| 401 | `UNAUTHORIZED` | The API key is missing, invalid or revoked. |
| 403 | `NOT_ENROLLED` | Card payments aren't set up for this business yet. |
| 404 | `NOT_FOUND` | No such object for your business, or no such endpoint. |
| 409 | `INVOICE_CLOSED`, `TERMS_URL_REQUIRED` | The request conflicts with the object's state or your settings. |
| 422 | `IDEMPOTENCY_KEY_REUSED` | The Idempotency-Key was already used with a different request. |
| 429 | `RATE_LIMITED`, `TOO_SOON` | Slow down: wait the number of seconds in the `Retry-After` header. |
| 5xx | `INTERNAL_ERROR`, `EMAIL_FAILED` | Something failed on our side. Retrying with the same Idempotency-Key is safe. |

## Error codes

| Code | Meaning and what to do |
| --- | --- |
| `INVALID_INPUT` | A field is missing or invalid, e.g. `amountCents: The smallest link is $1.00`. Fix the named field. |
| `INVALID_JSON` | The body isn't valid JSON. Send `Content-Type: application/json` and a JSON object. |
| `NOT_EDITABLE` | You tried to change a payment link's price or another field that is fixed once a link exists. Create a new link instead. |
| `INVALID_IDEMPOTENCY_KEY` | The Idempotency-Key header is empty, too long or has unprintable characters. Use a UUID. |
| `UNAUTHORIZED` | Send a live key as `Authorization: Bearer ak_live_…`. |
| `NOT_ENROLLED` | Finish verification in the dashboard; the API opens once card payments are set up. |
| `NOT_FOUND` | The id doesn't belong to your business, or the path isn't an endpoint. Unknown paths under `/api/v1` answer this JSON too, never an HTML page. |
| `INVOICE_CLOSED` | The invoice is already paid, refunded or void, so it can't be sent again. |
| `TERMS_URL_REQUIRED` | `requireTermsConsent` needs a link to your terms in Settings → Payments first. |
| `IDEMPOTENCY_KEY_REUSED` | Use a new Idempotency-Key for a new request. |
| `RATE_LIMITED` | The key made too many requests this minute. Wait for `Retry-After`. |
| `TOO_SOON` | The invoice was already emailed in the last 24 hours. Wait for `Retry-After`. |
| `EMAIL_FAILED` | We couldn't send the email. Try again. |
| `INTERNAL_ERROR` | Something went wrong on our side. Try again with the same Idempotency-Key. |

## Handling errors

- Branch on `error.code`, never on the message text, which may be reworded.
- Show `error.message` to the person who can fix it; it is written to be read.
- On `429`, wait the number of seconds in `Retry-After`, then retry.
- On `5xx` or a network failure, retry the same request with the same `Idempotency-Key`: you get the original result if the first attempt went through.
- Don't retry other `4xx` errors unchanged: fix the request first.

**A small client with retries (Node.js 18+)**

```javascript
const API = "https://anytime-capital.com/api/v1";

export async function anytime(path, { method = "GET", body, idempotencyKey } = {}) {
  for (let attempt = 1; ; attempt++) {
    const res = await fetch(API + path, {
      method,
      headers: {
        Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`,
        ...(body ? { "Content-Type": "application/json" } : {}),
        ...(idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {}),
      },
      body: body ? JSON.stringify(body) : undefined,
    });
    const data = await res.json();
    if (res.ok) return data;

    const retryable = res.status === 429 || res.status >= 500;
    if (!retryable || attempt === 3) {
      const error = new Error(`${data.error.code}: ${data.error.message}`);
      error.code = data.error.code;
      error.status = res.status;
      throw error;
    }
    const wait = Number(res.headers.get("Retry-After") ?? 2 ** attempt);
    await new Promise((resolve) => setTimeout(resolve, wait * 1000));
  }
}
```

---

Anytime Capital is a fintech company, not a bank. Payment processing, banking and payout services are provided by Anytime Capital's licensed partners. Rates shown are for US card payments; international cards, currency conversion and disputes may carry additional fees shown in your dashboard before you accept a payment.
