Authentication and permissions
GraphQL uses the same authentication model as REST. Send an OAuth2/OIDC access token as a Bearer token on both endpoints:
curl -X POST "https://api.tourfold.com/api/v2/graphql" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"query":"{ equipment(first: 1) { edges { node { id created_at } } } }"}'
Replace equipment with a definition slug from your own workspace, and note the shape: a connection must
be selected through edges { node { … } }, and page size is first — there is no limit argument.
Use the Authorization Code flow for user-facing applications and Client Credentials for server-to-server integrations. Flows, endpoints and token lifetimes are documented once, for both surfaces, under REST authentication.
Reading requires a write grant​
Querying the GraphQL API requires one of the custom-object write grants:
custom-objects:instances:createcustom-objects:instances:updatecustom-objects:instances:delete
There is no custom-objects:instances:read grant, and the authorization check is a single check on the
endpoint. A role with read-only access to custom objects therefore cannot use the GraphQL API at all —
neither the query endpoint nor the schema endpoint.
If you need a read-only integration today, either grant it one of the write grants above — accepting that this permits writes over REST — or use the REST custom-object endpoints, which do have read-level authorization. A dedicated read grant is planned; until it ships, this is the actual requirement rather than a cleaner one.
Both endpoints are gated identically, so a token that cannot query also cannot download the schema.
Per-type access​
Beyond the endpoint-level grant, individual custom object types can be access-restricted. Those checks run per type, during execution, and they do not fail the query:
| Situation | Result |
|---|---|
| A restricted type you may not read, at the root | An empty connection (edges: [], totalCount: 0) |
| A restricted type reached through a to-one relation | null |
| A restricted type reached through a to-many relation | An empty connection |
| A type that exists only as a child of another record | Never available at the root; only through its parent |
The rest of the query still executes and still returns its data. This keeps one inaccessible branch from failing an otherwise valid request, but it has an important consequence: an empty result is not proof of an empty collection. If a type you know to be populated comes back empty, check the role's access to that type before concluding there are no records.
Authentication failures are HTTP errors, not GraphQL errors​
Authentication and authorization are enforced before the GraphQL engine runs, so they surface as HTTP
status codes with an RFC 9457 problem document — not as entries in a GraphQL
errors array:
| Status | Meaning |
|---|---|
401 | No token, an expired token, or an invalid token. |
403 | A valid token whose role lacks a custom-object write grant. The body names the grants that would have satisfied the check. |
A 200 response with errors in the body is therefore never an authentication problem — it is something
about the query itself. See Errors and limits.
Practical notes​
- Tokens are workspace-scoped. Use a token issued for the workspace whose data you are querying.
- Prefer short-lived access tokens with refresh tokens in user-facing applications; never embed a long-lived token in a client application.
- The GraphQL API is read-only, so a token used only for querying never needs grants beyond the one that unlocks the endpoint — but be aware that grant also permits writes over REST.