Receipts
A receipt is a signed, immutable record of a single authorization decision or event. Every /check call, authorization lifecycle event (create, revoke), and escalation resolution produces one.
Receipts are signed with an Ed25519 workspace signing key managed by Allowly. Anyone with your workspace's public key can verify a receipt offline, permanently, without contacting Allowly.
---
GET /v1/receipts/{receipt_id}
Fetch a single receipt by ID. Returns a discriminated union on status.
GET /v1/receipts/rcp_01HXZ2B...
Authorization: Bearer allowly_l1_s001_...
Pending — signing not yet complete
{
"status": "pending",
"receipt_id": "rcp_01HXZ2B...",
"ready_at_estimate": "2026-04-21T14:32:18.482Z",
"url": "https://api.allowly.ai/v1/receipts/rcp_01HXZ2B..."
}
Poll the same URL. Signing completes asynchronously after the decision.
Signed — full receipt, ready to verify
{
"status": "signed",
"receipt": {
"version": "1.0",
"receipt_id": "rcp_01HXZ2B...",
"workspace_id": "ws_01HXA1...",
"issued_at": "2026-04-21T14:32:17.482Z",
"decision": "allow",
"reason": "authorization_granted_scope_active",
"user_id": "emp_8821",
"agent_id": "referral_outreach",
"scope": "outreach.send",
"resource": "edge:emp_8821:conn_9f2a",
"context": {
"initiated_by": "user",
"origin": "chat",
"session_id": "sess_7f2"
},
"authorization_id": "auth_01HXZ2A...",
"policy_version": "2026-04-17.1",
"signature": {
"alg": "Ed25519",
"key_id": "workspace-signing-key-version",
"value": "base64url..."
}
}
}
Cache this locally. The receipt is immutable — it never changes after signing.
---
GET /v1/receipts
List receipts with filters. Returns summaries; fetch individual receipts for the full signed artifact.
GET /v1/receipts?authorization_id=auth_01HXZ2A...
Authorization: Bearer allowly_l1_s001_...
Supported query parameters:
| Parameter | Description |
|---|---|
authorization_id | Full authorization chain: creation + scope receipts + escalation resolutions + revocation |
user_id | All receipts for a user across all their authorizations |
resource | All receipts touching a specific resource |
session_id | All receipts from a specific agent session |
scope | Filter by scope name |
event | Filter by event (authorization.create, authorization.revoke, escalation.resolve) |
decision | Filter by outcome (allow, deny, confirm, escalate, authorization_granted, authorization_revoked, escalation_approved, escalation_rejected) |
from | ISO 8601 timestamp lower bound |
to | ISO 8601 timestamp upper bound |
cursor | Opaque pagination cursor returned as next_cursor from a prior response |
limit | Page size, default 50, maximum 100 |
{
"receipts": [
{
"receipt_id": "rcp_01HXZ2B...",
"authorization_id": "auth_01HXZ2A...",
"scope": "outreach.send",
"event": null,
"decision": "allow",
"signed": true,
"created_at": "2026-04-21T14:32:17.482Z"
}
],
"has_more": false,
"next_cursor": null
}
When has_more is true, pass next_cursor back on the next request:
GET /v1/receipts?authorization_id=auth_01HXZ2A...&cursor=eyJjcmVhdGVkX2F0Ijoi...
Authorization: Bearer allowly_l1_s001_...
---
Offline verification
Fetch your workspace's public keys — no API key required, so auditors can access this directly:
GET /v1/workspaces/`{workspace_id}`/keys
{
"workspace_id": "ws_01HXA1...",
"keys": [
{
"key_id": "workspace-signing-key-version",
"alg": "Ed25519",
"public_key": "base64url-32-bytes",
"active_from": "2026-04-01T00:00:00Z",
"active_until": null
}
]
}
Then verify with the reference verifier:
from allowly.verify import verify_receipt, load_keys_from_json
keys = load_keys_from_json(keys_doc)
verify_receipt(signed_receipt, keys) # raises VerificationError if invalid
The verifier checks: version, all required fields present, no unknown fields, scope/event decision pairing, Ed25519 signature over the canonical payload. See the receipt format spec for the full algorithm.
---
Receipt structure
All receipts share the same flat structure. Scope receipts have scope; event receipts have event. Exactly one of those fields is present.
| Field | Type | Description | |
|---|---|---|---|
version | string | Always "1.0" | |
receipt_id | string | ULID | |
workspace_id | string | Issuer identifier | |
issued_at | string | RFC 3339, UTC, millisecond precision | |
decision | string | allow, deny, confirm, escalate, authorization_granted, authorization_revoked, escalation_approved, or escalation_rejected | |
reason | string | Machine-readable reason code | |
user_id | string | Opaque user identifier | |
agent_id | string | Opaque agent identifier | |
scope | string \ | absent | Scope name for /check receipts. Absent on event receipts. |
event | string \ | absent | authorization.create, authorization.revoke, or escalation.resolve. Absent on scope receipts. |
resource | string \ | null | Target identifier, or null |
context | object | Issuer-defined metadata | |
authorization_id | string \ | null | Related authorization ID |
policy_version | string | Decision logic version at time of issue | |
signature | object | { alg, key_id, value } |