# Error codes

> Every refusal code Willo's MCP server returns and every response a webhook trigger sends, with what each one means.

Generated from Willo's code at each release.

Source: https://www.willo.ai/docs/reference/errors

A refused MCP call returns an error result whose code is one of these. The message beside it always says what to do. Webhook responses are listed below and on [Webhook request format](https://www.willo.ai/docs/reference/webhook).

## Connection refusals

Before any tool runs, the server checks the token and the business. A failed check is an HTTP error whose JSON body is `{"error": code, "message": …}`; the same code is in the `WWW-Authenticate` header.

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `invalid_token` | The Authorization header is missing, or the token is mistyped or unknown. |
| 401 | `no_access` | The person who created the token no longer has access to the business. |
| 401 | `token_expired` | The token's expiry date has passed. Create a new one. |
| 401 | `token_revoked` | The token was revoked on the dashboard. Create a new one. |
| 403 | `instance_inactive` | The business is paused or closed. |
| 403 | `instance_suspended` | The business is suspended. |
| 403 | `origin_not_allowed` | The request came from a web page origin Willo's MCP server doesn't accept. Connect from a desktop or command-line client. |
| 403 | `plan_lapsed` | The business's plan has lapsed, so its agents are paused too. |
| 403 | `plan_required` | The business's plan doesn't include connecting AI agents. An owner or admin can upgrade on the Billing page; existing tokens work again after. |
| 429 | `rate_limited` | Too many requests in the last minute. Wait for Retry-After. |
| 503 | `unavailable` | Willo's MCP server is starting up. Retry in a few seconds. |

## MCP refusal codes

| Code | Meaning |
| --- | --- |
| `approval_pending` | Willo's chat is waiting on a person to answer a card, so a new task would skip it. Ask them to answer it first. |
| `approved_running` | The approved call is still running on Willo's side. Check it with willo_get_approval. Do not call the tool again for it. |
| `binary` | The file is binary and cannot be returned as text. |
| `budget_exceeded` | The business has hit its per-minute limit for this kind of call (site code, backend management or Stripe reads). Wait and retry. |
| `credential_in_source` | Function source holds a hard-coded credential. Read it from an environment secret instead. |
| `docs_unavailable` | willo_docs could not reach the docs site. Retry shortly, or read www.willo.ai/docs directly. |
| `draft_unavailable` | The draft branch could not be confirmed, so nothing was written. Willo never falls back to the live site. |
| `edit_failed` | An edit's old text was not found exactly once in the file. Read the file and retry. |
| `gate_changed` | The rules for the call changed between the approval and the retry. Call again for a new card. |
| `gate_unreadable` | Willo could not read whether the business is drafting, so nothing was written. Retry in a minute. |
| `github_budget` | GitHub is rate-limiting writes for now. Retry in a few minutes. |
| `github_error` | GitHub, which holds the site's code, returned an error. Nothing was written. Retry shortly. |
| `github_not_configured` | Code access is not available on this environment right now. |
| `github_unavailable` | The draft branch could not be read from GitHub just now. Retry in a minute. |
| `handler_refused` | Willo's rules refused the action itself (the same refusal chat would give). The message says why. |
| `internal_error` | Something failed on Willo's side. Retry, and contact support@willo.ai if it persists. |
| `interrupted` | The server restarted while the call ran. Check the result before retrying. |
| `invalid_arguments` | An argument is missing, has the wrong type, or is not one the tool takes. The message names it. |
| `invalid_imports` | The function's import map is not valid JSON. |
| `invalid_package_json` | package.json would no longer be a valid JSON object. |
| `invalid_path` | The path is not a safe path inside the site's code. |
| `is_directory` | The path is a directory, not a file. |
| `lane_busy` | Willo's chat for this business is busy with another turn. Retry shortly. |
| `needs_approval` | A read tool was asked to do something chat would stop for. It is refused rather than run unasked. |
| `no_repo` | The business has no code repository yet. The first build creates it. |
| `no_thread` | The business has no chat thread yet. The owner opens Willo's chat once, then retry. |
| `not_a_file` | The path is a link or submodule, not a regular file. |
| `not_found` | The file, row, function or approval does not exist where you asked. |
| `not_text` | The file is not a text file, so it cannot be edited as text. |
| `offset_past_end` | The line offset is past the end of the file. |
| `pending_approval` | The call needs a person's approval. The result carries a dashboard link and a request id. Call the same tool with the same arguments after they approve. |
| `rank` | The tool changes the business and the person who created the token is a viewer. Acting needs editor access or higher. |
| `rate_limited` | Too many calls in the last minute, for this token or for the business. Wait and retry. |
| `repo_busy` | A site deploy is finishing, and writing to the live site now would break it. Retry in a minute. |
| `scope` | The tool changes the business and the token is read-only. Create a read + act token. |
| `secret_in_content` | The content you are writing holds a value shaped like an API key or token. Secrets never go in files, function source or SQL. |
| `secret_in_message` | The willo_task message holds a value shaped like an API key or token. Remove it. Secrets are added on the dashboard. |
| `sha_required` | Replacing an existing file needs the sha willo_read_file returned for it. |
| `stale_sha` | The file changed since you read it. Read it again and retry with the new sha. |
| `still_saving` | The write is still being saved. Check with willo_read_file before writing it again. |
| `task_running` | This token already has a willo_task in progress. Wait for it with its turn_id. |
| `too_large` | The file or content is over the size the tool accepts. |
| `too_many_cards` | This token already has the most approval cards it may hold open. Ask the person to answer them first. |
| `too_many_changes` | The change touches more packages than one approval card can list. Split it up. |
| `unknown_tool` | No tool by that name exists on the server, or this token cannot see it. |
| `write_capped` | The business has taken the most agent changes it accepts in a minute. Wait and retry. |

## Webhook responses

The body is JSON: `{"status": …}` on success, `{"error": …}` on a refusal.

| Status | Code | Meaning |
| --- | --- | --- |
| 200 | `duplicate` | A repeat of a call already accepted (same Idempotency-Key, or the same body on the same UTC day). No second run. |
| 202 | `queued` | The call was accepted and one run is queued. |
| 400 | `invalid_body` | The body could not be parsed as the Content-Type it declares. |
| 401 | `invalid_secret` | The URL's secret is wrong. |
| 401 | `signature_expired` | The signature's timestamp is outside the allowed window. |
| 401 | `signature_invalid` | The signature does not match the body. |
| 401 | `signature_malformed` | The X-Willo-Signature header is not in the t=<unix>,v1=<hex> format. |
| 401 | `signature_required` | The trigger requires a signature and the call carries no X-Willo-Signature header. |
| 404 | `unknown_trigger` | No active trigger has this URL. It was rotated, revoked, archived, or never existed. |
| 409 | `program_not_active` | The program is paused, still a draft, or no longer uses a webhook trigger. |
| 413 | `payload_too_large` | The body is over the size limit. |
| 415 | `unsupported_media_type` | The Content-Type is not JSON, form-encoded or plain text. |
| 429 | `daily_cap` | The program has reached its runs-a-day limit. Calls run again tomorrow. Sent with `Retry-After`. |
| 429 | `queue_full` | The program has the most events it may hold waiting. Retry after Retry-After. Sent with `Retry-After`. |
| 429 | `rate_limited` | Too many calls in the last minute. Wait for Retry-After. Sent with `Retry-After`. |
| 503 | `unavailable` | Webhook triggers are temporarily unavailable. Retry later, honouring Retry-After when it is sent. |
