What is AuthZEN?
AuthZEN (AuthZ ENhancement) is an OpenID Foundation initiative that standardizes fine-grained authorization, similar to how OpenID Connect standardized authentication.
AuthZEN creates interoperability between:
- Policy Enforcement Points (PEPs): Applications that need authorization decisions.
- Policy Decision Points (PDPs): Services that evaluate policies and return decisions.
IndyKite is an AuthZEN-compliant PDP that uses its Identity Knowledge Graph (IKG) to make intelligent, context-aware authorization decisions.
Why use AuthZEN?
What problem does AuthZEN solve for interoperability?
Problem: The authorization landscape is fragmented. Different vendors use proprietary APIs, protocols, and policy languages, creating vendor lock-in and integration challenges.
AuthZEN's Solution: A standardized communication protocol between PEPs and PDPs. Any application can query any AuthZEN-compliant PDP, regardless of the underlying policy engine.
IndyKite Benefit: Organizations can use IndyKite's advanced KBAC capabilities alongside other AuthZEN-compliant components without vendor lock-in.
How does AuthZEN simplify development?
Problem: Developers often "reinvent the authorization wheel" for each application, leading to inconsistent implementations and security vulnerabilities.
AuthZEN's Solution: Standardized JSON-based request/response formats with core entities:
subject: Who is requesting accessaction: What operation they want to performresource: What they want to accesscontext: Additional environmental data
IndyKite Benefit: Developers use the standard AuthZEN interface instead of learning a proprietary API, reducing integration time.
Why externalize authorization?
Problem: Embedding authorization logic in applications makes it difficult to manage, update, and audit. Fine-grained access control (e.g., "Can Alice view this specific document at this time?") becomes unscalable.
AuthZEN's Solution: Externalize authorization to dedicated PDPs that make dynamic, real-time decisions based on subject, resource, action, and context.
IndyKite Benefit: IndyKite's Identity Knowledge Graph enables complex, relationship-based authorization. Access decisions are based on "who you are, what you're trying to do, with what, from where, and why" — not just static roles.
How does AuthZEN improve security and compliance?
Problem: Inconsistent authorization creates security gaps and makes compliance difficult to demonstrate.
AuthZEN's Solution: Continuous authorization enforcement, dynamic separation of duties, and clear audit trails.
IndyKite Benefit: The IKG continuously updates with real-time data, enabling dynamic and adaptive access decisions that support "least privilege" and "zero trust" policies.
How do I make an AuthZEN request?
Single Evaluation Request
Endpoint:
- EU:
POST https://eu.api.indykite.com/access/v1/evaluation - US:
POST https://us.api.indykite.com/access/v1/evaluation
Request Syntax
{
"subject": {
"type": "<string>",
"id": "<string>"
},
"resource": {
"type": "<string>",
"id": "<string>"
},
"action": {
"name": "<string>"
},
"context": {
"input_params": {
"<key>": "<value>"
}
}
}
What does each field mean?
| Field | Description | Example |
subject.type |
The type of entity requesting access (maps to node label in IKG) | "User", "Service" |
subject.id |
Unique identifier for the subject (maps to external_id in IKG) | "alice@example.com" |
resource.type |
The type of resource being accessed (maps to node label in IKG) | "Document", "API" |
resource.id |
Unique identifier for the resource (maps to external_id in IKG) | "project_alpha_specs.pdf" |
action.name |
The operation being requested | "CAN_READ", "CAN_EDIT" |
context.input_params |
Additional contextual data for policy evaluation. Also carries location parameters for 3.0-kbac policies on a composite IKG (see below) |
{"ip_address": "192.168.1.100"}, {"region": "east"} |
Request Example
{
"subject": {
"type": "User",
"id": "alice@example.com"
},
"resource": {
"type": "Document",
"id": "project_alpha_specs.pdf"
},
"action": {
"name": "CAN_READ"
},
"context": {
"input_params": {
"ip_address": "192.168.1.100",
"time_of_day": "14:30:00Z",
"device_type": "laptop"
}
}
}
Response Syntax
{
"decision": <boolean>,
"context": {
"advice": [{
"error": "<string>",
"error_description": "<string>"
}]
}
}
What does the response contain?
| Field | Description |
decision |
true = access allowed, false = access denied |
context.advice |
Optional array of advice objects explaining why access was denied |
advice.error |
Error code (e.g., "insufficient_user_authentication") |
advice.error_description |
Human-readable explanation of the denial |
Response Example (Denied)
{
"decision": false,
"context": {
"advice": [{
"error": "insufficient_user_authentication",
"error_description": "Authentication is expired"
}]
}
}
How does IndyKite process an AuthZEN request?
When IndyKite receives an AuthZEN request, it:
- Maps request to graph entities: The
subject,resource,action, andcontextare mapped to nodes and relationships in the Identity Knowledge Graph.
- Evaluates KBAC policies: Policies defined as graph traversals are evaluated against real-time data.
Example policy logic:
- "Allow
UsertoCAN_READDocumentIFUserIS_MEMBER_OFTeamANDDocumentIS_ASSIGNED_TOProjectANDTeamIS_ASSIGNED_TOProject" - "Deny access IF
context.ip_addressis NOT intrusted_network"
- "Allow
- Returns decision: An AuthZEN-compliant response with
decision(true/false) and optionaladviceexplaining the decision.
How do I batch multiple evaluations?
Use the batch endpoint to evaluate multiple authorization requests in a single call.
Endpoint:
- EU:
POST https://eu.api.indykite.com/access/v1/evaluations - US:
POST https://us.api.indykite.com/access/v1/evaluations
Batch Request Syntax
{
"subject": {
"type": "<string>",
"id": "<string>"
},
"resource": {
"type": "<string>",
"id": "<string>"
},
"action": {
"name": "<string>"
},
"context": {
"input_params": {},
"policy_tags": ["<string>"]
},
"evaluations": [{
"subject": {
"type": "<string>",
"id": "<string>"
},
"resource": {
"type": "<string>",
"id": "<string>"
},
"action": {
"name": "<string>"
},
"context": {
"input_params": {},
"policy_tags": ["<string>"]
}
}]
}
How do default values work in batch requests?
Fields defined at the top level serve as defaults for all evaluations. Each evaluation can override these defaults.
| Field | Required | Description |
subject |
Optional | Default subject for all evaluations. Useful when the same user accesses multiple resources. |
resource |
Optional | Default resource for all evaluations. Overridden by evaluation-specific resource. |
action |
Optional | Default action for all evaluations. Overridden by evaluation-specific action. |
context |
Optional | Default context for all evaluations. Overridden by evaluation-specific context. |
evaluations |
Required | Array of individual evaluation objects to process. |
What are policy_tags?
The policy_tags field allows you to filter which policies are evaluated for a request. Only policies with matching tags will be considered.
Batch Request Example
{
"subject": {
"type": "User",
"id": "alice@example.com"
},
"action": {
"name": "CAN_READ"
},
"evaluations": [
{
"resource": {
"type": "Document",
"id": "doc_001"
}
},
{
"resource": {
"type": "Document",
"id": "doc_002"
}
},
{
"resource": {
"type": "Folder",
"id": "folder_001"
},
"action": {
"name": "CAN_LIST"
}
}
]
}
This example evaluates:
- Can Alice READ doc_001?
- Can Alice READ doc_002?
- Can Alice LIST folder_001? (overrides default action)
Which KBAC policy versions answer AuthZEN requests?
The decisions behind every AuthZEN endpoint come from KBAC policies. Two policy versions exist, selected by meta.policy_version in the policy JSON:
| Aspect | 2.0-kbac |
3.0-kbac |
| Condition Cypher handling | Rewritten by the platform into evaluation and search variants | Raw: runs as authored; the platform only pins subject/resource and appends the projection |
| Composite-database routing (data residency) | None: always the default database | USE graph.byName(...) clauses, static or via location parameters supplied in context.input_params |
| Allowed Cypher clauses | No CALL, no RETURN |
CALL { } subqueries and inner RETURNs allowed; mutating clauses still blocked |
| Subject node requirement | Must be an identity node (is_identity: true at ingest) |
Any node: matched by type and external ID |
| User (bearer) token at decision time | Optional; binds the subject to the token's identity (internal-node-ID pin, exposed as $subject_id) |
Optional; the token's subject must equal the requested subject or the call is denied with 403 Forbidden |
| External (resolver-backed) properties in the condition | Allowed | Rejected at creation |
| Policy JSON schema | Identical: only meta.policy_version differs |
|
2.0-kbac subjects must be identity nodes: the subject node must have been ingested with is_identity: true (Capture API). A subject ingested as a plain entity never matches a 2.0-kbac condition - the decision is false with no error, which makes this the first thing to check when a correct-looking policy always denies. 3.0-kbac matches the subject by type and external ID only and does not require is_identity.
3.0-kbac does not require a composite database: only USE routing (static or dynamic) does. A 3.0-kbac policy without a USE clause evaluates against the default database as plain raw Cypher.
A valid 2.0-kbac condition is also a valid 3.0-kbac condition: you can carry a policy over just by changing meta.policy_version, with three caveats: $subject_id must not be referenced (see below), external (resolver-backed) properties are rejected at creation, and the platform-bound parameters ($subject_external_id, $subject_type, $resource_external_id, $resource_type) are bound automatically and never supplied via input_params.
$subject_id is not available in 3.0-kbac
A 3.0-kbac condition must not reference $subject_id: on a composite IKG the subject's internal node ID is not stable across locations, so 3.0-kbac identifies subjects by type and external ID only. Creating a policy that references it fails with 422 Unprocessable Entity:
{
"message": "Unprocessable Entity",
"errors": [
"invalid policy config: parameter \"$subject_id\" is reserved and cannot be referenced"
]
}
No replacement is needed: the platform already pins the subject by type and external ID in every 3.0-kbac query.
On 2.0-kbac, $subject_id names the internal-node-ID pin applied when the request carries a user token: the platform binds the subject to the token's identity automatically. A 2.0-kbac condition can reference it, but it then counts as a regular input parameter - the request must carry subject_id under context.input_params, and a user token on the request overrides the supplied value with the token identity's internal node ID. Since the pin already happens automatically, conditions rarely need to reference it.
Optional condition.filter: graph-free pre-checks
Next to cypher, a policy's condition accepts an optional filter: a boolean expression tree evaluated without touching the graph, against the request's context.input_params and the user token's claims. The decision is true only when both the Cypher condition and the filter hold. It works identically on 2.0-kbac and 3.0-kbac.
- Branch nodes combine
operandswithAND/OR(two or more operands) orNOT(exactly one). - Leaf nodes compare an
attributeagainst avaluewith=,<>,<,<=,>,>=,IN(array value),=~,STARTS WITH,ENDS WITH,CONTAINS,IS NULL,IS NOT NULL. "$token.<claim>"resolves to a user-token claim and"$name"to an input param (dot paths reach into object params); any other JSON value is a literal. Wrap datetime comparisons as{"type": "datetime", "value": "<RFC3339 or $param>"}.- A leaf may carry an
advicemap of string key/values, returned undercontext.advicewhen the filter denies (see resourceauthz-3).
"condition": {
"cypher": "MATCH (subject:Person)-[:CAN_AFFORD]->(resource:Server)",
"filter": {
"operator": "AND",
"operands": [
{ "operator": "=", "attribute": "$token.plan", "value": "premium" },
{ "operator": "IN", "attribute": "$channel", "value": ["web", "mobile"],
"advice": { "error": "unsupported_channel" } }
]
}
}
How do I request a decision in a specific location?
On a composite IKG (see the data residency guide), a 3.0-kbac policy can route with a dynamic parameter, for example USE graph.byName($region). The AuthZEN request then supplies the logical location (a key of the project's alias_mapping) through context.input_params: no new endpoint or field is involved:
{
"subject": {
"type": "Person",
"id": "person-alice"
},
"resource": {
"type": "Car",
"id": "car-kitt"
},
"action": {
"name": "CAN_DRIVE"
},
"context": {
"input_params": {
"region": "east"
}
}
}
This works identically on /access/v1/evaluation, /access/v1/evaluations (put the location in the default or per-evaluation context), and the three search endpoints.
Failure modes:
- Omitting a required location parameter, passing a non-string, or naming a location that is not in
alias_mappingfails the call with422 Unprocessable Entity. - Routing with
USE(static or via a location parameter) against a project without a composite database fails with422 Unprocessable Entity(policy requires a composite database). - Bearer-token calls where the token's subject differs from the requested
subjectare denied with403 Forbidden.
How do I read the policies behind the decisions?
An application agent can list the active KBAC policies of its own project at runtime, without Service Account credentials. The policies come back exactly as they were stored through the Config API, inlined as JSON objects, together with their tags. Typical uses: show an administrator which rules currently apply, pick the policy_tags to send on an evaluation, or let an agent discover which actions and resource types exist for a subject type before it asks for decisions.
Endpoint:
- EU:
GET https://eu.api.indykite.com/access/v1/policies - US:
GET https://us.api.indykite.com/access/v1/policies
Authentication: X-IK-ClientKey: <AppAgent-token>. The agent must hold the ReadAuthZConfigs API permission - this is a separate permission from Authorization, and it is not granted to existing agents automatically (see the Environment guide). No user token is involved.
Query parameters
| Parameter | Required | Description |
subject_type |
Optional | Return only the policies whose subject.type equals this node type (2-64 characters, a valid node label such as Person or _Application). Omitted: every policy is returned. |
Response Syntax
{
"results": [{
"policy": {
"meta": { "policy_version": "<string>" },
"subject": { "type": "<string>" },
"actions": ["<string>"],
"resource": { "type": "<string>" },
"condition": { "cypher": "<string>" }
},
"tags": ["<string>"]
}]
}
| Field | Description |
results |
One entry per policy. Empty array when nothing matches - a subject_type no policy uses is not an error. |
results[].policy |
The policy definition as it was stored: the policy string sent to POST /configs/v1/authorization-policies, parsed into a JSON object (no escaping to undo). Its shape follows the policy version, so meta, subject, actions, resource, and condition (including an optional filter) appear exactly as authored. |
results[].tags |
The policy's tags - the values matched by context.policy_tags on evaluation. Always an array, [] when the policy has none. |
Which policies are included?
- KBAC policies (
2.0-kbacand3.0-kbac) with statusACTIVE- the same set the decision endpoints evaluate. Inactive policies are not listed. - Only the project the calling agent belongs to; there is no project parameter.
- ContX IQ policies (
1.0-ciq) are not part of this listing.
Policy IDs, names, and timestamps are not returned; use the Config API with Service Account credentials when you need to manage a policy rather than read it.
Request Example
GET /access/v1/policies?subject_type=Person
{
"results": [
{
"policy": {
"meta": { "policy_version": "2.0-kbac" },
"subject": { "type": "Person" },
"actions": ["CAN_DRIVE"],
"resource": { "type": "Car" },
"condition": { "cypher": "MATCH (subject:Person)-[:DRIVES]->(resource:Car)" }
},
"tags": []
},
{
"policy": {
"meta": { "policy_version": "2.0-kbac" },
"subject": { "type": "Person" },
"actions": ["CAN_RIDE"],
"resource": { "type": "Bus" },
"condition": { "cypher": "MATCH (subject)-[:HAS]->(ticket:Ticket)-[:FOR]->(resource)" }
},
"tags": []
}
]
}
Errors
401with{"message": "insufficient API access level for appAgent"}: the agent lacks theReadAuthZConfigspermission. HoldingAuthorizationalone is not enough.422 Unprocessable Entitywith amessageand anerrorsarray:subject_typeis not a valid node type (too short, too long, or not a valid label).
Full example: resource authz-9.
What credentials do I need?
- AppAgent credentials: Required for all AuthZEN requests. The agent needs the
AuthorizationAPI permission for the decision and search endpoints, andReadAuthZConfigsforGET /access/v1/policies. - User access token: Required if subject is a user (not _Application). To see which
subject.type/subject.ida token resolves to, callGET /contx-iq/v1/whoami(see the ContX IQ guide).
Authentication header: X-IK-ClientKey: <AppAgent-token>
Next Steps
- KBAC concepts: Dynamic Authorization Guide
- Data residency and 3.0-kbac routing: Data Residency Guide
- Full examples: Developer Hub Resources
- Credentials guide: Credentials Guide
- OpenID AuthZEN specification: https://openid.net/wg/authzen/
