Read-only GraphQL gateway over Supabase Postgres materialized views — list, inspect schema, and fetch paginated rows through a single uniform API.
POST https://spine.optizon.app/graphqlAuthorization: Bearer <API_KEY> (every /graphql request)application/json
curl -sS -X POST https://spine.optizon.app/graphql \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"query":"{ materializedViews { schema name qualifiedName isPopulated } }"}'
Or open /playground for an interactive sandbox (when enabled).
{
materializedViews(schemas: ["app", "dashboard"]) {
schema
name
qualifiedName
isPopulated
}
}
Pass schemas: null or omit the argument to list across every allow-listed schema. Non-allow-listed schemas are silently filtered.
{
materializedView(schema: "dashboard", name: "mv_sales") {
qualifiedName
isPopulated
approximateRowCount
columns {
name
dataType
ordinalPosition
nullable
}
definition
}
}
Returns null if the view does not exist or the schema is not allow-listed (the two are indistinguishable). The columns, definition, and approximateRowCount fields are batched per request — fetching three views with all four lazy fields issues at most three DB round-trips, not nine.
{
materializedViewData(
schema: "dashboard"
name: "mv_sales"
limit: 50
offset: 0
orderBy: [{ column: "sale_date", direction: DESC }]
) {
rows
columns { name dataType }
pageInfo { limit offset returned hasMore }
}
}
limit defaults to 100 and is clamped to MAX_PAGE_SIZE (default 1000). offset is capped at MAX_OFFSET (default 100,000) — exceed it and the server returns INVALID_ARGUMENT directing you to use a smaller offset (keyset pagination is Phase 2). Each row is a JSON scalar (object keyed by column name); type fidelity follows pgx defaults — timestamps as RFC 3339, arrays as JSON arrays, numerics as JSON numbers when representable.
| extensions.code | Meaning |
|---|---|
NOT_FOUND | MV does not exist or is not allow-listed (indistinguishable on purpose). |
INVALID_ARGUMENT | Unknown orderBy.column, negative offset, offset above cap, etc. |
INTERNAL | Unexpected server/database error. Detail is logged server-side; the client message is generic — do not expect raw Postgres text. |
GET /healthz | Process liveness — always 200 when up. No auth. |
GET /readyz | 200 if a Postgres ping succeeds within 1 s, 503 otherwise. No auth. |
GET /metrics | Prometheus text format: HTTP request count + latency, DB query latency, pool stats. No auth. |
POST /graphql | Bearer-token required. Single endpoint for all queries. |
GET /playground | Apollo Sandbox UI (when ENABLE_PLAYGROUND=true). |
pg_catalog, information_schema, and the pg_temp_* / pg_toast_temp_* families are always rejected.orderBy.column) pass catalog existence + [A-Za-z_][A-Za-z0-9_]* validation before being double-quoted into SQL. limit / offset are parameterized.crypto/subtle.ConstantTimeCompare.Standard GraphQL introspection is enabled — clients can query __schema and __type directly:
curl -sS -X POST https://spine.optizon.app/graphql \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"query":"{ __schema { types { name } } }"}'