Skip to main content

GraphQL

The GraphQL API allows you to query and manipulate your database data using a dynamically generated schema. Each table in your schema is automatically mapped to GraphQL types, queries, and mutations.

Endpoint and Authentication

HTTP

The HTTP endpoint is schema-specific (see Schemas for more information):

POST https://api.centia.io/api/graphql/schema/{schema_name}

Authentication is handled via a Bearer token in the Authorization header (see OAuth for more information).

POST https://api.centia.io/api/graphql/schema/public
Authorization: Bearer <your_token>
Content-Type: application/json

{
"query": "query { ... }"
}

WebSocket

All GraphQL operations — queries, mutations, and subscriptions — are also available over WebSocket:

wss://event.centia.io?token=<your_token>

Send operations as JSON messages with type, schema, and query:

{
"type": "subscribe",
"id": "q1",
"schema": "public",
"query": "query { getArtists { artist_id legal_name } }"
}

This works identically for mutations:

{
"type": "subscribe",
"id": "m1",
"schema": "public",
"query": "mutation { insertArtists(objects: [{ legal_name: \"Jane Doe\" }]) { artist_id } }"
}

Introspection

You can use standard GraphQL introspection to explore the available types and fields. This is useful for tools like GraphiQL or Apollo Studio.

query {
__schema {
types {
name
}
}
}

Queries

For every table in your database, the API provides a query field named get[TableName] (in camelCase).

Basic Query

To fetch records from a table named artists:

Request, basic query
POST https://api.centia.io/api/graphql/schema/public
Content-Type: application/json
Authorization: Bearer <your_token>

{
"query": "query { getArtists { artist_id legal_name } }"
}

Filtering

The where argument supports complex filtering using logical and comparison operators. Simple equality filters are also supported.

Simple Equality:

{ getArtists(where: { artist_id: 1 }) { legal_name } }

Supported Operators:

  • eq: Equal
  • neq: Not equal
  • gt: Greater than
  • gte: Greater than or equal
  • lt: Less than
  • lte: Less than or equal
  • in: Included in array
  • like: Case-sensitive pattern match
  • ilike: Case-insensitive pattern match

Logical Operators:

  • and: Logical AND
  • or: Logical OR
  • not: Logical NOT
Request, filtered query
POST https://api.centia.io/api/graphql/schema/public
Content-Type: application/json
Authorization: Bearer <your_token>

{
"query": "query { getArtists(where: { and: [ { legal_name: { ilike: \"%Linus%\" } }, { instrument: { eq: \"guitar\" } } ] }) { legal_name instrument } }"
}

Pagination

Use limit and offset for pagination.

Request, pagination
POST https://api.centia.io/api/graphql/schema/public
Content-Type: application/json
Authorization: Bearer <your_token>

{
"query": "query { getArtists(limit: 10, offset: 20) { artist_id legal_name } }"
}

Relationships

If there are foreign key constraints between tables, the GraphQL API automatically exposes them as nested fields.

Request, relationships
POST https://api.centia.io/api/graphql/schema/public
Content-Type: application/json
Authorization: Bearer <your_token>

{
"query": "query { getAlbums { title bands { name subgenre } } }"
}

Mutations

The API provides insert, update, and delete mutations for each table.

Insert

Use insert[TableName] to add new records. You can provide a single record via data or multiple records via objects.

Request, insert mutation
POST https://api.centia.io/api/graphql/schema/public
Content-Type: application/json
Authorization: Bearer <your_token>

{
"query": "mutation { insertArtists(objects: [{ legal_name: \"John Doe\", instrument: \"Piano\" }]) { artist_id legal_name } }"
}

Update

Use update[TableName] to modify existing records. Use the where argument to specify which records to update and data for the new values.

Request, update mutation
POST https://api.centia.io/api/graphql/schema/public
Content-Type: application/json
Authorization: Bearer <your_token>

{
"query": "mutation { updateArtists(where: { artist_id: { eq: 1 } }, data: { instrument: \"Electric Guitar\" }) { artist_id instrument } }"
}

Delete

Use delete[TableName] to remove records.

Request, delete mutation
POST https://api.centia.io/api/graphql/schema/public
Content-Type: application/json
Authorization: Bearer <your_token>

{
"query": "mutation { deleteArtists(where: { artist_id: { eq: 1 } }) { artist_id } }"
}

Subscriptions

Subscriptions let you receive real-time database change events (INSERT, UPDATE, DELETE) over the WebSocket connection using GraphQL syntax.

