Back to all guides
KBAC

AuthZEN: Authorization Decisions on the Knowledge Graph

How to ask IndyKite for an authorization decision with the AuthZEN REST API: single and batch evaluations, subject, resource and action searches, the tokens involved, and the KBAC policies that produce the answer.

ikg

What is AuthZEN?

AuthZEN is the OpenID Foundation standard for asking an authorization service a question of the form "may this subject perform this action on this resource?" and getting a yes/no answer. The application that asks is the Policy Enforcement Point (PEP); the service that answers is the Policy Decision Point (PDP). IndyKite is the PDP: it answers from KBAC policies evaluated against the IndyKite Knowledge Graph (IKG). Policies are written through the Config API (the Policy Administration Point) and the graph is the data the decision is made on (the Policy Information Point). The picture above shows these four roles.

AuthZEN does for authorization what OpenID Connect did for authentication: it replaces vendor-specific authorization APIs with one request and response format, so any application can ask any AuthZEN-compliant PDP and switch or combine PDPs without rewriting the calls. Every request is built from four entities:

  • subject: who is asking (a user, a service, an agent).
  • action: what they want to do.
  • resource: what they want to do it to.
  • context: anything else the decision needs, such as a purchase amount or a region.

Externalizing authorization this way keeps the rules out of application code: they are managed, updated and audited in one place, and a fine-grained question such as "may this person drive this car right now?" is answered per request instead of being approximated by static roles. What IndyKite adds is the answer itself: the decision comes from relationships in the graph, so it changes as the data changes, without redeploying the application.

Terms used in this guide

Term Meaning
IKGThe IndyKite Knowledge Graph: a property graph of nodes and relationships, filled through the Capture API (see the Data Schema guide).
KBACKnowledge-Based Access Control: a policy is a graph pattern (Cypher) between a subject and a resource; the decision is true when the pattern matches (see the Dynamic Authorization guide).
KBAC policyA configuration object with a subject type, a list of actions, a resource type and a condition. Created with POST /configs/v1/authorization-policies and Service Account credentials, or with Terraform indykite_authorization_policy. Versions 2.0-kbac and 3.0-kbac are documented here.
Application Agent (AppAgent)The credential your application calls the runtime APIs with, sent as X-IK-ClientKey. It needs the Authorization API permission for the decision and search endpoints (see the Credentials guide).
User tokenThe end user's OAuth 2.0 access token, sent as Authorization: Bearer. Optional on every AuthZEN endpoint. A Token Introspect configuration validates it and maps its subject to a node.
Delegation tokenAn optional token in X-IK-Token, minted by the IndyKite Token Service, describing the chain of agents acting for a user.
Identity nodeA node ingested with is_identity: true. Required for the subject of a 2.0-kbac policy.
Input parameterA value supplied under context.input_params and read by a policy as $name.
Composite IKGA graph split across locations for data residency; only relevant to 3.0-kbac routing (see the Data Residency guide).

A complete example

The graph below is the one the AuthZEN resources of this library ingest. People drive cars, and a person holds a ticket for a bus.

Example graph: Person(knightrider) -DRIVES-> Car(kitt); Person(satchmo) -DRIVES-> Car(cadillacv16); Person(alice) -DRIVES-> Car(cadillacv16); Person(karel) -HAS-> Ticket(listek) -FOR-> Bus(harmonika)

Goal: the application asks whether knightrider may drive kitt. Four steps: ingest, policy, request, response.

Step 1: Ingest the graph

Nodes and relationships go in through the Capture API with AppAgent credentials. The people are identity nodes. The full payload is in resource authz-1; the relevant part:

