Reference
Error codes
Every refusal code Willo's MCP server returns and every response a webhook trigger sends, with what each one means.
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.
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. |