Runyard Docs
Concepts

Approvals

Approval cards - kinds, the ask contract, resolutions, timed approvals with fallbacks, and per-kind consequences.

An approval is a declared question with a declared consequence, presented to a human as a card in the web app, over Telegram, or through the API/MCP. Cards can hold a run before it executes (waiting_approval), pause a workflow mid-run at an engine gate, or simply record a decision. A run held on an approval waits indefinitely — waiting never fails it.

Kinds

KindWhat it represents
workflow_gateA workflow paused at a gate; the decision releases or denies that step.
escalationAutonomous recovery gave up; a human records how to proceed.
side_effectA gated side effect asking permission before it runs.
customAny ad-hoc approval raised via the API.

The ask contract

The creator of a card states the ask up front, and every surface renders it verbatim:

{
  "action": "Queue the release workflow for runner execution.",
  "reason": "This run publishes to the public site.",
  "audience": "operators",
  "options": [{ "id": "ship", "label": "Ship it", "effect": "Publishes immediately" }]
}
  • action — what exactly happens on approve.
  • reason — why a human is needed.
  • audience — who is asked: operators (default) or admins.
  • options — optional named choices (up to 8).

An ask without both action and reason is treated as absent; such cards fall back to a heuristic ask explicitly marked derived: true, so a guessed question is never presented as an authored one.

Status and resolution

A card's status is only ever pending or resolved. What was decided and by what live in separate fields:

FieldValues
resolutionapproved, rejected, changes_requested, superseded (the run ended first), or option:<id>
resolvedViahuman, fallback_timer (autopilot), engine (decided on the runner), policy (auto-approved by policy), system

Timed approvals with fallback

By default a card is blocking: nothing happens until someone decides. Supplying timeoutMs or timeoutAt at creation makes it a timed approval:

  • With a fallback decision (approve or reject), the configured decision is applied automatically when the timer elapses, recorded with resolvedVia: "fallback_timer".
  • Without a fallback, the elapsed card is flagged fallback_required and keeps waiting — no decision is invented, and the linked run is never failed by an elapsed timer.

Per-kind consequences

Every card answers "what will my decision do" honestly, computed from what resolving actually performs:

  • workflow_gate — approving a card holding a waiting_approval run releases it; approving a mid-run gate applies the decision to the paused workflow on the runner, which resumes past the gate. Reject/changes-requested cancel a held run, or take the workflow's own deny path mid-run.
  • escalation — the run already ended: any decision records the call but does not restart the run (re-run it from the run page).
  • side_effect — approve allows the gated side effect; reject or changes-requested skip it. The run's completed work is unchanged either way.
  • custom — approving a held run releases it to the queue; reject/changes-requested cancel it. On a card without a held run, the decision is simply recorded.

The API exposes these as whatHappensIfApproved / whatHappensIfRejected / whatHappensIfChangesRequested / whatHappensIfIgnored in each card's context, alongside the ask, the linked run, and deep links.

API & MCP

EndpointMCP toolNotes
GET /api/approvalslist_approvals, list_pending_approvals?status=pending|resolved.
GET /api/approvals/{id}get_approvalFull card with context.
POST /api/approvalscreate_approval{title, description, runId?, ask, timeoutMs?/timeoutAt?, fallback?}.
POST /api/approvals/{id}/approveapprove_runTakes an approval id, not a run id.
POST /api/approvals/{id}/rejectreject_runA held run is cancelled, never failed.
POST /api/approvals/{id}/request-changesrequest_changes_runComment describes the requested changes.
curl -X POST -H "Authorization: Bearer $RUNYARD_TOKEN" -H "Content-Type: application/json" \
  -d '{"comment": "Looks good, ship it."}' \
  https://hub.example.com/api/approvals/appr_123/approve

Resolving approvals requires the api, mcp, or approvals scope; Telegram WebApp sessions are approvals-scoped by design (see Tokens & scopes).

On this page