Authdog answers authorization questions over the OpenID Foundation's AuthZEN Authorization API 1.0. Your application acts as the Policy Enforcement Point (PEP) and calls Authdog as the Policy Decision Point (PDP): send a subject, an action, and a resource; get back permit or deny.
Reach for AuthZEN when you want a vendor-neutral wire contract, when a gateway or sidecar in your stack already speaks it, or when you want one decision endpoint no matter which model — RBAC, ABAC, or FGA — the environment enforces.
Endpoints
The PDP is mounted at the API origin root, outside the /v1 base path, because the specification fixes these paths and interop tooling probes them verbatim.
| Endpoint | Purpose |
|---|---|
POST /access/v1/evaluation |
One decision for one subject, action, and resource. |
POST /access/v1/evaluations |
Boxcarred batch of up to 100 evaluations. |
POST /access/v1/search/subject |
Who can perform this action on this resource? |
POST /access/v1/search/resource |
What resources can this subject act on? |
POST /access/v1/search/action |
What actions can this subject perform on this resource? |
GET /.well-known/authzen-configuration |
Discovery document. Unauthenticated, per section 9.1. |
Discovery advertises all five endpoints and honors X-Forwarded-Host and X-Forwarded-Proto, so the advertised URLs match the origin your PEP actually calls.
curl https://api.authdog.com/.well-known/authzen-configurationAuthentication
Every endpoint except discovery is authenticated with an environment API secret (adenv_...) as a bearer token. The token alone identifies the environment whose policies decide the request, so no tenant or environment appears in the path.
curl -X POST "https://api.authdog.com/access/v1/evaluation" \
-H "Authorization: Bearer $AUTHDOG_ENVIRONMENT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"subject": { "type": "user", "id": "usr_123" },
"action": { "name": "can_read" },
"resource": { "type": "document", "id": "roadmap" }
}'The response is the spec's decision shape:
{
"decision": true,
"context": {
"reason_admin": { "reason_code": "role_grant" },
"reason_user": { "en": "Permitted by an assigned role." }
}
}The environment secret is a PEP credential, not an end-user token. Keep it server-side; never ship it to a browser or mobile client.
How decisions are made
The PDP dispatches on the environment's permission model, reusing the same engines that enforce access inside Authdog — a decision served over AuthZEN can never disagree with what Authdog enforces internally.
- RBAC: the action is normalized to a CRUD verb and checked against the subject's role grants, including roles inherited through groups.
- ABAC: the request is converted into policy input and evaluated against the environment's enabled policies.
- FGA: the action is mapped onto a relation and checked against the environment's authorization model and relationship tuples.
See Authorization concepts for how to choose a model, and Fine-grained authorization for the relationship model.
Action names
AuthZEN leaves the action namespace to the PDP. Authdog normalizes action names onto four coarse verbs — create, read, update, delete — so PEPs can send whichever dialect they already use:
| Sent by the PEP | Normalized verb |
|---|---|
create, can_create, insert, add, write, can_write, POST |
create |
read, can_read, view, can_view, GET, list, HEAD, search |
read |
update, can_update, edit, can_edit, modify, PUT, PATCH |
update |
delete, can_delete, remove, destroy |
delete |
An unrecognized action name falls back to read, the least privileged verb. That means an unknown action can never widen access — but it also won't grant what you intended, so keep your PEP on a name from the table.
Subjects
Subject types user, identity, and subject resolve against the environment's directory, matched by user id or external id. A subject that cannot be resolved is denied with reason code subject_not_found; a resolved but deactivated user is denied with subject_inactive.
Reason codes are stable and safe to log: role_grant, no_matching_role, policy_allow, policy_deny, no_policies, relation_holds, relation_missing, subject_not_found, subject_inactive, and evaluation_error.
Batching
POST /access/v1/evaluations evaluates up to 100 items in one request. Top-level subject, action, resource, and context act as defaults; each item overrides only what it sets.
{
"subject": { "type": "user", "id": "usr_123" },
"resource": { "type": "document", "id": "roadmap" },
"evaluations": [
{ "action": { "name": "can_read" } },
{ "action": { "name": "can_update" } },
{ "action": { "name": "can_delete" } }
],
"options": { "evaluations_semantic": "deny_on_first_deny" }
}Supported semantics are execute_all (the default), deny_on_first_deny, and permit_on_first_permit. When a batch short-circuits, the remaining entries are filled with the short-circuit decision rather than omitted, so the response array always lines up with the request array.
A batch shares one environment lookup and memoizes subject and grant reads, so evaluating 50 items costs a handful of database round trips, not 50.
Search
The search endpoints answer the reverse questions — enumeration rather than a yes/no. Results are paginated forward-only: pass the next_token from a response's page block to fetch the next page.
curl -X POST "https://api.authdog.com/access/v1/search/resource" \
-H "Authorization: Bearer $AUTHDOG_ENVIRONMENT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"subject": { "type": "user", "id": "usr_123" },
"action": { "name": "can_read" },
"resource": { "type": "document" }
}'Search evaluates every candidate it enumerates, so it costs far more than a single evaluation. Use it for admin surfaces and access reviews, not on a hot request path.
Failure behavior
The PDP is fail-closed. An unresolvable subject, an unknown permission model, or an internal error produces decision: false with a reason — never a partial permit.
| Status | Meaning |
|---|---|
400 |
Malformed JSON or a body that fails spec validation. |
401 |
Missing or invalid environment API secret. |
502 |
The decision point was unreachable or returned no decision. |
Errors use one envelope on every endpoint, so a PEP can log a single shape:
{ "error": { "status": 400, "message": "subject.id is required" } }A 502 means no decision was made. Your PEP must treat it as a deny, not as a permit.