Quickstart
Get from zero to your first signed receipt in under 10 minutes.
1. Get an API key
Create a workspace at allowly.ai and generate an API key from the API Keys page. It looks like:
allowly_l1_s001_3f9a...
2. Create an authorization
When your user authorizes your agent, call POST /v1/authorizations and store the returned authorization_id.
curl -X POST https://api.allowly.ai/v1/authorizations \
-H "Authorization: Bearer allowly_l1_s001_..." \
-H "Content-Type: application/json" \
-d '{
"user_id": "usr_8821",
"agent_id": "my_agent",
"scopes": [
{ "name": "email.read" },
{ "name": "email.send", "constraints": { "max_per_day": 5 } }
],
"requires_confirm_for": ["email.send"],
"expires_at": "2026-12-31T00:00:00Z"
}'
{
"authorization_id": "auth_01HXZ2A...",
"created_at": "2026-04-21T14:30:00.000Z",
"expires_at": "2026-12-31T00:00:00.000Z",
"receipt": {
"status": "pending",
"receipt_id": "rcp_01HXZAUTHORIZATION...",
"ready_at_estimate": "2026-04-21T14:30:01.000Z",
"url": "https://api.allowly.ai/v1/receipts/rcp_01HXZAUTHORIZATION..."
}
}
Store authorization_id in your database, keyed to your user.
user_id is written into signed receipts. Prefer an opaque internal ID like usr_8821. If you need to derive it from email, use the SDK's local HMAC helper before calling Allowly; do not send raw email by default. See PII-safe identifiers.
Optional Pro+: add a spend cap
On Pro and Enterprise workspaces, if the agent action has an estimated cost, set budget_limit_micros when you create the authorization.
{
"user_id": "usr_8821",
"agent_id": "research_agent",
"scopes": [{ "name": "llm.enrich" }],
"expires_at": "2026-12-31T00:00:00Z",
"budget_limit_micros": 50000000
}
50000000 is $50.00. Budgeted /check calls must include estimated_cost_micros.
3. Check before every action
Before your agent acts, call /v1/check with the stored authorization_id:
curl -X POST https://api.allowly.ai/v1/check \
-H "Authorization: Bearer allowly_l1_s001_..." \
-H "Content-Type: application/json" \
-d '{
"authorization_id": "auth_01HXZ2A...",
"scopes": ["email.read"],
"resource": "gmail:thread:abc123",
"context": { "initiated_by": "user", "origin": "chat" }
}'
{
"authorization_id": "auth_01HXZ2A...",
"user_id": "usr_8821",
"agent_id": "my_agent",
"authorization_expires_at": "2026-12-31T00:00:00Z",
"policy_version": "2026-04-17.1",
"results": {
"email.read": {
"decision": "allow",
"reason": "authorization_granted_scope_active",
"receipt": {
"status": "pending",
"receipt_id": "rcp_01HXY...",
"ready_at_estimate": "2026-04-21T14:32:18.482Z",
"url": "https://api.allowly.ai/v1/receipts/rcp_01HXY..."
}
}
}
}
For budgeted authorizations, request one scope and include the estimated cost:
{
"authorization_id": "auth_01HXZ2A...",
"scopes": ["llm.enrich"],
"estimated_cost_micros": 24000
}
Act on decision immediately. The signed receipt is delivered asynchronously — you don't need to wait for it.
4. Handle all four decisions
result = await client.check(
authorization_id=authorization_id,
scopes=["email.send"],
resource="gmail:thread:abc",
)
scope_result = result.results["email.send"]
if scope_result.decision == "allow":
send_the_email()
elif scope_result.decision == "confirm":
# Prompt your user in-app, then:
await client.confirmations.approve(
scope_result.confirm_nonce,
approved=True,
ttl_seconds=60,
)
# Re-call /check — now returns allow
elif scope_result.decision == "escalate":
# Route to the configured third-party approver, then:
await client.escalations.approve(
scope_result.escalation_id,
resolved_by="manager:8821",
)
# Re-call /check — now returns allow once
elif scope_result.decision == "deny":
raise PermissionError(scope_result.reason)
5. Fetch and verify the signed receipt
The receipt is signed asynchronously shortly after the decision. Fetch it when you need audit evidence:
curl https://api.allowly.ai/v1/receipts/rcp_01HXY... \
-H "Authorization: Bearer allowly_l1_s001_..."
{
"status": "signed",
"receipt": {
"version": "1.0",
"receipt_id": "rcp_01HXY...",
"workspace_id": "ws_01HXA1...",
"issued_at": "2026-04-21T14:32:17.482Z",
"decision": "allow",
"reason": "authorization_granted_scope_active",
"user_id": "usr_8821",
"agent_id": "my_agent",
"scope": "email.read",
"resource": "gmail:thread:abc123",
"context": { "initiated_by": "user", "origin": "chat" },
"authorization_id": "auth_01HXZ2A...",
"policy_version": "2026-04-17.1",
"signature": {
"alg": "Ed25519",
"key_id": "workspace-signing-key-version",
"value": "base64url..."
}
}
}
Verify it offline — no API call needed:
from allowly.verify import verify_receipt, load_keys_from_json
import httpx
keys_doc = httpx.get(
f"https://api.allowly.ai/v1/workspaces/{workspace_id}/keys"
).json()
keys = load_keys_from_json(keys_doc)
verify_receipt(signed_receipt, keys) # raises VerificationError if invalid
6. Revoke when the user opts out
curl -X DELETE https://api.allowly.ai/v1/authorizations/auth_01HXZ2A... \
-H "Authorization: Bearer allowly_l1_s001_..." \
-H "Content-Type: application/json" \
-d '{ "revoked_by": "user" }'
Future /check calls against this authorization_id return deny with reason: "authorization_revoked", each producing its own signed scope receipt.