A protected action (an action for short) is the record of one tool call an agent made through the Gateway. When an agent calls an exposed tool, the Gateway decides the call (allow, require approval or deny), records the decision as an action, and follows that action until it reaches a final state: denied, rejected, expired, cancelled, succeeded or failed.
Actions are what you review on the Actions page, what approvers decide on, and what receipts are issued for. Every action belongs to one agent and one tool, and carries the exact arguments of the call.
What an action records
| Field | What it is |
|---|---|
| Agent | The agent whose gateway API key made the call, and the key's environment (live or test). |
| Tool | The exposed tool that was called, with its MCP server. See Tools and MCP servers. |
| Action | The K-Block action the tool maps to, for example refund.create. |
| Arguments | The exact arguments of the call, and their digest. Members who can read actions see them. |
| Amount | The amount and currency read from the arguments through the tool's amount mapping, if it has one. |
| Decision | allow, require_approval or deny. |
| Rule | The policy, policy version (with its digest) and the rule that decided, or the policy's default. Also how the decision was enforced (enforce or observe). |
| Status | Where the action is in its lifecycle (below). |
| Error code and detail | Why it was denied, expired or failed, when that applies. |
| Idempotency key | The key the agent sent, if any. |
| Expiry | Until when an approval can be given and an approved call can still run. |
The signed records behind an action (the task, proposal, approval or rejection, cancellation, execution and result) are listed on the action page under K-Block chain. Which of them a receipt contains is explained in Receipts and evidence bundles.
How the Gateway decides a call
For a new call the Gateway checks the gateway API key and its agent, the tool and its binding, the arguments and the amount, the agent's policy, whether a delegation grants the action and, for require_approval, whether there are eligible approvers. The outcome is recorded all at once or not at all: the action and its decision, together with its signed records and approval request when the call has them (a denied call has neither).
- Recorded denials. A policy denial (
POLICY_DENIED) and the checks around it (NO_POLICY,NO_DELEGATION,NO_APPROVERS,INVALID_AMOUNTand others) are recorded as actions with statusdenied, and they count toward the organization's monthly limit. - Refusals that record nothing. An unknown tool name (
UNKNOWN_TOOL), a tool whose binding needs re-authorization (TOOL_REAUTHORIZATION_REQUIRED), invalid arguments (INVALID_ARGUMENTS), a reused idempotency key (IDEMPOTENCY_KEY_REUSED), a reached plan limit (PLAN_LIMIT_EXCEEDED) and authentication failures are answered without creating an action.
The Gateway does not check the arguments against the tool's input schema. INVALID_ARGUMENTS means arguments K-Block cannot record or sign: not a JSON object, too large, nested more than 28 levels deep, containing a non-integer number or U+0000, or holding two member names that differ only by letter case.
The codes the agent can receive are listed in Error codes.
Statuses
An action has exactly one of ten statuses. Seven of them are final: nothing leaves them.
| Status | Meaning | Final |
|---|---|---|
| denied | The call was denied. Nothing ran. | Yes |
| pending_approval | Waiting for an approver. | No |
| approved | Allowed by the policy, or approved by a person, and not executed yet. An allowed call normally runs at once, in the same request. A call a person approved runs when the agent repeats it, or at once if the agent is still waiting. For a decision-only tool, it is authorized for the caller instead (authorized). | No |
| rejected | An approver rejected it. Nothing ran. | Yes |
| expired | It can no longer run: the approval request expired (APPROVAL_EXPIRED), the approved call was not executed in time (TASK_EXPIRED), or it was superseded (SUPERSEDED). Nothing ran. | Yes |
| cancelled | The organization cancelled it before it ran. | Yes |
| executing | The approval has been consumed and the upstream call is in progress. | No |
| succeeded | The upstream ran the call and answered with a result. | Yes |
| failed | The execution failed after the approval was consumed (UPSTREAM_FAILED), or it was interrupted (INTERRUPTED). The upstream may still have acted; see Outcome unknown. | Yes |
| authorized | Decision only: K-Block authorized the exact call and handed it to the caller, which is responsible for executing it. K-Block executed nothing and cannot confirm whether the caller did. | Yes |
approved covers two cases: an allow decision that has not executed yet, and a person's approval. The action's decision (allow or require_approval) tells them apart.
The full transition table, with the error codes and the webhook events of each transition, is in Action statuses.
Lifecycle
Decided
denied, approved or pending_approval
Approved
by policy or by a person
Executing
approval consumed
Finished
succeeded or failed, with a receipt
- Decision. A deny ends the action as
denied. An allow makes itapproved. Arequire_approvalmakes itpending_approvaland creates an approval request. - Approval. An eligible approver approves (
approved) or rejects (rejected) it in the dashboard. If nobody decides in time it becomesexpiredwithAPPROVAL_EXPIRED. See Approvals. - Execution. The Gateway executes an allowed action right away, in the call that was decided. It executes an action a person approved when the agent repeats the same call, or while the agent is still waiting for the decision. Before it consumes the approval it re-checks the gateway API key, the agent, the tool and its binding, the delegation and, for a call the policy allowed, the policy now in force. An approved action that is not executed before its expiry becomes
expiredwithTASK_EXPIRED. - Result. The Gateway records the upstream's answer as
succeededorfailedand issues a receipt. See Execution and results.
At any point before execution, owners and admins can cancel a pending_approval or approved action with Cancel action… on the action page. The cancellation is signed in the browser by the organization principal, so the principal's key must be in that browser. An executing action cannot be cancelled.
Superseded actions
An open action (pending_approval or approved) is closed as expired with SUPERSEDED when it can no longer run as it was decided. Most of these checks happen when the agent repeats the call; replacing the organization principal, a rollback or an emergency stop can close actions at once.
- Any open action: the tool was re-authorized after a change of its binding, or the organization principal was replaced.
- A pending action, or one the policy allowed: also when the signed proposal no longer matches the tool, or the delegation it relied on was revoked (or its revocation is pending).
- An action the policy allowed: also when the agent's active policy version, the tool's exposed name or its amount mapping changed, or the observe window that relaxed it ended.
An action a person approved is not closed when its delegation is revoked: it stays approved and every retry is refused (VERIFICATION_FAILED) until it expires or is cancelled. After a superseded action, the identical call gets a new decision.
Receipts
Receipts are issued for succeeded, failed, rejected and cancelled actions (in rare cases an interrupted execution that K-Block closes as failed has no receipt; the action page shows the failure either way). Denied and expired actions have no receipt: a receipt ends with a signed result, rejection or cancellation, and these actions end without one. See What a receipt proves.
Repeating a call: idempotency and resume
The Gateway identifies a call by its agent, its tool and its exact arguments. What happens when an agent sends the same call again depends on whether it sends an idempotency key in _meta (kblock/idempotency-key, 1–200 characters):
| Situation | Result |
|---|---|
| Same idempotency key, same call | The action of that key. A finished action returns its stored outcome (a succeeded call returns the same stored result; nothing runs again); an open one continues as below. |
| Same idempotency key, another tool or other arguments | Refused with IDEMPOTENCY_KEY_REUSED. |
| A new idempotency key | A new action with its own decision, even while an identical call is open. |
No key, the same call is still open (pending_approval or approved) | The call resumes that action: it reports the pending approval, or executes the approved action. |
| No key, the earlier identical call already finished | A new action with a new decision. |
Changing any argument always makes a new action. An approval covers only the exact call it was given for. Which of the agent's gateway keys sends the repeated call, and that key's environment, play no part in matching it: any key of the same agent resumes the open action.
A repeated call is resolved by the tool's exposed name first: if the tool was disabled or renamed in the meantime, even a call with a known idempotency key is refused with UNKNOWN_TOOL instead of returning the stored outcome.
An agent can also wait for a decision in the same call by sending kblock/wait-seconds in _meta. K-Block limits how long one call can wait: 60 seconds by default; a longer request is lowered to the limit (see Limits and supported versions). If the wait ends before anyone decides, the agent receives approval_pending and can repeat the call later. See the TypeScript client and Python for how the SDKs send these.
The exactly-once boundary
K-Block guarantees that each protected action, and each approval, is consumed at most once. K-Block records at most one execution per action and per approval request. If the same approved call arrives several times at once, it executes once; the other attempts get replay (EXECUTION_IN_PROGRESS or REPLAY).
What this does not cover:
- The upstream call itself is at most once, not exactly once. The Gateway never retries an upstream call. If the call was sent and no valid answer came back, the action is recorded as
failedand the approval stays consumed. - Identical calls without an idempotency key are merged only while the first one is open. Once it has finished, the same call is a new action.
- The guarantee covers the calls made through K-Block. It says nothing about what the upstream does with duplicate requests from other sources.
Outcome unknown
Sometimes K-Block cannot know whether the upstream acted. There is no separate "outcome unknown" status: these cases end as failed, with a detail that says so.
| What happened | What K-Block records | What you should do |
|---|---|---|
| The request reached the upstream but no valid answer came back (timeout, cut connection, malformed answer) | failed (UPSTREAM_FAILED); the detail says the upstream may have acted. Receipt issued. | Check the effect in the upstream system before repeating the call. |
| The execution was interrupted after the approval was consumed and before the result was recorded | The action shows executing until K-Block closes it, then becomes failed (INTERRUPTED, detail interrupted; upstream outcome unknown), usually with a receipt. | Check the upstream before repeating the call. |
| K-Block could not confirm that the approval was consumed | The upstream is not called and the agent receives DATABASE_UNAVAILABLE (not retryable). If the consumption did take effect, K-Block later records the action as failed (INTERRUPTED) with the detail that the upstream was not called; otherwise the action is still approved. | Check the action before you repeat the call. |
| The upstream answered but its result could not be stored as it is | failed (UPSTREAM_FAILED) with RESULT_NOT_STORABLE in the detail, without the upstream's result. Receipt issued. | Check the upstream: it answered, possibly with success. |
| The upstream answered but K-Block could not record the outcome (a K-Block service was temporarily unavailable) | The agent receives RESULT_NOT_RECORDED; the action shows executing until K-Block closes it as failed (INTERRUPTED). | Check the upstream: it answered, possibly with success. |
K-Block closes interrupted executions automatically, normally 11 to 15 minutes after they started. Until then the action shows executing; do not repeat the call before you have checked its effect upstream. If an action stays executing much longer, contact K-Block support and give them the action's ID and the time the call was made.