API Documentation v1

Verify email addresses in real-time using SMTP probing, MX analysis, and catch-all detection. Track engagement after sending. Integrates in minutes with any language.

Authentication

Every API request requires your API key. Pass it in the Authorization header:

Authorization: Bearer ev_live_YOUR_API_KEY_HERE

Alternatively, use the X-API-Key header:

X-API-Key: ev_live_YOUR_API_KEY_HERE
Security: Never expose your API key in frontend code, URLs, or query strings. Keys passed via query params are rejected.

Base URL

https://verifycatch.com/api/v1

All endpoints below are relative to this base URL.

Rate Limits

PlanRequests/minMonthly IncludedOverage
Free10100Hard cap
Starter ($29/mo)605,000$0.008/each
Pro ($99/mo)30025,000$0.005/each
Enterprise ($299/mo)1,000100,000$0.003/each

When you hit your rate limit, the API returns 429 Too Many Requests with a Retry-After header.

Error Handling

Errors return a JSON body with error and message fields:

{
  "error":   "usage_limit_reached",
  "message": "Monthly limit reached (100 verifications). Upgrade to continue.",
  "used":    100,
  "limit":   100
}
StatusMeaning
400Bad request (missing/invalid params)
401Invalid or missing API key
402Monthly usage limit reached
403Forbidden (email not verified, account suspended)
429Rate limit exceeded
500Server error

Verify Single Email

POST /api/v1/verify

Verify one email address in real-time. Response takes 2-15 seconds depending on the mail server.

Request

curl -X POST https://verifycatch.com/api/v1/verify \
  -H "Authorization: Bearer ev_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email": "john@example.com"}'
FieldTypeRequiredDescription
emailstringYesEmail address to verify (max 320 chars)
refreshbooleanNoSet true to skip cache and force a fresh SMTP probe

Response (200 OK)

{
  "email":       "john@example.com",
  "domain":      "example.com",
  "score":       92,
  "tier":        "deliverable",
  "tierLabel":   "OK",
  "result":      "ok",
  "quality":     "deliverable",
  "provider":    "Google Workspace",
  "isRole":      false,
  "isDisposable": false,
  "mx": [
    { "exchange": "aspmx.l.google.com", "priority": 1 }
  ],
  "smtp": {
    "connected":     true,
    "mailboxExists": true,
    "responseCode":  250,
    "latencyMs":     342
  },
  "catchAll": {
    "isCatchAll": false
  },
  "spf":          true,
  "dmarc":        true,
  "engagement": {
    "verdict":      "active",       // active | engaged | silent | at_risk | dead | null
    "bestScore":    85,
    "totalSends":   12,
    "totalOpens":   9,
    "totalBounces": 0,
    "totalClicks":  3,
    "complaints":   0,
    "lastOpenAt":   "2026-10-12T08:15:00.000Z",
    "userCount":    2
  },
  "verifiedAt":  "2026-05-15T10:30:00.000Z",
  "cached":      false
}
When engagement data exists for the email (from prior sends via the webhook tracking), the engagement object is included and the engagement signal feeds into the overall score. Emails with active engagement on catch-all domains are promoted to deliverable. Emails that consistently bounce are demoted to undeliverable.

Verify Bulk (Async)

POST /api/v1/verify/bulk

Submit up to 1,000,000 emails for asynchronous verification. Returns a job ID to poll for results. Emails are deduplicated automatically.

Request

curl -X POST https://verifycatch.com/api/v1/verify/bulk \
  -H "Authorization: Bearer ev_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"emails": ["alice@gmail.com", "bob@yahoo.com", "invalid@fake.xyz"], "webhook_url": "https://yoursite.com/callback"}'
FieldTypeRequiredDescription
emailsarrayYesArray of email strings (max 1,000,000)
webhook_urlstringNoURL to receive a POST when the job completes
file_namestringNoOriginal filename for reference (max 255 chars)

Response (202 Accepted)

{
  "jobId":             "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status":            "processing",
  "total":             3,
  "duplicatesRemoved": 0,
  "pollUrl":           "/api/v1/jobs/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "resultsUrl":        "/api/v1/jobs/a1b2c3d4-e5f6-7890-abcd-ef1234567890/results",
  "downloadUrl":       "/api/v1/jobs/a1b2c3d4-e5f6-7890-abcd-ef1234567890/download"
}

Job Status

GET /api/v1/jobs/{jobId}

Poll the status of a bulk verification job.

curl https://verifycatch.com/api/v1/jobs/a1b2c3d4-... \
  -H "Authorization: Bearer ev_live_YOUR_KEY"

Response

