GraphQL
GraphQL is a transport, not a second authorization path. Every resolver calls the same document service as REST, so policies behave identically.
Endpoint: POST /api/v1/graphql. An IDE is served at /graphiql (and /p/:pid/graphiql per
project).
Two layers of schema
Section titled “Two layers of schema”Generic — collection-agnostic fields that work everywhere, over a JSON scalar: find,
get, vectorSearch, create, update, replace, delete.
Typed — generated from each collection’s stored JSON Schema. For
a collection named posts:
| Field | |
|---|---|
| Type | Posts |
| Queries | posts(filter, limit, skip) · posts_by_id(id) · `posts_search(text |
| Mutations | createPosts(data: PostsInput!) · updatePosts(id, data: PostsPatchInput!) ($set semantics) · deletePosts(id) |
Dashes in collection names become underscores in field names.
query { posts(filter: { status: "published" }, limit: 10) { id title createdAt authorId_ref { name } }}mutation { createPosts(data: { title: "Hello", status: "draft" }) { id }}System fields
Section titled “System fields”id!, ownerId, createdAt! and updatedAt! are injected on every typed object type and
win over same-named user properties. They never appear in input types — the server stamps
them.
Relations
Section titled “Relations”Each top-level x-ref string property gets a sibling <field>_ref field that resolves the
target document:
{ posts { title authorId_ref { name email } } }Forbidden or missing resolves to null. Existence is never leaked.
Graceful degradation
Section titled “Graceful degradation”One bad collection schema never breaks the project’s schema. Specifically:
- A collection whose schema fails to generate falls back to the generic API with a warning.
- Property types that cannot be represented degrade to a JSON scalar or a string.
- Field names that are not valid GraphQL names (dots, dashes) or that start with
__— such as Mongoose’s__v— are dropped from typed types but stay readable through the generic fields. - A collection whose schema yields no usable properties keeps its typed queries but skips typed mutations.
Authorization
Section titled “Authorization”Scope is derived from the operation: a query needs read, a mutation needs write.
Collection policies apply on top, exactly as in REST.
The endpoint always returns HTTP 200, with problems in the GraphQL errors array. Errors
are flattened to { message } only — no stacks, no source locations.
Limits
Section titled “Limits”Query source is capped at 20,000 characters.
From the SDK
Section titled “From the SDK”const data = await client.graphql<{ posts: Array<{ id: string; title: string }> }>( `query ($limit: Int) { posts(limit: $limit) { id title } }`, { limit: 10 },);It throws ApiError(400, 'GRAPHQL') when the response carries errors and no data; otherwise
it returns data directly.