On this page

Approvals

Wexa can pause a piece of work and wait for a person to approve it. Every approval in Wexa goes into one Inbox and is decided there. This page says where approvals come from, what the Inbox shows, who may decide, how long an approval stays open, and then walks one held call from end to end.

Where approvals come from

A tool call is held for approval when one of these applies:

  • A consequential write made with an API key or an SDK session. This is on by default for every project, admin API keys included. See the write gate below.
  • A non-administrator committing a change to the project ontology in a project that is in Advanced mode.
  • A connector action the platform does not mark as safe to run without approval. See run-connector-action.
  • A call touching data the data catalog has classified as sensitive, where catalog synchronisation is switched on.

The rest of the platform raises approvals into the same Inbox: flow previews, anomalies (continue, restart or terminate the run), analytics gates, governance gates, permission requests, capability activations, marketplace submissions, policy revisions, proactive cards, app requests made with the request-approval tool, promoted agent actions, connector access and ontology proposals. Deciding the approval resumes or stops the work that was paused.

The write gate

In every project, a consequential tool call made with an API key or an SDK session waits for approval. This includes table row inserts and updates, table create and rename, trigger changes and connector actions, as well as tools that were already consequential, such as delete-table-rows and save-context. Calls made by a person signed in to the console are not held by this gate, and neither are calls made with any other kind of sign-in.

A project admin turns the gate off, or back on, in Settings → Projects, with the switch Hold consequential writes from API keys and SDK sessions for approval. Only an admin of that project can change it. A change can take up to 30 seconds to apply.

The write gate is checked before the ontology rule. While it is on, an ontology commit made with an admin's API key is held too, in Simple and in Advanced mode.

The Inbox

Open Actions → Approvals in the console (/actions/approval-flows). The Inbox tab lists every approval for the selected project, of every type, together with the organization-level approvals of your organization. The count on the tab is the number of open approvals.

You can filter by status, risk, requester, date range and type, and sort by priority, newest or risk.

A member without the review capability does not see the Inbox. They see a note that their approvals are routed to their administrators.

What each entry shows

  • A one-line summary of what was asked, its type, and an Org tag for an organization-level approval.
  • Why it was raised.
  • Who requested it: the user, and the agent when an agent acted for them.
  • How long it has waited, and the time left before its deadline, or how long it is overdue.
  • The client and the connector the request came from, when it came from one.
  • Its risk: Low, Medium or High.
  • Who it is with, and who delegated it, if it was delegated.

Full context opens the rest: the status, the deadline and expiry time, the gate, the source, the payload and inputs, and, once decided, who decided it and the reason they gave.

For a held tool call the Inbox shows the summary line. The complete arguments that will run are in args on GET /v1/approvals/{id}. Read them before you approve.

Deciding

  • Approve. A reason is optional and is recorded with the decision.
  • Reject. A reason is required. The requester sees it.
  • Anomalies offer Continue and Restart from agent, which approve with that choice, and Terminate, which rejects the run and needs a reason.
  • Bulk decide. Select low-risk entries, or use Select all low-risk, then Approve all or Reject. Rejecting in bulk needs one reason for all of them. Medium and High risk approvals are always decided one at a time.
  • Delegate. Hand the approval to a user or to a role such as org_admin, with an optional note. You need the same authority to delegate as to decide.

A decision happens once. If someone else decided the approval first, or it has expired, your decision is refused with 409 and the entry shows why, for example approval is already approved or approval has expired and can no longer be decided.

If the paused work cannot apply your decision, the decision is not recorded and the approval stays open. The entry shows the reason, and you can try again.

The Gates tab

The Gates tab lists the gates configured for the project, with the approvals each one is holding. Approve and Reject on a gate decide that gate's oldest pending approval.

Who may approve

The approver is always the person signed in. It is taken from your scope, never from the request body. A decision request from the console that names an approver, with a field such as user_id, approver or decided_by, is refused with 400:

400 {"error":"user_id is a server-bound argument: the approver is taken from your signed-in scope and may not be supplied"}

The REST decision endpoints read only reason (or comment) from the body and ignore every other field.

Then these rules apply:

  • Your own request. You may approve it only in a project you administer, as a project admin of that project or as an organization admin. Anyone else is refused with you cannot approve your own request. An API key acts for the admin who created it, so that admin may approve their own key's held calls in a project they administer.
  • Someone else's request. You must be above the requester in your organization's hierarchy. If the gate names a role, your role must meet it. If the gate names a person, only that person may decide.
  • Organization-level approvals, such as marketplace submissions and policy revisions, need an organization admin. Nobody may decide their own.
  • Out of scope. An approval in another organization, or in a project your credential is not for, is not found. You get 404, not a permission error.

The REST endpoints POST /v1/approvals/{id}/approve and .../reject follow the same rules.

Deadlines

Each approval gets a deadline from its risk:

RiskDeadline
High24 hours
Medium48 hours
Low72 hours

