First verify in under 5 minutes.
curl -s https://api.skink.dev/v1/verify \
-H "Authorization: Bearer $SKINK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"jane@acme.com"}'
{ "status": "deliverable", "confidence": 0.91, "safe_to_send": true, "disposable": false, "recommendation": "send" }Get a key.
- Sign up at app.skink.dev.
- 100 free credits. No card. They never expire.
- Create an API key in the dashboard. It is shown once.
Server-side only
A leaked key spends your credits. If exposed, revoke it from the dashboard.
Verify.
One POST, two headers, a JSON body. Pick your language.
curl -s https://api.skink.dev/v1/verify \
-H "Authorization: Bearer $SKINK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"jane.doe@acme.com"}'Read the result.
{
"email": "jane.doe@acme.com",
"status": "deliverable",
"sub_status": "catchall_resolved",
"confidence": 0.91,
"did_you_mean": null,
"free": false,
"role": false,
"disposable": false,
"safe_to_send": true,
"accept_all": true,
"risk_score": 0.04,
"mx_provider": "microsoft365",
"signals": {
"syntax_valid": true,
"mx_found": true,
"is_catchall": true,
"catchall_resolution": "verified",
"reasons": [
"SMTP accepted (250)",
"Catch-all domain — SMTP alone cannot determine individual mailbox existence",
"Additional verification checks resolved mailbox existence"
]
},
"recommendation": "send",
"model_version": "v0-stage1-4",
"processed_at": "2026-09-25T14:23:01Z",
"duration_ms": 412
}Which field do I read?
- safe_to_send
- One boolean, one decision. Branch on this to send or not.
- recommendation
- send, risky_send or suppress. Use it to route risky_send to a slower lane.
- status
- What we found. Use it for list cleaning and reporting.
- disposable
- Temp-mail or burner address. Independent of the rest. Gate signups on it.
safe_to_send: start here
trueforsend, and forrisky_sendat confidence 0.5 or higher.falseforsuppress, disposable and deferred results.- Not a delivery guarantee. A full mailbox still bounces. For list cleaning, filter on
status.
Verdict
- status
- deliverable, undeliverable, risky or unknown.
- sub_status
- Stable reason key for the status. Treat it as an open set.
- confidence
- 0 to 1. How certain we are the mailbox exists.
- recommendation
- send, risky_send or suppress.
- safe_to_send
- Boolean you can branch on directly.
Address flags
- disposable
- Known temp-mail or burner domain.
- role
- Role address such as info@ or sales@.
- free
- Free-mail provider such as gmail.com.
- accept_all
- The domain accepts mail for any local part.
- did_you_mean
- Suggested correction for a likely typo, else null.
disposable covers a 74k-domain list refreshed continuously, plus signals that catch a new burner domain before it is listed.
Risk and detail
- risk_score
- 0 to 1 fraud and abuse risk. Separate from confidence.
- risks
- Risk flags. Absent when none apply.
- mx_provider
- Detected mailbox provider or gateway, else generic or none.
- signals.reasons
- Plain-English explanation of the verdict.
- job_id
- Only with allow_async_followup on a deferred result. Poll GET /v1/verify/{job_id}.
Status reference.
status
- deliverable
- Safe to send.
- undeliverable
- Hard bounce. Do not send.
- risky
- Role address, full mailbox, or an unresolved catch-all.
- unknown
- No verdict: timeout, greylist or DNS failure. Free.
sub_status values are stable keys for parsing. Treat the set as open and ignore values you do not recognize.
Catch-all
- catchall_resolved
- Domain accepts all mail. Verdict reached from additional checks.
- catchall_uncertain
- Domain accepts all mail. Could not be confirmed.
Mailbox
- invalid_mailbox
- Mailbox does not exist.
- mailbox_full
- Mailbox exists but cannot receive.
- disposable
- Temp-mail or burner domain.
- risk_signal_downgrade
- Downgraded from deliverable for a risk reason.
Domain
- syntax_error
- Malformed address.
- no_mx
- Domain has no mail server.
- dns_failure
- DNS lookup failed.
No verdict
- probe_inconclusive
- No clear answer from the receiving server.
- greylist_timeout
- Server deferred. Retry later.
- smtp_throttled
- Receiving server rate-limited the check. Retry later.
- smtp_skipped
- Live check skipped by fast_fail.
- timeout
- No verdict within timeout_ms.
Request options.
All optional, in the JSON body of POST /v1/verify. fast_fail and skip_catchall_probes also work on /v1/bulk.
- timeout_msinteger, 100 to 30000
- Maximum time spent on one address. Lower is faster but can return a coarser verdict.default 15000
- fast_failboolean
- Skips the live mailbox check. Well under 100 ms. Anything not caught by syntax, DNS or disposable checks returns unknown with sub_status smtp_skipped.default false
- skip_catchall_probesboolean
- Returns a catch-all domain as risky right away instead of resolving it.default false
- effortstandard or thorough
- thorough spends longer on hard cases. More latency, same credit cost. Not allowed with fast_fail. Raise timeout_ms to match.default standard
- min_confidencenumber, 0.35 to 0.9
- Sets the safe_to_send bar for risky_send results. Never overrides suppress, disposable or deferred results.default 0.5
- allow_async_followupboolean
- On a deferred result, returns a job_id. Poll GET /v1/verify/{job_id} for the upgraded verdict.default false
- retainboolean
- false stores nothing about this verification.default true
Only need to know if it is disposable?
Send fast_fail and read disposable
- Answers in milliseconds from the disposable-domain list. The mailbox is never checked.
- Unlisted addresses return
unknown, which is free. - A disposable hit is billed like any known result.
- Without
fast_fail, a full verification also catches burner domains not yet listed. - Works the same on
/v1/bulk. Every result row carriesdisposable.
curl -s https://api.skink.dev/v1/verify \
-H "Authorization: Bearer $SKINK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"user@mailinator.com","fast_fail":true}'
{ "status": "risky", "sub_status": "disposable", "disposable": true,
"recommendation": "suppress", "safe_to_send": false }Bulk & feedback.
- 1
Submit
POST/v1/bulkUp to 50,000 emails in
source.emails, or an uploadedsource.file_id. Returns202with a job. - 2
Track
GET/v1/bulk/{jobId}Poll for status, or pass a
callback_urlwhen you submit. - 3
Fetch
GET/v1/bulk/{jobId}/resultsSame fields as a single verify, without
signals. Available for 24 hours after completion.
Bounce feedback
Report what happened to a send. Body: email, outcome, and optional verified_at in ISO 8601.
- hard_bounce
- Permanent failure.
- soft_bounce
- Temporary failure.
- complaint
- Recipient marked the message as spam.
- delivered
- Accepted for delivery.
curl -s https://api.skink.dev/v1/bulk \
-H "Authorization: Bearer $SKINK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"source":{"emails":["a@acme.com","b@acme.com"]},"callback_url":"https://example.com/hook"}'Billing, limits, errors.
Errors
Errors are JSON with error and message.
- 400 bad_request
- Invalid body or conflicting options.
- 401 unauthorized
- Missing or invalid API key.
- 402 payment_required
- Out of credits.
- 403 scope_insufficient
- Key is not allowed to use this endpoint.
- 404 not_found
- Unknown job or file.
- 429 rate_limited
- Per-key request limit reached.
SDKs.
Plain HTTPS and JSON works everywhere and needs nothing installed. Official SDKs for Node.js, Python and Ruby wrap the same API.
npm install @skink/sdk
Have a question this page didn’t answer?
See the FAQ