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 / sanctionsfraud_review— fraud-engine decisionrequires_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"
}
}