{
  "jobId":     "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status":    "completed",     // processing | completed | failed
  "total":     3,
  "completed": 3,
  "progress":  100,
  "summary": {
    "deliverable":   2,
    "risky":         0,
    "undeliverable": 1
  },
  "createdAt": "2026-05-15T10:30:00.000Z",
  "error":     null,
  "links": {
    "results":  "/api/v1/jobs/a1b2c3d4-.../results",
    "download": "/api/v1/jobs/a1b2c3d4-.../download"
  }
}

Job Results

GET /api/v1/jobs/{jobId}/results

Get paginated verification results for a completed job.

curl "https://verifycatch.com/api/v1/jobs/a1b2c3d4-.../results?page=1&page_size=100" \
  -H "Authorization: Bearer ev_live_YOUR_KEY"
Query ParamDefaultDescription
page1Page number
page_size100Results per page (max 1000)
tierallFilter by tier: deliverable, risky, undeliverable

Download CSV

GET /api/v1/jobs/{jobId}/download

Download all results as a CSV file.

curl -O https://verifycatch.com/api/v1/jobs/a1b2c3d4-.../download \
  -H "Authorization: Bearer ev_live_YOUR_KEY"

Account Info

GET /api/v1/account

Get your current plan, usage, and remaining quota.

curl https://verifycatch.com/api/v1/account \
  -H "Authorization: Bearer ev_live_YOUR_KEY"

Response

{
  "plan":         "free",
  "planName":     "Free",
  "month":        "2026-10",
  "used":         42,
  "included":     100,
  "remaining":    58,
  "overage":      0,
  "overageRate":  null,
  "rateLimit":    10
}

Engagement Feedback Loop

VerifyCatch automatically incorporates real-world engagement data into verification scoring. When you send emails and track opens, clicks, and bounces through our ESP webhooks or pixel tracking, that ground-truth data feeds back into future verifications.

How It Works

When you verify an email, the scoring engine checks for prior engagement data across all users. If engagement history exists, it adds Signal 11: Engagement Feedback (up to 15 points) to the score:

VerdictPointsConditionEffect on Tier
active 15/15 Opens confirmed, engagement score ≥ 70 Catch-all / risky → deliverable
engaged 10/15 Some opens detected, score < 70 Positive boost, no tier override
silent 5/15 Delivered but no opens or bounces Mild positive (inbox exists)
at_risk 2/15 Mixed signals (some bounces, some opens) Minimal boost
dead 0/15 Bounced with no opens, or spam complaints Any tier → undeliverable

Tier Overrides

Engagement data is ground truth — it represents what actually happened when email was delivered. Two verdicts override the SMTP-based tier:

Getting Started

To build your engagement data:

The feedback loop is a compounding advantage — the more email you send and track, the more accurate your verifications become. Engagement data is aggregated across all API users, so the entire platform benefits.

Engagement: Score Breakdown

GET /api/v1/engagement/breakdown

Get a count of emails by engagement status (active, at_risk, inactive, dead).

curl https://verifycatch.com/api/v1/engagement/breakdown \
  -H "Authorization: Bearer ev_live_YOUR_KEY"

Response

{
  "breakdown": [
    { "status": "active",   "count": 1240 },
    { "status": "at_risk",  "count": 356 },
    { "status": "inactive", "count": 89 },
    { "status": "dead",     "count": 42 }
  ]
}

Engagement: Email Scores

GET /api/v1/engagement/scores

List engagement scores for tracked emails with filters and pagination.

Query ParamDefaultDescription
statusallFilter: active, at_risk, inactive, dead
sortByengagement_scoreSort field
orderDESCASC or DESC
limit50Results per page (max 200)
offset0Pagination offset

Response

{
  "rows": [
    {
      "email":            "alice@acme.com",
      "engagement_score": 85,
      "status":           "active",
      "total_sends":      24,
      "total_opens":      18,
      "total_clicks":     6,
      "total_bounces":    0,
      "last_open_at":     "2026-10-07T14:22:00.000Z"
    }
  ],
  "total": 1727
}

GET /api/v1/engagement/score/:email

Get the engagement score for a single email address.

Engagement: Events

GET /api/v1/engagement/events

List engagement events (opens, clicks, bounces, etc.) with filters.

Query ParamDescription
emailFilter by email address
eventTypeFilter: open, click, bounce, delivery, complaint
campaignIdFilter by campaign
limitResults per page (default 50, max 200)
offsetPagination offset

Engagement: Pixel Tracking

POST /api/v1/engagement/pixel/generate

Generate a 1x1 tracking pixel to embed in your HTML emails for open tracking.

Request

curl -X POST https://verifycatch.com/api/v1/engagement/pixel/generate \
  -H "Authorization: Bearer ev_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email": "recipient@example.com", "campaignId": "welcome-series"}'

Response

