Check Approval Status
GET /api/v1/approvals/:id -- poll for the current status of an approval request and retrieve the signed decision.
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
GET https://signedapproval.net/api/v1/approvals/:id
Authorization: Bearer sa_live_...Response (Pending)
{
"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)
{
"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)
{
"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)
{
"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:
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.
POST https://signedapproval.net/api/v1/approvals/:id/remind
Authorization: Bearer sa_live_... (the key that created the request){
"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.
/verify endpoint is available without authentication for cryptographic verification.