Skip to content

MaintenanceThe former company search endpoint goes into maintenance on November 9Migration guide

Endpoints // Contact

Verify Email

Last verified

Available inAPISDK

Check whether an email address can receive mail and get a verdict (valid, risky, invalid or unknown) by polling or webhook.

POST/v2/contact/email/verify

Credits

1 credit per verdict, whatever it says.

A check that fails is refunded.

Introduction

The Verify Email endpoint checks whether an email address can receive mail, and returns one verdict: valid, risky, invalid or unknown. Processing is asynchronous: the API acknowledges the request with a webhookId, then you retrieve the verdict by polling (recommended, no public endpoint required) or through a webhook callback (advanced, push delivery).

Use this endpoint to clean a list before a send, or to check an address that comes from a form, a CRM or another source. Addresses returned by Find Email are already verified, so you do not need to check them again.

This endpoint costs 1 credit per verdict, whatever the verdict says, unknown included. A check that fails without a verdict is refunded. See Verdicts.

Authorization

NameTypeRequiredDescription
apikeystringYesYour API key from the developer dashboard. Pass it in the Authorization: Bearer YOUR_API_KEY header.

Request body

NameTypeRequiredDescription
emailstringYesThe email address to check, such as jane@acme.com.
webhookUrlstringOptionalHTTPS URL that receives the result, including error outcomes. Omit it to poll for the verdict instead.

A request with a missing or malformed email is refused with a 422 and costs nothing: no job starts.

Idempotency-Key header

A check is debited when its job is created. If the response gets lost (a timeout, a dropped connection), sending the request again would start and bill a second check. To retry safely, send an Idempotency-Key header with a key you choose for this check, and send the same key again when you retry.

NameTypeRequiredDescription
Idempotency-KeystringOptionalA key you choose for this check: 1 to 255 printable ASCII characters without spaces, such as a UUID. Use a new key for each new check.

For 24 hours, a request with the same key and the same body returns the response of the first one, unchanged, with the same webhookId and the header Idempotent-Replayed: true. No new check starts and no credits are debited: the replayed quotas describe the first request. Keys are scoped to your workspace and to this endpoint. A replayed request still counts toward your per-minute rate limit.

StatusCodeWhen
409IDEMPOTENCY_KEY_IN_USEThe first request with this key is still being processed. Retry after the Retry-After seconds to receive its response.
422IDEMPOTENCY_KEY_REUSEDThe key was already used with a different body. Use a new key for a new check.
400INVALID_IDEMPOTENCY_KEYThe key is empty, longer than 255 characters, or contains a space or a non-ASCII character.

A first request that ends in an error is not kept: it started nothing and cost nothing, so a retry with the same key runs as a new request.

Idempotency-Key

curl -X POST https://api.reversecontact.com/v2/contact/email/verify \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 4f1c2a9e-3b7d-4c55-9a8e-1d2f3a4b5c6d" \
  -d '{"email":"jane@acme.com"}'

Verdicts

The verdict is the status field of the result. It is not the status of the job: when you poll, data.status is the job (succeeded) and data.result.status is the verdict.

VerdictMeaningWhat to do
validThe address can receive mail.Send.
riskyThe address may receive mail, but delivery is not guaranteed. A catch-all domain is a common cause.Send with care, or keep it out of a cold send.
invalidThe address cannot receive mail.Do not send.
unknownThe check was inconclusive.Treat it as unverified. You can check it again later.

Every verdict costs 1 credit.

Response structure

This endpoint replies immediately with a webhookId. You then retrieve the result in one of two ways:

  1. Polling (recommended): call GET /v2/webhooks/:webhookId until the status is succeeded. With ?wait=20, each call waits up to 20 seconds and answers as soon as the result is ready. No webhook URL is needed. See the Polling guide.
  2. Webhook callback (advanced): if you provided a webhookUrl (or a workspace default is configured), Reverse Contact POSTs the verdict to that URL once the check completes. See Webhook callback payload (advanced) below.
FieldTypeDescription
successbooleantrue if the job was created successfully
dataobject

Initial job acknowledgement. Save the webhookId to retrieve the result via polling (GET /v2/webhooks/:webhookId) or to correlate an upcoming webhook callback (null on error responses).

Show child attributes
FieldTypeDescription
statusstringJob status, always "created" for the initial response
webhookIdstringUnique identifier (UUID). Save it to poll the result at GET /v2/webhooks/:webhookId, or to correlate an incoming webhook callback.
pollUrlstringConvenience hint pointing to the polling endpoint for this job (e.g. /v2/webhooks/{webhookId}).
FieldTypeDescription
errornullAlways null on success
quotasobject

Credits and rate limit usage after this request

Show child attributes
FieldTypeDescription
creditsConsumednumberCredits debited when the job is created: 1
workspaceobject

Workspace-level limits

Show child attributes
FieldTypeDescription
idstringYour workspace identifier
hasUnlimitedCreditsbooleanWhether workspace credits are unlimited
allowDailyOvercostbooleanWhether the workspace may exceed its daily cap
creditsobject