{
  "pixelUrl":    "https://verifycatch.com/api/v1/engagement/pixel/1-ab3f...gif?e=...",
  "pixelTag":    "<img src=\"...\" width=\"1\" height=\"1\" />",
  "trackingId": "1-ab3f9e2c..."
}
Paste the pixelTag into your email HTML before the closing </body> tag. When the recipient opens the email, an open event is recorded automatically.

Engagement: Click Tracking

POST /api/v1/engagement/click/generate

Wrap a URL with a tracking link. When clicked, the recipient is redirected and a click event is recorded.

Request

curl -X POST https://verifycatch.com/api/v1/engagement/click/generate \
  -H "Authorization: Bearer ev_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email": "recipient@example.com", "url": "https://yoursite.com/offer", "campaignId": "welcome-series"}'

Response

{
  "trackingUrl": "https://verifycatch.com/api/v1/engagement/click/a1b2c3...",
  "linkId":      "a1b2c3...",
  "originalUrl": "https://yoursite.com/offer"
}

Engagement: ESP & Outreach Tool Webhooks

Receive engagement events automatically from your Email Service Provider or cold outreach tool. Configure your platform to POST events to one of these URLs:

PlatformWebhook URL
SendGridhttps://verifycatch.com/api/v1/webhooks/sendgrid?key=YOUR_API_KEY
Mailgunhttps://verifycatch.com/api/v1/webhooks/mailgun?key=YOUR_API_KEY
Postmarkhttps://verifycatch.com/api/v1/webhooks/postmark?key=YOUR_API_KEY
Amazon SEShttps://verifycatch.com/api/v1/webhooks/ses?key=YOUR_API_KEY
Smartleadhttps://verifycatch.com/api/v1/webhooks/smartlead?key=YOUR_API_KEY
Instantlyhttps://verifycatch.com/api/v1/webhooks/instantly?key=YOUR_API_KEY

For detailed setup instructions, see the Webhook Setup Guide.

Webhook endpoints use API key authentication via the ?key= query parameter (not the Authorization header), since ESPs send events directly without custom headers.

List Jobs

GET /api/v1/jobs

List all bulk verification jobs for your account, with pagination.

curl "https://verifycatch.com/api/v1/jobs?page=1&limit=20" \
  -H "Authorization: Bearer ev_live_YOUR_KEY"
Query ParamDefaultDescription
page1Page number
limit20Jobs per page (max 100)

Response

{
  "jobs":  [/* array of job objects */],
  "total": 42,
  "page":  1,
  "pages": 3
}

Re-verify Job

POST /api/v1/reverify/{jobId}

Re-run verification on all emails from a previously completed job. Creates a new job and counts against your quota.

curl -X POST https://verifycatch.com/api/v1/reverify/a1b2c3d4-... \
  -H "Authorization: Bearer ev_live_YOUR_KEY"

Response (202)

{
  "jobId":       "new-job-uuid",
  "total":       150,
  "status":      "processing",
  "sourceJobId": "a1b2c3d4-...",
  "pollUrl":     "/api/v1/jobs/new-job-uuid",
  "resultsUrl":  "/api/v1/jobs/new-job-uuid/results",
  "downloadUrl": "/api/v1/jobs/new-job-uuid/download"
}

Pause / Resume Job

PATCH /api/v1/jobs/{jobId}

Pause a running job or resume a paused one.

curl -X PATCH https://verifycatch.com/api/v1/jobs/a1b2c3d4-... \
  -H "Authorization: Bearer ev_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"action": "pause"}'
Body ParamRequiredDescription
actionYespause or resume

Cancel / Delete Job

DELETE /api/v1/jobs/{jobId}

Cancel a running job or delete a completed one. Unprocessed emails are refunded to your quota.

curl -X DELETE https://verifycatch.com/api/v1/jobs/a1b2c3d4-... \
  -H "Authorization: Bearer ev_live_YOUR_KEY"

Response

{
  "message":  "Job cancelled",
  "refunded": 85
}

Result Tiers

Every verification returns a tier and score (0-100):

TierResultScoreMeaningAction
deliverable ok80-100 Mailbox exists, SMTP confirmedSafe to send
risky risky / catch_all40-79 Catch-all domain, role address, or inconclusiveSend with caution
undeliverable invalid0-39 Mailbox doesn't exist, domain dead, or hard bounceDo not send
unknown unknownvaries Server unreachable, timeout, or blockedRetry later

Scoring Signals

The verification score (0-100) is computed from up to 11 weighted signals. Each signal is included in the signals array of every verification response:

#SignalMax PtsWhat It Measures
1MX Records15Domain has mail exchange records
2Mail Provider20Provider reputation (Google, M365, etc.)
3Mailbox (SMTP Probe)30RCPT TO probe confirms mailbox existence
4Catch-All Detection15Domain rejects fake addresses (not catch-all)
5SPF Record8Sender authentication policy exists
6DMARC Policy7Domain-level email authentication policy
7Not Role Address5Personal address, not a shared role account
8Pattern Quality0 (-5)Penalizes suspicious local-part patterns
9M365 Identity Oracle25Cross-layer account check for Microsoft 365 domains
10Google Workspace Oracle25Cross-layer account check for Google domains
11Engagement Feedback15Ground-truth from real email delivery (opens, bounces, clicks)

