<!-- Generated by scripts/gen-docs.ts from @bfl/contract. Do not edit. -->

# Errors

Branch on `error.code`, never on `error.message`. Full list: https://betterfans.link/docs/errors

| Code | Status | What to do |
| --- | --- | --- |
| `invalid_parameter` | 400 | A query parameter has a value the route does not accept, a required one is missing, or the body does not match. `error.param` names the parameter. Fix the value and send the request again. Unknown query parameters are ignored, so a misspelled name has no effect instead of failing. |
| `invalid_cursor` | 400 | A cursor belongs to one list with one set of filters. Pass `nextCursor` back unchanged with the same parameters, or leave `cursor` out to start again from the first page. |
| `idempotency_key_required` | 400 | Every `POST /v1/accounts/{accountId}/actions` needs an `Idempotency-Key` header. Generate a UUID for each action you intend and send the same value when you retry that action. |
| `method_not_allowed` | 405 | Call OnlyFans and Call Fansly only read. To send, take back or change anything, create an action instead. It waits for a person to approve it, unless the key has auto-approve on. |
| `not_supported_on_platform` | 400 | The account's platform does not support this route, action type or tool. Each route in the API reference lists its platforms, and `GET /v1/accounts` shows each account's `platform`. For an action, `error.param` is `type`. Sending the same request again fails the same way. |
| `missing_api_key` | 401 | Send the key as `Authorization: Bearer <key>` or in the `x-api-key` header. |
| `invalid_api_key` | 401 | No key matches. Check that the whole key was copied and that nothing was added around it. Keys are shown once, so a lost key has to be replaced with a new one. |
| `revoked_api_key` | 401 | Someone revoked this key in the dashboard, or it was rolled and the old key stopped working. Create a new key. |
| `expired_api_key` | 401 | The key passed the expiry date set when it was created. Create a new key. |
| `missing_scope` | 403 | The key has only the `read` scope and the call needs `write`. Create a key with the `write` scope for code that asks for writes. |
| `writes_disabled` | 403 | Writes are switched off for this account. An owner or admin can switch them on in the account's settings in the dashboard. Reads keep working. |
| `missing_permission` | 403 | In the dashboard: your workspace role does not allow this. `error.permission` names the permission. Ask an owner or admin. Over MCP, `search_api` and `describe_endpoint` return it to a test key or to a workspace with no linked account: connect with a live key and link an account first. |
| `dashboard_key_only` | 403 | Only the dashboard runs a pending action, after a person approves it. API and OAuth keys create actions and read their status. An action from a key with auto-approve on runs when the key creates it. |
| `workspace_suspended` | 403 | BetterFans Link suspended live calls for this workspace. Synced reads, test mode and the dashboard keep working, so nothing you stored is lost. Write to hello@betterfans.link to find out why and to have it lifted. Retrying does not help until it is lifted. |
| `quota_exceeded` | 402 | The workspace used every live call its plan includes for this period. `error.limit` says how many were allowed, how many were used and when the period resets in `resetsAt`. Synced reads and test mode keep working. Live calls resume at the reset, or at once when an owner upgrades the plan or allows overage on the Billing page. See plans and limits. |
| `plan_limit_reached` | 402 | The plan does not allow one more of something: an account, a teammate or a webhook endpoint. `error.limit.name` names the limit, with `allowed` and `used`. Nothing you already have is removed. Remove one first, or ask an owner to choose a larger plan on the Billing page. See plans and limits. |
| `account_not_found` | 404 | The account id is not linked to your workspace, your key is limited to other accounts, or a test key asked for an account that is not a sandbox creator. BetterFans Link answers 404, never 403, so it never reveals whether an account exists. List the accounts your key can use with `GET /v1/accounts`. |
| `fan_not_found` | 404 | Fan ids are user ids on the account's platform. Find the fan with `GET /v1/accounts/{accountId}/fans?search=` and use the `id` it returns. |
| `chat_not_found` | 404 | A chat id is the fan's id. Use a fan id from List fans or List chats. |
| `action_not_found` | 404 | Check the id, or list actions with `GET /v1/actions`. |
| `link_not_found` | 404 | Check the id, or create a new link with `POST /v1/links`. |
| `route_not_found` | 404 | The method and path match no route. Paths start with `/v1`. Compare yours with the API reference. |
| `resource_not_found` | 404 | Something the request refers to does not exist. Check every id in the path. |
| `idempotency_conflict` | 409 | You reused an `Idempotency-Key` with a different body. One key stands for one intended action: use a new key for a new action. The same key with the same body returns the original action, so retries are safe. |
| `action_not_pending` | 409 | The action was already approved, rejected, run or it failed. Read its status with `GET /v1/actions/{actionId}`. |
| `action_expired` | 409 | Pending actions expire after 24 hours without a decision. Create the action again with a new `Idempotency-Key`. |
| `account_unavailable` | 409 | The platform is not accepting calls for this account. `error.accountStatus` is one of `needs_relink`, `awaiting_2fa`, `awaiting_selfie`, `restricted` or `disconnected`. Live reads, Call OnlyFans and Call Fansly stop until the account is healthy again, and approved actions cannot run. Reads without `fresh=true` keep working. Do not retry until the status changes: see account status for what fixes each one. |
| `rate_limited` | 429 | The key sent more requests than its rate limit allows. Wait the number of seconds in the `Retry-After` header, then retry. See rate limits. |
| `onlyfans_error` | 502 | OnlyFans answered a live read with an error. Retry after a short wait. If it keeps failing, read the account with `GET /v1/accounts/{accountId}`: its status may have changed. |
| `onlyfans_timeout` | 502 | OnlyFans did not answer in time. Retry later, or leave out `fresh=true` to read synced data. |
| `fansly_error` | 502 | Fansly answered a live read with an error. Retry after a short wait. If it keeps failing, read the account with `GET /v1/accounts/{accountId}`: its status may have changed. |
| `fansly_timeout` | 502 | Fansly did not answer in time. Retry later, or leave out `fresh=true` to read synced data. |
| `internal_error` | 500 | Something failed inside BetterFans Link. In test mode it comes back as `503` while the sandbox is briefly unavailable. Retry with a growing delay. If it keeps happening, send the `requestId` to hello@betterfans.link. |
