curl -X POST https://verification.didit.me/v3/phone/check/ \
-H 'x-api-key: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"phone_number": "+14155552671",
"code": "123456",
"voip_number_action": "DECLINE"
}'import os, requests
resp = requests.post(
"https://verification.didit.me/v3/phone/check/",
headers={
"x-api-key": os.environ["DIDIT_API_KEY"],
"Content-Type": "application/json",
},
json={
"phone_number": "+14155552671", # same number as the send call
"code": "123456",
"voip_number_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["phone"]["carrier"], result["phone"]["is_virtual"])const res = await fetch('https://verification.didit.me/v3/phone/check/', {
method: 'POST',
headers: {
'x-api-key': 'YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
phone_number: '+14155552671', // same number as the send call
code: '123456',
voip_number_action: 'DECLINE',
}),
});
const data = await res.json();
if (data.status === 'Approved') {
// full phone report is in data.phone (carrier, is_virtual, matches, ...)
}<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://verification.didit.me/v3/phone/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([
'phone_number' => '+14155552671',
'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/phone/check/"
payload := strings.NewReader("{\n \"phone_number\": \"+14155552671\",\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/phone/check/")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"phone_number\": \"+14155552671\",\n \"code\": \"123456\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://verification.didit.me/v3/phone/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 \"phone_number\": \"+14155552671\",\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.",
"phone": {
"status": "Approved",
"phone_number_prefix": "+1",
"phone_number": "4155552671",
"full_number": "+14155552671",
"country_code": "US",
"country_name": "United States",
"carrier": {
"name": "AT&T",
"type": "mobile"
},
"is_disposable": false,
"is_virtual": false,
"verification_method": "whatsapp",
"verification_attempts": 1,
"verified_at": "2026-06-12T01:24:47.311323Z",
"warnings": [],
"lifecycle": [
{
"type": "PHONE_VERIFICATION_MESSAGE_SENT",
"timestamp": "2026-06-12T01:23:39.580554+00:00",
"details": {
"status": "Success",
"reason": null,
"channel": "whatsapp",
"actual_channel": "whatsapp"
},
"fee": 0.0709
},
{
"type": "PHONE_DELIVERY_DELIVERED",
"timestamp": "2026-06-12T01:23:43.118220+00:00",
"details": {
"channel": "whatsapp",
"status": "delivered"
},
"fee": 0
},
{
"type": "VALID_CODE_ENTERED",
"timestamp": "2026-06-12T01:24:47.311201+00:00",
"details": {
"code_tried": "123456",
"status": "Approved"
},
"fee": 0
},
{
"type": "PHONE_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 Phone Code
Verify the OTP delivered by POST /v3/phone/send/ and get the final verification result plus phone intelligence: carrier name and line type, virtual (VoIP) and disposable flags, and duplicate usage of the number across your sessions.
How the check finds the verification. Matching is by your application plus the E.164 phone_number — 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, 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 phone: null; the third wrong code finalizes the verification as Declined with a VERIFICATION_CODE_ATTEMPTS_EXCEEDED warning and returns the full phone report.
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 or high-risk number), 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 phone carries the full report (carrier, is_virtual, is_disposable, warnings, lifecycle, matches); on Failed and Expired or Not Found the request_id is a one-off random UUID.
Risk actions. duplicated_phone_number_action, disposable_number_action, and voip_number_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 phone.warnings without affecting the status.
Billing. Checks are free — delivery is billed on the send side (see Phone Verification Pricing).
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 (4–8 digits, 123456 included) returns a static Approved payload with a simplified flat phone object (status, phone_number, country_code, carrier_name, line_type, flags); 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 limits. Shared write budget of 300 requests/min per API key, plus the upstream anti-abuse cap of 4 attempts per number per hour; exceeding either returns 429.
Social footprint. Set enable_social to true to add a phone_social block: the online platforms this number is registered on, with per-category counts and any profile details available. profiles_registered: 0 means the platforms were checked and the number 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 phone_social unit per request on top of this endpoint’s own price.
curl -X POST https://verification.didit.me/v3/phone/check/ \
-H 'x-api-key: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"phone_number": "+14155552671",
"code": "123456",
"voip_number_action": "DECLINE"
}'import os, requests
resp = requests.post(
"https://verification.didit.me/v3/phone/check/",
headers={
"x-api-key": os.environ["DIDIT_API_KEY"],
"Content-Type": "application/json",
},
json={
"phone_number": "+14155552671", # same number as the send call
"code": "123456",
"voip_number_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["phone"]["carrier"], result["phone"]["is_virtual"])const res = await fetch('https://verification.didit.me/v3/phone/check/', {
method: 'POST',
headers: {
'x-api-key': 'YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
phone_number: '+14155552671', // same number as the send call
code: '123456',
voip_number_action: 'DECLINE',
}),
});
const data = await res.json();
if (data.status === 'Approved') {
// full phone report is in data.phone (carrier, is_virtual, matches, ...)
}<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://verification.didit.me/v3/phone/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([
'phone_number' => '+14155552671',
'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/phone/check/"
payload := strings.NewReader("{\n \"phone_number\": \"+14155552671\",\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/phone/check/")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"phone_number\": \"+14155552671\",\n \"code\": \"123456\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://verification.didit.me/v3/phone/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 \"phone_number\": \"+14155552671\",\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.",
"phone": {
"status": "Approved",
"phone_number_prefix": "+1",
"phone_number": "4155552671",
"full_number": "+14155552671",
"country_code": "US",
"country_name": "United States",
"carrier": {
"name": "AT&T",
"type": "mobile"
},
"is_disposable": false,
"is_virtual": false,
"verification_method": "whatsapp",
"verification_attempts": 1,
"verified_at": "2026-06-12T01:24:47.311323Z",
"warnings": [],
"lifecycle": [
{
"type": "PHONE_VERIFICATION_MESSAGE_SENT",
"timestamp": "2026-06-12T01:23:39.580554+00:00",
"details": {
"status": "Success",
"reason": null,
"channel": "whatsapp",
"actual_channel": "whatsapp"
},
"fee": 0.0709
},
{
"type": "PHONE_DELIVERY_DELIVERED",
"timestamp": "2026-06-12T01:23:43.118220+00:00",
"details": {
"channel": "whatsapp",
"status": "delivered"
},
"fee": 0
},
{
"type": "VALID_CODE_ENTERED",
"timestamp": "2026-06-12T01:24:47.311201+00:00",
"details": {
"code_tried": "123456",
"status": "Approved"
},
"fee": 0
},
{
"type": "PHONE_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"
}Handle provider outages
If the phone provider is temporarily unavailable, this endpoint returns502 with a stable machine-readable code:
{
"detail": "The phone verification provider is temporarily unavailable.",
"code": "phone_provider_unavailable"
}
Authorizations
Body
The same phone number used in the matching POST /v3/phone/send/ call, in E.164 format. This is what links the check to the send.
20"+14155552671"
The OTP the end user received. Must be 4–8 numeric digits — anything else returns 400 without consuming an attempt. A well-formed but wrong code returns 200 with status: "Failed" and consumes one of the 3 attempts.
4 - 8^[0-9]{4,8}$"123456"
What to do when the same number was already used by a different user (different vendor_data) of your application. DECLINE flips the final status to Declined; NO_ACTION records the risk in phone.warnings and fills phone.matches.
NO_ACTION, DECLINE What to do when the carrier lookup flags the number as a temporary/burner line (phone.is_disposable). DECLINE flips the final status to Declined; NO_ACTION records the risk in phone.warnings.
NO_ACTION, DECLINE What to do when the carrier lookup reports a virtual line type — voip, isp, or vpn (phone.is_virtual). DECLINE flips the final status to Declined; NO_ACTION records the risk in phone.warnings.
NO_ACTION, DECLINE When true, also look up the social footprint of the number - the messaging, social, e-commerce and professional platforms it is registered on. Billed as one phone_social unit per request in addition to this endpoint's own price.
true
Action when the social footprint check finds the number 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. phone 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/phone/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 number 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 phone 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.