Subscribing

After connecting to the WebSocket (see WebSocket above), send a message to register a subscription:

{
"type": "subscribe",
"id": "sub1",
"schema": "public",
"query": "subscription { ArtistsMessageAdded { artist_id legal_name instrument } }"
}

The server responds with an acknowledgement:

{
"type": "subscribe_ack",
"id": "sub1"
}

Subscription Field Names

Each table gets three subscription fields following the pattern {TableName}Message{Event}:

  • {TableName}MessageAdded — triggered on INSERT
  • {TableName}MessageUpdated — triggered on UPDATE
  • {TableName}MessageDeleted — triggered on DELETE

The table name is in PascalCase, matching the same convention as queries and mutations.

Filtering

Use the where argument to only receive events matching specific conditions. The same JSON object syntax used in queries and mutations is supported:

Subscribe with filter
{
"type": "subscribe",
"id": "sub2",
"schema": "public",
"query": "subscription { ArtistsMessageUpdated(where: {instrument: {eq: \"guitar\"}}) { artist_id legal_name instrument } }"
}

Supported operators:

  • eq, neq, gt, gte, lt, lte, in, like, ilike

Logical operators:

  • AND, OR, NOT
Subscribe with complex filter
{
"type": "subscribe",
"id": "sub3",
"schema": "public",
"query": "subscription { ArtistsMessageAdded(where: {OR: [{instrument: {eq: \"guitar\"}}, {instrument: {eq: \"piano\"}}]}) { artist_id legal_name } }"
}

A SQL-style string syntax is also supported:

Subscribe with string filter
{
"type": "subscribe",
"id": "sub4",
"schema": "public",
"query": "subscription { ArtistsMessageAdded(where: \"instrument = 'guitar'\") { artist_id legal_name } }"
}
note

Subscription filtering only works with scalar column types (integer, float, boolean, string, etc.). Complex types such as geometric types, range types, and JSON are not supported in subscription where clauses.

Column Selection

The fields you select in the subscription body determine which columns are included in the event payload. Only matching columns are returned.

JavaScript Example

const ws = new WebSocket(
`wss://event.centia.io?token=${encodeURIComponent(token)}`
);

ws.onopen = () => {
// Query over WebSocket
ws.send(JSON.stringify({
type: "subscribe",
id: "q1",
schema: "public",
query: `query { getArtists { artist_id legal_name } }`
}));

// Subscribe to real-time events
ws.send(JSON.stringify({
type: "subscribe",
id: "sub1",
schema: "public",
query: `subscription {
ArtistsMessageAdded(where: {instrument: {eq: "guitar"}}) {
artist_id
legal_name
instrument
}
}`
}));
};

ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log("Received:", data);
};

Data Types

The GraphQL API supports various PostgreSQL data types, mapping them to appropriate GraphQL types:

  • Scalars: integer, float, boolean, string, JSON , ID, Date, Time, DateTime.
  • Geometric Types: Point, Line, Lseg, Box, Circle, Path, Polygon.
  • Range Types: Int4range, Int8range, Numrange, Tsrange, Tstzrange, Daterange.
  • Arrays: Any type can be returned as a list (e.g., [String], [Int]).

Any complex types can be used as input arguments for mutations. Type hints are not required because the schemas are dynamically generated from the database relations.

Se also Types.

Consider a generated schema like this:

Schema
type Tours {
id: ID!
artist_name: String!
tour_name: String!
from_to: Daterange!
}

"""
Date is accepted in almost any reasonable format, including ISO 8601, SQL-compatible, traditional POSTGRES, and others.
"""
scalar Date

"""Range type for daterange, with lower and upper bounds of type Date."""
type Daterange {
lower: Date
upper: Date
lowerInclusive: Boolean
upperInclusive: Boolean
}

type Mutation {
insertTours(data: JSON, objects: [JSON]): [Tours]
updateTours(id: ID, where: JSON, data: JSON): [Tours]
deleteTours(id: ID, where: JSON): [Tours]
}

Then you can insert a new tour using the following mutation. The Daterange field is represented as JSON:

Operation
mutation InsertTours($data: JSON) {
insertTours(data: $data) {
id
from_to {
lower
upper
}
}
}
variables
{
"data": {
"artist_name": "Guns n’ Roses",
"tour_name": "Europe: Summer 2026",
"from_to": {
"lower": "2010-04-06",
"upper": "2011-03-07",
"lowerInclusive": true,
"upperInclusive": true
}
}
}