How a GraphQL request actually travels over HTTP
GraphQL is a query language and an execution engine, not a transport. When you use it over the web it rides
on ordinary HTTP, and the convention is remarkably narrow: one endpoint, one method, one content
type. Every operation — reading a single field or writing a dozen records — goes to the same URL,
usually something like https://api.example.com/graphql, as a POST carrying the
header Content-Type: application/json.
The body is where newcomers most often go wrong. It is not raw GraphQL text. It is a JSON object wrapping your GraphQL document as a string value:
| Field | Required | What it holds |
|---|---|---|
query | Yes | The operation document as a string — used for mutations too, despite the name |
variables | No | A JSON object of typed values referenced by $name in the document |
operationName | No | Which named operation to execute when the document defines more than one |
That is why the Body tab above starts with {"query":"{ __typename }"}. It is the smallest
complete GraphQL request there is: __typename is a meta-field every schema exposes, so it
succeeds against any conforming server and is a reliable way to confirm you have the right URL and the right
credentials before you debug anything more elaborate. Because the escaping is fiddly — your GraphQL is a
string inside JSON, so every quote in it needs a backslash — the Format JSON button
is genuinely useful for spotting a malformed payload before you blame the server.
Queries, mutations and why the field is still called "query"
A query reads data and should have no side effects; a mutation writes. Both are sent in the same
query field of the JSON body, which trips people up constantly. To send a mutation you simply
write a document that begins with the mutation keyword:
{"query":"mutation { createOrder(quantity: 2) { id status } }"}. There is no separate transport
field, no different endpoint and no different HTTP method.
The other structural difference from a REST API is that you decide the shape of the response. Because you enumerate the fields you want, adding one more field to your selection set is the difference between a lean payload and an expensive one — and the size counter beside the response makes that trade-off visible immediately. Requesting nested relations that each fan out to more rows is the classic way to make a fast GraphQL API slow, and comparing the reported response time between two selection sets is the cheapest profiling you will ever do.
Using variables instead of string interpolation
Never build a query by concatenating values into the document text. Declare typed variables in the operation
signature — query GetUser($id: ID!) { user(id: $id) { name email } } — and pass their values in
the sibling variables object. The server validates them against the declared types before
execution, which catches mistakes early, keeps the document itself constant so the server can cache its
parsed form, and removes any possibility of injection through the query string. The second sample button
above sends exactly this shape if you would like to see it rendered.
Reading a GraphQL response: the 200 OK trap
This is the single most important thing to understand when testing GraphQL, and it is the opposite of the habit a REST API teaches you. GraphQL reports failures inside the response body, not in the HTTP status line. A query that references a field which does not exist, violates an authorisation rule or blows up in a resolver will still very often come back as 200 OK. If you write a client that only checks the status code, it will cheerfully treat total failure as success.
What you check instead is the errors array. A GraphQL response contains data,
errors, or both. Each error entry carries a human-readable message, a
path naming the exact field that failed, locations pointing into your document, and
usually an extensions object holding a machine-readable code such as
UNAUTHENTICATED or GRAPHQL_VALIDATION_FAILED. Partial success is normal and
designed-in: three of four requested fields may resolve while the fourth returns null with an
accompanying error. The syntax-highlighted response pane above makes that structure easy to scan.
HTTP status codes have not disappeared entirely, though, and when one does appear it tells you something
specific: the failure happened before GraphQL execution began. A
400 usually means malformed JSON or a missing query field.
A 401 means a gateway rejected your credentials without consulting the
schema — if you authenticate with a bearer token, run it through the
JWT decoder to check the exp claim before assuming the schema
is at fault. A 405 almost always means the request went out as
GET when the server only accepts POST. The full
status code reference covers the rest.
Introspection, headers and practical debugging
GraphQL schemas describe themselves. Sending
{"query":"{ __schema { queryType { name } types { name kind } } }"} asks the server to list its
own types, which is how editors and documentation browsers discover what an API offers. Many production
deployments switch introspection off on purpose to reduce their attack surface, so an error saying
__schema is unavailable is a deliberate policy rather than a broken request. The lighter
__typename meta-field is never disabled and remains the best connectivity check.
Beyond Content-Type and Authorization, GraphQL gateways lean on custom headers more
than most REST APIs do — tenant selectors, schema version pins, persisted-query identifiers and client name
and version headers used for per-client rate limiting are all common. Add them in the Headers tab. Our
HTTP headers guide explains the standard ones, and the
HTTP methods reference is worth a look if you want to understand why
GraphQL's collapse of every operation onto a single POST costs you HTTP-level caching, which is
the main trade-off against a REST API design.
When a query finally works, Copy as cURL turns the whole thing — endpoint, headers, auth and escaped body — into a shell command you can drop into a script, a CI check or a bug report. It is also the clearest way to hand a reproducible failing case to whoever maintains the schema.
Requests are sent from our proxy rather than your machine, so loopback and private network addresses are blocked. Put a development GraphQL server behind a public URL with a tunnelling service before testing it here.
Related tools and references
API Tester home
The general-purpose HTTP request console for any endpoint.
REST API Tester
Walk a REST resource through its full create, read, update and delete lifecycle.
Webhook Testing Guide
Verify signatures, handle retries and replay captured webhook payloads.
HTTP Status Codes
Decode the status codes that appear before GraphQL execution starts.
HTTP Headers
What each request and response header is asking of you.
JSON Formatter
Tidy an escaped GraphQL payload or a large response document.
GraphQL testing FAQ
How do I send a GraphQL request over HTTP?
POST to the single GraphQL endpoint with Content-Type: application/json and a JSON body
containing a required "query" string plus optional "variables" and
"operationName". The query field carries your GraphQL document as text, whether
that document is a query or a mutation.
Why does my request return 200 OK but no data?
GraphQL reports application-level problems in the body rather than the status line, so a failed operation
still returns 200. Read the errors array: each entry has a message, the path to the
field that failed and usually an extensions.code. A null data beside a populated
errors array is the normal shape of a failure.
How do I pass variables?
Declare them in the signature — query GetUser($id: ID!) { user(id: $id) { name } } — and put
their values in a sibling "variables" object such as {"id":"42"}. Never interpolate
values into the query string; variables are typed, server-validated and injection-safe.
Can I run an introspection query?
Yes, when the server permits it. Send {"query":"{ __schema { types { name kind } } }"}. Many
production servers disable introspection deliberately and will answer with an error saying
__schema is unavailable — a configuration choice, not a problem with your request.
Do GraphQL requests have to be POST?
Not strictly. Some servers also accept a GET with the query URL-encoded into a parameter, which makes the response cacheable and suits persisted queries. POST with a JSON body is the near-universal default because it has no URL length limit and every server supports it, so start there and only try GET if the API documents it.
How do I send a mutation?
In the same "query" field, with a document that starts with the mutation keyword:
{"query":"mutation { createOrder(quantity: 2) { id status } }"}. There is no separate field,
endpoint or HTTP method for mutations.