Code examples
JavaScript and TypeScript
NewCall the Reverse Contact API from Node.js, Bun or Deno with a typed client generated from the OpenAPI spec, or with a small fetch helper.
- Last verified
Introduction
There is no Reverse Contact package to install. The API is JSON over HTTPS, so the fetch built into Node.js 18 and later, Bun and Deno is all a call needs. For types and autocompletion on every request and response, generate a client from the OpenAPI spec this site publishes at https://docs.reversecontact.com/openapi.json, which is updated with each docs release.
Keep the API key on the server, in an environment variable such as RC_API_KEY. Never send it to a browser: see Authentication.
Typed client from the OpenAPI spec
openapi-typescript turns the spec into TypeScript types, and openapi-fetch is a small fetch wrapper that checks every path, body and response against them. openapi-typescript needs TypeScript 5, hence typescript@5.
Install
npm install openapi-fetch
npm install --save-dev openapi-typescript typescript@5
npx openapi-typescript https://docs.reversecontact.com/openapi.json -o reversecontact.d.tsbun add openapi-fetch
bun add --dev openapi-typescript typescript@5
bunx openapi-typescript https://docs.reversecontact.com/openapi.json -o reversecontact.d.tsRun the last command again whenever the changelog announces a change to an endpoint you call.
Enrich a person
import createClient from 'openapi-fetch'
import type { paths } from './reversecontact'
const rc = createClient<paths>({
baseUrl: 'https://api.reversecontact.com',
headers: { Authorization: `Bearer ${process.env.RC_API_KEY}` }
})
// Enrich a person from a work email: 2 credits when found, a 404 is free.
const { data, error, response } = await rc.POST('/v2/enrich/persons', {
body: { email: 'jane.doe@acme.com' }
})
if (data) {
const person = data.data?.person
console.log(person?.firstName, person?.lastName, person?.currentPositionTitle)
console.log('Credits used:', data.quotas.creditsConsumed)
} else if (response.status === 404) {
console.log('No confident match: nothing was charged')
} else {
throw new Error(`${response.status} ${error?.error.code}: ${error?.error.message}`)
}The paths keep their /v2 prefix (/v2/enrich/persons), as in the spec. On Enrich, a 404 is not an error to retry: it means no profile matched with confidence, and it costs nothing.
Async jobs
The live fetch endpoints, Find Email and Find Phone answer with a job. Poll it until it succeeds or fails: polling is free and does not count toward your rate limit. Give the loop a maximum duration, and stop on a poll that answers an error: 404 WEBHOOK_EVENT_NOT_FOUND for an unknown job, 410 WEBHOOK_EVENT_EXPIRED for a result older than 72 hours.
Live fetch and polling
const { data: job, error: jobError } = await rc.POST('/v2/fetch/persons/live', {
body: { url: 'https://social.com/in/janedoe' }
})
const webhookId = job?.data?.webhookId
if (!webhookId) throw new Error(jobError?.error.message ?? 'No job created')
// Results usually arrive within seconds, but can take tens of minutes.
const deadline = Date.now() + 30 * 60 * 1000
for (;;) {
if (Date.now() > deadline) throw new Error(`Job ${webhookId} still running: poll it again later`)
await new Promise((resolve) => setTimeout(resolve, 5000))
const { data: poll, error: pollError, response } = await rc.GET('/v2/webhooks/{webhookId}', {
params: { path: { webhookId } }
})
// A 5xx is temporary: poll again. 404 and 410 mean there is nothing left to wait for.
if (!poll) {
if (response.status >= 500) continue
throw new Error(`${response.status} ${pollError?.error.code}: ${pollError?.error.message}`)
}
if (poll.data?.status === 'succeeded') {
console.log(poll.data.result)
break
}
if (poll.data?.status === 'errored') throw new Error(poll.data.errorCode ?? 'Job errored')
}Every 5 to 10 seconds suits a background job. See Polling for the intervals that fit other cases, and for how long a result stays available.
Without code generation
When you would rather not generate types, this helper is enough. It follows the retry rule of Error handling & retries: on a 429, wait the number of seconds in error.details.retryAfter, then retry; on a 500, 502, 503 or 504, retry with exponential backoff, a few times at most. It reads the status before the body, because a 502 or 503 from the network edge may not be JSON. A 429 with the code DAILY_LIMIT_EXCEEDED is the daily limit: it is not retried, since it does not clear within minutes.
fetch helper
const API = 'https://api.reversecontact.com/v2'
const BACKOFF = new Set([500, 502, 503, 504])
const MAX_ATTEMPTS = 5
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
/** One call to the API. 429: waits error.details.retryAfter. 500, 502, 503, 504: backs off. */
export async function rc(path: string, body?: unknown) {
for (let attempt = 1; ; attempt++) {
const response = await fetch(`${API}${path}`, {
method: body === undefined ? 'GET' : 'POST',
headers: {
Authorization: `Bearer ${process.env.RC_API_KEY}`,
'Content-Type': 'application/json'
},
body: body === undefined ? undefined : JSON.stringify(body)
})
// The status first: a 502 or 503 from the network edge may not be JSON.
if (BACKOFF.has(response.status) && attempt < MAX_ATTEMPTS) {
await sleep(Math.min(1000 * 2 ** attempt, 15000))
continue
}
const json = await response.json().catch(() => ({
success: false,
error: { code: `HTTP_${response.status}`, message: 'The response is not JSON' }
}))
if (response.status === 429 && json.error?.code !== 'DAILY_LIMIT_EXCEEDED' && attempt < MAX_ATTEMPTS) {
await sleep((json.error?.details?.retryAfter ?? 60) * 1000)
continue
}
return { status: response.status, ...json }
}
}
const result = await rc('/enrich/persons', { email: 'jane.doe@acme.com' })
if (result.success) console.log(result.data.person.currentPositionTitle)
else if (result.status === 404) console.log('No confident match: nothing was charged')
else console.error(result.status, result.error.code, result.error.message)Here the paths start after /v2. What a 404 means depends on the endpoint: on Enrich, no confident match; on a cached fetch, a profile that is not in the cache; on /v2/webhooks, an unknown job. Every error other than the retried ones needs a change to the request or the account before it can succeed.
FAQ
Is there an official Reverse Contact npm package?
No. The Reverse Contact API is JSON over HTTPS, so fetch is enough, and a typed client generated from https://docs.reversecontact.com/openapi.json with openapi-typescript and openapi-fetch gives types for every endpoint.
Can I call the Reverse Contact API from the browser?
Not with your API key: anyone can read a key shipped in front-end code. Call the API from your server, keep the key in an environment variable such as RC_API_KEY, and send the browser only what it needs.
Which Reverse Contact API errors should my code retry?
On a 429, wait the number of seconds in error.details.retryAfter, then retry, except for DAILY_LIMIT_EXCEEDED, the daily limit. Retry 500, 502, 503 and 504 with exponential backoff, a few times at most. Every other error needs a change to the request or the account first.