Schema and discovery
These are the routes a client calls to learn what entities exist and what shape they
have. Paths are shown as an external client sees them, with the /data/ prefix the
gateway strips before forwarding; the service itself serves
the discovery and export routes at /schema/... and the two catalogue routes at
/admin/data/....
What each export format contains, which variants a field appears in, and the field-type mappings are on Schema export and discovery. This page is the wire contract: paths, parameters, bodies, and status codes.
Routes
Section titled “Routes”| Method | Path | Returns | Content type |
|---|---|---|---|
GET | /data/schema | Schema summary — name lists only | application/json |
GET | /data/schema/entities | Entity inventory with counts | application/json |
GET | /data/schema/entities/{entity} | One entity’s fields and relations | application/json |
GET | /data/schema/graphql | Generated GraphQL SDL | text/plain; charset=utf-8 |
GET | /data/schema/openapi.json | OpenAPI 3.1 document | application/json; charset=utf-8 |
GET | /data/schema/jsonschema.json | JSON Schema 2020-12 bundle | application/schema+json; charset=utf-8 |
GET | /data/schema/jsonschema/{entity} | JSON Schema for one entity | application/schema+json; charset=utf-8 |
GET | /data/schema/zod.ts | Zod TypeScript bundle | application/typescript; charset=utf-8 |
GET | /data/schema/zod/{entity} | Zod for one entity | application/typescript; charset=utf-8 |
GET | /data/schema/cue | CUE bundle | text/x-cue; charset=utf-8 |
GET | /data/schema/cue/{entity} | CUE for one entity | text/x-cue; charset=utf-8 |
GET | /data/admin/data/references/{entity} | Foreign keys pointing at an entity | application/json |
GET | /data/admin/data/hashes | Compiled-query catalogue | application/json |
Request
Section titled “Request”Every route on this page is GET and takes the four tenant headers plus a bearer token:
GET /data/schema/entities/order HTTP/1.1Host: api.example.comX-Customer: acmeX-Product: erpX-Env: prodX-Tenant: mainAuthorization: Bearer <token>| Parameter | In | Required | Meaning |
|---|---|---|---|
datastore | query | no | Which datastore to describe. Omit for the schema’s default. |
entity | path | on per-entity routes | Exact entity name. Each export route trims its own extension, so order and order.json are the same request on the JSON Schema route. |
There is no anonymous mirror for any of these routes. /data/anon/rest/* and
/data/anon/graphql serve unauthenticated callers, but schema discovery and the exports
always require a token — a published anonymous integration has to be handed its schema
out of band.
Schema summary
Section titled “Schema summary”GET /data/schema returns names only, never definitions. Every list is a sorted array of
strings.
{ "name": "erp", "version": "1.4.0", "default_datastore": "main", "datastores": ["main", "reporting"], "entities": ["order", "order_item"], "relations": [], "types": ["credit_score"], "enums": ["OrderStatus"], "traits": ["auditable"], "embeddables": ["address"], "stateflows": ["order_lifecycle"], "views": [], "functions": []}Entity inventory
Section titled “Entity inventory”GET /data/schema/entities lists every entity with counts, sorted by name. fields and
relations are integers, not arrays.
{ "entities": [ { "name": "order", "fields": 12, "relations": 2 }, { "name": "order_item", "fields": 7, "relations": 1 } ], "count": 2}Entity detail
Section titled “Entity detail”GET /data/schema/entities/{entity} is the shape call. Both fields and relations come
back in declaration order.
{ "name": "order", "table_name": "orders", "primary_key": "id", "materialization": 0, "fields": [ { "name": "id", "type": "", "required": true, "nullable": false, "unique": true, "primary_key": true, "computed": false, "default": "ulid()" }, { "name": "risk_band", "type": "credit_score", "required": false, "nullable": true, "unique": false, "primary_key": false, "computed": false, "default": null } ], "relations": [ { "name": "items", "type": 2, "to_entity": "order_item", "from_field": "id", "to_field": "order_id" } ]}| Top-level key | Type | Meaning |
|---|---|---|
name | string | Entity name as declared |
table_name | string | Physical table, which may differ from name |
primary_key | string | Single-column primary key |
materialization | int | Storage ordinal — 0 table, 1 view, 2 remote, 3 contract, 4 virtual |
fields | array | Columns, in declaration order |
relations | array | Relations, in declaration order |
| Field key | Type | Meaning |
|---|---|---|
name | string | Column name as declared |
type | string | Declared type spelling, or empty — see the caution below |
required | bool | Declared required |
nullable | bool | Declared nullable |
unique | bool | Declared unique |
primary_key | bool | True when the name matches the entity’s primary_key |
computed | bool | True for computed fields |
default | any | Declared default; null when unset |
| Relation key | Type | Meaning |
|---|---|---|
name | string | Relation name, and the include key used to eager-load it |
type | int | Cardinality ordinal — 0 unspecified, 1 one-to-one, 2 one-to-many, 3 many-to-one, 4 many-to-many |
to_entity | string | Target entity |
from_field | string | Column on this entity |
to_field | string | Column on the target entity |
Fields carrying a pii, phi, pci, gdpr or noexport
compliance marker are still listed
here. That gate applies to the export formats only, not to this route or the SDL.
GraphQL SDL
Section titled “GraphQL SDL”GET /data/schema/graphql returns the generated SDL as plain text, not JSON.
curl -H "Authorization: Bearer $TOKEN" https://api.example.com/data/schema/graphqlCodegen exports
Section titled “Codegen exports”Four of the eight renderers are served over HTTP. Each has a bundle route and, except OpenAPI, a per-entity route.
| Format | Bundle | Per-entity |
|---|---|---|
| OpenAPI 3.1 | GET /data/schema/openapi.json | none |
| JSON Schema 2020-12 | GET /data/schema/jsonschema.json | GET /data/schema/jsonschema/{entity} |
| Zod (TypeScript) | GET /data/schema/zod.ts | GET /data/schema/zod/{entity} |
| CUE | GET /data/schema/cue | GET /data/schema/cue/{entity} |
curl -H "Authorization: Bearer $TOKEN" https://api.example.com/data/schema/jsonschema/order.jsonThe per-entity JSON Schema route emits that entity’s three variants plus only the enums its fields reference. The CUE bundle path has no file extension.
The Protocol Buffers, Avro, OData CSDL and AsyncAPI renderers have no route — there is no
way to fetch a .proto, an .avsc, an OData $metadata document or an AsyncAPI
document over HTTP.
Entity references
Section titled “Entity references”GET /data/admin/data/references/{entity} lists every foreign key pointing at the
named entity — the “what breaks if I drop this” view. Rows are sorted by from_entity,
then from_field.
{ "tenant": "acme:erp:prod:main", "datastore": "main", "entity": "order", "count": 1, "references": [ { "from_entity": "order_item", "from_field": "order_id", "to_field": "id", "on_delete": "cascade", "on_update": "noaction" } ]}on_delete and on_update render as cascade, setnull, restrict or noaction. Both
keys are always present: noaction is the zero value, so a foreign key that declares
neither behaviour reports noaction rather than omitting the key. datastore echoes the
?datastore= parameter verbatim and is "" when you omit it. Outgoing references are not
returned; read them from the target entity’s own detail response.
Query-hash catalogue
Section titled “Query-hash catalogue”GET /data/admin/data/hashes is the replacement for the retired /data/hashes/list.
{ "tenant": "acme:erp:prod:main", "datastore": "main", "count": 0, "hashes": []}Admin plane access and error shape
Section titled “Admin plane access and error shape”The two /data/admin/data/* routes above sit behind the admin policy’s Read gate, which
admits the admin and superadmin roles (role matching is case-insensitive). Both have
superadmin mirrors that target a sibling tenant by name —
/data/superadmin/tenant/{tenant}/references/{entity} and
/data/superadmin/tenant/{tenant}/hashes — where {tenant} is the bare tenant segment,
not a full four-part key.
Status codes
Section titled “Status codes”For /data/schema/*:
| Status | Code | Cause |
|---|---|---|
| 200 | — | Success |
| 400 | v2.missing_entity | The {entity} path segment was empty |
| 403 | v2.no_tenant | No tenant key on the request — tenant headers missing, or the service was called directly |
| 404 | v2.entity_not_found | The entity is not in the tenant’s resolved schema; check the spelling and ?datastore= |
| 429 | v2.rate_limited | The caller’s rate-limit bucket is empty |
| 500 | v2.engine_unavailable | The tenant’s engine could not be resolved or built |
| 500 | v2.export_render_failed | A renderer failed while producing the document |
Every route on this page is wrapped by the authenticated gate, which spends one rate-limit
token per request. A 429 carries Retry-After alongside the X-RateLimit-Limit,
X-RateLimit-Remaining and X-RateLimit-Reset headers the successful responses also set.
For the two admin routes, a missing role returns 403 with the coded envelope; the other
conditions return 400, 404 and 500 with the bare {"error": "..."} body described above.
Making a definition change visible
Section titled “Making a definition change visible”These routes render from the tenant’s cached schema. Loading new definitions does not
change what they return until the cached engine is dropped with DELETE /data/cache,
documented on Sync, transfer, and schema.
Routes removed since v1
Section titled “Routes removed since v1”| Retired route | Replacement |
|---|---|
GET /data/entities/list | GET /data/schema/entities |
GET /data/entities/list/{entity} | GET /data/schema/entities/{entity} |
GET /data/entities/references/{entity} | GET /data/admin/data/references/{entity} |
GET /data/hashes/list | GET /data/admin/data/hashes |
POST /data/entities/index/{entity} | none |
POST /data/entities/validate | No direct replacement. A dry-run plan generation covers the same check — see Sync, transfer, and schema. |
The tag and store filters the retired list routes accepted do not exist on any
replacement. ?datastore= is the only selector.