Skip to main content

GraphQL Overview

Tourfold exposes a GraphQL API alongside the REST API. It exists for one job the REST surface does not do well: reading custom object records with filters, sorting, pagination and relation traversal in a single request, against a schema generated from your own object definitions.

What it covers, and what it does not​

QueriesCustom object records and the relations between them.
MutationsNone. GraphQL is read-only. Create, update and delete go through the REST custom objects endpoints.
SubscriptionsNone. Use webhooks for change notifications.
Core resourcesNot exposed. Tours, jobs, areas, users, vehicles and documents are REST-only.

The schema is specific to your workspace. It is derived from your custom object definitions, so two workspaces have different types, and yours changes when you add a field or a relation. There is therefore no static schema document to publish — you download the current one from the API (see Schema and naming).

Endpoints​

QueryPOST https://api.tourfold.com/api/v2/graphql
Schema (SDL)GET https://api.tourfold.com/api/v2/graphql/schema

Both need Authorization: Bearer <token>, and both need a custom-object write grant — see Authentication and permissions, which explains why reading requires a write grant today.

Request format​

POST /api/v2/graphql takes the standard GraphQL-over-HTTP JSON envelope:

FieldTypeNotes
queryStringRequired. The document to execute. Maximum 10,000 characters.
variablesObjectOptional. Values for the document's variable definitions.
operationNameStringOptional. Required only when query holds more than one operation.

operationName keeps GraphQL's own camelCase spelling rather than Tourfold's snake_case REST JSON convention: this envelope belongs to the GraphQL protocol, not to the REST contract.

Quickstart​

Every root field is a Relay connection, so you select through edges { node { … } } rather than reading a plain list. There is no limit argument — page size is first (or last); see Pagination.

The examples throughout this section use two custom objects from a fictional workspace, Alpine Facility Services: equipment (building plant, e.g. a rooftop HVAC unit) and maintenance_request, related so each piece of equipment has many maintenance requests. Adapt the field names to your own schema.

quickstart-first-page.graphql
query FirstEquipmentPage {
equipment(first: 2, order_by: [{ name: asc }]) {
totalCount
pageInfo {
hasNextPage
endCursor
}
edges {
cursor
node {
id
name
asset_tag
operational
}
}
}
}
curl -X POST "https://api.tourfold.com/api/v2/graphql" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"query": "query FirstEquipmentPage { equipment(first: 2, order_by: [{ name: asc }]) { totalCount pageInfo { hasNextPage endCursor } edges { cursor node { id name asset_tag operational } } } }",
"operationName": "FirstEquipmentPage"
}'
{
"data": {
"equipment": {
"totalCount": 3,
"pageInfo": { "hasNextPage": true, "endCursor": "Y3Vyc29yOjE=" },
"edges": [
{
"cursor": "Y3Vyc29yOjA=",
"node": {
"id": "6f3d2a10-0000-4000-8000-000000000101",
"name": "Basement Chiller CH-02",
"asset_tag": "AFS-CH-02",
"operational": true
}
},
{
"cursor": "Y3Vyc29yOjE=",
"node": {
"id": "6f3d2a10-0000-4000-8000-000000000102",
"name": "Passenger Lift LIFT-01",
"asset_tag": "AFS-LIFT-01",
"operational": true
}
}
]
}
},
"extensions": {
"cost": { "estimated_rows": 23, "limit": 100000 }
}
}

Responses report what they cost​

A response carries extensions.cost whenever the request reached cost analysis: estimated_rows is how far the server calculated the response could expand, and limit is the budget it is measured against. It is reported on success too, not only on refusals, so you can tune a document before it starts being rejected.

It is absent when the request failed earlier than analysis — a syntax or validation error — so treat it as optional. See Errors and limits.

Errors​

There are two distinct failure modes, and they do not look alike:

  • Transport failures — a missing or expired token, a malformed JSON body, a query over the 10,000-character maximum — are HTTP 4xx with an RFC 9457 problem document.
  • Query failures — anything the GraphQL engine or Tourfold rejects about the document itself — are HTTP 200 with an errors array in the body. data may still be partially populated.

Never infer success from the status code alone: check for errors. Full detail, including the stable extensions.code catalog, is in Errors and limits.

Typical workflow​

  1. Obtain a Bearer token exactly as for REST, with a custom-object write grant (Authentication and permissions).
  2. Download your workspace's schema from GET /api/v2/graphql/schema and point your GraphQL client or IDE at it (Schema and naming).
  3. Build queries with where and order_by (Filtering and sorting).
  4. Page with first/after (Pagination).
  5. Watch extensions.cost and stay inside the published limits (Errors and limits).