# JavaScript & Node.js Email Verification API — Ravn

Canonical: https://askravn.com/integrations/javascript

Integrations · JavaScript

# Ravn from Node, with plain fetch

Use Node’s built-in fetch to POST JSON to https://api.askravn.com/v1/tools/<tool> with your Bearer key; treat status "ok" as data, "not_found" as a free miss, and honour Retry-After when you get a 429.
[Start free: 1,000 lookups](https://askravn.com/#start) [Read the docs](https://askravn.com/docs)

Setup
## Before you write code

- / 01
### Use Node 18 or newer
Built-in `fetch` means no dependencies. The client below runs as an ES module (`.mjs`) or in any modern server runtime.
- / 02
### Keep the key on the server
Set `RAVN_API_KEY` in your environment. Never call the API from browser code: anyone could read the key from the page.
- / 03
### Paste the client
Copy `ravn.mjs` below and import `call` wherever you need it.
- / 04
### Try a single call
`node ravn.mjs` runs the example at the bottom against one address.
Code
## ravn.mjs

```
const API = 'https://api.askravn.com/v1/tools/';
const KEY = process.env.RAVN_API_KEY;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

export class RavnError extends Error {
constructor(http, code, message) {
super(http + ' ' + code + ': ' + message);
this.http = http;
this.code = code;
}
}

// Returns results, or null on a free not_found miss.
export async function call(tool, input, { maxWaitSec = 300, tries = 4 } = {}) {
for (let attempt = 0; attempt < tries; attempt++) {
const res = await fetch(API + tool, {
method: 'POST',
headers: { Authorization: 'Bearer ' + KEY, 'Content-Type': 'application/json' },
body: JSON.stringify(input),
});
if (res.status === 429) {
const wait = Number(res.headers.get('retry-after') ?? 30);
const { error } = await res.json();
if (wait > maxWaitSec) throw new RavnError(429, error.code, 'retry in ' + wait + 's');
await sleep(wait * 1000);
continue;
}
if (res.status === 503 && attempt < tries - 1) {
await sleep(2 ** attempt * 1000);
continue;
}
const body = await res.json();
if (!res.ok) throw new RavnError(res.status, body.error.code, body.error.message);
return body.status === 'ok' ? body.results : null;
}
throw new RavnError(503, 'gave_up', 'retries exhausted');
}

// Example: decide what to do with a signup address
if (import.meta.url === 'file://' + process.argv[1]) {
const v = await call('validate_email', { email: 'jane@acme.com' });
if (!v) console.log('no verdict');
else if (v.status === 'valid' && ['A', 'B'].includes(v.risk_score)) console.log('accept');
else if (v.status === 'risky') console.log('review:', v.reason);
else console.log('reject:', v.reason);
}
```

Recipes
## Where to put it

- / 01
### Signup form check
Call `validate_email` in your signup handler. Reject `invalid` with reason `bad_syntax` or `no_mx` and show a “did you mistype?” message; let `risky` through but flag it.
- / 02
### Nightly list clean
Read new contacts, chunk them into arrays of 1,000, and send each to `validate_emails`. Store `status` and `risk_score` on each contact.
- / 03
### Company profile on demand
When a sales rep opens an account, call `scrape_website` with its domain and cache the result. See [the website scraper API](https://askravn.com/features/website-scraper-api) for fields.
Good to know
## Results, misses and errors
The client returns `results` for a hit and `null` for a miss. For `validate_emails`, results is an array with one verdict per unique address, each with its own `email`, so build a map keyed on the lowercased address to join back to your rows.
Errors throw `RavnError` with the HTTP status and a code such as `tool_not_enabled` or `unknown_tool`. Catch `provider_key_required` to show a friendly message rather than a stack trace. Each response’s `metadata.request_id` is worth logging. See [the envelope reference](https://askravn.com/docs#envelope) and [how Ravn works](https://askravn.com/how-it-works).
FAQ
## Common questions.
[All questions →](https://askravn.com/faq)Can I use this client in a serverless function?Yes. It uses only built-in fetch and timers. On platforms with short timeouts, lower `maxWaitSec` so a 429 throws instead of sleeping past the function limit.Can I call Ravn straight from the browser?Not with your key. A key in front-end code is public. Call it from your server or a serverless function and return only the verdict to the browser.Should a signup form block risky addresses?Usually not. `risky` often means a catch-all company domain, where real people do receive mail. Block `invalid`, accept `valid`, and treat `risky` as a soft flag.What does a 413 mean from my Node code?The request body was over 1 MB. That only happens with very large batches; keep `validate_emails` calls to 1,000 addresses and you will stay under it.Does this work with TypeScript?Yes. The JSON Schemas from `GET /v1/tools` describe every input, so you can generate types from them or write a small interface for the tools you use.

## More in [integrations](https://askravn.com/integrations)
Every page uses the same account, plan and tools. [See pricing →](https://askravn.com/pricing)
- [Make.com Email Verification & EnrichmentCall Ravn from a Make scenario with the HTTP app’s Make a request module: JSON body, Bearer header, parsed response you can map and filter..](https://askravn.com/integrations/make)
- [n8n Email Verification & EnrichmentUse the n8n HTTP Request node with Header Auth to verify emails, find work addresses and enrich companies with Ravn inside any n8n workflow..](https://askravn.com/integrations/n8n)
- [Python Email Verification API ClientCall Ravn from Python with requests: one POST per tool, branch on status, retry 429 using Retry-After, and turn error codes into clear exceptions..](https://askravn.com/integrations/python)
- [Zapier Email Verification WebhookVerify and enrich emails in a Zap with the Webhooks by Zapier POST action and a Filter on the result, on Zapier plans that include Webhooks..](https://askravn.com/integrations/zapier)
- [Attio Contact Enrichment & VerificationVerify emails and fill work addresses for Attio people records: export a list to CSV, enrich with the Ravn CLI, and import the columns back..](https://askravn.com/integrations/attio)
- [Claude Contact Data ConnectorAdd Ravn to Claude as a custom connector and ask for verified emails, company facts and phone checks in plain English, on web, desktop and mobile..](https://askravn.com/integrations/claude)
