Query the GraphQL surface
The REST API maps one endpoint to one resource: fetch a contact, then fetch its conversations, then fetch each conversation’s messages. The developer GraphQL surface is the shaped-read counterpart — you describe the tree you want, and one request returns a contact with its conversations and messages already nested, or a segment with its contacts pulled in a single round-trip. The surface is read-only and deliberately small. It covers the four highest-traffic CPaaS/CDP resource types — contacts, conversations, messages, and segments — so resolution-shaped reads stop needing client-side joins. For everything else (sending messages, managing channels, mutating anything), use the REST API.When to use GraphQL vs REST
Auth & roles
The GraphQL endpoints sit behind the same guard as every other developer route: authenticate with an API key (X-API-Key: dv_live_sk_…) or a dashboard session, and the calling key or session must carry an owner, admin, or developer role. See Authentication.
Your first request
The query endpoint isPOST /api/v1/developer/graphql, with a { "query", "variables?", "operationName?" } JSON body. This query fetches one contact with its conversations and each conversation’s recent messages in a single round-trip:
errors array. Per GraphQL-over-HTTP convention a query that parses but fails during execution still returns 200 OK with data: null (or partial data) and an errors list — it does not come back as a REST-style error envelope.
Schema introspection
The full schema is published as an SDL document, served alongside the OpenAPI spec, AsyncAPI contract, and Postman collection:Query with entry points contact, contacts, conversation, conversations, and segments, plus Contact, Conversation, Message, and Segment:
The surface runs on a deliberately minimal query language: a single
query operation, variables with defaults, field arguments, nested selection sets, and aliases. Mutations, subscriptions, fragments, directives, and __schema introspection are not supported — the SDL endpoint above is the schema contract.Variables, operation names, and limits
query— the GraphQL document string, capped at 20 KB.variables— a JSON object of values referenced as$namein the query. Variable definitions in the query can declare defaults; a request-supplied value overrides the default.operationName— required when the document contains more than one operation. If it names an operation that isn’t in the document, you getUnknown operation named "…"inerrors.
- Depth cap — a selection set nested more than 8 levels is rejected.
- Complexity cap — each resolver-backed field costs one unit, and every list field multiplies everything beneath it by its (bounded) page size; a selection set estimated over 1000 units is rejected with
Query is too complex: …. Reduce nesting, lower thelimitarguments, or drop repeated aliased subtrees.
200 OK with { "data": null, "errors": [{ "message": "…" }] }.
Inspector responses carry Cache-Control: no-store and are never cached.
Prototype in the dashboard Explorer
Before wiring code, open Developer → GraphQL in the dashboard. The Explorer runs queries against your live tenant with your session, renders the schema next to the editor, and shows the exactdata/errors envelope your code would receive. Iterate on the query shape there, then paste the finished document into your integration.
Pagination & nested collections
List arguments are clamped server-side; values outside the clamp fall back to the defaults below.
Top-level lists page by
offset (0 starts at the newest row by creation time; conversations order by last activity). Nested collections take a limit only — page their parent (e.g. further conversations) rather than the nested list itself.
Tenant isolation: execution is always scoped to the calling key’s tenant. A GraphQL request can never reach another tenant’s data, and there is no cross-tenant query mode.
Walkthrough A — export a contact with all conversations and messages
One REST join costs three sequential requests (GET /contacts/:id, GET /conversations?contact_id=…, then GET /messages?conversation_id=… per conversation). The GraphQL version is a single request:
data.contact is null with no errors entry — check for null before unpacking.
Walkthrough B — traverse to a segment’s contacts
Resolve segment ids, then pull contacts on the members’ lifecycle stage — the query asks for the exact shape so the response carries just the fields you consume:contact(id:) → conversations → messages — all in the same request, still one round-trip end to end.
Errors & failure modes
GraphQL responses differ from the REST envelope in one important way: a query-level failure is not an HTTP error. Parse failures, validation rejections, and per-field resolver errors all return200 OK with an errors array next to data (which is null or partially populated on failure). Transport-level failures — auth, body validation, rate limits — still come back as HTTP error codes.
Use REST error-handling patterns for the HTTP-level failures, and treat the
errors array as the GraphQL-level failure channel.
Rate limits
Both endpoints share the tenant-scoped authenticated write bucket — 60 requests per minute per tenant. Buckets are per tenant, so a heavy integration cannot crowd out your dashboard, but your own scripts share the same window. On429, read details.retry_after (seconds) and back off instead of retrying on a fixed schedule.
Cheap request shapes stay cheap: batch the reads you need into one nested query rather than hammering the endpoint with single-field requests.
Frequently asked questions
Can I write through GraphQL?
No. The surface is read-only — there are no mutations. Sends, updates, and administrative actions all stay on the REST API.Why did my query return 200 with an errors array?
That’s the GraphQL-over-HTTP convention: the request was well-formed at the HTTP layer, and the failure is inside the query. Read errors[0].message; common causes are a syntax error, an unknown field, or the complexity cap.
How do I list more than 100 conversations?
Page the parent:conversations(limit: 100, offset: …) for the next batch. Nested collections carry at most one page per parent by design — they’re shaped reads, not bulk export. For bulk export use the REST list endpoints with pagination.
See also
- API integration — the REST counterpart these queries complement
- Developer portal — the try-it console, usage stats, key governance, and SDK catalogs
- Pagination — cursor paging on the REST list endpoints
- Error handling examples — REST envelope failures share shape with this surface’s transport-level errors