# Errors and retries

## Errors

Errors always have the same shape: a stable `code` to branch on, a `message` for people, and often a `hint` that says what to do next.

```json
{
  "error": {
    "code": "invalid_input",
    "message": "A payment for this subscription is going through right now.",
    "hint": "Try again in a minute."
  }
}
```

| Code | Status | Means |
|---|---|---|
| `invalid_input` | 400 | The input or the request doesn't make sense now. `details` lists fields. |
| `unauthenticated` | 401 | Missing, invalid or revoked key. |
| `forbidden` | 403 | The key lacks a permission, or this kind of caller can't run this command. |
| `policy_denied` | 403 | A store rule blocks this change. |
| `not_found` | 404 | No such thing in this store. |
| `plan_stale` | 409 | Something changed since the plan was made. Make a new plan. |
| `plan_not_pending` | 409 | The plan was already applied, rejected, or is waiting for approval. |
| `already_undone` | 409 | That change was already undone. |
| `plan_expired` | 410 | Plans last 15 minutes (72 hours while waiting for approval). |
| `idempotency_key_reused` | 422 | That `Idempotency-Key` was used for a different request. |
| `not_reversible` | 422 | That change can't be undone. |
| `upstream_rejected` | 422 | Shopify refused the change. The message says why. |
| `internal` | 500 | Our fault. Send us the `x-request-id`. |

Every response has an `x-request-id` header. Include it when you contact us.

## Retrying safely

Networks fail. To retry a change without risk of making it twice, send an `Idempotency-Key` header (any unique string up to 255 characters):

```bash
curl -X POST https://regular-subs.fly.dev/api/v1/subscriptions/sub_123/pause \
  -H "Authorization: Bearer $USUAL_KEY" \
  -H "Idempotency-Key: pause-sub_123-2026-11-07" \
  -H "Content-Type: application/json" -d '{}'
```

If a request with that key already succeeded, you get the original response back and nothing happens again. Reusing a key with a different request is refused, so a bug can't quietly apply the wrong change.

Many commands are also harmless to repeat: asking for a frequency a subscription already has returns `"status": "noop"` and changes nothing.

## Pagination

Lists return a page and a `nextCursor`. Pass it back as `cursor` (or `before`, for activity) to get the next page. `null` means you've reached the end.

```bash
curl "https://regular-subs.fly.dev/api/v1/subscriptions?limit=100&cursor=eyJpZCI6..." -H "Authorization: Bearer $USUAL_KEY"
```
