Errors
This content is not available in your language yet.
A GraphQL error comes back in the errors array of the JSON body. Each entry has a human message and a machine-readable extensions.code; branch on the code, never on the message text, which can change. Most errors arrive with HTTP status 200, so check errors on every response, not only on non-2xx ones. A few are refused before the operation runs and come back with HTTP 400 (marked below).
{ "errors": [ { "message": "Agent #7 not found", "locations": [{ "line": 2, "column": 3 }], "path": ["sdkStart"], "extensions": { "code": "NOT_FOUND", "status": 404, "originalError": { "message": "Agent #7 not found", "error": "Not Found", "statusCode": 404 } } } ], "data": null}extensions.originalError repeats the underlying refusal (for input validation its message is the list of every failed rule). extensions.status is present on NOT_FOUND, CONFLICT, BAD_GATEWAY and INTERNAL_SERVER_ERROR. A few refusals add their own extensions.originalError.code for a client that wants to branch further, for example OUTBOUND_URL_REFUSED (below).
The codes
Section titled “The codes”extensions.code | HTTP | Real example message | Usual cause | Retry? |
|---|---|---|---|---|
UNAUTHENTICATED | 200 | Invalid API key | Missing, wrong, expired or revoked credential, or the wrong kind of credential for the operation. See the Authentication cheat sheet | No, fix the credential |
FORBIDDEN | 200 | Platform is not specified! | The x-huat-platform: customer header is missing, a member called an owner-only operation, or the session token belongs to another session | No |
BAD_REQUEST | 200 | Session 4211 is not active (status: ended) | The input broke a rule of the operation: a field over its limit, two arguments that cannot be combined, a resource in the wrong state | No, change the input |
BAD_USER_INPUT | 400 | Variable "$input" got invalid value { agentId: 7, customerId: "ticket-1001" }; Field "customerId" is not defined by type "SdkStartInput". | The variables do not match their GraphQL types: an unknown field, a missing required field, a wrong type or a value outside an enum. A few operations also use it, with HTTP 200, for their own checks (for example an invalid workflow graph) | No, change the variables |
NOT_FOUND | 200 | Agent #7 not found | The id does not exist in your workspace. Another workspace’s id answers the same, by design | No |
CONFLICT | 200 | Approval #118 was already approved by user #3 at 2026-10-12T03:21:44.000Z | The resource changed state first, for example a request already decided or expired | No, re-read the resource |
GRAPHQL_VALIDATION_FAILED | 400 | Cannot query field "rating" on type "SdkEvaluationResult". | The query asks for a field or argument that does not exist, or nests deeper than 7 levels | No, fix the query |
QUERY_TOO_COMPLEX | (refused before running) | Query is too complex: 61. Maximum allowed complexity: 50 | The query selects more than 50 fields in total. Select only what you need | No, trim the query |
BAD_GATEWAY | 200 | The MCP server could not list its tools: connection refused: fetch failed (with extensions.status 502) | Your own MCP server could not be reached or did not answer properly (listMcpTools). The fault is on that server’s side, not Wetel’s | Yes, once the server is fixed |
TOO_MANY_REQUESTS | 200 | Too many requests. Try again in about 2 minutes. (with extensions.status 429 and extensions.retryAfterSeconds; on staging, production rollout pending; before: INTERNAL_SERVER_ERROR ThrottlerException: Too Many Requests) | A rate limit refused the call. Wait retryAfterSeconds | Yes, after the wait |
INTERNAL_SERVER_ERROR | 200 | ThrottlerException: Too Many Requests (with extensions.status 429) | A rate limit (status 429, see Rate limiting) or an unexpected failure on our side | Yes, with backoff |
Each operation’s reference page lists the errors it typically returns, with the full JSON body: for example Sessions and messages and Approvals.
A refused outbound URL
Section titled “A refused outbound URL”Live in production since 2026-10-21. A URL Wetel would call out to (an MCP connector’s serverUrl, a custom action’s url, an agent’s externalSyncUrl, a webhook node’s url) that does not parse, does not resolve, or resolves to a private or reserved address answers BAD_REQUEST with extensions.originalError.code OUTBOUND_URL_REFUSED and extensions.originalError.reason INVALID_URL, UNRESOLVABLE_HOST or PRIVATE_ADDRESS. Before, it answered INTERNAL_SERVER_ERROR. Change the URL; do not retry. See Refused URLs and an unreachable MCP server.
A compressed request body
Section titled “A compressed request body”Live in production since 2026-10-23. A request with a Content-Encoding other than identity is refused with HTTP 415 before GraphQL runs, so there is no errors array: the body is {"statusCode":415,"code":"UNSUPPORTED_CONTENT_ENCODING",...} (the public edge may answer a plain 415 page). Resend without Content-Encoding; see Send plain request bodies.
Handling errors in a client
Section titled “Handling errors in a client”- Read the body as JSON whatever the HTTP status.
- If
errorsis present and non-empty, takeerrors[0].extensions.code. - Retry only
INTERNAL_SERVER_ERROR, with exponential backoff; whenextensions.statusis 429, wait for the rate-limit window (usually 60 seconds) to pass. - For every other code, log the full
errorsentry and fix the request: retrying the same request gives the same answer.
The generated samples on every reference page do steps 1 and 2 already. For background on how codes are produced, see Error Handling and Status Codes.