Documentation Index
Fetch the complete documentation index at: https://docs.kaireonai.com/llms.txt
Use this file to discover all available pages before exploring further.
/api/v1/schema-joins is the CRUD surface for SchemaJoin rows — declarative definitions that link two DataSchema tables on a primary-key/foreign-key pair. Joins drive customer-360 enrichment, segment overlap calculations, and reach-estimate queries that span more than one schema.
What it does
EachSchemaJoin row stores { primarySchemaId, primaryKey, foreignSchemaId, foreignKey, joinType, autoEnrich, status }. The route at src/app/api/v1/schema-joins/route.ts validates that both schemas exist and that the named key fields exist on each schema before persisting (route.ts:124-128). Invalid combinations are rejected with a 400 and a precise error message rather than allowed to fail at query time.
The route handles four verbs:
GET— list all joins for the tenant, enriched with each schema’sdisplayNamefor the UI.POST— create a new join after schema/field validation.PUT— update fields on an existing join, re-validating any changed key combinations.DELETE— remove a join (hard delete).
Quick start
Create a join fromcustomers to accounts so customer reads automatically pull account fields:
How it works
Authentication and roles
GET requires viewer, editor, or admin (route.ts:63). POST, PUT, and DELETE require editor or admin (route.ts:103, 154, 211). Every handler then calls requireTenant(request) and scopes all DB operations by tenantId — no cross-tenant joins are possible.
Field validation
validateSchemaField at route.ts:40-56 runs once per side on POST. It looks up the schema by (id, tenantId) and the named field by (schemaId, name). The error messages are precise:
Schema {id} not found— when the schema id does not exist for the tenant.Field "{name}" not found in schema "{schemaName}"— when the schema exists but the named key column is missing.
PUT, the validation only runs for sides whose primarySchemaId/primaryKey or foreignSchemaId/foreignKey actually changed (route.ts:186-193).
List enrichment
GET joins the result against prisma.dataSchema.findMany to attach primarySchemaName and foreignSchemaName (using displayName first, falling back to name, then "Unknown") so the UI does not need a second round trip per row (route.ts:74-90).
Reference
GET /api/v1/schema-joins
List all schema joins for the tenant, ordered by createdAt descending.
Response
Returned atroute.ts:92.
Status codes
| Code | When |
|---|---|
| 200 | Returns the array (possibly empty) |
| 401 / 403 | Caller fails authentication or role check |
| 500 | Unexpected DB error |
POST /api/v1/schema-joins
Create a new join after schema/field validation.
Request body
Validated byCreateSchemaJoinSchema at route.ts:13-21.
Display name. Must be at least 1 character.
Id of the primary
DataSchema. Must belong to the tenant.Field name on the primary schema. Must exist as a
SchemaField row for primarySchemaId.Id of the foreign
DataSchema. Must belong to the tenant.Field name on the foreign schema. Must exist as a
SchemaField row for foreignSchemaId."left" (default) or "inner".When
true, customer-360 reads and enrichment stages auto-pull this join. When false, the join exists in the catalog but is opt-in per query.Response
201 Created with the created SchemaJoin row.
Status codes
| Code | When | Source |
|---|---|---|
| 201 | Created | route.ts:143 |
| 400 | Invalid JSON body | route.ts:112 |
| 400 | Schema/field validation failure (with precise message) | route.ts:117-128 |
| 401 / 403 | Caller fails authentication or role check | route.ts:103-106 |
| 500 | Unexpected DB error | route.ts:145 |
PUT /api/v1/schema-joins
Update fields on an existing join. Re-runs validation for any side whose schema or key changed.
Request body
Validated byUpdateSchemaJoinSchema at route.ts:23-33.
Id of the join to update.
New display name.
New primary schema id. When set, key validation re-runs against
primaryKey.New primary key field name.
New foreign schema id. When set, key validation re-runs against
foreignKey.New foreign key field name.
"left" or "inner".Toggle the auto-enrichment flag.
"active" or "inactive". inactive joins stay in the catalog but are skipped by enrichment paths.Response
200 OK with the updated SchemaJoin row.
Status codes
| Code | When | Source |
|---|---|---|
| 200 | Updated | route.ts:200 |
| 400 | Invalid JSON body or validation failure | route.ts:163, 167-170 |
| 401 / 403 | Caller fails authentication or role check | route.ts:154-157 |
| 404 | Join not found for tenant | route.ts:178 |
| 500 | Unexpected DB error | route.ts:202 |
DELETE /api/v1/schema-joins?id={joinId}
Hard-delete a join. The id is read from the query string, not the path (route.ts:216).
Query parameters
Id of the join to delete.
Response
Status codes
| Code | When | Source |
|---|---|---|
| 200 | Deleted | route.ts:226 |
| 400 | Missing id query parameter | route.ts:217 |
| 401 / 403 | Caller fails authentication or role check | route.ts:211-214 |
| 404 | Join not found for tenant | route.ts:222 |
| 500 | Unexpected DB error | route.ts:228 |
Required headers
| Header | Required | Read at | Purpose |
|---|---|---|---|
Content-Type | POST / PUT only | Next.js framework | application/json |
X-API-Key | Yes (one of the two) | tenant.ts:97 | API key (krn_…) |
X-Tenant-Id | Yes (one of the two) | tenant.ts:113 | Direct tenant id |
Honest limits
- The route does not enforce a uniqueness constraint on
{ primarySchemaId, primaryKey, foreignSchemaId, foreignKey }. Operators can register multiple joins with the same key pair (e.g., oneactiveand oneinactive) — the consumer-side enrichment code picks the firstactiverow it finds. joinTypeis restricted to"left"and"inner"by the Zod schema (route.ts:19, 30). Right and outer joins are not supported in V1 — enforce them by inverting the primary/foreign relationship instead.DELETEis unconditional — no FK guard prevents removing a join that an active enrichment stage relies on. Callers should checkstatus: "inactive"for a phased deprecation rather than deleting in production.- The route uses query-string
idforDELETE, not a path param, to allow batch tools to pass the id outside the URL path.
Related
- Schemas — register the
DataSchemarows referenced byprimarySchemaIdandforeignSchemaId. - Customers — Unified Profile — consumes auto-enrich joins for customer-360 reads.
- Segment Overlap — reads cross-schema joins to compute audience intersections.
- Reach Estimate — uses joins for cross-schema rule reach calculations.