POST /capture/v1/nodes
{
  "nodes": [
    {
      "external_id": "knightrider",
      "type": "Person",
      "is_identity": true,
      "properties": [
        {
          "type": "email",
          "value": "knightrider@demo.com"
        }
      ]
    },
    {
      "external_id": "kitt",
      "type": "Car",
      "properties": [
        {
          "type": "manufacturer",
          "value": "pontiac"
        }
      ]
    }
  ]
}
POST /capture/v1/relationships
{
  "relationships": [
    {
      "source": {
        "external_id": "knightrider",
        "type": "Person"
      },
      "target": {
        "external_id": "kitt",
        "type": "Car"
      },
      "type": "DRIVES"
    }
  ]
}

Step 2: Write the policy

A KBAC policy names a subject type, the actions it grants, a resource type, and the graph pattern that must exist between the two. In the pattern, the variable subject is the requesting subject and resource is the requested resource; the platform binds both from the request.

{
  "meta": {
    "policy_version": "2.0-kbac"
  },
  "subject": {
    "type": "Person"
  },
  "actions": [
    "CAN_DRIVE"
  ],
  "resource": {
    "type": "Car"
  },
  "condition": {
    "cypher": "MATCH (subject:Person)-[:DRIVES]->(resource:Car)"
  }
}

Store it through the Config API (Service Account credentials). The policy JSON travels as a string in the policy field:

POST /configs/v1/authorization-policies
Authorization: Bearer <service-account-token>
{
  "project_id": "gid:AAAABbbbCCCC...",
  "name": "person-can-drive-car",
  "display_name": "A person can drive the cars they drive",
  "policy": "{\"meta\":{\"policy_version\":\"2.0-kbac\"},\"subject\":{\"type\":\"Person\"},\"actions\":[\"CAN_DRIVE\"],\"resource\":{\"type\":\"Car\"},\"condition\":{\"cypher\":\"MATCH (subject:Person)-[:DRIVES]->(resource:Car)\"}}",
  "status": "ACTIVE",
  "tags": []
}

Step 3: Ask for the decision

The application calls the evaluation endpoint with its AppAgent credential. No user token is needed: the subject is identified by subject.type and subject.id, which are the node's type and external_id.

curl -X POST https://eu.api.indykite.com/access/v1/evaluation \
  -H "X-IK-ClientKey: $CLIENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "subject": {
    "type": "Person",
    "id": "knightrider"
  },
  "resource": {
    "type": "Car",
    "id": "kitt"
  },
  "action": {
    "name": "CAN_DRIVE"
  }
}'

Step 4: Read the response

{
  "decision": true
}

The same request for "id": "cadillacv16" returns {"decision": false}: knightrider has no DRIVES relationship with that car. A denial carries nothing else unless a policy filter attached advice (see advice).

Endpoint reference

All endpoints are under https://eu.api.indykite.com or https://us.api.indykite.com, take Content-Type: application/json, and authenticate with X-IK-ClientKey. Authorization: Bearer and X-IK-Token are optional everywhere (see Tokens).

Endpoint Question Answer
POST /access/v1/evaluationMay this subject perform this action on this resource?decision (boolean)
POST /access/v1/evaluationsSeveral such questions in one callOne decision per question, in order
POST /access/v1/search/subjectWhich subjects of a type may perform this action on this resource?List of subjects
POST /access/v1/search/resourceOn which resources of a type may this subject perform this action?List of resources
POST /access/v1/search/actionWhich actions may this subject perform on this resource?List of action names
GET /access/v1/policiesWhich active KBAC policies does my project have?The policies as stored, with their tags

POST /access/v1/evaluation

