Approvals

Human-in-the-loop, for the calls you do not want an agent making unattended.

What a pause looks like

Some tools are approval-gated. When the model calls one, the run does not execute it — it yields, persists as awaiting_approval, and your stream ends on a needs_approval frame:

text/event-stream
event: needs_approval
data: {"runId":"01936c2f-…","approvalId":"7c41…","toolName":"send_email",
       "argsJson":"{\"to\":\"…\"}","approvableScopes":["run","session"],
       "expiresAt":"2026-09-02T09:12:44Z"}

argsJson is the model's own arguments, so you can show a human exactly what is about to happen. Render it as text — it is model output, not your data.

Resuming

request
curl -N -X POST https://api.askamy.co.za/v1/runs/01936c2f-…/approvals/7c41… \
  -H "X-Api-Key: $AMY_API_KEY" -H "Content-Type: application/json" \
  -d '{"decision": "approve_once"}'

POST /v1/runs/{runId}/approvals/{approvalId}. The continuation streams on that response, with the same event contract as the original run. So a paused run is not a separate integration: it is the same stream, resumed on a second call. The body can carry four fields:

application/json
{
  "decision": "approve_for_run",
  "note": "Checked the recipient list with finance.",
  "mcpAuth": [ { "serverSlug": "crm", "token": "…" } ],
  "mcpServers": [ … ]
}
  • decision — required; one of the values below.
  • note — optional free text from the approver, at most 2,000 characters. It is kept with the decision, and appears as a user turn when the conversation is read back.
  • mcpAuth — optional; a token per MCP server, as on a start call (at most 16, each token at most 8,192 characters). Send it again on resume whenever the run uses a server that needs a token. Tokens are never stored, so the continuation does not have the one you sent when the run started. A registered server that needs a token and gets none is left out of the continuation — its tools are simply not offered, and the run carries on without them — so a run that depends on that server will not finish what it started. See Tools and MCP.
  • mcpServers — optional; the inline servers the run was started with, declared again (at most 16). They must be exactly the ones the run started with: a different, added or omitted server is refused with inline_mcp_mismatch. A run that had none sends none.

Decisions

The decision field takes one of:

  • approve_once — allow this one call. approve is a back-compatible alias.
  • approve_for_run — allow it for the rest of this run.
  • approve_for_session — allow it for the rest of the conversation. The run needs a conversationId, or this is a 400.
  • approve_always — allow it for this end user from now on, across every future run. It binds to the run's own endUserRef, so a run started without one cannot take this decision (400), and the grant applies to that end user alone — never to the rest of the organisation. There is no /v1 call to take it back, so offer it only where you mean it.
  • deny — refuse; the run continues and the model is told.

Scopes

approve_once and deny are always available. The approvableScopes list on the pause — values run, session and always — names the wider standing grants that this particular pause will accept, for the rest of the run, the conversation, or always. Offer only what the frame lists; a wider decision it does not list is a 400 naming decision, and the list exists precisely so you do not have to guess. session appears only when the tool allows it and the run has a conversation; always only when the tool allows a permanent grant and the run has an end user to bind it to.

What a resume can answer

  • 200 — the decision was taken and the continuation streams.
  • 400 — a decision that is not one of the values above, a scope the pause does not admit, an over-long note, or a malformed mcpAuth / mcpServers.
  • 404 — no such run or approval for you, including a run another API client started.
  • 409 — the approval was already decided, the run is no longer waiting, or the pause has expired.
  • 403 — a team run's assignment approval, decided with a key that may not decide it. Team runs are a Console feature; if you do not use them, ignore this case.
  • 202 — a team run's assignment approval was taken. Team runs are a Console feature; if you use them, nothing streams for this answer and the team run carries on in the background.

Two answers arrive in-band instead, when the state changes between the check and the stream: approval_not_pending and inline_mcp_mismatch. See Errors.

Expiry

Every pause carries an expiresAt. After it, the pause can no longer be decided and the run ends as expired — so a run left waiting on a human who never came back does not sit open forever. Show the deadline; do not assume a pause is still live because you have not been told otherwise.

If you submitted the run

A submitted (non-streaming) run pauses the same way. You will not see the frame — read GET /v1/runs/{runId}, whose pendingApprovals carries the same information (approvalId, toolName, argsJson, approvableScopes, expiresAt), then resume with the call above. The continuation is a stream on that call.

Cancelling does not stop a run that is waiting on an approval — the cancel call answers cancelled: false. Decide the pause; left undecided, it expires.