A held tool call is Medium risk, so its deadline is 48 hours. A gate can set its own review time for the actions it governs, and that replaces the default.

Past the deadline the approval is overdue. It can still be decided. If the gate names someone to escalate to, the approval moves to them after the time the gate sets.

At twice the deadline the approval is expired. It can no longer be decided, and a decision is refused with 409. For a held tool call the caller must make the call again.

Statuses

StatusMeaning
pendingWaiting for a decision.
overduePast its deadline and still waiting. It can still be decided.
approvedApproved. The paused work resumes.
rejectedRejected. The paused work stops.
expiredPast twice its deadline. It can no longer be decided.

While a decision is being applied, the entry shows Deciding… for a moment.

An approved held call also carries consumed: true once its resume token has been used. The SDKs report that as the status consumed.

Notifications

When an approval is created, each person who may decide it gets a console notification. When it is decided, the requester gets a notification with the decision and the reason.

A held call, walked end to end

This is one sequence, in order, with an API key in a project where the write gate is on.

1. The call is held. An insert-table-rows call comes back 202 with an approval id and a resume token instead of a result:

{
  "status": "pending_approval",
  "approval": {
    "approval_id": "ar_6abb9f3c0b5ae73d6cdd6d41",
    "resume_token": "hrt_ar_6abb9f3c0b5ae73d6cdd6d41.3f9c2e7a51b04d8e96a1c7f0d2b5e8a4c6f1093e7d2a5b8c4e0f6a1d9b3c7e25",
    "message": "approval required: consequential write by an SDK caller requires approval. Re-call this tool with resume_token after approval.",
    "lifecycle_id": "qlc_000412"
  }
}

The caller keeps the resume token. It is returned here and nowhere else, and it is the only way back into this held call.

2. An approver sees it. It appears in the Inbox, and in GET /v1/approvals:

{
  "id": "ar_6abb9f3c0b5ae73d6cdd6d41",
  "tool": "insert-table-rows",
  "what": "insert-table-rows",
  "why": "consequential write by an SDK caller requires approval",
  "requested_by": "6abb9e42c0b5ae73d6cdd6c1",
  "scope": { "org_id": "6abb9e42c0b5ae73d6cdd6bb", "project_id": "6abb9e42c0b5ae73d6cdd6cb",
             "user_id": "6abb9e42c0b5ae73d6cdd6c1", "role": "", "grants": null, "auth_kind": "" },
  "args": { "table_id": "tbl_orders", "rows": [ { "col_status": "shipped" } ] },
  "status": "pending",
  "created_at": "2026-09-29T10:15:00Z",
  "expires_at": "2026-10-01T10:15:00Z",
  "lifecycle_id": "qlc_000412"
}

expires_at is the deadline. After it the approval is overdue. It expires at twice the deadline.

3. An approver approves it in the Inbox, or with POST /v1/approvals/ar_6abb9f3c0b5ae73d6cdd6d41/approve.

4. The caller re-calls the same tool with the resume token, and the call runs. The SDKs can wait for the decision and resume for you.

5. The token is now spent. Presenting it again fails at S6:approval with 403:

{"error":"S6:approval","error_description":"resume rejected: approval not approved (not approved, already used, or not yours)"}

The same answer comes back for a rejected or expired approval, and for a token presented by a different user, for a different tool or in a different project.

The approved arguments are the ones that run

The arguments held with the approval replace whatever the resuming caller sends. An approver is approving a fixed payload, and there is no window between approval and execution in which the request can be changed.

What the trail records

Every stage of a held call is recorded. The gateway's own trail, at GET /v1/audit, has these events:

ActionActorKind
insert-table-rows.approval.requestedthe callerApprovals
approval.approved or approval.rejectedthe approver, when they decide over RESTApprovals
insert-table-rows.approval.redeemedthe callerApprovals
insert-table-rows.executedthe callerTool calls

The approval's own events, requested, decided, consumed and expired, go to the platform audit trail in the console, under Governance → Audit & Lineage, whether the decision was made in the Inbox or over REST. A decision made over REST is flagged when it was self-approved. Read audit and compliance for how long each trail lasts.

Running the queue

  • Watch the deadlines. An overdue approval can still be decided. An expired one cannot, and the requester has to ask again.
  • Decide from the arguments. Reject when they are unfamiliar. Rejecting costs the requester one new request.
  • Decide whether you want the write gate. If your API keys run unattended jobs, either keep the Inbox staffed or have a project admin turn the gate off for that project.
  • Remember the mode coupling for ontology commits. A project switched back to Simple stops holding non-administrator ontology commits. The write gate still holds commits made with an API key or an SDK session. See project mode administration.

The concept treatment of why these gates exist is in policy and approvals. This page is the operating manual for the people who decide approvals.

What is not here

gateway.policy.denials.total exists in the gateway's metrics code and nothing writes to it. Do not build an alert on it. To watch the queue, use the Inbox or GET /v1/approvals?status=pending. See observability.