{
  "subject": {
    "type": "<node type>",
    "id": "<node external_id>"
  },
  "resource": {
    "type": "<node type>",
    "id": "<node external_id>"
  },
  "action": {
    "name": "<action>"
  },
  "context": {
    "input_params": {
      "<name>": <any>
    },
    "policy_tags": [
      "<tag>"
    ]
  }
}
Field Required Rules
subject.type, resource.typeyesA node type: 2 to 64 characters, a letter or underscore followed by letters, digits or underscores. Selects the policies whose subject.type and resource.type match.
subject.id, resource.idyesThe node's external_id: 1 to 256 characters.
action.nameyes2 to 50 characters from letters, digits, ., :, _ and /; no hyphen. Selects the policies whose actions list contains it, compared exactly and case-sensitively. It is not looked up in the graph.
context.input_paramswhen a policy reads $nameKeys: 2 to 20 characters, a letter followed by letters, digits or underscores. String values: 1 to 256 characters. Numbers, booleans, arrays and objects are accepted. Every selected policy must find every parameter it references.
context.policy_tagsnoEach tag 1 to 20 alphanumeric characters. When present, only policies carrying at least one of the tags are evaluated; policies without tags are left out.

Other fields, including subject.properties, other keys under context, and options, are ignored without an error.

Response. Allowed:

{
  "decision": true
}

Denied, when a policy's graph pattern matched but its filter attached advice:

{
  "decision": false,
  "context": {
    "advice": [
      {
        "error": "insufficient_user_authentication",
        "error_description": "Authentication is missing or expired",
        "sub": "$token.exp"
      }
    ]
  }
}

Denied without advice is {"decision": false}. context is absent when there is nothing to report. The single evaluation never returns a reason: anything that prevents a decision is an HTTP error (see Errors).

POST /access/v1/evaluations

Several evaluations in one call, which saves a round trip per question when one subject is checked against many resources or one resource against many actions. Top-level subject, resource, action and context are defaults; each entry of evaluations supplies what differs.

{
  "subject": {
    "type": "Person",
    "id": "karel"
  },
  "action": {
    "name": "CAN_DRIVE"
  },
  "evaluations": [
    {
      "subject": {
        "type": "Person",
        "id": "knightrider"
      },
      "resource": {
        "type": "Car",
        "id": "kitt"
      }
    },
    {
      "subject": {
        "type": "Person",
        "id": "knightrider"
      },
      "resource": {
        "type": "Car",
        "id": "cadillacv16"
      }
    },
    {
      "resource": {
        "type": "Bus",
        "id": "harmonika"
      },
      "action": {
        "name": "CAN_RIDE"
      }
    },
    {
      "resource": {
        "type": "Bus",
        "id": "harmonika"
      }
    }
  ]
}
{
  "evaluations": [
    {
      "decision": true
    },
    {
      "decision": false
    },
    {
      "decision": true
    },
    {
      "decision": false
    }
  ]
}

Rules:

  • A default is replaced, not merged. An entry that sets context uses only its own input_params and policy_tags; the default context is not consulted for that entry. The same holds for subject, resource and action. In the example, the third entry keeps the default subject karel and overrides the action; the fourth keeps both defaults and asks whether karel may drive a bus, which no policy grants.
  • Results come back in request order, one per entry, each with the same shape as a single evaluation response.
  • One failing entry does not fail the call. The call returns 200; the entry gets "decision": false and context.reason explains it, for example "reason": "invalid_argument: missing or wrong input params, 'max_price'". The same situations are HTTP errors on the single endpoint.
  • Field validation still applies to the merged entries. An entry whose merged subject, resource or action is missing or malformed is refused for the whole call with 400 and the field messages listed under errors.

A search leaves one side of the question open and returns every graph node, or every action, that would make the decision true. The open side gives only a type. Results are deduplicated and there is no paging.

Endpoint Request body Response
POST /access/v1/search/subjectsubject.type, action.name, resource.type, resource.id, optional context{"results": [{"type": "...", "id": "..."}]}
POST /access/v1/search/resourcesubject.type, subject.id, action.name, resource.type, optional context{"results": [{"type": "...", "id": "..."}]}
POST /access/v1/search/actionsubject.type, subject.id, resource.type, resource.id, optional context{"results": [{"name": "..."}]}

The three searches on the example graph, all with X-IK-ClientKey only. "Who may drive kitt?":

