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 recordincident_list(filter, limit, offset, sortBy, sortDir)— a filtered, paged listincident_aggregate(filter, groupBy)— count / sum / avg / min / maxincident_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:
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
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):
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
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
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.querytool — mutations there are rejected. - Your live schema = your objects through
buildGraphqlSdl; re-fetch after a schema change.