curl -X POST https://verification.didit.me/v3/email/send/ \
-H 'x-api-key: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"email": "alice@example.com",
"options": { "code_size": 6, "locale": "en" },
"vendor_data": "user-1234"
}'import os, requests
resp = requests.post(
"https://verification.didit.me/v3/email/send/",
headers={
"x-api-key": os.environ["DIDIT_API_KEY"],
"Content-Type": "application/json",
},
json={
"email": "alice@example.com",
"options": {"code_size": 6, "locale": "en"},
"vendor_data": "user-1234",
},
timeout=15,
)
resp.raise_for_status()
print(resp.json()) # {request_id, status, reason, vendor_data, metadata}
# Then verify with POST /v3/email/check/ using the same emailconst res = await fetch('https://verification.didit.me/v3/email/send/', {
method: 'POST',
headers: {
'x-api-key': 'YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'alice@example.com',
options: { code_size: 6, locale: 'en' },
vendor_data: 'user-1234',
}),
});
if (!res.ok) throw new Error(`Email send failed: ${res.status}`);
const data = await res.json();
console.log(data); // { request_id, status, reason, vendor_data, metadata }
// Then verify with POST /v3/email/check/ using the same email<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://verification.didit.me/v3/email/send/",
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',
'options' => [
'locale' => 'en'
],
'vendor_data' => 'user-1234'
]),
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/send/"
payload := strings.NewReader("{\n \"email\": \"alice@example.com\",\n \"options\": {\n \"locale\": \"en\"\n },\n \"vendor_data\": \"user-1234\"\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/send/")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"email\": \"alice@example.com\",\n \"options\": {\n \"locale\": \"en\"\n },\n \"vendor_data\": \"user-1234\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://verification.didit.me/v3/email/send/")
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 \"options\": {\n \"locale\": \"en\"\n },\n \"vendor_data\": \"user-1234\"\n}"
response = http.request(request)
puts response.read_body{
"request_id": "e39cb057-92fc-4b59-b84e-02fec29a0f24",
"status": "Success",
"reason": null,
"vendor_data": "user-1234",
"metadata": null
}Send Email Code
Send a one-time passcode (OTP) to an email address, then verify it with POST /v3/email/check/.
How send and check pair up. Verification state is keyed by your application plus the email address — the check call does not take request_id. Each new send creates a pending verification that lives for 5 minutes: call the check with the same address and the code the user received before the window closes. Calling send again for the same address while a verification is pending generates a fresh code for that same verification (status: "Retry", same request_id); the previous code stops working. At most one retry is attached this way (two sends total); a further send starts a fresh verification with a new request_id. The 5-minute window is measured from the first send and is not extended by retries.
Deliverability pre-check. Before anything is sent, the address goes through syntax and DNS/MX validation. Addresses that cannot receive mail return 200 with status: "Undeliverable" and reason: "email_can_not_be_delivered" — no email is sent, nothing is billed, and the verification is immediately finalized as Declined (a follow-up check returns Expired or Not Found). A downstream send failure reports the same way.
Code format and branding. Codes are 4–8 characters (options.code_size, default 6), numeric by default; set options.alphanumeric_code: true for uppercase letters and digits (the check comparison is case-insensitive). The email template is localized via options.locale (54 supported languages) and can use your application’s white-label branding via options.use_white_label_customization.
Billing. One Email Verification API credit per successful send (status: "Success"), charged at send time. Retry and Undeliverable sends are free, and checks are free.
Session persistence. Every new verification is persisted as an API-type session: request_id is a real session id you can pass to GET /v3/session/{sessionId}/decision/, the verification appears in the Business Console, and status.updated webhooks fire as it progresses.
Sandbox. Sandbox API keys skip delivery and billing: after request validation (malformed input still returns 400), the endpoint returns a static Success payload with a random request_id; no email is sent and nothing is persisted. Use code 123456 on the sandbox check.
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.
curl -X POST https://verification.didit.me/v3/email/send/ \
-H 'x-api-key: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"email": "alice@example.com",
"options": { "code_size": 6, "locale": "en" },
"vendor_data": "user-1234"
}'import os, requests
resp = requests.post(
"https://verification.didit.me/v3/email/send/",
headers={
"x-api-key": os.environ["DIDIT_API_KEY"],
"Content-Type": "application/json",
},
json={
"email": "alice@example.com",
"options": {"code_size": 6, "locale": "en"},
"vendor_data": "user-1234",
},
timeout=15,
)
resp.raise_for_status()
print(resp.json()) # {request_id, status, reason, vendor_data, metadata}
# Then verify with POST /v3/email/check/ using the same emailconst res = await fetch('https://verification.didit.me/v3/email/send/', {
method: 'POST',
headers: {
'x-api-key': 'YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'alice@example.com',
options: { code_size: 6, locale: 'en' },
vendor_data: 'user-1234',
}),
});
if (!res.ok) throw new Error(`Email send failed: ${res.status}`);
const data = await res.json();
console.log(data); // { request_id, status, reason, vendor_data, metadata }
// Then verify with POST /v3/email/check/ using the same email<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://verification.didit.me/v3/email/send/",
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',
'options' => [
'locale' => 'en'
],
'vendor_data' => 'user-1234'
]),
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/send/"
payload := strings.NewReader("{\n \"email\": \"alice@example.com\",\n \"options\": {\n \"locale\": \"en\"\n },\n \"vendor_data\": \"user-1234\"\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/send/")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"email\": \"alice@example.com\",\n \"options\": {\n \"locale\": \"en\"\n },\n \"vendor_data\": \"user-1234\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://verification.didit.me/v3/email/send/")
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 \"options\": {\n \"locale\": \"en\"\n },\n \"vendor_data\": \"user-1234\"\n}"
response = http.request(request)
puts response.read_body{
"request_id": "e39cb057-92fc-4b59-b84e-02fec29a0f24",
"status": "Success",
"reason": null,
"vendor_data": "user-1234",
"metadata": null
}Authorizations
Body
Recipient email address. Malformed addresses return 400; syntactically valid addresses that cannot receive mail (failed DNS/MX validation) return 200 with status: "Undeliverable".
"alice@example.com"
OTP format, localization, and branding options. All fields are optional.
Show child attributes
Show child attributes
Optional device and network signals about the end user, forwarded to the anti-fraud layer to improve detection of abusive or automated traffic. All fields are optional.
Show child attributes
Show child attributes
Optional caller-controlled identifier (your internal user id, an email, a UUID, etc.) persisted on the session and echoed back in the send response, the matching check response, webhooks, and the Business Console. Use it to correlate Didit's request_id with your user record.
Optional free-form JSON object persisted on the session and echoed back in the send response, the matching check response, webhooks, and the Business Console.
Response
Send acknowledged. Inspect status: Success and Retry mean a code is on its way; Undeliverable means the address cannot receive mail and the verification is already finalized as Declined. request_id is the persisted session id (same id on a Retry).
Session id of the verification. A Retry send returns the same request_id as the original send. This id appears in the Business Console, is returned again by a finalized POST /v3/email/check/, and can be passed to GET /v3/session/{sessionId}/decision/.
Success — OTP emailed to a new verification (billed). Retry — fresh OTP emailed for the pending verification created by a previous send (free). Undeliverable — the address failed deliverability validation or the message could not be sent; the verification is immediately finalized as Declined and nothing is billed.
Success, Retry, Undeliverable email_can_not_be_delivered when status is Undeliverable; null otherwise.
email_can_not_be_delivered, null Echo of the vendor_data stored on the session (from the first send).
Echo of the metadata stored on the session (from the first send).