curl --request POST \
--url https://api.optimaldial.com/api/v2/contacts \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"phone": "+15551234567",
"validation_tier": "standard",
"properties": {},
"callback_url": "<string>",
"callback_secret": "<string>",
"idempotency_key": "<string>",
"full_name": "Alex Rivera",
"first_name": "Alex",
"last_name": "Rivera"
}
'import requests
url = "https://api.optimaldial.com/api/v2/contacts"
payload = {
"phone": "+15551234567",
"validation_tier": "standard",
"properties": {},
"callback_url": "<string>",
"callback_secret": "<string>",
"idempotency_key": "<string>",
"full_name": "Alex Rivera",
"first_name": "Alex",
"last_name": "Rivera"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
phone: '+15551234567',
validation_tier: 'standard',
properties: {},
callback_url: '<string>',
callback_secret: '<string>',
idempotency_key: '<string>',
full_name: 'Alex Rivera',
first_name: 'Alex',
last_name: 'Rivera'
})
};
fetch('https://api.optimaldial.com/api/v2/contacts', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.optimaldial.com/api/v2/contacts",
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' => '+15551234567',
'validation_tier' => 'standard',
'properties' => [
],
'callback_url' => '<string>',
'callback_secret' => '<string>',
'idempotency_key' => '<string>',
'full_name' => 'Alex Rivera',
'first_name' => 'Alex',
'last_name' => 'Rivera'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$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://api.optimaldial.com/api/v2/contacts"
payload := strings.NewReader("{\n \"phone\": \"+15551234567\",\n \"validation_tier\": \"standard\",\n \"properties\": {},\n \"callback_url\": \"<string>\",\n \"callback_secret\": \"<string>\",\n \"idempotency_key\": \"<string>\",\n \"full_name\": \"Alex Rivera\",\n \"first_name\": \"Alex\",\n \"last_name\": \"Rivera\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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://api.optimaldial.com/api/v2/contacts")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"phone\": \"+15551234567\",\n \"validation_tier\": \"standard\",\n \"properties\": {},\n \"callback_url\": \"<string>\",\n \"callback_secret\": \"<string>\",\n \"idempotency_key\": \"<string>\",\n \"full_name\": \"Alex Rivera\",\n \"first_name\": \"Alex\",\n \"last_name\": \"Rivera\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.optimaldial.com/api/v2/contacts")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"phone\": \"+15551234567\",\n \"validation_tier\": \"standard\",\n \"properties\": {},\n \"callback_url\": \"<string>\",\n \"callback_secret\": \"<string>\",\n \"idempotency_key\": \"<string>\",\n \"full_name\": \"Alex Rivera\",\n \"first_name\": \"Alex\",\n \"last_name\": \"Rivera\"\n}"
response = http.request(request)
puts response.read_body{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"organization_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"phone": "+15551234567",
"properties": {},
"status": "queued",
"credits_charged": 1,
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z",
"optimaldial_status": "1 - Likely Right-Party Answer",
"error_message": "<string>",
"validation_tier": "standard",
"full_name": "Alex Rivera",
"first_name": "<string>",
"last_name": "<string>",
"optimaldial_answer_intent": "Likely Answer",
"optimaldial_identity": "Very High"
}{
"detail": "<string>"
}{
"detail": "<string>"
}{
"detail": {
"error": "rate_limit_exceeded",
"scope": "per_key",
"limit": 123,
"retry_after_seconds": 123
}
}Create a contact
Submits one phone number for processing. Charges 1 credit and stores the
contact in queued status.
The same request as POST /api/v1/contacts — same credits, same
idempotency semantics, same callbacks, same underlying contact — plus
optional name fields that enable OptimalDial Identity for the
contact. Supply full_name, or both first_name and last_name;
supplying only one of first_name / last_name returns 422. Names
inside properties are echoed back but never used for identity.
Identity does not change what a contact costs.
The response carries the v2 result fields (null until processing completes) and echoes the submitted name fields.
Re-posting with the same idempotency_key returns the existing contact
without an additional credit charge; name fields on the replay are
ignored.
curl --request POST \
--url https://api.optimaldial.com/api/v2/contacts \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"phone": "+15551234567",
"validation_tier": "standard",
"properties": {},
"callback_url": "<string>",
"callback_secret": "<string>",
"idempotency_key": "<string>",
"full_name": "Alex Rivera",
"first_name": "Alex",
"last_name": "Rivera"
}
'import requests
url = "https://api.optimaldial.com/api/v2/contacts"
payload = {
"phone": "+15551234567",
"validation_tier": "standard",
"properties": {},
"callback_url": "<string>",
"callback_secret": "<string>",
"idempotency_key": "<string>",
"full_name": "Alex Rivera",
"first_name": "Alex",
"last_name": "Rivera"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
phone: '+15551234567',
validation_tier: 'standard',
properties: {},
callback_url: '<string>',
callback_secret: '<string>',
idempotency_key: '<string>',
full_name: 'Alex Rivera',
first_name: 'Alex',
last_name: 'Rivera'
})
};
fetch('https://api.optimaldial.com/api/v2/contacts', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.optimaldial.com/api/v2/contacts",
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' => '+15551234567',
'validation_tier' => 'standard',
'properties' => [
],
'callback_url' => '<string>',
'callback_secret' => '<string>',
'idempotency_key' => '<string>',
'full_name' => 'Alex Rivera',
'first_name' => 'Alex',
'last_name' => 'Rivera'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$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://api.optimaldial.com/api/v2/contacts"
payload := strings.NewReader("{\n \"phone\": \"+15551234567\",\n \"validation_tier\": \"standard\",\n \"properties\": {},\n \"callback_url\": \"<string>\",\n \"callback_secret\": \"<string>\",\n \"idempotency_key\": \"<string>\",\n \"full_name\": \"Alex Rivera\",\n \"first_name\": \"Alex\",\n \"last_name\": \"Rivera\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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://api.optimaldial.com/api/v2/contacts")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"phone\": \"+15551234567\",\n \"validation_tier\": \"standard\",\n \"properties\": {},\n \"callback_url\": \"<string>\",\n \"callback_secret\": \"<string>\",\n \"idempotency_key\": \"<string>\",\n \"full_name\": \"Alex Rivera\",\n \"first_name\": \"Alex\",\n \"last_name\": \"Rivera\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.optimaldial.com/api/v2/contacts")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"phone\": \"+15551234567\",\n \"validation_tier\": \"standard\",\n \"properties\": {},\n \"callback_url\": \"<string>\",\n \"callback_secret\": \"<string>\",\n \"idempotency_key\": \"<string>\",\n \"full_name\": \"Alex Rivera\",\n \"first_name\": \"Alex\",\n \"last_name\": \"Rivera\"\n}"
response = http.request(request)
puts response.read_body{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"organization_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"phone": "+15551234567",
"properties": {},
"status": "queued",
"credits_charged": 1,
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z",
"optimaldial_status": "1 - Likely Right-Party Answer",
"error_message": "<string>",
"validation_tier": "standard",
"full_name": "Alex Rivera",
"first_name": "<string>",
"last_name": "<string>",
"optimaldial_answer_intent": "Likely Answer",
"optimaldial_identity": "Very High"
}{
"detail": "<string>"
}{
"detail": "<string>"
}{
"detail": {
"error": "rate_limit_exceeded",
"scope": "per_key",
"limit": 123,
"retry_after_seconds": 123
}
}Authorizations
OptimalDial API key, prefixed od_live_, sent as Authorization: Bearer ....
Mint keys in the in-app developer panel; they are scoped to a single organization.
Body
The POST /api/v2/contacts request body: everything in ContactCreate
plus optional name fields that enable OptimalDial Identity for the
contact. Supply full_name, or both first_name and last_name.
Supplying only one of first_name / last_name returns 422 with
"Identity requires either full_name, or both first_name and
last_name." When full_name and the first/last pair are all present,
full_name is what identity is scored against. Names are trimmed of
surrounding whitespace; a blank value is treated as absent. Names
placed inside properties are echoed back but are not used for
identity. Identity carries no extra credit charge.
Any common format. Normalized to E.164. Must be US or Canadian.
"+15551234567"
Validation pricing tier. standard (default) charges 1 credit.
max is more thorough and is billed so a batch of contacts totals
ceil(1.5 × count) credits — a lone MAX contact costs 2, and each
additional pair adds 3 (exactly 1.5×). The amount deducted for this
contact is returned as credits_charged. Case-insensitive.
standard, max Arbitrary extra fields echoed back in the result payload. Use these to correlate the result with your own row IDs.
Optional per-contact webhook URL. Receives a signed POST when the contact's result is ready, with the same retry semantics as registered org-level webhook endpoints (6 attempts with backoff).
HMAC-SHA256 secret used to sign the callback_url payload.
Optional. Re-posting with the same key returns the existing contact without an extra credit charge. Scoped per (organization, api_key).
255The contact's full name. Enables OptimalDial Identity on its
own. Takes precedence over first_name / last_name when all
three are supplied.
200"Alex Rivera"
Enables OptimalDial Identity when supplied together with
last_name. Not sufficient on its own.
200"Alex"
Enables OptimalDial Identity when supplied together with
first_name. Not sufficient on its own.
200"Rivera"
Response
Contact created (or returned via idempotency).
The v2 view of a contact. Identical to Contact except that it echoes
the submitted name fields and reports the identity result:
| Field | v1 | v2 |
|---|---|---|
optimaldial_status | "Likely Answer" | "1 - Likely Right-Party Answer" |
optimaldial_answer_intent | — | "Likely Answer" |
optimaldial_identity | — | "Very High" |
full_name / first_name / last_name | — | As submitted, or null |
E.164-normalized phone number (US/CA only).
"+15551234567"
Arbitrary extra fields echoed back from creation.
queued → processing → one of completed, failed, or
discarded. failed means processing finished without a result
for this contact; discarded means OptimalDial dropped it before
processing. Both refund the credit, both set error_message, and
both are delivered as a contact.failed event to your webhook
endpoints and the contact's callback_url — check status on
the payload to tell them apart.
queued, processing, completed, failed, discarded x >= 0The nine-tier OptimalDial_Status value. Falls back to the Answer Intent value for contacts processed before identity.
"1 - Likely Right-Party Answer"
Why the contact is failed or discarded; null otherwise.
Validation pricing tier this contact was created with.
standard, max The full_name submitted on creation, or null.
"Alex Rivera"
The first_name submitted on creation, or null.
The last_name submitted on creation, or null.
Whether the number is likely to pick up, on its own.
"Likely Answer"
How strongly the signals indicate the number belongs to your contact. Requires a name on creation (full_name, or both first_name and last_name). Null when no contact in the processing batch supplied a name; Unknown when this contact had no name but another contact in its batch did, or when there was no signal either way. Unknown is not "safe".
Very High, High, Medium, Low, Unknown, null