Tool errors
Every error from connecting, syncing and calling a tool endpoint, with its cause and fix.
Where errors show
| Place | Shows |
|---|---|
| The response to opening the route | Connect errors |
| The endpoint row under Endpoints on Agent → Tools, after Sync failed: | Sync errors |
median tools list and GET /v1/tool-endpoints |
The same sync errors, per endpoint |
median tools endpoint sync, POST /v1/tool-endpoints/sync, Sync and Change endpoint on the endpoint row |
Sync request errors |
Tool call entries on Logs, request cards in the thread, median tools test |
Call errors |
| Your route’s HTTP responses | Route responses |
| Your server’s console when the route loads | Load errors |
Connect errors
Opening the route with Authorization: Bearer $MEDIAN_KEY connects it. A
success looks like this:
{
"connected": true,
"endpoint": "https://acme.com/api/median",
"tools": 2,
"message": "Connected. Median is syncing 2 tools."
}
tools counts the tools in your config. The sync runs after the response, so
check the endpoint row or median tools list for the result.
| Code | Status | Message | Fix |
|---|---|---|---|
unauthorized |
401 | Send Authorization: Bearer $MEDIAN_KEY to connect this endpoint. Manifest reads and tool calls require a valid median-signature. | Send the same key the server holds. The route also answers this when MEDIAN_KEY is not set on the server |
invalid_median_key |
500 | Median: MEDIAN_KEY must start with median_key_. Copy the Median key from Settings under API. | Use the Median key, not the browser key |
invalid_median_key |
500 | Median: MEDIAN_KEY is malformed. Copy it again from Settings under API. | Copy the whole key again from Settings → API |
median_endpoint_unknown |
400 | Median: MEDIAN_TOOLS_URL is not a URL. Set it to this route’s public address, like https://example.com/api/median. | Set MEDIAN_TOOLS_URL to a full URL, such as https://acme.com |
median_unreachable |
502 | Median could not be reached to connect this route. Check the network and open it again. | Allow outbound HTTPS from your server to api.median.sh |
invalid_api_key |
401 | That Median key does not match any organization. Copy the median_key_ key again from Settings under API. | The key was revoked. Create a key on Settings → API and deploy it |
legacy_api_key |
400 | This is a legacy API key. Create a Median key in Settings under API, set it as MEDIAN_KEY, and try again. | Create a Median key |
missing_median_key |
400 | Create a Median key in Settings under API before connecting tools. That key belongs in MEDIAN_KEY on your server. | Seen from Add endpoint or the CLI. Create a key first |
invalid_url |
400 | Use an HTTPS URL, or HTTP on localhost for testing. | Use https:// |
invalid_url |
400 | That address points inside a network, not at the internet. | Use a public address, or a tunnel |
invalid_url |
400 | Endpoint URLs have to be 512 characters or fewer. | Shorten the URL |
invalid_url |
400 | Enter a valid URL. | Fix the URL |
duplicate_endpoint |
400 | An endpoint with this URL is already connected. | Seen from Change endpoint. Remove the other endpoint first |
too_many_tool_endpoints |
400 | You can connect 10 tool endpoints at once. Remove one before adding another. | Remove an endpoint you no longer use |
rate_limited |
429 | Too many requests. Wait a moment and try again. | Wait a minute and open the route again |
median_refused_connection |
Varies | Median refused the connection with HTTP <status>. Check MEDIAN_KEY and try again. |
Check the key and the status |
Sync errors
A failed sync keeps the last good tool set. The agent keeps using it.
| Message after Sync failed: | Cause | Fix |
|---|---|---|
| The endpoint refused the signature. Open the route once to reconnect it with that deployment’s MEDIAN_KEY. | Your route answered 401. It holds a different key than the one this endpoint is bound to | Reconnect |
The endpoint answered <status>. |
Any other non-2xx. 404 is usually the wrong path. 500 is often a missing MEDIAN_KEY |
Check the URL and your server logs |
| The endpoint did not answer with JSON. Is the URL pointing at the route exported by median()? | The URL serves a page, not the route | Point it at the route file’s path |
| The endpoint did not answer. Check the URL and that your server is up. | No answer in 10 seconds, or no connection | Check the URL, DNS and that the server is up |
<origin> is this deployment’s own machine, not yours. Median calls your endpoint from the cloud… |
The URL is localhost |
Use a tunnel |
The endpoint redirected to <url>, which syncs never follow. Point it there instead. |
Your server redirects, often apex to www |
Use the URL in the message |
| The endpoint redirected, which syncs never follow. | A redirect to somewhere Median will not name | Use the final URL |
| That address points inside a network, not at the internet. | The URL is a private address | Use a public address |
| The manifest is too large to read. | Over 500 KB | Cut descriptions or tools |
The tool name "x" already comes from <url>. Tool names have to be unique across every endpoint. |
Another endpoint serves a tool with the same name | Rename one, or remove the old endpoint |
These endpoints would declare N tools together, and the organization limit is 20. |
More than 20 tools across endpoints | Remove tools or endpoints |
tools[0].name "x" is one of the agent’s own tools. Pick another name. |
A reserved name | Rename the tool |
tools[0].description is over 500 characters. |
Description too long | Shorten it |
tools[0].inputSchema has N fields, and the most a tool can take is 20. |
Too many inputs | Split the tool |
tools[0].inputSchema is over 8000 characters. |
Schema too long, often from long field descriptions or enums | Shorten them |
The manifest version is N, and this backend speaks version 1. Update @mediansh/agent-tools or Median, whichever is behind. |
Package and Median disagree | Update @mediansh/agent-tools |
| Could not sync the tools. Try again. | Unexpected failure in Median | Sync again |
| Too many requests. Wait a moment and try again. | Manual syncs are limited to 60 an hour, bursts of 10 | Wait |
A hand-rolled endpoint can also fail on the manifest’s shape. Those messages
name the field, like
tools[2].inputSchema.additionalProperties has to be false. The manifest
format is in the tool endpoint reference.
Sync request errors
Asking for a sync can fail before any sync runs. Over the API,
no_endpoint and endpoint_required return status 400, and
endpoint_not_found returns 404.
| Code | Message | Fix |
|---|---|---|
no_endpoint |
Connect a tool endpoint before syncing. | Connect an endpoint first |
endpoint_required |
Pass the endpoint URL to sync when more than one is connected. | Pass a connected endpoint’s URL with --url <url> on median tools endpoint sync, or ?url= on the request |
endpoint_not_found |
No tool endpoint is connected at <url>. Run GET /v1/tool-endpoints to see the ones that are. |
The url matches no connected endpoint. Check it against median tools list |
endpoint_not_found |
This tool endpoint is no longer available. Refresh the page. | Seen from Sync and Change endpoint. The endpoint was removed. Refresh the page |
Call errors
| Message | Cause |
|---|---|
| The endpoint did not answer in time. | No answer in 10 seconds, or the connection failed |
The endpoint answered <status>. <code>: <message> |
Your route answered non-2xx. The code and message come from your error body |
| The endpoint redirected, which tool calls never follow. | Your server redirected the POST |
| The endpoint answered with too much to read. | The body was over 100 KB |
| The tool failed without saying why. | An error envelope with no message |
<reason>: <detail> |
Your tool returned ok: false, known: false, allowed: false or outcome: "blocked". reason defaults to tool_refused |
<reason>: <detail> The action may have completed. Inspect its state before retrying and reuse the same tool call id. |
Your tool returned outcome: "unknown" |
| The tool is no longer available. | An approved request whose tool was switched off, removed, or moved to another endpoint |
| The tool returned no result. Check whether the action completed before retrying. | An approved call or a teammate run reported nothing for 10 minutes. The hourly check marks it, so it can take up to 70 minutes to show |
| The tool request failed. Check the result before retrying. | Median failed while running an approved call |
What the agent does when a tool fails
| Your endpoint | The agent |
|---|---|
| Returns a result | Answers from it in its own words |
Returns a refusal from the table above, or execute throws |
Tells the customer what your reason or message means, and does not retry |
| Answers non-2xx, times out, redirects, or sends over 100 KB | Is told only that the tool is not answering. It says what it knows for sure and hands off. Your error text does not reach it |
| An approved call fails | The thread shows <tool> did not run: <error> and the conversation turns unread. The agent treats the action as not done and hands off rather than promise it |
Route responses
What median() answers. Every error uses { "error": { "code", "message" } }.
| Code | Status | Cause |
|---|---|---|
method_not_allowed |
405 | Not a GET or POST |
missing_secret |
500 | No MEDIAN_KEY on the server and no key option |
invalid_median_key |
500 | MEDIAN_KEY is malformed |
missing_signature |
401 | No median-signature header. A proxy in front of your server may strip it |
malformed_signature |
401 | The header is not t=<ms>,v1=<hex> |
stale_timestamp |
401 | The signature is more than 5 minutes from your server’s clock. Fix the clock |
invalid_signature |
401 | Median signed with a different key. Reconnect |
invalid_body |
400 | The body is not a JSON object, or has no tool |
unknown_op |
400 | Median asked for something this package version does not know. Update @mediansh/agent-tools |
unknown_tool |
404 | Median’s copy lists a tool your deployed config no longer has. Sync |
invalid_input |
400 | The input failed validation. See below |
execution_failed |
200 | execute threw. The message goes back, the stack trace stays on your server |
diagnostics_unsupported |
200 | Median asked for server diagnostics and the config has none. Not a fault |
invalid_input messages name the first failing field: x is required.,
x must be a string., x must be a number., x must be a boolean.,
x must be one of: a, b., Unexpected field "y". This tool's input allows: x.
Validation does not coerce types, and null counts as absent.
Load errors
These throw when the route module loads, so every tool on the route is down until you fix them.
| Message | Fix |
|---|---|
Median: the tool name "x" is invalid. Names start with a letter… |
A letter, then letters, numbers and underscores, up to 64 characters |
Median: the tool "x" has no description… |
Add one sentence |
Median: the tool "x" has risk "y". Use “low”, “medium”, “reviewed” or “high”. |
Fix the risk |
Median: the field "k" on "x" was not built with the schema builder… |
Use p.string(), p.number(), p.boolean() or p.enum() |
Median: the tool "x" has no execute function. |
Add execute |
Median: two plugins both define a tool called "x". Take one of them out. |
Remove one plugin |
Median: your tool "x" has the same name as one from the navigation plugin. Rename yours, or drop the plugin. |
Rename your tool |
navigation() has its own load errors. See Pages and highlights.
Approval and run errors
| Code | Message |
|---|---|
approval_settled |
This request has already been reviewed. |
approval_expired |
This request expired because the conversation ended. |
approval_expired |
This request expired without a decision. |
approval_not_found |
This request is unavailable in your inbox. |
tool_not_found |
<tool> is not one of your switched on tools. |
tool_not_found |
<tool> is unavailable or disabled. Check its settings in Agent > Tools. |
invalid_input |
Names the field, like “orderNumber is needed to run this.” or “days has to be a number.” |
too_many_running |
Three tools are already running in this conversation. Wait for one to finish. |
conversation_archived |
Move this conversation out of the archive before running a tool. |
invalid_request |
Use conversationId or as, not both. A conversation uses its stored visitor identity. |
The rules behind these are in How tool calls run.
The agent does not use a tool
| Check | How |
|---|---|
| The endpoint synced | The row under Endpoints on Agent → Tools, or median tools list |
| The tool is on | The tool’s switch on Agent → Tools, or median tools enable <name> |
| The conversation started after your deploy | Open conversations keep the last sync. Press Sync in the menu at the end of the endpoint’s row |
| The description says when to use it | The agent picks tools by their description |
| The turn had calls left | The agent sends 3 calls per turn |
Common setups
The tunnel URL changed
Median calls your route from the cloud, so a local server needs a tunnel. Each new tunnel hostname connects as a new endpoint. Its tools then clash with the old endpoint’s, and its sync fails with “The tool name “orderStatus” already comes from …“.
Set the address in the server's environment
MEDIAN_TOOLS_URL=https://your-tunnel.example.comRestart the dev server so it reads the new value.
Open the route once
curl -H "authorization: Bearer $MEDIAN_KEY" http://localhost:3000/api/medianRemove the old endpoint
Use Remove endpoint in the menu at the end of the endpoint’s row, or
median tools endpoint remove <id>.
Change endpoint on the old endpoint also works, and keeps each tool’s switch.
Preview deployments add endpoints
Every preview URL that opens the route with the bearer header becomes an endpoint. Its tools clash with production’s, the organization fills toward 10 endpoints, and every endpoint syncs before each new conversation’s first reply. A preview behind a login answers 401 or redirects, and its sync fails.
- Open the route only from production.
- Remove preview endpoints on Agent → Tools.
You rotated the Median key
Median signs calls to an endpoint with the key that connected it. After you
change MEDIAN_KEY, your route rejects those signatures until you reconnect.
Create the new key
Deploy it
MEDIAN_KEY on your server.Open the route once with the new key
curl -H "authorization: Bearer $MEDIAN_KEY" https://acme.com/api/medianRevoke the old key
If you revoke first, calls fail with invalid_signature and syncs show “The
endpoint refused the signature” until you open the route again.
navigation() finds no pages
On an edge runtime there is no file system to read routes from. The route
logs “Median: navigation() found no pages, so the agent has no way to move
anybody.” and serves no goToPage tool. Pass routes to navigation(), or
run the route on Node 20.16 or later, or Bun. See
Pages and highlights.