Check Approval Status

GET /api/v1/approvals/:id -- poll for the current status of an approval request and retrieve the signed decision.

Key Concepts

After creating an approval request, use this endpoint to check its status. The request transitions through states: pending (waiting for decision), approved (human approved),rejected (human rejected), or expired (TTL elapsed).

When the status is "approved" or "rejected", the response includes the full signed decision with the Ed25519 signature that can be independently verified.

Endpoint

text
GET https://signedapproval.net/api/v1/approvals/:id
Authorization: Bearer sa_live_...

Response (Pending)

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

Response (Approved)

JSON
{
  "id": "req_abc123def456",
  "status": "approved",
  "action": "Deploy v2.1.0 to production",
  "context": { "environment": "production" },
  "created_at": "2026-03-23T14:00:00.000Z",
  "expires_at": "2026-03-23T15:00:00.000Z",
  "decision": {
    "id": "dec_xyz789abc012",
    "decision": "approved",
    "method": "passkey",
    "signed_payload": "eyJ2IjoxLCJyaWQiOiJyZXFfYWJjMTIz...",
    "signature": "Wm9tSW1hZ2luYXJ5U2lnbmF0dXJl...",
    "decided_at": "2026-03-23T14:02:30.000Z"
  }
}

Response (Rejected)

JSON
{
  "id": "req_abc123def456",
  "status": "rejected",
  "action": "Deploy v2.1.0 to production",
  "decision": {
    "id": "dec_xyz789abc012",
    "decision": "rejected",
    "method": "totp",
    "signed_payload": "eyJ2IjoxLCJyaWQiOiJyZXFfYWJjMTIz...",
    "signature": "UmVqZWN0aW9uU2lnbmF0dXJl...",
    "decided_at": "2026-03-23T14:05:00.000Z"
  }
}

Response (Expired)

JSON
{
  "id": "req_abc123def456",
  "status": "expired",
  "action": "Deploy v2.1.0 to production",
  "created_at": "2026-03-23T14:00:00.000Z",
  "expires_at": "2026-03-23T15:00:00.000Z"
}

Expired requests have no decision field -- no signed artifact is produced for expirations.

Polling Pattern

If you are not using webhooks, poll this endpoint to wait for a decision:

javascript
async function waitForApproval(requestId: string, apiKey: string) {
  const maxWait = 300_000; // 5 minutes
  const interval = 3_000;  // 3 seconds
  const start = Date.now();

  while (Date.now() - start < maxWait) {
    const res = await fetch(
      `https://signedapproval.net/api/v1/approvals/${requestId}`,
      { headers: { Authorization: `Bearer ${apiKey}` } }
    );
    const data = await res.json();

    if (data.status !== 'pending') {
      return data; // approved, rejected, or expired
    }

    await new Promise(r => setTimeout(r, interval));
  }

  throw new Error('Timed out waiting for approval');
}

Remind the Approver

If a request is still pending, you can re-send the push notification to the approver (and any delegates). Use this from a "Re-notify" button or a scheduled reminder; it never changes the request or its TTL.

text
POST https://signedapproval.net/api/v1/approvals/:id/remind
Authorization: Bearer sa_live_...   (the key that created the request)
JSON
{
  "ok": true,
  "request_id": "req_abc123def456",
  "push_sent": true,
  "reminded_at": "2026-03-23T14:30:00.000Z"
}

push_sent: false means the approver has no registered device (they can still decide on the web dashboard). Sandbox (sa_test_) requests are a no-op and returnsandbox: true.

Errors: 404 unknown request or not yours, 409 not_pending already decided or expired, 429 remind_rate_limited (one reminder per request per minute; see Retry-After). Every reminder is written to the audit log as approval.reminded.

Note
The status endpoint requires the same API key that created the request (or an internal service token). The public /verify endpoint is available without authentication for cryptographic verification.
Tip
For production systems, prefer webhooks over polling. Webhooks deliver decisions instantly and reduce the load on both your application and SignedApproval's API.