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..."
  }
}
FieldRequiredDescription
user_idyesOpaque identifier of the end-user. Use an internal ID or the SDK email HMAC helper, not raw email.
agent_idyes for inlineOpaque identifier of the agent (e.g. referral_outreach).
bundle_idyes for bundle flowReusable agent scope bundle ID. If present, omit agent_id and scopes.
scopesyes for inlineArray of { name, constraints? } objects. name is a dotted permission name.
requires_confirm_fornoArray of scope names that should return confirm before allowing.
requires_escalation_fornoPro+ array of scope names that should return escalate before allowing.
escalation_targetsnoOptional map from escalated scope name to approver label, such as manager, security, or compliance.
expires_atyesRFC 3339 timestamp. Authorizations must expire — there are no perpetual authorizations.
budget_limit_microsnoPro+ authorization-level spend cap in micro-USD. Requires estimated_cost_micros on /v1/check; see Budget Gate.
metadatanoOpaque 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

StatusReason
404Authorization not found in this workspace
409Authorization 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_...:

  1. One authorization.create receipt (the grant)
  2. Zero or more scope receipts (every check that matched)
  3. Zero or more escalation.resolve receipts (when third-party approvals are resolved)
  4. At most one authorization.revoke receipt (if revoked)

Every receipt is independently signed and ordered by issued_at.