curl -X POST https://verification.didit.me/v3/email/check/ \
-H 'x-api-key: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"email": "alice@example.com",
"code": "123456",
"disposable_email_action": "DECLINE"
}'import os, requests
resp = requests.post(
"https://verification.didit.me/v3/email/check/",
headers={
"x-api-key": os.environ["DIDIT_API_KEY"],
"Content-Type": "application/json",
},
json={
"email": "alice@example.com", # same address as the send call
"code": "123456",
"disposable_email_action": "DECLINE",
},
timeout=15,
)
resp.raise_for_status()
result = resp.json()
print(result["status"]) # Approved / Declined / Failed / Expired or Not Found
if result["status"] in ("Approved", "Declined"):
print(result["email"]["is_breached"], result["email"]["is_disposable"])const res = await fetch('https://verification.didit.me/v3/email/check/', {
method: 'POST',
headers: {
'x-api-key': 'YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'alice@example.com', // same address as the send call
code: '123456',
disposable_email_action: 'DECLINE',
}),
});
const data = await res.json();
if (data.status === 'Approved') {
// full email report is in data.email (breaches, is_disposable, matches, ...)
}<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://verification.didit.me/v3/email/check/",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'email' => 'alice@example.com',
'code' => '123456'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://verification.didit.me/v3/email/check/"
payload := strings.NewReader("{\n \"email\": \"alice@example.com\",\n \"code\": \"123456\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://verification.didit.me/v3/email/check/")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"email\": \"alice@example.com\",\n \"code\": \"123456\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://verification.didit.me/v3/email/check/")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"email\": \"alice@example.com\",\n \"code\": \"123456\"\n}"
response = http.request(request)
puts response.read_body{
"request_id": "e39cb057-92fc-4b59-b84e-02fec29a0f24",
"status": "Approved",
"message": "The verification code is correct.",
"email": {
"status": "Approved",
"email": "alice@example.com",
"is_breached": false,
"breaches": [],
"is_disposable": false,
"is_undeliverable": false,
"verification_attempts": 1,
"verified_at": "2026-06-12T01:24:47.311323Z",
"warnings": [],
"lifecycle": [
{
"type": "EMAIL_VERIFICATION_MESSAGE_SENT",
"timestamp": "2026-06-12T01:23:39.580554+00:00",
"details": {
"status": "Success",
"reason": null
},
"fee": 0.03
},
{
"type": "VALID_CODE_ENTERED",
"timestamp": "2026-06-12T01:24:47.311201+00:00",
"details": {
"code_tried": "123456",
"status": "Approved"
},
"fee": 0
},
{
"type": "EMAIL_VERIFICATION_APPROVED",
"timestamp": "2026-06-12T01:24:47.384292+00:00",
"details": null,
"fee": 0
}
],
"matches": []
},
"vendor_data": "user-1234",
"metadata": null,
"created_at": "2026-06-12T01:24:47.401719+00:00"
}Check Email Code
Verify the OTP delivered by POST /v3/email/send/ and get the final verification result plus email intelligence: deliverability, breach exposure (is_breached and the breaches list from a breach-intelligence database), disposable-provider detection, and duplicate usage of the address across your sessions.
How the check finds the verification. Matching is by your application plus the email address — request_id is not an input. The most recent pending verification created within the last 5 minutes is checked. If there is none (never sent, already finalized, undeliverable at send time, or older than 5 minutes) the endpoint returns 200 with status: "Expired or Not Found".
Attempt budget. Each verification allows 3 code attempts. The first two wrong codes return status: "Failed" with the attempts remaining and email: null; the third wrong code finalizes the verification as Declined with an EMAIL_CODE_ATTEMPTS_EXCEEDED warning and returns the full email report. Codes are compared case-insensitively (relevant for alphanumeric_code sends); only the most recently sent code is valid.
Outcomes. Approved — correct code and no declining risk. Declined — terminal: the code was correct but a declining risk matched (a DECLINE action below, a blocklisted address, or the address found undeliverable at finalization), or the attempt budget was exhausted. Failed — wrong code, attempts remaining. Expired or Not Found — nothing to check. On Approved/Declined the request_id equals the send’s request_id (the session id) and email carries the full report (is_breached, breaches, is_disposable, is_undeliverable, warnings, lifecycle, matches); on Failed and Expired or Not Found the request_id is a one-off random UUID.
Risk actions. duplicated_email_action, breached_email_action, and disposable_email_action decide what happens when the corresponding risk is detected on a correct code: DECLINE flips the final status to Declined; NO_ACTION (default) records the risk in email.warnings without affecting the status.
Billing. Checks are free — the credit is consumed by the successful send.
Session persistence. A finalized check updates the session created by the send (visible in the Business Console, queryable via GET /v3/session/{sessionId}/decision/) and fires a status.updated webhook.
Sandbox. Sandbox API keys skip all processing: any well-formed code (123456 included) returns a static Approved payload with a simplified email object (status, email, is_breached, is_disposable, is_undeliverable); malformed input still returns 400. Nothing is persisted.
Authentication. Send your application’s API key in the x-api-key header. Missing or invalid credentials return 403 ({"detail": "You do not have permission to perform this action."}) — this API never returns 401.
Rate limit. Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns 429.
Social footprint. Set enable_social to true to add a email_social block: the online platforms this address is registered on, with per-category counts and any profile details available. profiles_registered: 0 means the platforms were checked and the address was found on none of them - a valid, billable answer. The block is absent when the add-on was not requested or the check could not be completed, and it is not billed in that case. Billing adds one email_social unit per request on top of this endpoint’s own price.
curl -X POST https://verification.didit.me/v3/email/check/ \
-H 'x-api-key: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"email": "alice@example.com",
"code": "123456",
"disposable_email_action": "DECLINE"
}'import os, requests
resp = requests.post(
"https://verification.didit.me/v3/email/check/",
headers={
"x-api-key": os.environ["DIDIT_API_KEY"],
"Content-Type": "application/json",
},
json={
"email": "alice@example.com", # same address as the send call
"code": "123456",
"disposable_email_action": "DECLINE",
},
timeout=15,
)
resp.raise_for_status()
result = resp.json()
print(result["status"]) # Approved / Declined / Failed / Expired or Not Found
if result["status"] in ("Approved", "Declined"):
print(result["email"]["is_breached"], result["email"]["is_disposable"])const res = await fetch('https://verification.didit.me/v3/email/check/', {
method: 'POST',
headers: {
'x-api-key': 'YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'alice@example.com', // same address as the send call
code: '123456',
disposable_email_action: 'DECLINE',
}),
});
const data = await res.json();
if (data.status === 'Approved') {
// full email report is in data.email (breaches, is_disposable, matches, ...)
}<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://verification.didit.me/v3/email/check/",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'email' => 'alice@example.com',
'code' => '123456'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://verification.didit.me/v3/email/check/"
payload := strings.NewReader("{\n \"email\": \"alice@example.com\",\n \"code\": \"123456\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://verification.didit.me/v3/email/check/")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"email\": \"alice@example.com\",\n \"code\": \"123456\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://verification.didit.me/v3/email/check/")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"email\": \"alice@example.com\",\n \"code\": \"123456\"\n}"
response = http.request(request)
puts response.read_body{
"request_id": "e39cb057-92fc-4b59-b84e-02fec29a0f24",
"status": "Approved",
"message": "The verification code is correct.",
"email": {
"status": "Approved",
"email": "alice@example.com",
"is_breached": false,
"breaches": [],
"is_disposable": false,
"is_undeliverable": false,
"verification_attempts": 1,
"verified_at": "2026-06-12T01:24:47.311323Z",
"warnings": [],
"lifecycle": [
{
"type": "EMAIL_VERIFICATION_MESSAGE_SENT",
"timestamp": "2026-06-12T01:23:39.580554+00:00",
"details": {
"status": "Success",
"reason": null
},
"fee": 0.03
},
{
"type": "VALID_CODE_ENTERED",
"timestamp": "2026-06-12T01:24:47.311201+00:00",
"details": {
"code_tried": "123456",
"status": "Approved"
},
"fee": 0
},
{
"type": "EMAIL_VERIFICATION_APPROVED",
"timestamp": "2026-06-12T01:24:47.384292+00:00",
"details": null,
"fee": 0
}
],
"matches": []
},
"vendor_data": "user-1234",
"metadata": null,
"created_at": "2026-06-12T01:24:47.401719+00:00"
}Authorizations
Body
The same email address used in the matching POST /v3/email/send/ call. This is what links the check to the send.
"alice@example.com"
The OTP the end user received: 4–8 digits, or 4–8 letters/digits when alphanumeric_code: true was used at send time. Comparison is case-insensitive.
10"123456"
What to do when the same address was already used and approved by a different user (only previously Approved verifications count) (different vendor_data) of your application. DECLINE flips the final status to Declined; NO_ACTION records the risk in email.warnings and fills email.matches.
NO_ACTION, DECLINE What to do when the address appears in known data breaches (email.is_breached). DECLINE flips the final status to Declined; NO_ACTION records the risk in email.warnings.
NO_ACTION, DECLINE What to do when the domain belongs to a disposable/temporary-mail provider (email.is_disposable). DECLINE flips the final status to Declined; NO_ACTION records the risk in email.warnings.
NO_ACTION, DECLINE When true, also look up the social footprint of the address - the email, social, professional, e-commerce and entertainment platforms it is registered on. Billed as one email_social unit per request in addition to this endpoint's own price.
true
Action when the social footprint check finds the address registered on none of the platforms it covers. Only NO_ACTION and DECLINE are accepted on this endpoint; REVIEW is available on the workflow configuration key of the same name.
NO_ACTION, DECLINE Response
Check completed — wrong codes and missing verifications also return 200; inspect status, not the HTTP code. email is populated only on finalized outcomes (Approved/Declined), null on Failed, and absent on Expired or Not Found.
On Approved/Declined: the session id of the matched verification — identical to the request_id returned by POST /v3/email/send/. On Failed and Expired or Not Found: a random one-off UUID that cannot be looked up later.
Approved — correct code, no declining risk. Declined — terminal: a declining risk matched or the attempt budget (3) was exhausted. Failed — wrong code, attempts remaining. Expired or Not Found — no pending verification for this address in the last 5 minutes.
Approved, Declined, Failed, Expired or Not Found Human-readable explanation of the outcome, including the number of attempts remaining after a wrong code.
Full email report. Present (non-null) only on finalized outcomes (Approved/Declined); null on Failed and absent on Expired or Not Found.
Show child attributes
Show child attributes
vendor_data of the matched verification's session. null on Expired or Not Found.
metadata of the matched verification's session. null on Expired or Not Found.
Timestamp of this check response.