Create Approval Request

POST /api/v1/approvals/request -- submit an action for human approval and receive a request ID to track the decision.

Key Concepts

The create request endpoint is the entry point for the approval flow. Your application describes the action that needs human approval, specifies a time-to-live, and optionally includes metadata and a callback URL. The response includes a request ID that you use to poll for the decision.

Endpoint

text
POST https://signedapproval.net/api/v1/approvals/request
Authorization: Bearer sa_live_...
Content-Type: application/json

Request Body

JSON
{
  "action": "Deploy v2.1.0 to production",
  "ttl_seconds": 3600,
  "context": {
    "environment": "production",
    "version": "2.1.0",
    "commit": "abc123"
  },
  "requires_individual_decision": true,
  "callback_url": "https://your-app.com/webhook/approval"
}

Field descriptions:

  • action (required, string) -- Human-readable description of the action needing approval. This is shown to the approver in the dashboard and push notification. Maximum 500 characters.
  • ttl_seconds (optional, integer) -- How long the request stays pending before expiring. If omitted, the approver's default TTL is used. Must be between 60 and 604800 (1 minute to 7 days).
  • context (optional, object) -- Structured key-value data attached to the request and shown to the approver. Known keys get first-class rendering on every approver surface (iOS, watch, dashboard): agent_name, agent_purpose, tool, reason, tool_arguments (a nested object -- “what the agent will do”; redact secrets before sending), risk_arg_findings (a list of {detector, severity, snippet}), tool_arguments_fingerprint (a short hash of the arguments, shown copyable), and source. Unknown keys are still shown as plain rows.
  • requires_individual_decision (optional, boolean, default false) -- The request must be decided on its own. It is shown to the approver as “needs individual review”, excluded from select-all, and POST /api/v1/approvals/bulk-respond returns it as { "skipped": true, "reason": "requires_individual_decision" } instead of deciding it. Set it for irreversible or high-blast-radius actions (production promotes, payments, data deletion). Approvals created by the GitHub, GitLab and Bitbucket integrations are treated this way automatically.
  • callback_url (optional, string) -- URL to POST the decision to when the approver decides. Must be HTTPS in production. The callback is HMAC-signed with your per-API-key signing secret; retrieve it from GET /api/v1/account/webhook-secret.

Response

Success (201 Created):

JSON
{
  "id": "req_abc123def456",
  "status": "pending",
  "action": "Deploy v2.1.0 to production",
  "metadata": {
    "environment": "production",
    "version": "2.1.0",
    "commit": "abc123"
  },
  "created_at": "2026-03-23T14:00:00.000Z",
  "expires_at": "2026-03-23T15:00:00.000Z"
}

Error responses:

JSON
// 400 Bad Request -- missing required fields
{ "error": "action is required" }

// 401 Unauthorized -- invalid or missing API key
{ "error": "Invalid API key" }

// 429 Too Many Requests -- rate limited
{ "error": "Rate limit exceeded", "retry_after": 30 }

Examples

Minimal request:

bash
curl -X POST https://signedapproval.net/api/v1/approvals/request \
  -H "Authorization: Bearer sa_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{"action": "Run database migration"}'

Full request with all options:

bash
curl -X POST https://signedapproval.net/api/v1/approvals/request \
  -H "Authorization: Bearer sa_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "action": "Transfer $2,500 to vendor Acme Corp",
    "ttl_seconds": 300,
    "context": {
      "agent_name": "AP Clerk",
      "agent_purpose": "Pays approved vendor invoices under $5,000",
      "tool": "Create Transfer (Banking)",
      "tool_arguments": { "amount": 2500, "currency": "USD", "vendor": "Acme Corp", "invoice": "INV-2026-0042" },
      "tool_arguments_fingerprint": "9f2c4a7d1b3e8c05"
    },
    "requires_individual_decision": true,
    "callback_url": "https://api.example.com/webhooks/approval"
  }'

Using internal service token with approver routing:

bash
curl -X POST https://signedapproval.net/api/v1/approvals/request \
  -H "Authorization: Bearer internal-your_service_token" \
  -H "X-Approver-Email: approver@example.com" \
  -H "Content-Type: application/json" \
  -d '{"action": "CleverAgent: Navigate to bank portal and initiate wire", "ttl_seconds": 300}'
Note
The action text is hashed (SHA-256, first 16 chars) and included in the signed payload. Choose descriptive action text -- it helps the approver make an informed decision and creates a clear audit trail.
Tip
Use the context field to pass structured context that helps the approver. For example, include the commit SHA, environment name, dollar amount, or any other relevant details. Context is shown in the dashboard, the iOS app and on Apple Watch alongside the action text; the keys listed above (tool_arguments, risk_arg_findings, …) get dedicated sections. Set requires_individual_decision on anything that should never be swept into an “approve all”.