# Webhook Jar: error handling > Every error the Webhook Jar API (https://webhookjar.com/v1) and its catch URLs can answer, when each happens, and whether to retry. The full reference is at https://webhookjar.com/llms.txt. ## Error handling ### The error shape Every API answer that is not 2xx is JSON with one `error` object: ```json { "error": { "code": "not_found", "message": "No inbox ib_zz01 on this account" } } ``` - Branch on `error.code`. It is stable and never changes meaning. - Show `error.message` to a person, or log it. It is plain English, names the field or value at fault, and may be reworded. - Unknown fields may be added to answers later. Ignore what you do not know. ### Every API error code | HTTP | code | When | What to do | |---|---|---|---| | 400 | `bad_request` | A parameter or the body is missing, not JSON, or the wrong type or range. The message names the field. | Fix the request. Do not retry it unchanged. | | 401 | `no_key` | No `Authorization: Bearer wj_live_...` header, or the key was rolled or revoked. | Get a key at https://webhookjar.com/account/api. Do not retry with the same key. | | 403 | `plan` | A Solo feature (replay, forwarding) called with a Free key, or all of the plan's inboxes (3 on Free, 25 on Solo) are in use. | Delete an inbox, upgrade at https://webhookjar.com/pricing, or skip the feature. Do not retry unchanged. | | 404 | `not_found` | No such inbox or request on this account, an inbox that expired or was closed, or no such endpoint. | Check the id. Ids are only visible to the account that owns them. | | 405 | `method` | The endpoint exists but not with this method. The `Allow` header lists the methods it takes. | Use a method from `Allow`. | | 429 | `slow_down` | Over the per-minute or per-day call limit, or over 300 new inboxes an hour. | Wait the `Retry-After` seconds, then retry. Refused calls do not count against the day. | | 500 | `server` | Something failed on our side. It is logged. | Retry after 1, 2, then 4 seconds. Stop after 3 tries. | Success codes that are not errors: - `201` from `POST /v1/inboxes`: the inbox was made. - `204` from `DELETE`: done, no body. - `204` from `GET /v1/inboxes/{id}/next`: nothing arrived within `wait` seconds. Not an error: call again, or fail your test. ### Retry rules, in short 1. Retry only `429`, `500` and, on catch URLs, `503`. 2. On `429` and `503`, wait exactly the `Retry-After` header (seconds), then retry once. 3. On `500`, back off 1, 2, 4 seconds; stop after 3 tries. 4. Never retry `400`, `401`, `403`, `404` or `405` without changing the request. 5. `POST /v1/inboxes` is not idempotent: a retry after a timeout can make a second inbox. List inboxes first if that matters. 6. `DELETE` is safe to retry: a second call answers `404 not_found`. ### Limits and the headers that report them | Plan | Keys | Calls a day | Calls a minute | New inboxes by API an hour | |---|---|---|---|---| | Free | 1 | 1,000 | 30 | 300 | | Solo | 5 | 25,000 | 300 | 300 | Calls are counted per account, across all its keys. The day starts again at 00:00 UTC. Every answer carries: - `X-RateLimit-Limit`: calls allowed a minute. - `X-RateLimit-Remaining`: calls left this minute. - `X-Calls-Day-Limit`: calls allowed a day. - `X-Calls-Today`: calls made today. - `Retry-After` (on `429` only): seconds to wait. ### Errors, endpoint by endpoint Every endpoint can also answer `401 no_key`, `405 method`, `429 slow_down` and `500 server`. - `POST /v1/inboxes`: `400` when the body is not JSON or `name` is not a string. `403 plan` when the plan's inboxes are all in use (3 on Free, 25 on Solo). `429` over 300 new inboxes an hour. - `GET /v1/inboxes`: no others. - `GET /v1/inboxes/{id}`: `404` when the inbox is not yours, expired or closed. - `PATCH /v1/inboxes/{id}`: `404` as above. `400` for an unknown field (only `name`, `answer` and `forward` change), `name` not a string, `answer` not an object, `answer.status` not one of 200, 201, 202, 204, 400, 401, 403, 404, 409, 410, 422, 429, 500, 502, 503, `answer.delay` not one of 0, 2, 10, 30 seconds, `answer.body` not a string or over 2,000 characters, `forward` not an object, or a forward address that is not public http or https (a private network, localhost, or this site). `403 plan` for `forward` on Free. - `DELETE /v1/inboxes/{id}`: `404` as above. - `GET /v1/inboxes/{id}/next`: `404` as above. `400` when `wait` is not a whole number from 0 to 25, or `after` is not a request id like `rq_1a2b`. `204` when nothing arrives in time. - `GET /v1/inboxes/{id}/requests`: `404` as above. `400` when `limit` is not 1 to 100, or `before` is not a request id. - `GET /v1/requests/{id}`: `404` when the request is not in one of your inboxes. - `DELETE /v1/requests/{id}`: `404` as above. - `POST /v1/requests/{id}/replay`: `404` as above. `403 plan` on Free. `400` when the inbox has no forward address. A `200` still reports a failed delivery: read `delivery.status` (`ok` or `failed`) and `delivery.error`. - `GET /v1/usage`: no others. Use it to check a key works. ### Catch URLs (no key) Senders post to `https://webhookjar.com/in/{post_token}` with any method, headers and body. These answers are for the sender, and use a flat shape, `{"error": "code", "message": "..."}`: | HTTP | error | When | What a sender should do | |---|---|---|---| | 200 | none | Caught and stored. The status and body can be changed per inbox with `answer`. | Nothing. | | 204 | none | A browser's CORS preflight (`OPTIONS`). Nothing is stored. | Nothing. | | 404 | `no_such_inbox` | No inbox at this address. | Check the URL. | | 410 | `inbox_expired` | The inbox passed its 48 hours. | Make a new inbox. | | 410 | `inbox_killed` | The inbox was closed. | Make a new inbox. | | 410 | `inbox_full` | An inbox without an account took its 500 requests. | Make a new inbox, or sign in to keep one. | | 429 | `slow_down` | Over 120 requests a minute into one inbox. `Retry-After: 60`. | Retry after a minute. | | 429 | `daily_limit` | The account's daily allowance (5,000 on Free, 50,000 on Solo) is used. `Retry-After` counts to 00:00 UTC. | Retry after midnight UTC. | | 503 | `busy` | Very busy for a moment. Nothing was saved. `Retry-After: 5`. | Send it again. Every major provider does this on its own. | A body over 1 MB is stored cut to 1 MB, and the request shows `truncated: true`.