KBAC 3.0: Raw-Cypher Policies with CALL { } Subqueries and USE Routing
This example is 3.0-kbac ONLY - every policy shown here is rejected on 2.0-kbac.
What 3.0-kbac changes:
A 2.0-kbac condition is rewritten by the platform and always runs against the default database. A 3.0-kbac condition is RAW Cypher: it runs exactly as authored, and you write the composite-database routing yourself.
Newly accepted Cypher keywords (rejected on 2.0-kbac, legal on 3.0-kbac):
1. USE graph.byName(...) - routes the match to a constituent database of the composite IKG. The argument must be a string literal or a single parameter; expressions are rejected at creation.
2. CALL { } subqueries - each subquery can carry its own USE clause, so one condition can combine matches from several constituents. The examples write them as CALL () { ... }, the explicit variable-scope form: the parentheses list the outer variables imported into the subquery (empty = import none).
3. RETURN inside a CALL { } subquery - required to hand rows back to the enclosing query. A top-level RETURN is still rejected: the platform appends the projection itself.
Still blocked in both versions: mutating clauses (CREATE, DELETE, DETACH, DROP, FOREACH, MERGE, REMOVE, SET).
Authoring rules specific to 3.0-kbac:
- The condition must still bind the variables subject and resource.
- $subject_id must not be referenced (composite node IDs are not stable across locations); rejected at creation.
- $subject_external_id, $subject_type, $resource_external_id, $resource_type are bound by the platform - never supplied via input_params.
- Schema is otherwise identical to 2.0-kbac: any valid 2.0 condition is also a valid 3.0 condition.
For dynamic, per-request location routing with USE graph.byName($param), see authz-8.
Use case
Scenario: A car-sharing platform runs a composite IKG (see the data residency guide): the default/global constituent holds proxy nodes and cross-location relationships, while full node data lives in per-location constituents such as ikcomposite.db2.
The authorization team wants decisions evaluated against a location constituent - not just the global proxies - because that is where the full nodes live:
Policy 1 (static USE): the whole condition runs inside ikcomposite.db2. "Can alice drive KITT?" is answered from the east database's full data.
Policy 2 (CALL subquery): the same routing expressed as a CALL { } subquery with an inner RETURN - the shape to build on when a condition needs to combine matches from more than one constituent (each subquery can carry its own USE clause).
Both policies answer the same AuthZEN calls as any other KBAC policy: nothing changes for the calling application.

