KBAC 3.0: Data Residency - Location-Routed AuthZEN Decisions
This example is 3.0-kbac ONLY - it makes AuthZEN decisions location-aware on a composite IKG.
How it works:
1. The policy condition starts with USE graph.byName($region). Because $region routes graph.byName(), it becomes a LOCATION PARAMETER.
2. Each AuthZEN request supplies a logical location in context.input_params (e.g. {"region": "east"}). Logical locations are the KEYS of the project's alias_mapping (global=db1&east=db2&west=db3) - callers never see or send physical database names.
3. Just before execution, IndyKite translates the logical location to the physical constituent (e.g. ikcomposite.db2) and runs the raw condition there.
The same request works on every AuthZEN endpoint: /access/v1/evaluation, /access/v1/evaluations (put the location in the default or per-evaluation context), and the three search endpoints.
Location parameter rules:
- A location parameter must not be referenced anywhere else in the Cypher (its value is rewritten to the physical alias at request time).
- It must arrive as a non-empty string, and must be a key of alias_mapping - otherwise the call fails with 422 Unprocessable Entity.
- Policies without any graph.byName($param) simply have no location parameters - nothing changes for them.
Residency is opt-in per policy: 2.0-kbac policies keep running against the default database, untouched.
Use case
Scenario: A car-sharing platform stores each jurisdiction's personal data in its own constituent database (east, west), as set up in resource residency-1. The mobile app knows which jurisdiction the signed-in user belongs to and asks for decisions in that location:
- "Can alice drive KITT?" with region=east -> true: alice's full data and her OWNS relationship live in the east constituent.
- The same question with region=west -> false: the west constituent has no such data.
- region=north -> 422 Unprocessable Entity: north is not a key of the project's alias_mapping.
- "Which cars can alice drive in east?" (search/resource with region=east) -> [car-kitt].
One policy serves every location; the caller picks the location per request.

