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
Base URL
https://verifycatch.com/api/v1
All endpoints below are relative to this base URL.
Rate Limits
| Plan | Requests/min | Monthly Included | Overage |
|---|---|---|---|
| Free | 10 | 100 | Hard cap |
| Starter ($29/mo) | 60 | 5,000 | $0.008/each |
| Pro ($99/mo) | 300 | 25,000 | $0.005/each |
| Enterprise ($299/mo) | 1,000 | 100,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
}
| Status | Meaning |
|---|---|
400 | Bad request (missing/invalid params) |
401 | Invalid or missing API key |
402 | Monthly usage limit reached |
403 | Forbidden (email not verified, account suspended) |
429 | Rate limit exceeded |
500 | Server 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"}'
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Email address to verify (max 320 chars) |
refresh | boolean | No | Set 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
}
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"}'
| Field | Type | Required | Description |
|---|---|---|---|
emails | array | Yes | Array of email strings (max 1,000,000) |
webhook_url | string | No | URL to receive a POST when the job completes |
file_name | string | No | Original 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 Param | Default | Description |
|---|---|---|
page | 1 | Page number |
page_size | 100 | Results per page (max 1000) |
tier | all | Filter 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:
| Verdict | Points | Condition | Effect 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:
- Active engagement on a catch-all or risky email promotes it to OK (Engagement Verified) — real opens prove the mailbox is alive
- Dead engagement (hard bounces, no opens, or spam complaints) demotes any email to Invalid (Bounced) — even if SMTP accepted it
Getting Started
To build your engagement data:
- Configure your ESP’s webhook to send events to VerifyCatch (SendGrid, Mailgun, Postmark, SES, Smartlead, or Instantly)
- Embed our tracking pixel in your emails for open tracking
- Wrap links with our click tracker for click tracking
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 Param | Default | Description |
|---|---|---|
status | all | Filter: active, at_risk, inactive, dead |
sortBy | engagement_score | Sort field |
order | DESC | ASC or DESC |
limit | 50 | Results per page (max 200) |
offset | 0 | Pagination 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 Param | Description |
|---|---|
email | Filter by email address |
eventType | Filter: open, click, bounce, delivery, complaint |
campaignId | Filter by campaign |
limit | Results per page (default 50, max 200) |
offset | Pagination 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..."
}
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:
| Platform | Webhook URL |
|---|---|
| SendGrid | https://verifycatch.com/api/v1/webhooks/sendgrid?key=YOUR_API_KEY |
| Mailgun | https://verifycatch.com/api/v1/webhooks/mailgun?key=YOUR_API_KEY |
| Postmark | https://verifycatch.com/api/v1/webhooks/postmark?key=YOUR_API_KEY |
| Amazon SES | https://verifycatch.com/api/v1/webhooks/ses?key=YOUR_API_KEY |
| Smartlead | https://verifycatch.com/api/v1/webhooks/smartlead?key=YOUR_API_KEY |
| Instantly | https://verifycatch.com/api/v1/webhooks/instantly?key=YOUR_API_KEY |
For detailed setup instructions, see the Webhook Setup Guide.
?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 Param | Default | Description |
|---|---|---|
page | 1 | Page number |
limit | 20 | Jobs 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 Param | Required | Description |
|---|---|---|
action | Yes | pause 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):
| Tier | Result | Score | Meaning | Action |
|---|---|---|---|---|
| deliverable | ok | 80-100 | Mailbox exists, SMTP confirmed | Safe to send |
| risky | risky / catch_all | 40-79 | Catch-all domain, role address, or inconclusive | Send with caution |
| undeliverable | invalid | 0-39 | Mailbox doesn't exist, domain dead, or hard bounce | Do not send |
| unknown | unknown | varies | Server unreachable, timeout, or blocked | Retry 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:
| # | Signal | Max Pts | What It Measures |
|---|---|---|---|
| 1 | MX Records | 15 | Domain has mail exchange records |
| 2 | Mail Provider | 20 | Provider reputation (Google, M365, etc.) |
| 3 | Mailbox (SMTP Probe) | 30 | RCPT TO probe confirms mailbox existence |
| 4 | Catch-All Detection | 15 | Domain rejects fake addresses (not catch-all) |
| 5 | SPF Record | 8 | Sender authentication policy exists |
| 6 | DMARC Policy | 7 | Domain-level email authentication policy |
| 7 | Not Role Address | 5 | Personal address, not a shared role account |
| 8 | Pattern Quality | 0 (-5) | Penalizes suspicious local-part patterns |
| 9 | M365 Identity Oracle | 25 | Cross-layer account check for Microsoft 365 domains |
| 10 | Google Workspace Oracle | 25 | Cross-layer account check for Google domains |
| 11 | Engagement Feedback | 15 | Ground-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.
catch_all to ok.
Response Fields
| Field | Type | Description |
|---|---|---|
email | string | Normalized email address |
domain | string | Domain part of the email |
score | number | Confidence score 0-100 |
tier | string | deliverable | risky | undeliverable | unknown |
result | string | ok | catch_all | risky | invalid | unknown |
provider | string | Email provider (Google, Microsoft 365, etc.) |
isRole | boolean | True if role-based (info@, admin@, etc.) |
isDisposable | boolean | True if disposable/temporary email |
mx | array | MX records for the domain |
smtp.connected | boolean | Whether SMTP connection was established |
smtp.mailboxExists | boolean | Whether RCPT TO was accepted |
smtp.responseCode | number | SMTP response code (250, 550, etc.) |
smtp.latencyMs | number | SMTP probe round-trip time in ms |
catchAll.isCatchAll | boolean | True if domain accepts all addresses |
spf | boolean | Whether domain has SPF record |
dmarc | boolean | Whether domain has DMARC record |
engagement | object|null | Engagement feedback data, or null if no prior send history exists |
engagement.verdict | string | active | engaged | silent | at_risk | dead |
engagement.bestScore | number | Highest engagement score observed (0-100) |
engagement.totalSends | number | Total emails delivered to this address |
engagement.totalOpens | number | Total tracked opens |
engagement.totalBounces | number | Total hard/soft bounces |
engagement.totalClicks | number | Total tracked link clicks |
engagement.complaints | number | Spam complaint count |
engagement.lastOpenAt | string|null | Most recent open timestamp |
engagement.userCount | number | Number of API users who have sent to this address |
verifiedAt | string | ISO 8601 timestamp of verification |
cached | boolean | True 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