POST /access/v1/search/subject
{
  "subject": {
    "type": "Person"
  },
  "action": {
    "name": "CAN_DRIVE"
  },
  "resource": {
    "type": "Car",
    "id": "kitt"
  }
}
{
  "results": [
    {
      "type": "Person",
      "id": "knightrider"
    }
  ]
}

"Which cars may knightrider drive?":

POST /access/v1/search/resource
{
  "subject": {
    "type": "Person",
    "id": "knightrider"
  },
  "action": {
    "name": "CAN_DRIVE"
  },
  "resource": {
    "type": "Car"
  }
}
{
  "results": [
    {
      "type": "Car",
      "id": "kitt"
    }
  ]
}

"What may knightrider do with kitt?":

POST /access/v1/search/action
{
  "subject": {
    "type": "Person",
    "id": "knightrider"
  },
  "resource": {
    "type": "Car",
    "id": "kitt"
  }
}
{
  "results": [
    {
      "name": "CAN_DRIVE"
    }
  ]
}

Only the CAN_DRIVE policy exists at this point, so the action search returns one name. With input parameters, add context.input_params to any of the three bodies exactly as on the evaluation (resource authz-4 does this for a resource and a subject search).

Two differences from the evaluation:

  • Subject search uses no tokens and no filter. The user token and the delegation token are not read, and a policy's condition.filter is not evaluated. A policy whose filter or Cypher reads a token claim therefore matches no subject in this search; resource and action searches do evaluate the filter and bind the claims.
  • An empty result is not an error. No matching policy, or no matching node, gives {"results": []}.

Full examples: resource authz-1.

GET /access/v1/policies

An application agent can list the active KBAC policies of its own project at runtime, for example to show an administrator which rules apply, to pick policy_tags, or to let an agent discover which actions and resource types exist for a subject type. The agent needs the ReadAuthZConfigs API permission, which is separate from Authorization and is not granted to existing agents automatically (see the Environment guide).

curl https://eu.api.indykite.com/access/v1/policies?subject_type=Person \
  -H "X-IK-ClientKey: $CLIENT_KEY"
{
  "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": []
    }
  ]
}
  • subject_type (optional query parameter, a valid node type of 2 to 64 characters) keeps only the policies with that subject.type. Omitted: every policy. A type no policy uses returns {"results": []}; a value that is not a valid node type is refused with 422.
  • results[].policy is the policy string sent to the Config API, parsed into an object, exactly as authored (including filter when present). results[].tags are the values matched by context.policy_tags; [] when the policy has none.
  • Only KBAC policies with status ACTIVE, only the calling agent's project. ContX IQ policies are not listed. Policy IDs, names and timestamps are not returned; use the Config API to manage a policy.
  • A user token or delegation token sent on this call is still validated and can fail with 401; it has no other effect.

Full example: resource authz-9.

Credentials and tokens

Header Required Effect
X-IK-ClientKeyalwaysThe AppAgent credential. Authorization permission for decisions and searches, ReadAuthZConfigs for the policy listing.
Authorization: Bearer <user token>optionalValidated by Token Introspect. Its claims become $token.<claim> in policies. On 2.0-kbac the subject must also be the token's identity, otherwise the decision is false without an error. On 3.0-kbac the token's subject must equal subject.type and subject.id, otherwise 403. Not read by the subject search.
X-IK-Token: <delegation token>optionalRaw token, no Bearer prefix. Validated by Token Introspect like the user token. Its claims become $ik_token.<claim>. Does not require a user token. When a user token with a sub claim is also present, both sub claims must be equal.

To see which subject.type and subject.id a user token resolves to, call GET /contx-iq/v1/whoami (see the ContX IQ guide). The subject is the same for both APIs.