Requirements
Prerequisites:
- A customer-hosted composite-database project with alias_mapping, e.g. global=db1&east=db2&west=db3 (see resource residency-1 for the full setup)
- Nodes ingested with locations: Person(person-alice) and Car(car-kitt) with an OWNS relationship in the east location
- ServiceAccount credentials: For creating policies (Bearer token)
- AppAgent credentials: For authorization queries (X-IK-ClientKey)
Required API access:
- POST /configs/v1/authorization-policies (create policy)
- POST /access/v1/evaluation (single check)
- POST /access/v1/search/resource (which resources, in this location)
Version requirement: meta.policy_version must be "3.0-kbac" - location parameters do not exist on 2.0-kbac.
Steps
Step 1: Create the location-routed 3.0-kbac policy
- Authentication: ServiceAccount credential (Bearer token)
- Action: POST the policy whose condition starts with USE graph.byName($region)
- Result: $region is registered as a location parameter of the policy
Step 2: Evaluate in the east location
- Authentication: AppAgent credential (X-IK-ClientKey header)
- Action: POST to /access/v1/evaluation with context.input_params.region = "east"
- Result: {decision: true} - evaluated inside the constituent mapped to east
Step 3: Evaluate the same question in the west location
- Action: Same request with region = "west"
- Result: {decision: false} - the west constituent holds no matching data
Step 4: See what happens with an unmapped location
- Action: Same request with region = "north" (not a key of alias_mapping)
- Result: 422 Unprocessable Entity - unknown location "north" for parameter "$region"
Step 5: Search resources within a location
- Action: POST to /access/v1/search/resource with region = "east"
- Result: The list of cars alice can drive according to the east constituent
Step 6: Cleanup
- Action: DELETE the policy configuration
Step 1
3.0-kbac policy with dynamic routing. $region routes graph.byName() and must not be referenced anywhere else in the Cypher. Platform-bound parameters ($subject_external_id, $subject_type, $resource_external_id, $resource_type) cannot be used as routing parameters, and $subject_id must not appear at all.
{
"meta": {
"policy_version": "3.0-kbac"
},
"subject": {
"type": "Person"
},
"actions": [
"CAN_DRIVE"
],
"resource": {
"type": "Car"
},
"condition": {
"cypher": "USE graph.byName($region) MATCH (subject:Person)-[:OWNS]->(resource:Car)"
}
}Request to create the location-routed policy using REST.
{
"project_id": "your_project_gid",
"description": "3.0-kbac data-residency policy. $region is a location parameter: it routes graph.byName() and must not be referenced anywhere else in the Cypher. Callers supply a logical location (an alias_mapping key such as east or west) in context.input_params.region; IndyKite translates it to the physical constituent database just before execution.",
"display_name": "policy - can drive (location-routed)",
"name": "policy-can-drive-location-routed",
"policy": "{\"meta\":{\"policy_version\":\"3.0-kbac\"},\"subject\":{\"type\":\"Person\"},\"actions\":[\"CAN_DRIVE\"],\"resource\":{\"type\":\"Car\"},\"condition\":{\"cypher\":\"USE graph.byName($region) MATCH (subject:Person)-[:OWNS]->(resource:Car)\"}}",
"status": "ACTIVE",
"tags": []
}Step 2
Evaluation routed to the east location. "east" is a logical location - a key of the project's alias_mapping - not a Neo4j database name. IndyKite resolves it to the physical constituent just before running the condition.
{
"subject": {
"type": "Person",
"id": "person-alice"
},
"resource": {
"type": "Car",
"id": "car-kitt"
},
"action": {
"name": "CAN_DRIVE"
},
"context": {
"input_params": {
"region": "east"
}
}
}Response - allowed: the OWNS relationship exists in the east constituent.
{
"decision": true
}The same east-routed evaluation in Python.
import http.client
import json
conn = http.client.HTTPSConnection("eu.api.indykite.com")
payload = json.dumps({
"subject": {"type": "Person", "id": "person-alice"},
"resource": {"type": "Car", "id": "car-kitt"},
"action": {"name": "CAN_DRIVE"},
# "east" is a logical location (an alias_mapping key), never a Neo4j database name
"context": {"input_params": {"region": "east"}}
})
headers = {
'Content-Type': "application/json",
'X-IK-ClientKey': ""
}
conn.request("POST", "/access/v1/evaluation", payload, headers)
res = conn.getresponse()
data = res.read()
Step 3
The identical question routed to the west location.
{
"subject": {
"type": "Person",
"id": "person-alice"
},
"resource": {
"type": "Car",
"id": "car-kitt"
},
"action": {
"name": "CAN_DRIVE"
},
"context": {
"input_params": {
"region": "west"
}
}
}Response - denied: no matching data in the west constituent. Same subject, same resource, same policy; only the location changed.
{
"decision": false
}Step 4
Request with a location that is not a key of alias_mapping.
{
"subject": {
"type": "Person",
"id": "person-alice"
},
"resource": {
"type": "Car",
"id": "car-kitt"
},
"action": {
"name": "CAN_DRIVE"
},
"context": {
"input_params": {
"region": "north"
}
}
}Response - the call fails before evaluation. The error names the logical location and the parameter; the physical database alias is never exposed.
{
"message": "Unprocessable Entity",
"errors": [
"unknown location \"north\" for parameter \"$region\""
]
}Step 5
Resource search scoped to the east location - which cars can alice drive there? Search endpoints take the location parameter exactly like evaluation.
{
"subject": {
"type": "Person",
"id": "person-alice"
},
"action": {
"name": "CAN_DRIVE"
},
"resource": {
"type": "Car"
},
"context": {
"input_params": {
"region": "east"
}
}
}Response - resource references from the east constituent.
{
"results": [
{
"type": "Car",
"id": "car-kitt"
}
]
}Step 6
Delete the policy when done.
{
"id": "your_policy_configuration_gid"
}API Endpoints
/configs/v1/authorization-policies/access/v1/evaluation/access/v1/search/resource