Every API in the catalog is a field on the root query type, named by its API id — the same id as
its reference page. /ref/dadjokes is the dadjokes field. An id containing a hyphen loses it:
the field name is the id lowercased with hyphens removed.
You need an account and a key before anything on this page will run; quickstart covers both. Prior GraphQL experience helps but is not assumed — the query language is small, and everything you need is on this page.
The explorer
The fastest way to write a query is not to write it in code. The GraphQL explorer in the dashboard is a browser-based editor with four panels:
- Query editor — with autocomplete driven by the live schema, so field names are suggested rather than guessed.
- Variables — a JSON object bound to the query's declared variables.
- Results — the response, including
errors. - Docs — the introspected schema. This is the authoritative list of what each API returns, and it is where to check a field name that GraphQL has normalised.
You have to be signed in; the explorer uses your account's key, so queries run there cost real credits exactly as they would from code.
Basic syntax
A query names fields, and the response mirrors the query's shape:
query {
dadjokes {
joke
}
}{
"data": {
"dadjokes": {
"joke": "I only know 25 letters of the alphabet. I don't know y."
}
}
}Ask for fewer fields and you get fewer fields. That is the whole point of the format — there is no "full response" to trim client-side.
Arguments
Parameters go inside a single input object, not as loose arguments. This is uniform across every
API, so once you have written one call you have written all of them:
query {
agecalculator(input: { dob: "1990-01-01" }) {
age_years
age_months
age_days
}
}The keys inside input are the same parameter names the endpoint's reference page documents. What
that page calls dob, GraphQL calls dob.
Response field names are the exception. GraphQL identifiers cannot contain hyphens, dots, spaces, colons, slashes or plus signs, so any REST field name with one is rewritten:
| REST field | GraphQL field |
|---|---|
age.years | age_years |
content-type | content_type |
2024_total | _2024_total |
The rule is: - + : . / \ and space become _, anything else outside [A-Za-z0-9_] is dropped,
and a leading digit gets an underscore in front. When in doubt, read the docs panel in the
explorer rather than transliterating by hand.
Variables
Hardcoding values into a query string means rebuilding the string per request. Declare variables instead and send them as a separate JSON object:
query GetAge($dob: String!) {
agecalculator(input: { dob: $dob }) {
age_years
}
}{
"query": "query GetAge($dob: String!) { agecalculator(input: { dob: $dob }) { age_years } }",
"variables": { "dob": "1990-01-01" }
}This is also the safe option: a value interpolated into a query string can break the query's syntax, and a variable cannot.
Querying several APIs at once
Name more than one root field and they all run:
query {
advice { id advice }
dadjokes { joke }
randomquote { quote author }
}{
"data": {
"advice": { "id": "117", "advice": "Don't be afraid to ask questions" },
"dadjokes": { "joke": "Why don't scientists trust atoms? They make up everything." },
"randomquote": {
"quote": "The only way to do great work is to love what you do.",
"author": "Steve Jobs"
}
}
}The three APIs are called in parallel, so the query costs about as much wall-clock time as the slowest one rather than the sum. Against three sequential REST calls that is usually the largest win available here.
Cost is per API, not per query: three 1-credit APIs cost 3 credits. And each one counts individually against your rate limit — a query naming five APIs is five calls as far as the per-minute ceiling is concerned, even though it is one HTTP request.
Free plans are limited to one API per query; paid plans allow several. Exceeding the cap is a
403 raised before anything runs, so an over-limit query costs nothing and the error message
names your actual ceiling.
Making the request from code
The endpoint is https://api.apiverve.com/v1/graphql, it is POST only, and it authenticates
with the same x-api-key header as every REST endpoint.
curl -X POST https://api.apiverve.com/v1/graphql \
-H "x-api-key: $APIVERVE_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"query":"query { dadjokes { joke } }"}'const res = await fetch('https://api.apiverve.com/v1/graphql', {
method: 'POST',
headers: {
'x-api-key': process.env.APIVERVE_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: 'query GetJoke { dadjokes { joke } }',
}),
});
const { data, errors } = await res.json();
if (errors) throw new Error(errors[0].message);
console.log(data.dadjokes.joke);import os, requests
res = requests.post(
"https://api.apiverve.com/v1/graphql",
headers={"x-api-key": os.environ["APIVERVE_API_KEY"]},
json={"query": "query { dadjokes { joke } }"},
timeout=35,
)
body = res.json()
if "errors" in body:
raise RuntimeError(body["errors"][0]["message"])
print(body["data"]["dadjokes"]["joke"])<?php
$ch = curl_init('https://api.apiverve.com/v1/graphql');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'x-api-key: ' . getenv('APIVERVE_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['query' => 'query { dadjokes { joke } }']),
]);
$body = json_decode(curl_exec($ch), true);
echo $body['data']['dadjokes']['joke'];No GraphQL client library is required — the request is a POST with a JSON body. If you already
use Apollo, urql or graphql-request, they work as-is; point them at the endpoint and set the
header.
Set a client timeout of at least 35 seconds. Each API in a query gets 30 seconds at the gateway, so a client that gives up at 10 will abandon queries that were about to succeed.
Handling errors
GraphQL does not use the REST envelope. There is no status field to check. Instead, a response
may carry data, errors, or both:
{
"errors": [
{
"message": "Insufficient credits. Need 10, have 5",
"extensions": { "code": "INSUFFICIENT_CREDITS" }
}
]
}Branch on extensions.code, not on message — the code is stable, the sentence is written for a
human and may be reworded.
code | HTTP | Meaning |
|---|---|---|
METHOD_NOT_ALLOWED | 405 | You sent a GET. Use POST. |
GRAPHQL_NOT_SUPPORTED | 400 | This host does not expose GraphQL. |
UNAUTHORIZED | 401 | No x-api-key header. |
INVALID_API_KEY | 401 | The key is not recognised. |
BAD_REQUEST | 400 | No query in the body, or an endpoint rejected the input. |
GRAPHQL_VALIDATION_ERROR | 400 | The query does not match the schema — usually a misspelled field. |
FILE_UPLOAD_NOT_SUPPORTED | 400 | An API in the query takes a file. Call it over REST. |
PLAN_LIMIT_EXCEEDED | 403 | More APIs in one query than the plan allows. |
INSUFFICIENT_CREDITS | 402 | Balance will not cover the query. Nothing was called. |
FORBIDDEN | — | An API refused this key — scoping, IP allow-list, or revocation. |
RATE_LIMITED | — | An API returned 429. Back off and retry. |
SERVICE_UNAVAILABLE | — | An upstream source is down. Retry with backoff. |
INTERNAL_SERVER_ERROR | — | Our side failed. Retry with backoff. |
NETWORK_ERROR | — | The gateway could not reach the API. Retry with backoff. |
The rows with no HTTP status are per-API failures raised inside a query that otherwise ran, which is where the format gets genuinely different from REST.
Partial success is the case to get right
If a query names three APIs and one fails, the response is HTTP 200 with data populated for
the two that worked and an errors entry for the one that did not. Code that checks res.ok and
nothing else will silently treat a missing result as a null:
const res = await fetch(endpoint, options);
const body = await res.json();
// Not enough — a 200 can carry errors.
if (!res.ok) throw new Error(`HTTP ${res.status}`);
if (body.errors?.length) {
const retryable = body.errors.some((e) =>
['RATE_LIMITED', 'SERVICE_UNAVAILABLE', 'INTERNAL_SERVER_ERROR', 'NETWORK_ERROR']
.includes(e.extensions?.code),
);
// body.data may still hold results for the APIs that succeeded.
handlePartial(body.data, body.errors, retryable);
}Decide deliberately whether a partial result is usable in your case. For a dashboard where each tile is independent, rendering two of three is better than failing the screen; for a calculation that needs all three inputs, it is not.
Practical notes
Ask for what you render. Requesting fields you discard costs bandwidth and nothing else — the credit is charged per API regardless of how many fields you name — but it also makes the query harder to read and hides which fields you actually depend on.
Draft in the explorer, ship from code. Autocomplete and schema validation catch a misspelled
field before it becomes a GRAPHQL_VALIDATION_ERROR in production.
Cache the same way you would cache REST. The response is deterministic for a given query and input, and a cached response costs no credits. The cache-window guidance in making requests applies unchanged.
Watch your balance from a REST call. GraphQL responses do not carry the
x-api-remaining-credits header, so if you need to monitor usage, read it from a REST call or
from analytics rather than expecting it here.
Do not put a query in a GET. Besides being rejected with 405, it would put your input in a
URL that ends up in logs.
Next
GraphQL overview has the limits, the cost model and what the gateway will not do. Errors covers the REST side of the same failures, and authentication covers the key that both use.