The delegation token carries the chain of agents acting for the user: $ik_token.sub is the user, $ik_token.act.sub the agent that made this call, $ik_token.act.act.sub the one before it, and so on. The Token Service sets a type on the link it adds (Agent unless configured otherwise) and keeps the links of the incoming token unchanged. A policy reads the chain through $ik_token.act... in its filter or its Cypher.

How a decision is computed

  1. Policies are selected by exact match of subject.type, resource.type and action.name against each active policy's subject.type, resource.type and actions, and by policy_tags when sent. No policy selected: {"decision": false}, not an error.
  2. Input parameters are bound. Every selected policy must find every $name its Cypher references in context.input_params, even if another policy would already allow. A missing one fails the request with 422 and missing or wrong input params, '<name>' (several names are comma-separated).
  3. Each policy's Cypher condition runs with subject and resource bound to the requested nodes. The condition holds when the pattern matches.
  4. Its filter runs, if the policy has one and the Cypher condition held. The filter is evaluated without the graph, against input parameters and token claims.
  5. Any policy that holds makes the decision true. KBAC has no deny rule: a policy can only allow, and a filter can only stop its own policy from allowing. Advice is returned only when the decision is false.

An error inside any selected policy, such as a missing parameter or a runtime failure, fails the whole decision on the single endpoint and marks that entry with a reason in a batch.

Advice

A filter leaf may carry an advice object: a free map of string keys and values written by the policy author. When that leaf fails, the graph condition had matched, and no other policy allows, the map is returned under context.advice. There is no fixed set of keys; error and error_description are a convention borrowed from OAuth. This policy denies a service action on a laptop when the user token is older than a moment fixed in the policy, and says why:

{
  "meta": {
    "policy_version": "2.0-kbac"
  },
  "subject": {
    "type": "Token"
  },
  "actions": [
    "CAN_SERVICE"
  ],
  "resource": {
    "type": "Laptop"
  },
  "condition": {
    "cypher": "MATCH (subject:Token)-[:_SAME_AS]->(person:Person)-[:OWNS]->(resource)",
    "filter": {
      "attribute": "$token.exp",
      "operator": ">",
      "value": 1767225600,
      "advice": {
        "error": "insufficient_user_authentication",
        "error_description": "Authentication is missing or expired",
        "sub": "$token.exp"
      }
    }
  }
}

Full example: resource authz-3. With AND, the advice of every failing operand is returned; with OR, none is returned when one operand succeeds.

KBAC policy reference

The policy JSON is the same for both versions; only meta.policy_version and the rules below differ. The Cypher guide covers how to write the pattern; the Data Residency guide covers routing on a composite IKG.

{
  "meta": {
    "policy_version": "2.0-kbac | 3.0-kbac"
  },
  "subject": {
    "type": "<node type>"
  },
  "actions": [
    "<action>"
  ],
  "resource": {
    "type": "<node type>"
  },
  "condition": {
    "cypher": "<Cypher pattern between subject and resource>",
    "filter": {
      "operator": "<op>",
      "attribute": "<$token.claim | $ik_token.path | $param>",
      "value": <any>,
      "operands": [],
      "advice": {
        "<key>": "<string>"
      }
    }
  }
}

2.0-kbac and 3.0-kbac

Aspect 2.0-kbac 3.0-kbac
Node labelsThe subject must be an identity node; the platform matches subject as an identity node of the subject type and every other node pattern as an ingested node. A subject ingested without is_identity: true never matches, so the decision is false with no error.The Cypher runs as written; the platform only pins subject and resource by type and external_id. A node pattern without a label matches any node, including property and trust score nodes. is_identity is not required.
User tokenOptional. When sent, the subject must be the token's identity; otherwise false.Optional. When sent, its subject must equal the requested subject; otherwise 403 with bearer token subject differs from requested subject (evaluation, evaluations, resource and action search).
USE graph.byName(...) and CALL { }Rejected at creation: USE clause is not allowed, CALL { } subquery is not allowed.Allowed. Only a USE clause needs a composite IKG; a 3.0-kbac policy without one runs on the default database. A top-level RETURN is still rejected.
Resolver-backed (external) properties in the conditionAllowed.Rejected at creation: invalid policy config: external properties cannot be used in data-residency policies.
$subject_idMay be referenced, but then counts as an input parameter the request must supply.Rejected at creation: invalid policy config: parameter "$subject_id" is reserved and cannot be referenced.
Platform-bound parameters $subject_external_id, $subject_type, $resource_external_id, $resource_typeBound by the platform. Do not reference them: a policy that does turns them into required input parameters.Reserved: referencing them is rejected at creation.

