✓ API Live — Self-serve keys available
Generate your API key in Dashboard → Settings and start verifying in seconds. Free tier: 500 verifications/month.
Get your API keyMachine-readable OpenAPI 3.0 spec — import into Postman or Insomnia, or generate a client SDK in your language.
Quick Start
Verify a single email with the authenticated POST /api/v1/verify-single endpoint. Pass your key as a Bearer token (you can also use an X-API-Key header or an ?api_key= query param — see Authentication):
cURL
curl -X POST https://kaijuverifier.com/api/v1/verify-single \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{"email":"user@example.com","use_smtp":true}'
JavaScript (fetch)
const r = await fetch('https://kaijuverifier.com/api/v1/verify-single', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY'
},
body: JSON.stringify({ email: 'user@example.com', use_smtp: true })
});
const data = await r.json();
console.log(data.status, data.score, data.grade);
Python (requests)
import requests
r = requests.post(
'https://kaijuverifier.com/api/v1/verify-single',
headers={'Authorization': 'Bearer YOUR_API_KEY'},
json={'email': 'user@example.com', 'use_smtp': True}
)
print(r.json())
PHP (cURL)
$ch = curl_init('https://kaijuverifier.com/api/v1/verify-single');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer YOUR_API_KEY'
],
CURLOPT_POSTFIELDS => json_encode(['email' => 'user@example.com', 'use_smtp' => true])
]);
$data = json_decode(curl_exec($ch), true);
No key yet? The POST /api/validate-email endpoint works without authentication for low-volume testing (IP rate-limited). For production, use the authenticated /api/v1/* endpoints below.
Client libraries
Generate a typed client in 50+ languages directly from our OpenAPI spec:
npx @openapitools/openapi-generator-cli generate \
-i https://kaijuverifier.com/openapi.json -g python -o ./kaiju-client
Or drop in one of these minimal single-file clients:
Python
import requests
class KaijuVerifier:
def __init__(self, api_key, base="https://kaijuverifier.com"):
self.s = requests.Session()
self.s.headers["Authorization"] = f"Bearer {api_key}"
self.base = base
def verify(self, email, use_smtp=True):
return self.s.post(f"{self.base}/api/v1/verify-single",
json={"email": email, "use_smtp": use_smtp}).json()
def verify_batch(self, emails, use_smtp=True):
return self.s.post(f"{self.base}/api/v1/verify-batch",
json={"emails": emails, "use_smtp": use_smtp}).json()
def submit_job(self, emails, use_smtp=True, dedupe=True):
return self.s.post(f"{self.base}/api/v1/jobs",
json={"emails": emails, "use_smtp": use_smtp, "dedupe": dedupe}).json()
def job_status(self, job_id):
return self.s.get(f"{self.base}/api/v1/jobs/{job_id}").json()
def job_results(self, job_id):
return self.s.get(f"{self.base}/api/v1/jobs/{job_id}/results").json()
def account(self):
return self.s.get(f"{self.base}/api/v1/account").json()
kaiju = KaijuVerifier("YOUR_API_KEY")
print(kaiju.verify("user@example.com"))
Node.js
class KaijuVerifier {
constructor(apiKey, base = "https://kaijuverifier.com") {
this.h = { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json" };
this.base = base;
}
_post(p, body) { return fetch(this.base + p, { method: "POST", headers: this.h, body: JSON.stringify(body) }).then(r => r.json()); }
_get(p) { return fetch(this.base + p, { headers: this.h }).then(r => r.json()); }
verify(email, useSmtp = true) { return this._post("/api/v1/verify-single", { email, use_smtp: useSmtp }); }
verifyBatch(emails, useSmtp = true) { return this._post("/api/v1/verify-batch", { emails, use_smtp: useSmtp }); }
submitJob(emails, useSmtp = true, dedupe = true) { return this._post("/api/v1/jobs", { emails, use_smtp: useSmtp, dedupe }); }
jobStatus(id) { return this._get(`/api/v1/jobs/${id}`); }
jobResults(id) { return this._get(`/api/v1/jobs/${id}/results`); }
account() { return this._get("/api/v1/account"); }
}
const kaiju = new KaijuVerifier("YOUR_API_KEY");
kaiju.verify("user@example.com").then(console.log);
PHP
class KaijuVerifier {
public function __construct(private string $apiKey, private string $base = "https://kaijuverifier.com") {}
private function req(string $method, string $path, ?array $body = null): array {
$ch = curl_init($this->base . $path);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer {$this->apiKey}", "Content-Type: application/json"],
]);
if ($body !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
return json_decode(curl_exec($ch), true) ?? [];
}
public function verify($e, $smtp = true) { return $this->req("POST", "/api/v1/verify-single", ["email" => $e, "use_smtp" => $smtp]); }
public function verifyBatch($emails, $smtp = true){ return $this->req("POST", "/api/v1/verify-batch", ["emails" => $emails, "use_smtp" => $smtp]); }
public function submitJob($emails, $smtp = true, $dedupe = true){ return $this->req("POST", "/api/v1/jobs", ["emails" => $emails, "use_smtp" => $smtp, "dedupe" => $dedupe]); }
public function jobStatus($id) { return $this->req("GET", "/api/v1/jobs/$id"); }
public function jobResults($id) { return $this->req("GET", "/api/v1/jobs/$id/results"); }
public function account() { return $this->req("GET", "/api/v1/account"); }
}
$kaiju = new KaijuVerifier("YOUR_API_KEY");
print_r($kaiju->verify("user@example.com"));
Response fields
| Field | Type | Description |
|---|---|---|
email | string | Original email input. |
normalized | string | Lowercased + plus-addressing stripped for Gmail. |
is_valid | bool | Safe-to-send decision. |
status | string | One of: valid, invalid, risky, unknown. |
reason | string | Machine-readable detail (e.g. user_unknown, catch_all, domain_no_mx, disposable). |
confidence | int | 0–100 confidence in the decision. |
score | int | Deliverability score 0–100. |
grade | string | Letter grade A–F based on score. |
suggestion | string|null | Typo correction if detected (e.g. user@gmail.com for user@gmial.com). |
checks | object | Sub-checks: syntax, dns, smtp, catch_all, is_disposable, role_based, free_provider, mx{count,providers,grade}. |
quota | object | Returned by authenticated /api/v1/* endpoints: limit, used_this_month, remaining. |
Status codes & rate limits
200— Verification completed.400— Missing or malformedemailfield.401— Invalid or missing API key.429— Rate limit exceeded.
Rate limits: 20 req/min for anonymous IPs (50 SMTP probes/day). Authenticated keys inherit their plan quota (Free 500/mo, Starter 10k/mo, Growth 50k/mo, Scale 250k/mo).
API Overview
The Kaiju Email Verification API allows developers to integrate our industry-leading email cleaning capabilities directly into their applications, CRMs, or registration forms. Verify emails in real-time to prevent fake signups or process bulk lists programmatically.
Authentication
All /api/v1/* endpoints require authentication. You can generate and manage your
API keys in Dashboard → Settings.
We accept three equivalent methods — pick whichever fits your client:
| Method | Where | Example |
|---|---|---|
| Bearer token | HTTP header (recommended) | Authorization: Bearer YOUR_API_KEY |
| API key header | HTTP header | X-API-Key: YOUR_API_KEY |
| Query parameter | URL (legacy) | ?api_key=YOUR_API_KEY |
?api_key= query param, which can leak into server/proxy logs.
Authorization Header (recommended)
Authorization: Bearer YOUR_API_KEY_HERE
Base URL
All API endpoints are served from the main domain. Use the full paths shown below:
https://kaijuverifier.com
Example: https://kaijuverifier.com/api/v1/verify-single
API Endpoints
POST /api/v1/verify-single
Verify a single email address in real-time. Ideal for registration forms and lead capture. Requires authentication; counts as 1 verification against your quota.
Parameters (JSON Body)
| Parameter | Type | Required | Description |
|---|---|---|---|
email |
string | Yes | The email address to verify. |
use_smtp |
boolean | No | Enable the live SMTP mailbox probe (default: false). Requires a plan with SMTP enabled. |
Example Request
curl -X POST "https://kaijuverifier.com/api/v1/verify-single" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"use_smtp": true
}'
Example Response
{
"success": true,
"email": "user@example.com",
"is_valid": true,
"status": "valid",
"score": 92,
"grade": "A",
"reason": null,
"suggestion": null,
"checks": {
"syntax": true,
"dns": true,
"smtp": true,
"catch_all": false,
"is_disposable": false,
"role_based": false,
"free_provider": false,
"mx": { "count": 5, "providers": ["google"], "grade": "A" }
},
"quota": {
"limit": 10000,
"used_this_month": 12,
"remaining": 9988
}
}
POST /api/v1/verify-batch
Verify a batch of email addresses in a single request. Each email counts as one verification against your quota. Maximum 10,000 emails per request (your plan may set a lower per-batch cap).
Request Body
{
"emails": [
"user1@example.com",
"user2@example.com",
"invalid@domain.com"
],
"use_smtp": true
}
Code Examples
curl -X POST "https://kaijuverifier.com/api/v1/verify-batch" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"emails": ["user1@example.com", "user2@example.com"],
"use_smtp": true
}'
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://kaijuverifier.com/api/v1/verify-batch");
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
"emails" => ["test@example.com"],
"use_smtp" => true
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer YOUR_KEY",
"Content-Type: application/json"
]);
$response = curl_exec($ch);
curl_close($ch);
?>
import requests
response = requests.post(
"https://kaijuverifier.com/api/v1/verify-batch",
headers={"Authorization": "Bearer YOUR_KEY"},
json={
"emails": ["test@example.com"],
"use_smtp": True
}
)
print(response.json())
Example Response
{
"success": true,
"batch_id": "batch_xxxxxxxxxxxx",
"status": "completed",
"summary": {
"total": 2,
"valid": 1,
"invalid": 1,
"accuracy": 50
},
"results": [
{
"email": "user1@example.com",
"is_valid": true,
"status": "valid",
"reason": null,
"score": 92,
"suggestion": null,
"checks": { "syntax": true, "dns": true, "smtp": true, "mx": { "count": 5, "providers": ["google"], "grade": "A" } }
}
],
"quota": {
"limit": 10000,
"used_this_month": 14,
"remaining": 9986
}
}
POST /api/v1/jobs
Submit a large list for asynchronous processing. Unlike /api/v1/verify-batch
(which verifies inline and returns when finished), the jobs API returns immediately with a
job_id. A background worker processes the list, and you either poll the
status endpoint or receive a job.completed webhook.
Ideal for lists too large for a single synchronous request (up to 1,000,000 emails per job).
Quota is reserved when the job is accepted, so a job that would exceed your monthly
limit is rejected up front with 429.
Request Body
{
"emails": ["user1@example.com", "user2@example.com", "..."],
"use_smtp": true,
"dedupe": false
}
Set "dedupe": true to trim whitespace, drop empty
entries and remove case-insensitive duplicates before quota is reserved — so
you're never charged to re-verify the same address. The response reports
submitted, duplicates_removed and the billed total.
Submit a job
curl -X POST "https://kaijuverifier.com/api/v1/jobs" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"emails": ["a@example.com", "b@example.com"], "use_smtp": true}'
Returns 202 Accepted:
{
"success": true,
"job_id": "job_ef2eb1f225da9e1c926c607a",
"status": "queued",
"submitted": 2,
"duplicates_removed": 0,
"total": 2,
"status_url": "https://kaijuverifier.com/api/v1/jobs/job_ef2eb1f225da9e1c926c607a"
}
GET /api/v1/jobs/{job_id} — poll status
Returns live progress. status moves queued →
processing → completed (or failed). A job you don't own
returns 404.
{
"success": true,
"job_id": "job_ef2eb1f225da9e1c926c607a",
"status": "processing",
"total": 2,
"processed": 1,
"valid_count": 1,
"invalid_count": 0,
"risky_count": 0,
"progress_pct": 50
}
DELETE /api/v1/jobs/{job_id} — cancel a job
Cancel a queued or processing job and refund the unprocessed
remainder of the quota reserved at submit. Completed, failed or already-cancelled jobs
return 409; the worker never resurrects a cancelled job.
curl -X DELETE "https://kaijuverifier.com/api/v1/jobs/JOB_ID" \
-H "Authorization: Bearer YOUR_API_KEY"
{
"success": true,
"job_id": "job_ef2eb1f225da9e1c926c607a",
"status": "cancelled",
"processed": 120,
"refunded": 880
}
GET /api/v1/jobs/{job_id}/results — fetch results
Returns the per-email verdicts once the job is completed. Polling results before
completion returns 409 Conflict. Results are paginated
(?page=1&per_page=1000). Add ?format=csv to download the
entire job as a CSV file (email,is_valid,status,reason,score,suggestion)
— handy for spreadsheets and one-shot list cleaning.
curl "https://kaijuverifier.com/api/v1/jobs/JOB_ID/results?format=csv" \
-H "Authorization: Bearer YOUR_API_KEY" -o cleaned_list.csv
{
"success": true,
"job_id": "job_ef2eb1f225da9e1c926c607a",
"status": "completed",
"total": 2,
"page": 1,
"per_page": 1000,
"results": [
{ "email": "a@example.com", "is_valid": true, "status": "valid", "reason": null, "score": 92, "suggestion": null },
{ "email": "b@example.com", "is_valid": false, "status": "invalid", "reason": "user_unknown", "score": 8, "suggestion": null }
]
}
GET /api/v1/jobs — list your jobs
Lists your recent jobs with their status and progress — useful for a dashboard view.
GET /api/v1/account
Check your plan, remaining quota and enabled features without spending a verification. Handy for dashboards, pre-flight checks and low-balance alerts. Takes no parameters.
Example Request
curl "https://kaijuverifier.com/api/v1/account" \
-H "Authorization: Bearer YOUR_API_KEY"
Example Response
{
"success": true,
"account": {
"email": "you@company.com",
"plan": "starter",
"plan_name": "Starter"
},
"quota": {
"limit": 10000,
"used": 12,
"remaining": 9988,
"reset_date": "2026-07-01"
},
"rate_limit_rpm": 60,
"features": {
"smtp": true,
"webhooks": false,
"catch_all": true
}
}
POST /api/validate-email
Public, no-auth single-email check for quick testing and client-side
widgets. Rate-limited per IP (no API key required, no plan quota consumed). For production
volume, use the authenticated /api/v1/verify-single endpoint above.
Example Request
curl -X POST "https://kaijuverifier.com/api/validate-email" \
-H "Content-Type: application/json" \
-d '{ "email": "user@example.com" }'
Response Object
The verification endpoints return a JSON object with the per-email verdict plus your live quota. See the full field reference in Response fields above and the worked examples under each endpoint. The key fields:
{
"email": "user@example.com",
"is_valid": true,
"status": "valid",
"score": 92,
"grade": "A",
"reason": null,
"suggestion": null,
"checks": {
"syntax": true,
"dns": true,
"smtp": true,
"catch_all": false,
"is_disposable": false,
"role_based": false,
"free_provider": false,
"mx": { "count": 5, "providers": ["google"], "grade": "A" }
},
"quota": { "limit": 10000, "used_this_month": 12, "remaining": 9988 }
}
| Field | Type | Description |
|---|---|---|
is_valid |
boolean | Safe-to-send decision. |
status |
string | valid, invalid, risky, or unknown. |
score / grade |
int / string | Deliverability score 0–100 and its letter grade (A–F). |
suggestion |
string|null | Did-you-mean typo correction, when detected. |
checks.mx |
object | MX records found: count, providers, grade. |
checks.is_disposable |
boolean | True if the domain is a known temporary email provider. |
quota |
object | Your remaining allowance: limit, used_this_month, remaining. |
Error Codes
Standard HTTP status codes are used to indicate success or failure.
| Code | Description |
|---|---|
200 |
OK - Request processed successfully. |
400 |
Bad Request - Invalid parameters or JSON format. |
401 |
Unauthorized - Invalid or missing API key. |
405 |
Method Not Allowed - Wrong HTTP method (e.g. GET on a POST endpoint). |
429 |
Too Many Requests - Rate limit or monthly quota exceeded. |
500 |
Internal Server Error - Something went wrong on our end. |
Webhooks
Instead of polling, register a webhook and KaijuVerifier will POST a signed JSON
payload to your endpoint when an event happens. Webhooks are managed in
Dashboard → Settings (available on paid
plans — the features.webhooks flag in GET /api/v1/account
tells you whether your plan has them enabled). You can register multiple endpoints; each is issued
its own signing secret, shown once at creation.
Events
| Event | Fires when |
|---|---|
batch.completed | A synchronous /api/v1/verify-batch request finishes. Delivered after the API response, so it never delays your call. |
job.completed | An asynchronous bulk job finishes processing. The recommended way to consume large jobs without polling. |
job.failed | An async bulk job errored out and was marked failed. Payload carries job_id and a short error string. |
Payload
The request body is JSON. The summary is always included; job.completed carries the
job_id so you can fetch results.
{
"event": "job.completed",
"job_id": "job_ef2eb1f225da9e1c926c607a",
"summary": { "total": 2, "valid": 1, "invalid": 1, "risky": 0 },
"timestamp": "2026-06-15T01:14:13+00:00"
}
Verifying the signature
Every delivery includes an X-Kaiju-Signature header of the form
sha256=<hex>, where <hex> is the HMAC-SHA256 of the
raw request body keyed with your endpoint's signing secret. Recompute it and
compare with a constant-time function before trusting the payload — reject anything that doesn't
match.
<?php
$secret = getenv('KAIJU_WEBHOOK_SECRET'); // shown once when you create the endpoint
$payload = file_get_contents('php://input'); // the RAW body, do not json_decode first
$sent = $_SERVER['HTTP_X_KAIJU_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $payload, $secret);
if (!hash_equals($expected, $sent)) {
http_response_code(401);
exit('invalid signature');
}
$event = json_decode($payload, true);
// ... handle $event, then respond 2xx quickly ...
http_response_code(200);
?>
import hmac, hashlib
def verify(raw_body: bytes, header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header or "")
Delivery, retries & idempotency
- Acknowledge fast: respond with any
2xxstatus as soon as you've stored the event. Do the heavy work afterwards — slow handlers count as failures. - Retries: the first attempt is best-effort at event time. Failed deliveries are queued and retried with exponential backoff; after repeated failures the event is moved to a dead-letter queue and stops retrying.
- Idempotency: a retried event is identical to the original, so deduplicate on
your side (e.g. by
job_id+event) — a handler may legitimately run more than once. - HTTPS only: endpoints must be public HTTPS URLs.
Integration Best Practices
- Timeout Handling: Set a timeout of at least 5 seconds for single verification requests to account for DNS lookups.
- Asynchronous Processing: For bulk requests, consider using a background job queue to avoid blocking your main application thread.
- Caching: Cache verification results for 24-48 hours to save API credits and improve performance.