Errors
Applies toEDITED MCPQuery APIJobs API
EDITED's surfaces report failures in two different shapes. MCP answers over JSON-RPC, so a failure can hide in one of three layers — the HTTP status, the JSON-RPC envelope, or the tool payload. The REST APIs (Query and Jobs) report everything through plain HTTP status codes, the way a REST client expects. Read the section for the surface you're on.
MCP
EDITED MCPFailures surface at three layers: the HTTP status (did the request reach the server and authenticate?), the JSON-RPC envelope (did the call itself succeed?), and the tool payload (did the tool run and then refuse?). Check all three — the third is the one a shared response handler usually misses.
HTTP status codes
| Status | Meaning |
|---|---|
200 | The request was processed — but the body may still carry a JSON-RPC error (below) |
400 | The body wasn't parseable JSON, wasn't a JSON-RPC envelope at all, or was a batch array over the 64-envelope cap — the error code is -32700 or -32600 |
401 | Missing access key, or the key isn't enabled for MCP — see Authentication & access |
5xx | Transient server-side failure — safe to retry with backoff |
JSON-RPC errors
A failed call returns an error envelope instead of a result:
{
"jsonrpc": "2.0",
"id": 1,
"error": { "code": -32602, "message": "Invalid request parameters" }
}
| Code | Meaning | Usual cause |
|---|---|---|
-32700 | Parse error | The body isn't valid JSON — arrives at HTTP 400 |
-32600 | Invalid request | The envelope wasn't usable. At HTTP 400, the body is valid JSON but not a JSON-RPC object or batch array — or a batch over the 64-envelope cap. At HTTP 200, a tools/call arrived with no usable name — either omitted outright, or because params was sent positionally and a non-object params is read as empty. Not caused by a missing MCP-Protocol-Version header — see Versioning |
-32601 | Method not found | Wrong method name — e.g. tool/call for tools/call |
-32602 | Invalid params | Malformed params: a non-object arguments, or a tool name whose namespace prefix we don't serve (mdmcp-search_brands, not search_brands) — check the Reference. Arguments that merely don't match a tool's input schema do not land here; that call succeeds at HTTP 200 and carries the refusal in its payload — see below |
-32603 | Internal error | Server-side failure while executing — safe to retry |
-32002 | Resource not found | Unknown resource URI — check the Reference for the ones this server publishes |
-32003 | Not authorised | Your key is valid, but your subscription doesn't include that backend — see below |
-32004 | Backend unavailable | That backend is being shed after repeated failures. Back off substantially longer than for -32603 — retrying quickly only extends it |
-32099 | Rate limited | Too many requests — see Rate limits |
Most tool refusals arrive in the payload, not on isError
When a tool runs and then refuses, the call succeeds at the protocol level and
the refusal is in the payload — note isError is false:
{
"isError": false,
"structuredContent": {
"error": {
"code": "invalid_input",
"message": "topic: Input should be 'filter_fields', 'metrics', …",
"field": "topic"
}
}
}
code | Meaning | Retry? |
|---|---|---|
invalid_input | Arguments refused. Often with field naming the one to change — the market-data tools set it consistently, research and messaging only sometimes. | No — fix the request |
not_entitled | The backend refused your subscription's access to this data. | No |
upstream_unavailable | A dependency is failing or slow. | Yes, with backoff |
internal | Our side is wrong: a malformed record, or a guard we can't satisfy. A retry returns the identical error, which is why it isn't worth one. Distinct from -32603 above, which is the gateway failing rather than a tool. | No |
A client branching only on isError reads an in-band refusal as a success, and
then finds no data where it expected some. A client branching only on the payload
crashes on the isError cases, where structuredContent is null. Check
isError first — when it is true the text is prose, not JSON — then check the
payload for error. See
Concepts → Response shape for which refusals use
which channel, and a ready-made unwrapper.
"Not provisioned" has three different shapes
Worth separating, because they arrive at three different layers and only the last
one is a not_entitled payload:
| What is wrong | How it arrives |
|---|---|
| The key is unrecognised, or not enabled for MCP | HTTP 401 — nothing reaches a tool |
| The key is valid, but your subscription does not include that backend | JSON-RPC -32003 at HTTP 200 |
| The key reaches the backend, which refuses the specific data | In-band not_entitled, isError: false |
Empty results aren't errors
A search that returns an empty list succeeded — no match cleared the relevance threshold. Rephrase the query, make it more specific, or batch several phrasings in one call (see Concepts → Tools).
This is distinct from a refusal: an empty answer arrives on the result branch
with no error at all.
Retrying
Every EDITED MCP tool is a read-only search with no side-effects, so retries
are always safe. Retry 5xx, -32603 and -32099 with exponential backoff;
on -32004 back off substantially longer, since that backend is already being
shed and quick retries prolong it. Don't retry 401, -32700, -32600 or
-32602 — those need a fix (key, request shape, or tool-name prefix), not
patience.
An isError: true result is worth exactly one retry. It carries no code
to branch on, and the channel mixes two unlike things: a genuine bug, and a
transient failure raised below the tool's own error handling. Retry once with
backoff; if it comes back identically, treat it as a bug and report it.
Among the in-band codes, only upstream_unavailable is worth retrying.
internal is not — it means a retry returns the same error, not that we are
having a bad minute.
REST APIs
Query API Jobs APIThe Query and Jobs APIs report success and failure through the HTTP status
code — there's no JSON-RPC envelope, and every error carries a message body
with more detail. As a rule of thumb a 4xx is your request to fix and a 5xx
is ours — but the line isn't clean: some request and access problems surface as a
500, so read the body, not just the status.
REST status codes
| Status | Meaning |
|---|---|
200 | Success — read the result from the body. |
204 | No Content — a Jobs cancel or resume returns 204 with an empty body. (A submit returns 200 with a job_id.) |
400 | Bad request — malformed syntax or unparseable body. The message body says more. |
401 | Unauthorised — the access key is missing. See Authentication & access. |
403 | Forbidden — the key isn't permitted for what you're calling (unknown, or a valid key without access to this surface or data), or the request used plain http (EDITED accepts https only). |
404 | Not found — an unknown job, option, or resource. |
409 Jobs API | The job can't change state: you can only cancel a processing, pending, or queued job, and only resume a canceled or errored one. |
422 Jobs API | Validation error — the request was well-formed JSON but invalid (e.g. a bad date range or filter). The message body says which field. |
429 | Too many requests — you've hit a rate limit. Wait, then retry. |
5xx | Server-side failure — usually transient (retry with backoff). But some request problems also come back as 500; read the body, and see the note under Retrying REST calls. |
Retrying REST calls
The read endpoints (Query, and the Jobs read/poll endpoints) are safe to retry.
429and5xx— usually transient; back off and retry with exponential backoff.429responses don't carry aRetry-After; wait a little longer each time, and reduce how many requests you fire in a burst. Cap your retries: a500that returns the same error every time is usually a request or access problem in disguise (see the note below), not a blip.504on a largeoption_historyrequest — the request timed out. Split it into smaller date ranges, or paginate, rather than retrying the same call.400,401,403,422,409— don't retry; each needs a fix (the request shape, the key,https, the body, or the job's state), not patience.
500 is transientA 500 isn't automatically ours to retry. Transient failures are transient — so
if the same 500 comes back on every attempt, something else is wrong, and
it's a request to fix rather than one to wait out.
The usual culprits surface as a 500 instead of a clean 4xx:
- a date range beyond the stored window — only daily (
D) granularity is limited, to about two years; a daily job reaching further back (say, all of 2023) fails rather than returning a validation error. Weekly (W) and monthly (M) granularity are kept for all time, so switch to one of those for older ranges; - an expired or revoked key;
- the wrong environment or region for your key.
These come back as an InternalServerError with a GatewayError detail:
{
"status_code": 500,
"type": "InternalServerError",
"detail": { "type": "GatewayError" }
}
Your actual response includes a detail.message with more specifics — worth
quoting if you contact support.
So when a 500 repeats, check the request, the key, and the environment before
treating it as transient.
A Jobs submit is not idempotent — retrying a submit that actually succeeded
creates a second, separate job, with its own job_id and result files. On an
ambiguous failure, list your jobs before resubmitting rather than firing the
request again.
Getting help
If you're stuck — whether a call failed outright or the surface just doesn't look the way you expect — email support@edited.com with the details that pin down what you hit:
- On MCP: the tool name and arguments you called; the full error
envelope, if there was one — plenty of problems arrive without an error at
all;
serverInfo.versionand the surfacefingerprintfrom yourinitializeresponse (see Versioning); and your session ID — read themdmcp+data://session_idresource right after the failing call so EDITED can find your requests in the logs. - On the REST APIs: the method and URL you called, the request body,
and the HTTP status and response body you got back. For a Jobs problem,
include the
job_id.