A 2.0-kbac condition is accepted as a 3.0-kbac condition when it uses none of the rejected items, but it can decide differently: the labels the platform no longer adds, and the token check that becomes an error, are behaviour changes, not just schema changes. Policies with the older 1.0-kbac version keep working and appear in the listing; this guide does not describe them.

condition.filter

A boolean tree evaluated without the graph, after the Cypher condition held. Attributes are token claims or input parameters; the graph is not visible to it.

Element Rule
operatorAND, OR (two or more operands; one gives invalid field operands: len is 1 and should be >= 2), NOT (exactly one operand), =, <>, <, <=, >, >=, IN, =~, STARTS WITH, ENDS WITH, CONTAINS, IS NULL, IS NOT NULL. operands only with AND, OR, NOT.
attributeRequired on a leaf, a string: $token.<claim>, $ik_token.<path> (dot paths walk the act chain, e.g. $ik_token.act.act.sub), $name or $name.path for an input parameter.
valueRequired for every comparison, must be absent for IS NULL and IS NOT NULL. A literal, a $ reference as above, an array for IN, or a typed object {"type": "datetime", "value": "<RFC 3339 or $param>"}.
CONTAINSThe attribute is a whitespace-separated list that contains the value as one of its items, as in an OAuth scope claim. Comma-separated lists and case differences do not match; a non-string attribute gives false. This differs from Cypher's substring CONTAINS.
IS NULLTrue for a token claim that is absent, including when the token itself was not sent. A referenced input parameter must exist; IS NULL is true when its value is null.
adviceA map of string keys and values on a leaf; returned as described under Advice.
Unknown fieldsRejected at creation.

Example, a read that also requires the cars.read scope in the user token (resource authz-6):

"condition": {
  "cypher": "MATCH (subject:Person)-[:DRIVES]->(resource:Car)",
  "filter": {
    "operator": "CONTAINS",
    "attribute": "$token.scope",
    "value": "cars.read"
  }
}

Token claims in the Cypher condition

The same claim sets are bound as Cypher parameters, so the pattern itself can compare graph data with a claim: $token.<claim> and $ik_token.<path>, on both versions and on every endpoint except the subject search.

{
  "meta": {
    "policy_version": "2.0-kbac"
  },
  "subject": {
    "type": "Person"
  },
  "actions": [
    "CAN_DRIVE"
  ],
  "resource": {
    "type": "Car"
  },
  "condition": {
    "cypher": "MATCH (subject:Person)-[:OWNS]->(resource:Car) WHERE resource.delegated_to = $ik_token.act.sub AND resource.driver = $token.sub"
  }
}
  • token and ik_token are reserved names. They are never input parameters: the platform binds them from the request's tokens and replaces anything the caller sends under those names.
  • A token that was not sent binds an empty claim set. A comparison such as = $ik_token.act.sub is then false and the decision is false, not an error; $ik_token.act.sub IS NULL is true.
  • Only the exact names are reserved. A misspelt $iktoken.act.sub is an ordinary input parameter, refused with 422 and missing or wrong input params, 'iktoken'.
  • A claim cannot route a query: USE graph.byName($token.region) is rejected at creation (see below).

Location routing (3.0-kbac on a composite IKG)

