Spine GraphQL

Read-only GraphQL gateway over Supabase Postgres materialized views — list, inspect schema, and fetch paginated rows through a single uniform API.

Endpoint: POST https://spine.optizon.app/graphql
Auth: Authorization: Bearer <API_KEY> (every /graphql request)
Content-Type: application/json

Quickstart

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).

Queries

List all materialized views

{
  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.

Inspect one view's columns + size

{
  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.

Fetch rows with pagination + ordering

{
  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.

Errors

extensions.codeMeaning
NOT_FOUNDMV does not exist or is not allow-listed (indistinguishable on purpose).
INVALID_ARGUMENTUnknown orderBy.column, negative offset, offset above cap, etc.
INTERNALUnexpected server/database error. Detail is logged server-side; the client message is generic — do not expect raw Postgres text.

Health & observability

GET /healthzProcess liveness — always 200 when up. No auth.
GET /readyz200 if a Postgres ping succeeds within 1 s, 503 otherwise. No auth.
GET /metricsPrometheus text format: HTTP request count + latency, DB query latency, pool stats. No auth.
POST /graphqlBearer-token required. Single endpoint for all queries.
GET /playgroundApollo Sandbox UI (when ENABLE_PLAYGROUND=true).

Limits & safety

Schema introspection

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 } } }"}'