Call an API with JavaScript fetch

Write the fetch call you will copy into every project from now on - one that reads the key from the environment, checks both layers of success, and fails with a message you can act on.

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:

first-call.mjs
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:

  1. fetch returned a Promise of a Response. await pauses this function until the response headers have arrived. The body has not been read yet.
  2. res.json() reads the body stream to the end and parses it. That is a second wait, which is why it also needs await. Forgetting it gives you a Promise where you expected an object, and body.data is undefined.
  3. The parsed body is the APIVerve envelope: status, error and data. The fields you care about live under data.

Try the same request here before going further, and look at the full shape of data:

Try it liveEmail Validator20 credits
GEThttps://api.apiverve.com/v1/emailvalidator
curl "https://api.apiverve.com/v1/emailvalidator?email=support%40myspace.com" \
  -H "x-api-key: YOUR_API_KEY"
Sign in or create a free account to run this with your own 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 getIt means
JSON 401No key was sent, or the key isn't recognised. Check the environment variable, then that you copied the current key.
JSON 403The 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.

Check yourself

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.

Run every example on your own key. Free plan, no card.

Scaling up?

Volume pricing, custom SLAs, and dedicated support for high-traffic teams.

Contact sales