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.
Before you write code
- / 01
Use Node 18 or newer
Built-in
fetchmeans 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_KEYin your environment. Never call the API from browser code: anyone could read the key from the page. - / 03
Paste the client
Copy
ravn.mjsbelow and importcallwherever you need it. - / 04
Try a single call
node ravn.mjsruns the example at the bottom against one address.
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);
}Where to put it
- / 01
Signup form check
Call
validate_emailin your signup handler. Rejectinvalidwith reasonbad_syntaxorno_mxand show a “did you mistype?” message; letriskythrough but flag it. - / 02
Nightly list clean
Read new contacts, chunk them into arrays of 1,000, and send each to
validate_emails. Storestatusandrisk_scoreon each contact. - / 03
Company profile on demand
When a sales rep opens an account, call
scrape_websitewith its domain and cache the result. See the website scraper API for fields.
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 and how Ravn works.
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
Every page uses the same account, plan and tools. See pricing →