A 3.0-kbac policy may start with USE graph.byName(...), either a string literal or a single parameter such as $region. The request supplies the logical location, a key of the project's alias_mapping, as an ordinary input parameter:

{
  "subject": {
    "type": "Person",
    "id": "knightrider"
  },
  "resource": {
    "type": "Car",
    "id": "kitt"
  },
  "action": {
    "name": "CAN_DRIVE"
  },
  "context": {
    "input_params": {
      "region": "east"
    }
  }
}

This works identically on all decision and search endpoints. Rules and their exact messages:

  • The routing parameter is used nowhere else in the Cypher: invalid policy config: parameter "$region" routes graph.byName() and cannot be referenced elsewhere.
  • The argument is a literal or one bare parameter: invalid policy config: graph.byName() argument must be a string literal or a single parameter. This is also what $token.region gets.
  • It cannot be a platform-bound name: invalid policy config: graph.byName() parameter "$subject_external_id" collides with a platform-bound parameter, and for the claim sets invalid policy config: graph.byName() cannot take "$token", the platform binds it to the token claims.
  • At request time, 422: location parameter "$region" must be a non-empty string; unknown location "mars" for parameter "$region"; location parameter "$region" requires a composite database, but the app space has none configured; and for a static USE without a composite IKG, policy requires a composite database, but the app space has none configured.

Full examples: resources authz-7 and authz-8.

Errors

Status Body and cause
400{"message": "Bad Request"}: the body is not valid JSON. With "errors": ["invalid field <path>"]: a field has the wrong JSON type. In a batch, an entry whose merged fields fail validation is reported here with the field messages.
401{"message": "Missing or malformed AppAgent credential token in X-IK-ClientKey header"} or {"message": "Invalid AppAgent JWT in X-IK-ClientKey header"}: the AppAgent credential. {"message": "Invalid token in Authorization header"} or {"message": "Invalid token in X-IK-Token header"}: that token is expired, not accepted by any Token Introspect configuration, sent with a Bearer prefix in X-IK-Token, or could not be introspected right now; in the last case a retry can succeed. {"message": "Token in X-IK-Token header is missing the sub claim"} and {"message": "Token in X-IK-Token header carries a different sub than the subject token"}: the two tokens are not about the same user. {"message": "insufficient API access level for appAgent"}: the agent lacks the permission the endpoint needs.
403{"message": "Forbidden", "errors": ["bearer token subject differs from requested subject"]}: a 3.0-kbac policy was selected and the user token's subject is not the requested subject.
406{"error": "Accept must be application/json or */*", ...}: the Accept header excludes JSON.
415{"error": "Content-Type must be application/json", ...}: missing or other content type.
422{"message": "Unprocessable Entity", "errors": [...]}. Field validation: missing field subject, missing field subject.type, invalid field subject.type: "A" does not meet rule min=2, invalid field subject.type: does not match regex "^[a-zA-Z_]\w+$", invalid field context.input_params[key]: string "..." must not be shorter than 1 and longer than 256 characters. Decision time: missing or wrong input params, '<name>' and the location messages above.
503{"message": "Unable to verify the AppAgent credential token, retry the request"}: the AppAgent credential could not be checked right now. Retry with the same credential.

What is IndyKite-specific compared to the AuthZEN specification

  • Additional inputs go under context.input_params and context.policy_tags; no other context key is read.
  • subject.properties, resource.properties, action.properties and options (including evaluations_semantic) are accepted and ignored. Every batch entry is evaluated; there is no short-circuit semantic.
  • subject.id and resource.id are node external_ids, subject.type and resource.type are node types, and action.name is matched against policy action lists, never against the graph.
  • The searches return complete, deduplicated result lists without paging, and the subject search evaluates no tokens and no filter.
  • The response adds context.advice (policy-authored maps) and, in batches, context.reason.
  • Credentials are IndyKite headers: X-IK-ClientKey, optional Authorization: Bearer, optional X-IK-Token.

Related