get-approval
Read one approval in this project: its status, who decided it, the reason they gave, the choice they picked and whether it has been consumed. It returns the same shape as GET /v1/approvals/{id}. For an approval opened with request-approval, reference is the reference you passed.
An approval in another project answers not found, the same as an id that does not exist.
Statuses
| Status | Meaning | Final |
|---|---|---|
pending | Waiting for a decision. | No |
overdue | Past its deadline (expires_at) and still waiting. It can still be decided. | Yes, for an SDK wait |
approved | A person approved it. decided_by, decided_at, and choice when one was picked. | Yes |
rejected | A person rejected it. decision_reason says why. | Yes |
expired | Past hard_expires_at with no decision. It can no longer be decided. | Yes |
An SDK that waits for a decision stops at overdue, the same as the wait on a held tool call. To keep
waiting for an overdue approval, call get-approval again later.
What it is called on each surface
The wire name is get-approval everywhere: it is what the Wexa MCP server advertises, the REST path is
POST /v1/get-approval, and each SDK exposes it under its own language's naming convention.
| Surface | Name |
|---|---|
| Wexa MCP server | get-approval |
| REST API | POST /v1/get-approval |
| TypeScript SDK | fabric.getApproval() |
| Python SDK | fabric.get_approval() |
Arguments
1 of the 1 argument is required. Omitting a required one is refused before the tool runs, at the validation stage, so it costs nothing and changes nothing.
| Argument | Type | Required | Notes |
|---|---|---|---|
approval_id | string | Required | Approval id, as returned by request-approval (ar_...). |
project_id, projectID, organization_id and executed_by are not arguments you pass: the
platform binds all four from the credential you authenticated with, and both SDKs refuse them
before the request leaves your process — see
server-bound arguments.
What it returns
The three surfaces wrap this differently, and code written against one will not read another correctly — see return shape.
| Field | Type | Notes |
|---|---|---|
id | string | The approval id. |
type | string | app_request for an approval opened with request-approval. |
status | string | See statuses. |
what, why | string | The title and reason the request was opened with. |
risk | string | low, medium or high. |
payload | object | The details sent with the request, when there were any. |
choices, choice | array, string | The options offered, and the one the approver picked. |
requested_by | string | The user the request was made as. |
decided_by, decided_at | string | Who decided it, and when. |
decision_reason | string | The reason the approver gave. Always present on a rejection. |
consumed | boolean | Present and true once a held tool call has used its approval. |
created_at, expires_at, hard_expires_at | string | When it was opened, its deadline, and when it expires. |
reference | string | The reference passed to request-approval. |
A real response, captured from a live call to get-approval:
{
"content": [
{
"type": "text",
"text": "{\"lifecycle_id\":\"qlc_000241\",\"result\":{\"args\":null,\"created_at\":\"2026-09-29T12:11:46.83Z\",\"decided_at\":\"2026-09-29T12:14:38.712Z\",\"decided_by\":\"6abbaa9b4a684dc3e5a7bda0\",\"decision_reason\":\"Supplier verified in the console\",\"expires_at\":\"2026-10-01T12:11:46.83Z\",\"hard_expires_at\":\"2026-10-03T12:11:46.83Z\",\"id\":\"ar_6abbab02fb130d22ba652a81\",\"lifecycle_id\":\"\",\"reference\":\"inbox-py-approve\",\"requested_by\":\"6abbaa9b4a684dc3e5a7bda0\",\"risk\":\"medium\",\"scope\":{\"auth_kind\":\"\",\"grants\":null,\"org_id\":\"6abbaa9b4a684dc3e5a7bda1\",\"project_id\":\"6abbaa9b4a684dc3e5a7bdb1\",\"role\":\"\",\"user_id\":\"6abbaa9b4a684dc3e5a7bda0\"},\"status\":\"approved\",\"tool\":\"\",\"type\":\"app_request\",\"what\":\"Pay supplier (Python wait)\",\"why\":\"Decided in the Fabric console Inbox\"}}"
}
],
"isError": false
}Keeping the lifecycle_id is what lets you ask later why a call was allowed, refused or held.
Calling it
The same call on all four surfaces. Pick a tab once and every code block in the documentation follows it.
A value in angle brackets is one only you can supply, and the platform refuses a literal <...>.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get-approval",
"arguments": {
"approval_id": "<approval id>"
}
}
}Errors
Both SDKs raise the same typed errors, chosen from the gateway's own error string first and its
HTTP status second. get-approval needs no grant, and reaches the classes every
tool reaches — listed under the common set. These are the ones specific to it:
| Error | Status | Raised when |
|---|---|---|
NotFound | 404 | approval_id matches no approval in this project. |
ValidationError | 422 | approval_id is missing, or a server-bound argument was sent. |
ConfigurationError | 5xx | get-approval is not available to you. Retrying will not help. |
Governance
Every call to get-approval runs through the same ten-stage lifecycle as every other Wexa tool: the
credential is resolved to a scope, quota is drawn down, the arguments are validated, policy rules are
evaluated, the call is executed, the result is shaped and redacted, and an audit record is written. The
lifecycle_id in the response is the handle to that record.
get-approval only reads, so it is not marked consequential: it is audited lightly and is never held for
approval. It still consumes quota and is still refused by policy like anything else.
See also
request-approval to open an approval from your app,
the tool index, errors,
and which identifier goes where.