Requirements
Prerequisites:
- A customer-hosted composite-database project (composite_db_name + alias_mapping configured; see the data residency guide and resource residency-1)
- Nodes ingested with locations, e.g. Person(person-alice) and Car(car-kitt) with an OWNS relationship in the east constituent (ikcomposite.db2)
- 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 (authorization check)
Version requirement: meta.policy_version must be "3.0-kbac". The same Cypher fails on 2.0-kbac with "USE clause is not allowed" / "Cypher contains forbidden clauses: [CALL]".
Steps
Step 1: Create a 3.0-kbac policy with a static USE clause
- Authentication: ServiceAccount credential (Bearer token)
- Action: POST the policy; the condition starts with USE graph.byName('ikcomposite.db2')
- Result: The Cypher is validated raw (EXPLAINed against the database, not rewritten); policy ID returned
Step 2: Create a 3.0-kbac policy with a CALL { } subquery
- Authentication: ServiceAccount credential (Bearer token)
- Action: POST the policy; the condition wraps the match in CALL () { USE ... MATCH ... RETURN subject, resource }
- Result: CALL, per-subquery USE, and the inner RETURN are all accepted - each would be rejected on 2.0-kbac
Step 3: Evaluate
- Authentication: AppAgent credential (X-IK-ClientKey header)
- Action: POST to /access/v1/evaluation with subject, action, resource - the request shape is unchanged
- Result: {decision: true} evaluated inside the routed constituent
Step 4: Cleanup
- Action: DELETE the policy configurations
Step 1
3.0-kbac policy with a static USE clause. The condition is raw Cypher: the platform keeps it verbatim, pins subject/resource by type + external_id, and replaces the projection. On 2.0-kbac this exact Cypher fails with 'USE clause is not allowed'.
{
"meta": {
"policy_version": "3.0-kbac"
},
"subject": {
"type": "Person"
},
"actions": [
"CAN_DRIVE"
],
"resource": {
"type": "Car"
},
"condition": {
"cypher": "USE graph.byName('ikcomposite.db2') MATCH (subject:Person)-[:OWNS]->(resource:Car)"
}
}Request to create the static-USE policy using REST.
{
"project_id": "your_project_gid",
"description": "3.0-kbac raw-Cypher policy. The condition is executed as authored: the USE graph.byName() clause pins the whole match to the ikcomposite.db2 constituent database. Rejected on 2.0-kbac (USE clause is not allowed).",
"display_name": "policy - can drive (static USE routing)",
"name": "policy-can-drive-static-use",
"policy": "{\"meta\":{\"policy_version\":\"3.0-kbac\"},\"subject\":{\"type\":\"Person\"},\"actions\":[\"CAN_DRIVE\"],\"resource\":{\"type\":\"Car\"},\"condition\":{\"cypher\":\"USE graph.byName('ikcomposite.db2') MATCH (subject:Person)-[:OWNS]->(resource:Car)\"}}",
"status": "ACTIVE",
"tags": []
}Step 2
3.0-kbac policy using a CALL { } subquery with its own USE clause and an inner RETURN - all three newly accepted keywords in one condition. On 2.0-kbac the CALL keyword alone fails with 'Cypher contains forbidden clauses: [CALL]'. Note the subquery RETURNs subject and resource so the platform can pin them.
{
"meta": {
"policy_version": "3.0-kbac"
},
"subject": {
"type": "Person"
},
"actions": [
"CAN_DRIVE"
],
"resource": {
"type": "Car"
},
"condition": {
"cypher": "CALL () { USE graph.byName('ikcomposite.db2') MATCH (subject:Person)-[:OWNS]->(resource:Car) RETURN subject, resource }"
}
}Request to create the CALL-subquery policy using REST.
{
"project_id": "your_project_gid",
"description": "3.0-kbac policy demonstrating the newly accepted keywords: a CALL { } subquery with a per-subquery USE graph.byName() clause and an inner RETURN. The example is written CALL () { ... } - the explicit variable-scope form, where the parentheses list the outer variables imported into the subquery (empty = none). Each of the three (CALL, USE, inner RETURN) is rejected on 2.0-kbac; on 3.0-kbac they are legal composite-database routing. Mutating clauses (CREATE, MERGE, SET, DELETE, ...) and a top-level RETURN remain blocked.",
"display_name": "policy - can drive (CALL subquery routing)",
"name": "policy-can-drive-call-subquery",
"policy": "{\"meta\":{\"policy_version\":\"3.0-kbac\"},\"subject\":{\"type\":\"Person\"},\"actions\":[\"CAN_DRIVE\"],\"resource\":{\"type\":\"Car\"},\"condition\":{\"cypher\":\"CALL () { USE graph.byName('ikcomposite.db2') MATCH (subject:Person)-[:OWNS]->(resource:Car) RETURN subject, resource }\"}}",
"status": "ACTIVE",
"tags": []
}Step 3
AuthZEN evaluation - the request shape is identical to any other KBAC evaluation; the routing lives entirely in the policy.
{
"subject": {
"type": "Person",
"id": "person-alice"
},
"resource": {
"type": "Car",
"id": "car-kitt"
},
"action": {
"name": "CAN_DRIVE"
}
}Response - the decision was evaluated inside the ikcomposite.db2 constituent.
{
"decision": true
}Step 4
Delete the policies when done.
{
"id": "your_policy_configuration_gid"
}