Authorizations
An authorization is a permission slip: *user X told agent Y it may do Z, until this time, with these limits.*
Authorizations are immutable after creation. To change what an agent may do, revoke the existing authorization and create a new one. This produces a clean audit chain: one authorization.revoke receipt, one authorization.create receipt.
---
POST /v1/authorizations
Create an authorization. Returns an authorization_id to store in your database.
You can create an authorization from an agent scope bundle, or inline with agent_id and scopes. Do not send both shapes in the same request.
See Decisions and Attributes for supported authorization fields, check fields, decision verbs, and scope constraints.
POST /v1/authorizations
Authorization: Bearer allowly_l1_s001_...
Content-Type: application/json
{
"user_id": "emp_8821",
"agent_id": "referral_outreach",
"scopes": [
{ "name": "contact.enrich" },
{
"name": "outreach.send",
"constraints": { "max_per_day": 5 }
},
{ "name": "candidate.delete" }
],
"requires_confirm_for": ["outreach.send"],
"requires_escalation_for": ["candidate.delete"],
"escalation_targets": {
"candidate.delete": "compliance"
},
"expires_at": "2026-12-31T00:00:00Z",
"budget_limit_micros": 50000000,
"metadata": {
"source": "csv_upload_v2",
"csv_hash": "sha256:abc123..."
}
}
| Field | Required | Description |
|---|---|---|
user_id | yes | Opaque identifier of the end-user. Use an internal ID or the SDK email HMAC helper, not raw email. |
agent_id | yes for inline | Opaque identifier of the agent (e.g. referral_outreach). |
bundle_id | yes for bundle flow | Reusable agent scope bundle ID. If present, omit agent_id and scopes. |
scopes | yes for inline | Array of { name, constraints? } objects. name is a dotted permission name. |
requires_confirm_for | no | Array of scope names that should return confirm before allowing. |
requires_escalation_for | no | Pro+ array of scope names that should return escalate before allowing. |
escalation_targets | no | Optional map from escalated scope name to approver label, such as manager, security, or compliance. |
expires_at | yes | RFC 3339 timestamp. Authorizations must expire — there are no perpetual authorizations. |
budget_limit_micros | no | Pro+ authorization-level spend cap in micro-USD. Requires estimated_cost_micros on /v1/check; see Budget Gate. |
metadata | no | Opaque object recorded in the authorization receipt (e.g. upload source, hash). |
Bundle-based request
{
"user_id": "emp_8821",
"bundle_id": "outreach_v1",
"expires_at": "2026-12-31T00:00:00Z",
"metadata": {
"source": "onboarding_modal"
}
}
Response (201)
{
"authorization_id": "auth_01HXZ2A0K1L2M3N4P5Q6R7S8T9",
"created_at": "2026-04-21T14:30:00.000Z",
"expires_at": "2026-12-31T00:00:00.000Z",
"budget_limit_micros": 50000000,
"budget_spent_micros": 0,
"requires_confirm_for": ["outreach.send"],
"requires_escalation_for": ["candidate.delete"],
"escalation_targets": {
"candidate.delete": "compliance"
},
"receipt": {
"status": "pending",
"receipt_id": "rcp_01HXZAUTHORIZATIONCREATE...",
"ready_at_estimate": "2026-04-21T14:30:01.000Z",
"url": "https://api.allowly.ai/v1/receipts/rcp_01HXZAUTHORIZATIONCREATE..."
}
}
Creating an authorization produces an authorization receipt (event: "authorization.create", decision: "authorization_granted"). This is the tamper-proof record that the user approved these scopes at this moment.
---
Agent scope bundles
Agent scope bundles are reusable workspace-scoped groups of scopes for one agent. They are creation-time helpers only: creating, deleting, or editing a bundle does not produce a receipt. The receipt is produced when an actual user authorization is created.
POST /v1/agent-scope-bundles
POST /v1/agent-scope-bundles
Authorization: Bearer allowly_l1_s001_...
Content-Type: application/json
{
"id": "outreach_v1",
"agent_id": "referral_outreach",
"scopes": [
{ "name": "contact.enrich" },
{ "name": "outreach.send", "constraints": { "max_per_day": 5 } },
{ "name": "candidate.delete" }
],
"requires_confirm_for": ["outreach.send"],
"requires_escalation_for": ["candidate.delete"],
"escalation_targets": {
"candidate.delete": "manager"
},
"default_expiry_days": 365,
"description": "ReferralFlow outreach approval"
}
GET /v1/agent-scope-bundles
Lists active agent scope bundles for the workspace.
DELETE /v1/agent-scope-bundles/{bundle_id}
Soft-deletes a bundle. Existing granted authorizations stay active because authorizations are immutable snapshots.
PII guidance
user_id and agent_id are embedded in every signed receipt permanently. Do not use raw email addresses, phone numbers, or legal names by default.
Use an internal opaque ID (ULID, UUID, database ID) or the SDK's local email HMAC helper:
from allowly.identifiers import from_email
user_id = from_email(user.email, pepper=os.environ["ALLOWLY_PII_PEPPER"])
import { identifiers } from "@allowly/sdk";
const userId = identifiers.fromEmail(user.email, {
pepper: process.env.ALLOWLY_PII_PEPPER!,
});
The helper runs in your app before the request reaches Allowly. Allowly does not provide a server endpoint that accepts raw email to mask or hash it. See PII-safe identifiers.
---
DELETE /v1/authorizations/{authorization_id}
Revoke an authorization. Idempotent.
DELETE /v1/authorizations/auth_01HXZ2A...
Authorization: Bearer allowly_l1_s001_...
Content-Type: application/json
Optional body:
{
"revoked_by": "user",
"notes": "user_toggled_off_in_settings"
}
Response (200)
{
"authorization_id": "auth_01HXZ2A...",
"revoked_at": "2026-05-15T09:12:00.000Z",
"receipt": {
"status": "pending",
"receipt_id": "rcp_01HXZAUTHORIZATIONREVOKE...",
"ready_at_estimate": "2026-05-15T09:12:01.000Z",
"url": "https://api.allowly.ai/v1/receipts/rcp_01HXZAUTHORIZATIONREVOKE..."
}
}
Revocation produces an authorization revoke receipt (event: "authorization.revoke", decision: "authorization_revoked"). All subsequent /check calls against this authorization_id return deny with reason: "authorization_revoked", each producing its own signed scope receipt.
The authorization row is never deleted — every historical receipt references the authorization_id.
Error responses
| Status | Reason |
|---|---|
| 404 | Authorization not found in this workspace |
| 409 | Authorization is already revoked |
---
The authorization chain
An auditor can reconstruct the full authorization story for any authorization_id via GET /v1/receipts?authorization_id=auth_...:
- One
authorization.createreceipt (the grant) - Zero or more scope receipts (every check that matched)
- Zero or more
escalation.resolvereceipts (when third-party approvals are resolved) - At most one
authorization.revokereceipt (if revoked)
Every receipt is independently signed and ordered by issued_at.