You have already made a request with curl or a playground. The jump to code is small in lines and large in consequences: a script runs unattended, so every way the call can fail has to be handled by something you wrote in advance. This lesson builds one function that does that properly, and you will reuse it for the rest of the course.
Everything here runs on Node 18 or newer, which ships fetch as a global. No package to install. The examples use ES modules (import/export and top-level await), so either name your files .mjs or add "type": "module" to package.json. Every lesson in this course sticks to that choice.
The smallest call that works
Start with the email validator, because its response is easy to read and its answer is obviously right or wrong. Set your key in the shell first so it never appears in a file:
# macOS / Linux
export APIVERVE_API_KEY="YOUR_API_KEY"
# Windows PowerShell
$env:APIVERVE_API_KEY = "YOUR_API_KEY"Then the call:
const res = await fetch(
'https://api.apiverve.com/v1/[email protected]',
{ headers: { 'x-api-key': process.env.APIVERVE_API_KEY } }
);
const body = await res.json();
console.log(body.data.isValid, body.data.isDisposable);Run it with node first-call.mjs and you should see true false. Three things happened, and each one is a place a real program goes wrong:
fetchreturned a Promise of aResponse.awaitpauses this function until the response headers have arrived. The body has not been read yet.res.json()reads the body stream to the end and parses it. That is a second wait, which is why it also needsawait. Forgetting it gives you a Promise where you expected an object, andbody.dataisundefined.- The parsed body is the APIVerve envelope:
status,erroranddata. The fields you care about live underdata.
Try the same request here before going further, and look at the full shape of data:
curl "https://api.apiverve.com/v1/emailvalidator?email=support%40myspace.com" \
-H "x-api-key: YOUR_API_KEY"Check data.isValid and data.isDisposable, then try an address at a domain you know is disposable.
That snippet is fine for a first run and wrong for anything else. It assumes the network worked, the server answered with JSON, the key was accepted, and the API found what you asked for. Each of those assumptions fails in production on a normal Tuesday.
Two layers of success
An HTTP API reports success twice: once in the status code on the response line, and once in the body. You have to check both.
What fetch considers a failure
fetch only rejects its Promise when it could not get a response at all: DNS failure, refused connection, a dropped network, a CORS block in the browser, or an aborted request. A 401, a 429 or a 500 is a perfectly successful HTTP exchange as far as fetch is concerned. The Promise resolves, and res.ok is false.
That surprises almost everyone who has used axios, which throws on non-2xx statuses. With fetch, if you never look at res.ok, your code will happily try to read data out of an error response.
res.ok is true for any status from 200 to 299. res.status gives you the exact number when you need to branch on it.
What the envelope adds
The body carries its own verdict. A successful call looks like this:
{
"status": "ok",
"error": null,
"data": { "email": "[email protected]", "isValid": true, "isDisposable": false }
}
A failed one keeps the same three keys, with status set to "error", data set to null, and error holding a sentence written for a human, such as "The 'email' parameter is required.". That sentence names the problem, so it is the message you want to surface, log or show.
Why check status when you already checked res.ok? Because the two can disagree. The batch endpoint returns 200 for a batch where individual items failed, and treating "the HTTP exchange worked" as "I have my answer" is the habit that causes that bug. Make the envelope check part of every call, and the multi-result paths will not catch you out later.
When the body is not JSON
There is one more case to plan for: a response that isn't the JSON envelope at all. APIVerve answers its own errors in JSON, but anything between you and the API can answer instead: a proxy, a captive Wi-Fi portal, a gateway timing out with an HTML error page, or the edge rejecting a header that is absurdly long. Calling res.json() on an HTML page throws a SyntaxError, and the error you see ("Unexpected token '<'") points nowhere near the real cause.
Key problems themselves come back as JSON, and the most common one is an environment variable that was never set: process.env.APIVERVE_API_KEY is undefined, the header goes out as the string "undefined", and the API answers 401. The short version of the key-related responses:
| You get | It means |
|---|---|
JSON 401 | No key was sent, or the key isn't recognised. Check the environment variable, then that you copied the current key. |
JSON 403 | The key is valid but not allowed to make this call (scoping, IP allow-list, revoked). |
So the parse step needs a guard: try to read JSON, and if that fails, fall back to the status code.
Your code calls fetch() on an APIVerve endpoint with an invalid but well-formed key. What happens to the Promise fetch returns?
Answer the check above to continue.
Common bugs, in the order you will hit them
- Forgetting the second
await.const body = res.json()gives you a Promise. Every property read on it isundefined. - Checking only
res.ok. Fine today, wrong the first time you fan out with batch. - Calling
res.json()twice. The body is a stream and can be read once. If you need it as text for logging and as JSON, read it withres.text()andJSON.parsethe string yourself. - Logging the request with its headers. That puts your key into whatever collects your logs. Log the URL and the status, never the
x-api-keyvalue. - No timeout.
fetchwill wait as long as the connection stays open. That is the first thing lesson three fixes.
Where this leaves you
You now have apiverve(id, params): it reads the key from the environment, encodes parameters safely, checks the HTTP status and the envelope, survives a non-JSON body, and throws an error that carries the status. That is a sound base, but it still trusts the network to answer promptly and never stumble.
Next, the same helper in Python with requests, including sessions and the params versus json distinction: Call an API with Python requests.