> Source: https://wexa.ai/docs/tools/get-approval

# 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`](/docs/tools/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](/docs/get-started/scope-and-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](/docs/tools/overview#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](#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`:

**Result of get-approval**

**REST API**

```json
{
  "lifecycle_id": "qlc_000240",
  "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"
  }
}
```

**Wexa MCP server**

```json
{
  "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
}
```

**TypeScript SDK**

```ts
// the object you get back IS the result — there is no `.result` to read
{
  args: null,
  choice: 'send',
  choices: ['send', 'hold'],
  created_at: '2026-09-29T12:11:46.826Z',
  decided_at: '2026-09-29T12:14:39.923Z',
  decided_by: '6abbaa9b4a684dc3e5a7bda0',
  expires_at: '2026-09-30T12:11:46.826Z',
  hard_expires_at: '2026-10-01T12:11:46.826Z',
  id: 'ar_6abbab02fb130d22ba652a7f',
  lifecycle_id: '',
  reference: 'inbox-js-1',
  requested_by: '6abbaa9b4a684dc3e5a7bda0',
  risk: 'high',
  scope: {
    auth_kind: '',
    grants: null,
    org_id: '6abbaa9b4a684dc3e5a7bda1',
    project_id: '6abbaa9b4a684dc3e5a7bdb1',
    role: '',
    user_id: '6abbaa9b4a684dc3e5a7bda0'
  },
  status: 'approved',
  tool: '',
  type: 'app_request',
  what: 'Send campaign C-99 (JS wait)',
  why: 'Decided in the Fabric console Inbox'
}

// The TS SDK names these camelCase — lifecycleId, traceId — where Python uses
// lifecycle_id and trace_id. Both are non-enumerable: readable, but omitted by
// Object.keys() and JSON.stringify(). `result.lifecycleId` here is undefined.
result.lifecycleId // 'qlc_000242'
```

**Python SDK**

```python
# a dict subclass; `result` is already unwrapped
{
 "args": None,
 "created_at": "2026-09-29T12:11:46.824Z",
 "decided_at": "2026-09-29T12:14:40.99Z",
 "decided_by": "6abbaa9b4a684dc3e5a7bda0",
 "decision_reason": "Legal hold on this customer",
 "expires_at": "2026-10-01T12:11:46.824Z",
 "hard_expires_at": "2026-10-03T12:11:46.824Z",
 "id": "ar_6abbab02fb130d22ba652a7e",
 "lifecycle_id": "",
 "reference": "inbox-py-reject",
 "requested_by": "6abbaa9b4a684dc3e5a7bda0",
 "risk": "medium",
 "scope": {
  "auth_kind": "",
  "grants": None,
  "org_id": "6abbaa9b4a684dc3e5a7bda1",
  "project_id": "6abbaa9b4a684dc3e5a7bdb1",
  "role": "",
  "user_id": "6abbaa9b4a684dc3e5a7bda0"
 },
 "status": "rejected",
 "tool": "",
 "type": "app_request",
 "what": "Delete customer data (Python wait)",
 "why": "Decided in the Fabric console Inbox"
}

out.lifecycle_id  # 'qlc_000243' — an attribute, not a key
```

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 `<...>`.

**Call get-approval**

**Wexa MCP server**

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get-approval",
    "arguments": {
      "approval_id": "<approval id>"
    }
  }
}
```

**REST API**

```bash
curl -sS https://fabric.wexa.ai/v1/get-approval \
  -H "authorization: Bearer $FABRIC_API_KEY" \
  -H "content-type: application/json" \
  -d '{"approval_id":"<approval id>"}'
```

**TypeScript SDK**

```ts
import { Fabric } from '@wexa-fabric/sdk'

const fabric = new Fabric()
const result = await fabric.getApproval("<approval id>")
```

**Python SDK**

```python
from wexa import Fabric

fabric = Fabric()
out = fabric.get_approval(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](/docs/tools/errors#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`](/docs/tools/request-approval) to open an approval from your app,
[the tool index](/docs/tools/overview), [errors](/docs/tools/errors),
and [which identifier goes where](/docs/tools/identifiers).
