Authio docs

Actions

Verdict shape

The JSON your endpoint returns. Every field is optional; an empty body is treated as allow.

Wire shape

{
  "decision": "allow" | "deny",
  "deny_reason": "string (optional, logged)",
  "deny_code": "string (optional, surfaced to the client)",
  "override_claims": { "...": any },
  "override_roles": ["role_slug_1", "role_slug_2"],
  "override_permissions": ["perm:slug:1", "perm:slug:2"],
  "append_audit": { "...": any }
}

An empty body or a body where decision is missing / unrecognised is treated as allow. This keeps post-event hooks (analytics-only post_token_mint) as simple as res.status(200).end().

Field reference

decision

"allow" or "deny". Missing or any unrecognised value is treated as "allow". A "deny" verdict always blocks the auth event regardless of the action’s configured fail_mode.

deny_reason · deny_code

Set when decision == "deny". deny_reason lands in the Authio audit log; deny_code is forwarded to the client as the response body’s code field so your SDK can switch on it. Conventional codes:

  • compliance_block — geo / region / sanctions
  • fraud_review — fraud-engine decision
  • requires_2fa — your app wants to force a step-up

override_claims

Free-form key/value map merged into the JWT at sign time. Authio silently drops keys in the reserved-claims set (iss, sub, aud, exp, iat, nbf, jti, kind, roles, permissions, and the widget-claim quad). Use this for tenant-specific extras like employee_id or team_lead. Only honoured on pre_token_mint.

override_roles · override_permissions

Replace the roles and permissions JWT claims with the customer’s authoritative values (Pattern 3 — see the walkthrough). Only honoured on pre_token_mint AND only when the project’s roles_action_override toggle is on (Dashboard → Authorization → Settings).

Role slugs that don’t exist in the project’s roles table are silently dropped and an audit-log entry (action.override_unknown_roles_dropped) is emitted. If every override slug is dropped, the override is rejected entirely and the JWT falls back to Authio-side roles — the spec’s "audit + downgrade" rule keeps a typo from accidentally signing the user out of their app.

append_audit

Free-form key/value map merged into the audit-event metadata for the auth event. Capped at 4 KiB of JSON; oversize values are silently dropped.

Worked examples

1. Plain allow

{}

2. Deny — fraud engine flagged the IP

{
  "decision": "deny",
  "deny_code": "fraud_review",
  "deny_reason": "fraud engine 92/100 risk score; sign-in blocked",
  "append_audit": {
    "fraud_engine_score": 92,
    "fraud_engine_ruleset": "sift-2026-q1"
  }
}

The user sees a 403 with { code: "fraud_review", message: "..." }.

3. Pattern 3 — Customer-as-source roles

{
  "decision": "allow",
  "override_roles": ["billing_admin", "auditor"],
  "override_permissions": [
    "invoices:read",
    "invoices:approve",
    "audit-log:read"
  ]
}

The minted JWT carries roles and permissions from your DB instead of Authio’s membership_roles table.

4. Decorate the JWT with a custom claim

{
  "decision": "allow",
  "override_claims": {
    "employee_id": "EMP-04812",
    "cost_center": "RND-3",
    "team_lead_user_id": "user_abc"
  }
}

Read next