GraphQL
Relata exposes a POST /graphql endpoint that translates a subset of GraphQL query syntax to Relata SQL and executes it through the same governed query path as /query (ACL, cell masking, tenant scoping, cluster fan-out). The translator is hand-rolled with no external GraphQL dependency — supply-chain safe by design.
Conformance: GraphQL query subset — field selection,
limit,where(single equality),__schema/__typeintrospection. Mutations, subscriptions, fragments, unions, and interfaces are not supported. For the full query surface, use SQL.
Endpoint
| Method | Path | Body |
|---|---|---|
POST | /graphql | application/json — {"query": "...", "variables": {...}} |
There is no GET form; /graphql accepts POST only.
Authentication
Same as /query. When RELATA_BEARER_TOKEN is set, include it:
curl -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"query": "{ Person { id name } }"}' \
http://localhost:9090/graphqlUnauthenticated requests receive HTTP 401 (application/problem+json).
Request body
| Field | Required | Notes |
|---|---|---|
query | yes | GraphQL query string. Empty → HTTP 400. |
variables | no | JSON object of $var bindings, bound server-side (see Variables). |
PURPOSE is read from the x-relata-purpose header (defaults to analytics) and prepended to the translated SQL, so every /graphql read is governed and audited exactly like a purpose-tagged SQL query.
Usage
Field selection
curl -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"query": "{ Person { id name email } }"}' \
http://localhost:9090/graphqlTranslates to SELECT id, name, email FROM Person LIMIT 100 (default limit 100).
limit and where
-d '{"query": "{ Person(limit: 5, where: {name: \"Alice\"}) { id name } }"}'Translates to SELECT id, name FROM Person WHERE name = 'Alice' LIMIT 5. The where argument accepts a single field = value equality predicate. String values are escaped to prevent injection; field names are restricted to [A-Za-z0-9_].
Variables
$var references in limit and where are bound from the variables object (#3260). A variable-definitions block (query Q($n: String) { ... }) is accepted and ignored — values always come from variables.
-d '{
"query": "query ByName($n: String, $lim: Int) { Person(limit: $lim, where: {name: $n}) { id name } }",
"variables": {"n": "Alice", "lim": 5}
}'Translates to SELECT id, name FROM Person WHERE name = 'Alice' LIMIT 5.
The whole where filter can also be a variable:
-d '{
"query": "{ Person(where: $f) { id name } }",
"variables": {"f": {"name": "Alice", "age": 30}}
}'Object variables are conjoined with AND (name = 'Alice' AND age = 30).
Never silent. A $var that is referenced but missing from variables, or whose value is null or wrong-typed (e.g. a string where limit needs an integer), returns an error envelope — the filter is never silently dropped. String variable values are bound as quoted literals, so variable content cannot inject SQL.
Introspection
-d '{"query": "{ __schema { types { name } } }"}'__schema / __type queries return the ontology's object type names:
{
"data": {
"__schema": {
"types": [
{"name": "Person"},
{"name": "Organization"}
]
}
},
"errors": []
}Response format
Every response uses the standard GraphQL envelope:
{
"data": [ {"id": "...", "name": "Alice"} ],
"errors": []
}- On success,
datais an array of row objects (capped byRELATA_MAX_RESULT_ROWS);errorsis[]. - On a translator or policy error, HTTP 400/500 with
{"data": null, "errors": [{"message": "..."}]}. - Malformed JSON or a missing
queryfield returns an RFC 7807application/problem+jsonerror.
Supported subset
| Construct | Status |
|---|---|
Field selection { Type { f1 f2 } } | Supported |
query { ... } wrapper or bare { ... } | Supported |
limit: N argument (default 100) | Supported |
where: { field: "value" } (single equality) | Supported |
where: $objVar (whole-filter object variable, AND-joined) | Supported |
__schema / __type introspection sentinel | Supported (type listing) |
mutation { ... } | 400 — "mutations not yet supported" |
| Subscriptions, fragments, unions, interfaces | Not supported (parse error) |
Variable binding ($var in limit / where) | Supported (#3260) — missing/mistyped variable is a hard error |
| Full GraphQL spec compliance | Deferred — use /query for the full SQL surface |
Cluster fan-out
When this node is a cluster coordinator with peers, the translated SQL is routed through the cluster-fan-out-aware path so results span all shards. A standalone node executes locally. The response envelope is identical either way.
SDK usage
The published SDKs expose the door directly — e.g. the Python SDK:
# Field selection.
rows = client.graphql("{ Person(limit: 5) { id name } }")
# Variables are bound server-side (#3260); a missing/mistyped variable raises.
rows = client.graphql(
"query ByName($n: String) { Person(where: {name: $n}) { id name } }",
variables={"n": "Alice"},
)Configuration
None required. /graphql is available on every deployment profile (free, server, cluster). Auth is mandatory on server/cluster and optional on free, identical to /query.
See also
- SQL Reference — the full dialect (use this beyond the subset above)
- SPARQL — the other query-language door
- Protocols — the full protocol-door catalogue