Every endpoint lives under one host and one path segment:
https://api.apiverve.com/v1/<endpoint>
GET endpoints take query parameters. POST endpoints take a JSON body. Both authenticate with
the same header and return the same envelope, so once one call works, the shape of every other
call is already familiar.
GET requests
Most endpoints are GET. Parameters go in the query string:
curl 'https://api.apiverve.com/v1/weather?city=London' \
-H 'x-api-key: YOUR_API_KEY'const params = new URLSearchParams({ city: 'London' });
const res = await fetch(`https://api.apiverve.com/v1/weather?${params}`, {
headers: { 'x-api-key': process.env.APIVERVE_API_KEY },
});
const { data } = await res.json();import os, requests
res = requests.get(
"https://api.apiverve.com/v1/weather",
params={"city": "London"},
headers={"x-api-key": os.environ["APIVERVE_API_KEY"]},
timeout=15,
)
data = res.json()["data"]Build query strings with your language's URL encoder rather than string concatenation. Values
like New York or a+b break a hand-built URL and produce a confusing 400 rather than an
obvious one.
POST requests
Endpoints that take structured input, long text, or a file use POST with a JSON body. These
need a Content-Type header; GET requests do not.
curl -X POST 'https://api.apiverve.com/v1/htmltopdf' \
-H 'x-api-key: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{ "html": "<h1>Hello</h1>", "options": { "format": "A4" } }'const res = await fetch('https://api.apiverve.com/v1/htmltopdf', {
method: 'POST',
headers: {
'x-api-key': process.env.APIVERVE_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({ html: '<h1>Hello</h1>', options: { format: 'A4' } }),
});Which method an endpoint uses is on its reference page — you never have to guess, and calling
with the wrong one returns a 405 rather than a silent failure.
Parameters
Each endpoint's reference page lists its parameters with type, whether they are required, and any constraints. Three rules apply everywhere:
- Required parameters produce a
400when missing, and the message names the parameter. - Optional parameters have documented defaults. Omitting one is not the same as sending it empty; an empty string is a value, and some endpoints will reject it.
- Premium parameters are accepted on every plan but only take effect on plans that include them. They do not error on a lower plan — they are ignored.
A parameter the endpoint does not declare is silently discarded. The call still succeeds and
still costs a credit; it just ignores your input. So a typo like citty=London on an endpoint
where city is optional returns a perfectly valid answer for the wrong question. If a parameter
seems to do nothing, check its spelling against the reference page first.
File uploads
Endpoints that accept a file take a multipart/form-data request. Set the field name the
endpoint documents, and let your HTTP client set the Content-Type — it has to include the
multipart boundary, which you cannot write by hand.
curl -X POST 'https://api.apiverve.com/v1/imagetotext' \
-H 'x-api-key: YOUR_API_KEY' \
-F 'file=@receipt.png'Size limits are plan-based, and an oversized upload is a 400 naming the ceiling:
| Plan | Maximum upload |
|---|---|
| Free, Hobby | 400 KB |
| Starter and above | 10 MB, or the endpoint's own limit |
Timeouts
Most endpoints answer in well under a second — each reference page shows that endpoint's measured p50. The exceptions are the ones doing real work: rendering a PDF, running OCR, screenshotting a page.
Set a client timeout of 30 seconds for rendering and extraction endpoints, and 10–15 seconds for everything else. Set one explicitly either way: most HTTP clients default to no timeout at all, which turns a slow upstream into a hung process.
const res = await fetch(url, {
headers: { 'x-api-key': KEY },
signal: AbortSignal.timeout(15_000),
});Concurrency
Requests in flight at the same moment are capped per plan, separately from the per-minute rate limit:
| Plan | Concurrent calls | Rate limit |
|---|---|---|
| Free | 1 | 5/min |
| Starter | 5 | 60/min |
| Pro | 20 | 180/min |
| Mega | 50 | No limit |
Concurrency is usually what an unbounded Promise.all hits first. A fixed pool sized to your
plan gets more throughput than firing everything at once and retrying the failures:
async function pool(items, size, fn) {
const out = [];
for (let i = 0; i < items.length; i += size) {
out.push(...(await Promise.all(items.slice(i, i + size).map(fn))));
}
return out;
}
const results = await pool(cities, 5, (city) => getWeather(city));Many inputs at once
For lists, batch requests are better than a pool: one HTTP request carrying many inputs, which counts as one request against your rate limit while charging credits per item.
curl -X POST 'https://api.apiverve.com/v1/emailvalidator/batch' \
-H 'x-api-key: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{ "items": [{ "email": "a@example.com" }, { "email": "b@example.com" }] }'Identifying your client
Set a User-Agent that names your application and version. It is optional, but it is the first
thing support looks at when tracing a pattern of calls, and it separates your traffic from the
generic HTTP-library default that thousands of other callers also send.
User-Agent: acme-billing/2.3 (+https://acme.example.com)
The x-av-client header is accepted for the same purpose by the official SDKs and extensions,
which set it automatically. You do not need to set it yourself.
Caching
Responses are not cached by the API, and no endpoint returns a Cache-Control that would let a
proxy cache one for you. That is deliberate — a cached response you did not ask for is a wrong
answer waiting to happen on the endpoints that change by the second.
Caching on your side is encouraged, and it is the cheapest optimisation available on this API, because a cached response costs zero credits. What is worth caching is decided by how fast the underlying data actually moves:
| Endpoint kind | Reasonable cache |
|---|---|
| Country, currency, timezone, ISO-code lookups | Weeks — these change on a scale of years |
| Geocoding, DNS, WHOIS, company records | Hours to a day |
| Weather, exchange rates, market data | Minutes |
| Validation, generation, fraud scoring | Do not cache — the input is the answer |
Key the cache on the endpoint plus the full normalised parameter set, so two calls that differ only in parameter order share an entry.
Retries and idempotency
GET endpoints are read-only and safe to retry freely. POST endpoints on this API are
transformations rather than record creation — sending the same body twice produces the same
answer and costs two credits, but it does not create anything twice.
That means there is no idempotency key to manage. The only cost of a duplicate call is the credit, which is why the retry guidance in errors is about avoiding pointless retries rather than about preventing double-writes.
Reading the response
Every endpoint returns the same three keys, so one handler covers your whole integration:
{
"status": "ok",
"error": null,
"data": { }
}Check res.ok and body.status before reading data — see errors for the
full treatment and response format for what is inside data.
Next
Response format covers the envelope and the premium-field rule. Rate limits covers the ceilings this page assumes. When you are ready to stop hand-writing HTTP, the SDKs wrap all of this.
When the inputs arrive as a list rather than one at a time, batch requests replace the loop. Headers, both sent and returned, are catalogued in request headers.