The loop
CHECKFU_API_KEY and CHECKFU_WORKSPACE_ID come from Get access, and APPROVAL_ID is the approval_… the wait event carried. The TypeScript snippets below use the TypeScript SDK with this client:
Detect the wait
Watch the Session stream for
run.requires_action. Key off the action payload, not the Session status. session.status_waiting tells you the Session is parked, but only the action tells you what it is parked on.Show the reviewer what was frozen
action_approval.pending carries the frozen context, and GET /v1/action-approvals/{id} returns the full record including context_summary.Branch on that context before rendering it:- A ToolInvocation ActionApproval names the Connection, tool and source versions, outbound scheme, host, port, method and path, Session, Run, and acting Principal. It carries no query string, headers, body, or argument values; arguments appear only as
arguments_hash. - A Custom-tool ActionApproval has
context_summary.kind === "custom_tool". It names the admitted agent definition and version, nullable installation and surface,tool_use_id, and logical tool name. It has no Connection, ToolSource, or outbound request. Read the exact arguments from the precedingagent.tool_useevent when building the review; execute them only after authority is granted.
Respond
Idempotency-Key is optional, but using a stable value preserves an encrypted copy of the exact HTTP response for retries.Continue the correct branch
For a CapabilityGateway decision or a Custom-tool denial, ActionApproval alone does not start a Run, dispatch, lease, or Sandbox for this branch. The same compute-closed Run stays parked after
action_approval.resolved lands in the log, followed by run.resumed; the normal continuation tells the harness whether the tool action was approved or refused. Do not treat a denial as a Session failure.An approved Custom-tool action is different. Wait for run.action_authorized, correlate its tool_use_id to the exact preceding agent.tool_use, execute that recorded tool action in your application, and post its result:run.action_authorized; the matching user.custom_tool_result is what closes that wait and admits the ordinary continuation Run.Handling the awkward cases
The last two are the freeze doing its job. An approval is a decision about one exact tool action, and any change to that tool action invalidates it rather than approximating consent.
Building the queue
For a live queue, drive off the event log rather than pollingGET /v1/action-approvals. action_approval.pending tells you an item appeared; action_approval.resolved tells you it was answered, including when someone else answered it first. That is what keeps two open review tabs from fighting.
Use the list endpoint for backfill and reconciliation, not as the primary signal.
Next steps
ActionApprovals
The freeze, the proof token, and the governance checks that produce a review.
Resume a stream
Make sure you never miss the wait event in the first place.