Credit balance

Show child attributes
FieldTypeDescription
totalnumberTotal credits in your plan
usednumberCredits consumed so far
leftnumberRemaining credits
dailyLimitobject

Daily request limit (null if not configured)

Show child attributes
FieldTypeDescription
limitnumberMaximum requests per day
usednumberRequests made today
leftnumberRemaining requests today
nextResetstringWhen the daily window resets (ISO 8601)
minuteRateLimitobject

Per-minute rate limit

Show child attributes
FieldTypeDescription
limitnumberMaximum requests per minute
usednumberRequests made in the current minute
leftnumberRemaining requests in the current minute
nextResetstringWhen the minute window resets (ISO 8601)
keyobject

API key-level limits (null when request is from dashboard)

Show child attributes
FieldTypeDescription
idstringAPI key identifier
dailyLimitobject

Daily limit for this key (null if not configured)

Show child attributes
FieldTypeDescription
limitnumberMaximum requests per day
usednumberRequests made today
leftnumberRemaining requests today
nextResetstringWhen the daily window resets (ISO 8601)
minuteRateLimitobject

Minute rate limit for this key

Show child attributes
FieldTypeDescription
limitnumberMaximum requests per minute
usednumberRequests made in the current minute
leftnumberRemaining requests in the current minute
nextResetstringWhen the minute window resets (ISO 8601)
metadataobject

Request tracking information

Show child attributes
FieldTypeDescription
requestIdstringUnique identifier for this request
executionTimeMsnumberTotal execution time in milliseconds

Webhook callback payload (advanced)

When the check returns a verdict, Reverse Contact sends a JSON POST request to your webhookUrl. The callback is not wrapped in the V2 response envelope. It always includes the webhookId from the initial acknowledgement and a data value.

Webhook callback (success)

Success payload

{
  "webhookId": "c2d3e4f5-a6b7-4c8d-9e0f-1a2b3c4d5e6f",
  "data": {
    "email": "jane@acme.com",
    "status": "valid"
  }
}
dataobject

The verdict for the address you sent.

Show child attributes
FieldTypeDescription
emailstringThe address that was checked
statusstringThe verdict: valid, risky, invalid or unknown. See Verdicts.

Webhook callback errors (advanced)

When the check cannot return a verdict, data holds an errorCode that gives the reason:

Webhook callback (error)

Error payload

{
  "webhookId": "c2d3e4f5-a6b7-4c8d-9e0f-1a2b3c4d5e6f",
  "data": {
    "errorCode": "fetch-data-error"
  }
}

data.errorCode takes one of these values:

CodeBilledDescription
invalid-requestNoThe address has a form the check cannot read
fetch-data-errorNoThe check failed before a verdict; safe to retry
webhook-url-invalidYesThe callback URL returned a 4xx response
webhook-url-erroredYesThe callback URL returned a 5xx response or a network error
webhook-url-timeoutYesThe callback URL did not respond in time
webhook-url-unreachableYesThe callback URL could not be reached

Code examples

Code Examples

import ReverseContact, { JobFailedError } from "@reverse-contact/sdk";

const client = new ReverseContact({ apiKey: process.env.RC_API_KEY });

const job = await client.contact.verifyEmail({ email: "jane@acme.com" });
console.log("Save this job id:", job.id); // resume after a restart with client.jobs.wait(id)

try {
  // Long polls until the job ends, 30 minutes at most by default.
  const done = await job.wait();
  console.log(done.data.result?.email, done.data.result?.status);
} catch (error) {
  if (error instanceof JobFailedError) console.log("Ended without a verdict:", error.errorCode, "credits:", error.creditsConsumed);
  else throw error;
}

Example response

Example Response

{
  "success": true,
  "data": {
    "status": "created",
    "webhookId": "c2d3e4f5-a6b7-4c8d-9e0f-1a2b3c4d5e6f",
    "pollUrl": "/v2/webhooks/c2d3e4f5-a6b7-4c8d-9e0f-1a2b3c4d5e6f"
  },
  "error": null,
  "metadata": {
    "requestId": "req_v1e2r3i4f5y6",
    "executionTimeMs": 95
  },
  "quotas": {
    "creditsConsumed": 1,
    "workspace": {
      "id": "ws_abc123",
      "credits": {
        "total": 10000,
        "used": 4537,
        "left": 5463
      },
      "dailyLimit": null,
      "minuteRateLimit": {
        "limit": 60,
        "used": 10,
        "left": 50,
        "nextReset": "2025-01-20T14:16:00.000Z"
      },
      "hasUnlimitedCredits": false,
      "allowDailyOvercost": false
    },
    "key": {
      "id": "key_abc123",
      "dailyLimit": null,
      "minuteRateLimit": {
        "limit": 60,
        "used": 10,
        "left": 50,
        "nextReset": "2025-01-20T14:16:00.000Z"
      }
    }
  }
}