Signals 8-11 are conditional — they only appear when relevant data is available (e.g., M365 oracle only runs on Microsoft 365 catch-all domains). The score is normalized: score = rawPoints / maxPossiblePoints × 100.

Signals 9-11 are tier overrides: When an oracle or engagement signal gives a definitive answer, it overrides the SMTP-based tier classification. For example, if M365 oracle confirms an account exists on a catch-all domain, the result is promoted from catch_all to ok.

Response Fields

FieldTypeDescription
emailstringNormalized email address
domainstringDomain part of the email
scorenumberConfidence score 0-100
tierstringdeliverable | risky | undeliverable | unknown
resultstringok | catch_all | risky | invalid | unknown
providerstringEmail provider (Google, Microsoft 365, etc.)
isRolebooleanTrue if role-based (info@, admin@, etc.)
isDisposablebooleanTrue if disposable/temporary email
mxarrayMX records for the domain
smtp.connectedbooleanWhether SMTP connection was established
smtp.mailboxExistsbooleanWhether RCPT TO was accepted
smtp.responseCodenumberSMTP response code (250, 550, etc.)
smtp.latencyMsnumberSMTP probe round-trip time in ms
catchAll.isCatchAllbooleanTrue if domain accepts all addresses
spfbooleanWhether domain has SPF record
dmarcbooleanWhether domain has DMARC record
engagementobject|nullEngagement feedback data, or null if no prior send history exists
engagement.verdictstringactive | engaged | silent | at_risk | dead
engagement.bestScorenumberHighest engagement score observed (0-100)
engagement.totalSendsnumberTotal emails delivered to this address
engagement.totalOpensnumberTotal tracked opens
engagement.totalBouncesnumberTotal hard/soft bounces
engagement.totalClicksnumberTotal tracked link clicks
engagement.complaintsnumberSpam complaint count
engagement.lastOpenAtstring|nullMost recent open timestamp
engagement.userCountnumberNumber of API users who have sent to this address
verifiedAtstringISO 8601 timestamp of verification
cachedbooleanTrue if result was served from cache

Plans & Pricing

View available plans programmatically:

GET /api/v1/plans (no auth required)

Code Examples

cURL

# Single verification
curl -X POST https://verifycatch.com/api/v1/verify \
  -H "Authorization: Bearer ev_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email": "test@gmail.com"}'

# Bulk verification
curl -X POST https://verifycatch.com/api/v1/verify/bulk \
  -H "Authorization: Bearer ev_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"emails": ["a@gmail.com", "b@yahoo.com"]}'

# Check engagement breakdown
curl https://verifycatch.com/api/v1/engagement/breakdown \
  -H "Authorization: Bearer ev_live_YOUR_KEY"

Python

# pip install requests
import requests

API_KEY = "ev_live_YOUR_KEY"
BASE    = "https://verifycatch.com/api/v1"
headers = {"Authorization": f"Bearer {API_KEY}"}

# Single verify
r = requests.post(f"{BASE}/verify",
    json={"email": "test@gmail.com"},
    headers=headers)

data = r.json()
print(f"{data['email']} -> {data['tier']} (score: {data['score']})")

# Check engagement
breakdown = requests.get(f"{BASE}/engagement/breakdown", headers=headers).json()
print(breakdown)

JavaScript / Node.js

const API_KEY = "ev_live_YOUR_KEY";
const BASE    = "https://verifycatch.com/api/v1";
const headers = {
  "Authorization": `Bearer ${API_KEY}`,
  "Content-Type": "application/json"
};

// Single verify
const res = await fetch(`${BASE}/verify`, {
  method: "POST",
  headers,
  body: JSON.stringify({ email: "test@gmail.com" })
});
const data = await res.json();
console.log(`${data.email} -> ${data.tier} (score: ${data.score})`);

// Check engagement
const eng = await fetch(`${BASE}/engagement/breakdown`, { headers });
console.log(await eng.json());

PHP

$apiKey = "ev_live_YOUR_KEY";
$base   = "https://verifycatch.com/api/v1";

$ch = curl_init("$base/verify");
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => [
        "Authorization: Bearer $apiKey",
        "Content-Type: application/json"
    ],
    CURLOPT_POSTFIELDS => json_encode(["email" => "test@gmail.com"])
]);

$response = json_decode(curl_exec($ch), true);
echo "{$response['email']} -> {$response['tier']} (score: {$response['score']})\n";

Need help? Check the Webhook Guide or head to the Dashboard