Errors
This page is the whole set, and the place to look when you want to handle errors once rather than per call. A tool page lists only what is specific to that tool, and links back here for the rest.
The common set
Seven classes are reachable from every tool, because every call runs the same
lifecycle: AuthError, ForbiddenError, ValidationError, PolicyDenied, QuotaExceeded,
UpstreamError and TimeoutError. Handle those once. What varies from tool to tool is only:
| Class | Reachable from |
|---|---|
NotFound | tools that take an id or a name that must already resolve to an object |
ApprovalRequired | the 32 tools marked consequential, which a policy may hold for a person; the project's write gate (on by default) also holds the 31 that write (all but query-context) when an API key or SDK session calls them |
ConfigurationError | the 56 plane-gated tools; the six always-registered tools cannot reach it |
Each tool page's error table is that tool's slice of the three.
How an error is chosen
The gateway answers a refused call with an error string and an HTTP status. Both SDKs turn that pair into a typed exception, choosing on the error string first and the status second, so the same refusal becomes the same class in both languages.
On REST and on the Wexa MCP server there is no typed error: REST gives you the status and the
error / error_description pair directly, and the MCP server reports a refusal as a JSON-RPC error
or as isError: true with the message in content[0].text.
The classes
Both SDKs share one base class, WexaError. Catching it catches everything below.
| Class | Status | Raised when |
|---|---|---|
AuthError | 401 | The credential is missing, malformed or revoked. |
ForbiddenError | 403 | The credential lacks the grant this tool needs, or its scope does not cover the project. |
PolicyDenied | 403 | A policy rule refused this specific call. Subclasses ForbiddenError, so catching that catches this too. The refusal names the rule. |
ValidationError | 400 / 422 | An argument is missing, of the wrong type, or is one the platform binds itself. Also raised client-side, before any request, for a server-bound argument. |
ApprovalRequired | 202 | A person must approve this call before it runs. Carries the approval id. |
ApprovalError | — | The approval was rejected, or the wait for it failed. |
QuotaExceeded | 429 | The project or organization is over its request rate or its credit balance. |
NotFound | 404 | An id in the arguments matches nothing in this project. |
ConflictError | 409 | The change collides with the object's current state. |
UpstreamError | 5xx | A service behind the gateway failed or was unreachable. |
ConfigurationError | 5xx | The tool is not available to you. Retrying will not help. |
Where the two SDKs differ
The classes are not quite identical, and code that catches by name has to account for it.
| TypeScript | Python | |
|---|---|---|
| Timeout | TimeoutError | TimeoutError_ — the trailing underscore avoids shadowing Python's builtin, and it subclasses UpstreamError rather than standing alone |
| Transport failure | TransportError | no equivalent; a transport failure surfaces as UpstreamError |
| Total classes | 13 plus WexaError | 12 plus WexaError |
Reading an error
Both SDKs attach the governance ids to the exception, and they are the fastest way to find out why a call was refused.
ValidationError: malformed after_id "<after_id>": expected a table_id from a previous page
[lifecycle_id=qlc_000050 trace_id=a43def45c69440ac982ecd49b4396df1]
The lifecycle_id is the handle to the audit record for the call. Quote it when asking why something
was allowed, refused or held.
See also
The tool index for the tools themselves.