GraphQL

Every object in your workspace gets a typed GraphQL API automatically — query a single record, a filtered list, an aggregate, and mutate (create / update / delete / transition). Every field resolves through the governed services (RBAC + audit), never raw SQL.

One endpoint: POST /api/builder/graphql · schema introspection: GET /api/builder/graphql.

How the schema is generated

The schema is derived from your objects: an object incident (label “Incident”) yields a Incident type plus these root fields:

  • incident(id: ID!) — one record
  • incident_list(filter, limit, offset, sortBy, sortDir) — a filtered, paged list
  • incident_aggregate(filter, groupBy) — count / sum / avg / min / max
  • incident_create(input) · incident_update(id, input) · incident_delete(id) · incident_transition(id, toState) — mutations

Scalars map by field type: number/currency → Float, boolean → Boolean, multiselect → [String!], everything else → String.

Example schema (SDL)

Rendered live by the same generator the endpoint uses, for the example object above:

graphql
enum SortDir { asc desc }
enum Aggregation { count sum avg min max }
type DeleteResult { id: ID! }

"""Incidents"""
type Incident {
  id: ID!
  short_description: String
  priority: String  # one of: low | high
  cost: Float
  assigned_to: String
  createdAt: String
  updatedAt: String
}

input IncidentInput {
  short_description: String
  priority: String
  cost: Float
  assigned_to: String
}

input IncidentFilter {
  id: ID
  short_description: String
  priority: String
  cost: Float
  assigned_to: String
}

type IncidentAggBucket { key: String  value: Float }

type Query {
  incident(id: ID!): Incident
  incident_list(filter: IncidentFilter, sortBy: String, sortDir: SortDir, limit: Int, offset: Int): [Incident!]!
  incident_aggregate(aggregation: Aggregation!, field: String, groupBy: String, filter: IncidentFilter): [IncidentAggBucket!]!
}

type Mutation {
  incident_create(input: IncidentInput!): Incident
  incident_update(id: ID!, input: IncidentInput!): Incident
  incident_delete(id: ID!): DeleteResult
  incident_transition(id: ID!, toState: String!): Incident
}

Querying

graphql
query {
  incident_list(filter: { priority: "high" }, limit: 20, sortBy: "createdAt", sortDir: "desc") {
    id
    short_description
    priority
    cost
    createdAt
  }
}

Aggregate (a real GROUP BY through the metric engine):

graphql
query {
  incident_aggregate(groupBy: "priority") {
    key
    count
  }
}

Reference fields support dot-walk in the selection (depth-capped at 5) — request a related record’s fields inline; the resolver loads them through the governed read path.

Mutating

graphql
mutation {
  incident_create(input: { short_description: "Card reader offline", priority: "high" }) {
    id
    short_description
  }
}

mutation {
  incident_transition(id: "rec_123", toState: "in_progress") {
    id
    status
  }
}

Mutations gate through the same RBAC + lifecycle rules as the UI: an illegal state transition, a missing-permission write, or a value blocked by a data policy is rejected with a GraphQL error.

Calling it

bash
curl -X POST https://your-workspace.axissynapse.app/api/builder/graphql \
  -H 'Content-Type: application/json' \
  -H 'Cookie: <your authenticated session>' \
  -d '{"query":"{ incident_list(limit: 5) { id short_description } }"}'

Responses follow the GraphQL envelope { data, errors }. Field-level errors return HTTP 200 with an errors array; only auth/transport failures return non-200. Try queries live in the Playground.

Notes

  • Fragments, directives, and subscriptions are not supported (hand-rolled parser, no SDK).
  • The agent fabric exposes a read-only graphql.query tool — mutations there are rejected.
  • Your live schema = your objects through buildGraphqlSdl; re-fetch after a schema change.