The gate

Ask before you act: an agent declares a registered action and its input, and your organisation's policy answers allow, refuse, or a person decides.

The other requests ask a named person to look at something. The gate asks your organisation's policy, before the action rather than after it. The agent says what it is about to do and with what arguments; rules an administrator wrote answer. Most calls are settled by a rule and never reach anybody, and when no rule settles one it becomes an ordinary approval.

  1. 1

    Register the actions

    Your deploy names the consequential things your software does — finance.refund — with a schema for their input.

  2. 2

    Write the policy

    An administrator writes rules against those actions. See Agent policy.

  3. 3

    Gate the call

    Before it acts, the agent calls the gate with the action and the input it will use, and branches on allowed.

Registering actions

An action is a stable name for something consequential, registered once per organisation so a rule can be written about it. Register from your deploy with deliverd actions push (see CLI) or the API (actions:write):

curl -X POST "$DELIVERD_URL/api/v1/actions" \
  -H "Authorization: Bearer $DELIVERD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "actionId": "finance.refund",
    "name": "Issue customer refund",
    "displayTitle": "Refund {{amount}} {{currency}} to {{customer_id}}",
    "editableFields": ["amount"],
    "inputSchema": {
      "type": "object",
      "required": ["customer_id", "amount", "currency"],
      "properties": {
        "customer_id": { "type": "string" },
        "amount": { "type": "number", "minimum": 0 },
        "currency": { "type": "string", "enum": ["GBP", "EUR", "USD"] }
      }
    }
  }'
  • It is an upsert: registering the same id again is the normal case, because it runs on every deploy. An administrator's title, risk category and per-field sensitivity labels are kept separately and survive it.
  • The schema is a small subset of JSON Schema: type, properties and required, with string, number, integer or boolean fields and enum, minimum, maximum, minLength, maxLength and pattern. An unknown keyword is refused (400 invalid_definition) rather than ignored.
  • editableFields names what an approver may correct if the gate sends the request to a person.
  • An agent key is refused (403 agent_not_permitted). An agent that could name actions could name its way around the rule that governs it. Register with a key that belongs to a person.

Calling the gate

TypeScript

const decision = await deliverd.gate({
  action: "finance.refund",
  externalId: `refund:${order.id}`,
  input: {
    customer_id: order.customerId,
    amount: 1240,
    currency: "GBP",
  },
});

if (!decision.allowed) return;
await stripe.refunds.create(decision.input);

gate() waits by default: a rule's answer returns at once, and a request that went to a person blocks until they decide. Pass wait: false to return immediately with a pending decision, or use deliverd.gates.create() in a handler that must not hold a connection open. In Python the call is deliverd.gate(action=…, external_id=…, input=…). Over MCP it is check_gate, with get_gate to read a pending decision back.

Over REST it is POST /api/v1/gates (gates:write), then GET /api/v1/gates/{id} (gates:read) while a person decides:

curl -X POST "$DELIVERD_URL/api/v1/gates" \
  -H "Authorization: Bearer $DELIVERD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "actionId": "finance.refund",
    "externalId": "refund:ord_8812",
    "input": { "customer_id": "cus_1", "amount": 1240, "currency": "GBP" },
    "reason": "Customer reported a duplicate charge"
  }'

A first ask answers 201. environment is optional and lets rules tell staging from production; reason is shown to whoever is asked to decide.

The answer

Branch on one field, allowed. status says why:

statusallowedWhat happenedWhat the agent does
allowedtrueA rule permitted it.The agent carries on. Nobody was interrupted, and the decision is still written down.
deniedfalseA rule refused it.The agent stops, and reads which rule and why. A refusal is an answer, not an error — it arrives as a 200 rather than down the failure path.
pendingfalseNo rule settled it.It became an ordinary approval: a named person decides on the page they already use, and the call can wait for them.
approvedtrueA person agreed to it.Carries on, with the input that came back.
rejected, expired, cancelledfalseNobody said yes.Does not act.

reason and rule name the rule that answered, basis says whether a rule or a person decided, approvalUrl is where a pending request is waiting, and changes lists anything the approver corrected.

Four things to know

Two scopes
gates:write raises a decision, gates:read polls one. Both are in the default set a user key gets, so a key you already have can try it.
A refusal is a 200
Policy saying no is a result you branch on, not an exception. There is no gate_denied error code and there deliberately never was — routing the single most ordinary outcome down every SDK's failure path would make the normal case look like a bug.
An agent key may call it
The only new surface of which that is true. Registering actions and writing policy both refuse an agent principal, because an agent that names the actions or writes the rules is deciding for itself. Submitting to a rule is the opposite of that — an agent that cannot ask can only act without asking.
externalId is required
Which no other POST in this API is. A random per-attempt key protects a retried request; it does not protect a retried decision, and a crashed agent re-running its refund step would otherwise raise a second approval for the same money. Only you know which two calls are the same unit of work.

With the same externalId, a retry re-reads its decision and answers 200 with reused: true instead of raising a second one.

It fails closed

  • An unregistered action is refused with 404 unknown_action; input the action's schema does not describe is 400 invalid_input, with a problems list. Neither proceeds.
  • An action no rule mentions goes to a person. This is the opposite of most permission systems, and deliberate: an action nobody has written a rule about is one nobody has decided about.
  • Gate policy rules are applied on the Business and Enterprise plans. On other plans they are stored but not applied, so every gated action goes to a person.
  • When your organisation's ethics rules flag a gated action, it goes to a person even if a rule would have allowed it; a rule that refuses turns the answer into denied.

The decision is advisory

Your agent reports what it intends to do, policy answers, and your agent then performs the action itself. Nothing in Deliverd re-checks that what ran matches what was agreed. An agent that misreports to the policy engine is misreporting to its own runtime, which is the trust boundary every client library already has — but it is a boundary, and this is where it sits. What the gate gives you is a question asked before the act, answered by somebody other than the caller, and written down either way.