Skip to content

Approvals ​

Routes are relative to <PUBLIC_BASE_URL>; request and response bodies are JSON unless stated otherwise. Authentication, scopes, pagination and the error format are described in the REST API guides.

GET /api/v1/approvals ​

List my approvals

Pending by default; history on request with status. Each row carries its context (question, workflow, trigger excerpt). With messageId, the approvals of one message, in a single page.

Access — Member session or API key with scope approvals:read.

Parameters

NameInTypeRequired
statusquery"pending" | "approved" | "rejected" | "expired"no
messageIdquerystringno
cursorquerystringno
limitqueryintegerno

Responses

200 — A page of approvals.

FieldTypeRequired
approvalsobject[]yes
nextCursorstring | nullyes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "approvals": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "status": {
            "type": "string",
            "enum": [ "pending", "approved", "rejected", "expired" ]
          },
          "title": { "type": "string" },
          "details": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
          "executionId": { "type": "string" },
          "workflowId": { "type": "string" },
          "workflowName": { "type": "string" },
          "nodeId": { "type": "string" },
          "trigger": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "messageId": { "type": "string" },
                  "fromName": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
                  "fromEmail": { "type": "string" },
                  "subject": { "type": "string" },
                  "receivedAt": { "type": "string" }
                },
                "required": [
                  "messageId",
                  "fromName",
                  "fromEmail",
                  "subject",
                  "receivedAt"
                ],
                "additionalProperties": false
              },
              { "type": "null" }
            ]
          },
          "expiresAt": { "type": "string" },
          "decidedBy": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
          "decidedAt": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
          "createdAt": { "type": "string" }
        },
        "required": [
          "id",
          "status",
          "title",
          "details",
          "executionId",
          "workflowId",
          "workflowName",
          "nodeId",
          "trigger",
          "expiresAt",
          "decidedBy",
          "decidedAt",
          "createdAt"
        ],
        "additionalProperties": false
      }
    },
    "nextCursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }
  },
  "required": [ "approvals", "nextCursor" ],
  "additionalProperties": false
}

400 — The request does not match its schema.

401 — No valid session or API key (auth.unauthenticated).

403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).

429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.

Error codes — request.bad_request. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.

POST /api/v1/approvals/{id}/decide ​

Decide an approval

Approve or reject from the application. The response is the approval in its new state, not the execution: the decision queues a resume, the rest arrives over the real-time channel. Already decided is a 409.

Access — Member session or API key with scope approvals:write.

Parameters

NameInTypeRequired
idpathstringyes

Request body (application/json)

FieldTypeRequired
decision"approve" | "reject"yes
JSON Schema
json
{
  "type": "object",
  "properties": { "decision": { "type": "string", "enum": [ "approve", "reject" ] } },
  "required": [ "decision" ]
}

Responses

200 — The approval, decided.

FieldTypeRequired
approvalobjectyes
resumedbooleanyes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "approval": {
      "type": "object",
      "properties": {
        "id": { "type": "string" },
        "status": {
          "type": "string",
          "enum": [ "pending", "approved", "rejected", "expired" ]
        },
        "title": { "type": "string" },
        "details": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
        "executionId": { "type": "string" },
        "workflowId": { "type": "string" },
        "workflowName": { "type": "string" },
        "nodeId": { "type": "string" },
        "trigger": {
          "anyOf": [
            {
              "type": "object",
              "properties": {
                "messageId": { "type": "string" },
                "fromName": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
                "fromEmail": { "type": "string" },
                "subject": { "type": "string" },
                "receivedAt": { "type": "string" }
              },
              "required": [ "messageId", "fromName", "fromEmail", "subject", "receivedAt" ],
              "additionalProperties": false
            },
            { "type": "null" }
          ]
        },
        "expiresAt": { "type": "string" },
        "decidedBy": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
        "decidedAt": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
        "createdAt": { "type": "string" }
      },
      "required": [
        "id",
        "status",
        "title",
        "details",
        "executionId",
        "workflowId",
        "workflowName",
        "nodeId",
        "trigger",
        "expiresAt",
        "decidedBy",
        "decidedAt",
        "createdAt"
      ],
      "additionalProperties": false
    },
    "resumed": { "type": "boolean" }
  },
  "required": [ "approval", "resumed" ],
  "additionalProperties": false
}

400 — The request does not match its schema.

401 — No valid session or API key (auth.unauthenticated).

403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).

429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.

This operation accepts an Idempotency-Key header: replaying the same request with the same key returns the original response instead of acting twice. See Idempotency.

Error codes — request.bad_request, approval.not_found, approval.already_decided. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.

Example request

json
{ "decision": "approve" }

Example response (200)

json
{
  "approval": {
    "id": "0192f1c2-3b4d-7e8f-9a0b-1c2d3e4f5a6b",
    "executionId": "0192f1c2-3b4d-7e8f-9a0b-1c2d3e4f5a6c",
    "workflowId": "0192f1c2-3b4d-7e8f-9a0b-1c2d3e4f5a6d",
    "workflowName": "Quotes over 5k",
    "title": "Send the quote to ACME?",
    "status": "approved",
    "createdAt": "2026-10-04T09:00:00.000Z",
    "expiresAt": "2026-10-05T09:00:00.000Z",
    "decidedAt": "2026-10-04T09:12:00.000Z"
  },
  "resumed": true
}

GET /api/v1/approvals/t/{token}/{decision} ​

Decide an approval from an email link

The green and red buttons of the email (approve or reject). No session: the token is the authorisation, consumed once. The answer is a standalone HTML page; a second click shows the decision that holds. Rate-limited per IP; 404 for an unknown token.

Access — Authorised by the token carried in the URL.

Parameters

NameInTypeRequired
tokenpathstringyes
decisionpath"approve" | "reject"yes

Responses

200 — The result page.

Content type : text/html

404 — Unknown token or decision.