ravn
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 Read the docs

Setup

Before you write code

  1. / 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.

  2. / 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.

  3. / 03

    Paste the client

    Copy ravn.mjs below and import call wherever you need it.

  4. / 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

  1. / 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.

  2. / 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.

  3. / 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 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 and how Ravn works.

FAQ

Common questions.

All questions →

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.