Data Residency: Composite-Database Project with Location-Routed Ingestion
This example walks through the full data-residency flow end to end:
1. Create a Project connected to your composite database (composite_db_name + alias_mapping)
2. Poll the project until ikg_status is ACTIVE
3. Create an Application, an Application Agent, and Application Agent Credentials
4. Upsert nodes with a per-node location so each node's data is stored in its constituent database
5. Upsert relationships with use_global_db so cross-location connections land in the global database
The result is one logical Identity Knowledge Graph whose records physically live in the locations you chose.
Use case
Scenario: Your application serves customers in two jurisdictions and each customer's
personal data must stay in its region, while shared infrastructure records and the
relationships connecting everything remain globally reachable:
- Person(alice) stored in the "east" location database
- Person(karel) stored in the "west" location database
- Device(hq-badge-printer) stored in the "global" database
- Car(car1) has no location and goes to the default database
- OWNS / USES relationships stored in the global database, connecting nodes across locations
Applications keep querying a single graph; the composite database handles the placement.
Note on reads: ContX IQ queries and KBAC/AuthZEN checks with 2.0-kbac policies execute
against the default constituent only (db_connection.name - the global database in this
example). Decisions that traverse proxy nodes and relationships keep working; the located
property data (alice's and karel's personal details) is not visible to those queries or
policy conditions. To evaluate authorization inside a location constituent, author the
policy as 3.0-kbac with USE graph.byName() routing - see resources authz-7 and authz-8.
Requirements
Prerequisites:
- ServiceAccount credentials: Created in IndyKite Hub for your Organization
- Organization ID: Your IndyKite organization identifier
- A customer-hosted Neo4j deployment where the composite database and its constituent
databases (db1, db2, db3 in this example) already exist. IndyKite connects to them
and configures routing - it does not create the databases. Composite databases are
available for customer-hosted IKGs only.
Required API access:
- POST /configs/v1/projects (create project)
- GET /configs/v1/projects/{id} (poll ikg_status)
- POST /configs/v1/applications (create application)
- POST /configs/v1/application-agents (create agent)
- POST /configs/v1/application-agent-credentials (generate credentials)
- POST /capture/v1/nodes (upsert located nodes)
- POST /capture/v1/relationships (upsert relationships to the global DB)
Steps
Step 1: Create the Project with the composite connection
- Authentication: ServiceAccount credential as Bearer token in Authorization header
- Action: POST to /configs/v1/projects with db_connection carrying composite_db_name and alias_mapping
- Input: alias_mapping maps logical locations to constituent databases (global=db1&east=db2&west=db3);
db_connection.name is the default constituent used for nodes without a location
- Result: Project ID returned; IKG provisioning starts asynchronously
Step 2: Poll the Project until the IKG is ACTIVE
- Authentication: ServiceAccount credential as Bearer token
- Action: GET /configs/v1/projects/{id} until ikg_status is ACTIVE
- Result: Response echoes db_connection.composite_db_name and alias_mapping; PENDING means
provisioning is still running, FAILED means it did not complete
Step 3: Create an Application
- Authentication: ServiceAccount credential as Bearer token
- Action: POST to /configs/v1/applications with the Project ID from Step 1
- Result: Application ID returned
Step 4: Create an Application Agent
- Authentication: ServiceAccount credential as Bearer token
- Action: POST to /configs/v1/application-agents with the Application ID from Step 3
- Result: Application Agent ID returned
Step 5: Generate Application Agent Credentials
- Authentication: ServiceAccount credential as Bearer token
- Action: POST to /configs/v1/application-agent-credentials with the Agent ID from Step 4
- Result: Credential JSON containing the API key (X-IK-ClientKey value); save it securely,
it cannot be retrieved again
Step 6: Upsert nodes with locations
- Authentication: AppAgent credential as API key (header: X-IK-ClientKey)
- Action: POST to /capture/v1/nodes with an optional location per node (a key of alias_mapping)
- Result: Each located node's full data is stored in its constituent database and a lightweight
proxy node (external_id, type, location only) is upserted into the global database;
nodes without a location go to the default database
Step 7: Upsert relationships into the global database
- Authentication: AppAgent credential as API key (header: X-IK-ClientKey)
- Action: POST to /capture/v1/relationships with use_global_db set to true
- Result: Relationships are created in the global database between proxy nodes, connecting
entities whose data lives in different locations
Step 1
POST request to create a project connected to a customer-hosted composite database. The composite database (ikcomposite) and its constituents (db1, db2, db3) must already exist in Neo4j. The alias_mapping keys (global, east, west) are the location values used later in Capture API requests.
{
"db_connection": {
"url": "neo4j://your-neo4j-host:7687",
"username": "neo4j",
"password": "example-password",
"name": "db1",
"composite_db_name": "ikcomposite",
"alias_mapping": "global=db1&east=db2&west=db3"
},
"description": "Project with a customer-hosted composite database for data residency",
"display_name": "Data residency project",
"name": "residency-project",
"organization_id": "gid-of-organization",
"region": "europe-west1"
}Step 2
GET request to poll the project until ikg_status is ACTIVE. Provisioning runs asynchronously; do not ingest data before the IKG is active. Example response shown.
{
"id": "gid-of-project",
"name": "residency-project",
"region": "europe-west1",
"ikg_status": "ACTIVE",
"db_connection": {
"url": "neo4j://your-neo4j-host:7687",
"username": "neo4j",
"name": "db1",
"composite_db_name": "ikcomposite",
"alias_mapping": "global=db1&east=db2&west=db3"
}
}Step 3
POST request to create an application within the project. The application represents your software system that will use IndyKite services.
{
"description": "Application description",
"display_name": "Application name",
"name": "app-name",
"project_id": "gid-of-project"
}Step 4
POST request to create an application agent. The agent is the authenticated identity that your application uses to call IndyKite APIs.
{
"api_permissions": [
"Authorization",
"Capture",
"ContXIQ",
"EntityMatching"
],
"application_id": "gid-of-application",
"description": "App Agent description",
"display_name": "App Agent name",
"name": "app-agent-name"
}Step 5
POST request to generate credentials for the application agent. The response contains the API key to use in the X-IK-ClientKey header. Save this securely - it cannot be retrieved again.
{
"application_agent_id": "gid-of-app-agent",
"display_name": "AppAgent Credentials name",
"expire_time": "2026-12-31T12:34:56-01:00"
}Step 6
POST request to upsert nodes with per-node locations. alice is stored in the east constituent, karel in west, the shared device in global, and car1 (no location) in the default database. Proxy nodes for all located nodes are automatically upserted into the global database.
{
"nodes": [
{
"external_id": "alice",
"is_identity": true,
"type": "Person",
"location": "east",
"properties": [
{
"type": "email",
"value": "alice@email.com"
},
{
"type": "given_name",
"value": "Alice"
}
]
},
{
"external_id": "karel",
"is_identity": true,
"type": "Person",
"location": "west",
"properties": [
{
"type": "email",
"value": "karel@email.com"
},
{
"type": "given_name",
"value": "Karel"
}
]
},
{
"external_id": "hq-badge-printer",
"is_identity": false,
"type": "Device",
"location": "global",
"properties": [
{
"type": "model",
"value": "BP-200"
}
]
},
{
"external_id": "car1",
"is_identity": false,
"type": "Car",
"properties": [
{
"type": "manufacturer",
"value": "Pontiac"
}
]
}
]
}Step 7
POST request to upsert relationships with use_global_db. The OWNS and USES relationships are stored in the global database, connecting nodes whose full data lives in different location databases.
{
"use_global_db": true,
"relationships": [
{
"source": {
"external_id": "alice",
"type": "Person"
},
"target": {
"external_id": "car1",
"type": "Car"
},
"type": "OWNS"
},
{
"source": {
"external_id": "karel",
"type": "Person"
},
"target": {
"external_id": "hq-badge-printer",
"type": "Device"
},
"type": "USES"
}
]
}API Endpoints
/configs/v1/projects/configs/v1/applications/configs/v1/application-agents/configs/v1/application-agent-credentials/capture/v1/nodes/capture/v1/relationships