curl -X POST https://api.60db.ai/dialer/click2call \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"agent_number": "9812345678",
"customer_number": "9876543210",
"callback_url": "https://your-app.example.com/webhooks/qcall",
"callback_secret": "a-long-random-string-you-choose",
"agent_ring_timeout": 30,
"customer_ring_timeout": 45,
"max_duration": 3600,
"metadata": { "lead_id": "L-991", "campaign": "q3-renewals" }
}'
const res = await fetch('https://api.60db.ai/dialer/click2call', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SIXTYDB_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
agent_number: '9812345678',
customer_number: '9876543210',
callback_url: 'https://your-app.example.com/webhooks/qcall',
callback_secret: process.env.QCALL_WEBHOOK_SECRET,
metadata: { lead_id: 'L-991' },
}),
});
const { data } = await res.json();
if (data.replayed) console.log('already placed:', data.call.call_id);
else console.log('dialling:', data.call.call_id);
import os, requests
res = requests.post(
"https://api.60db.ai/dialer/click2call",
headers={"Authorization": f"Bearer {os.environ['SIXTYDB_API_KEY']}"},
json={
"agent_number": "9812345678",
"customer_number": "9876543210",
"callback_url": "https://your-app.example.com/webhooks/qcall",
"callback_secret": os.environ["QCALL_WEBHOOK_SECRET"],
"metadata": {"lead_id": "L-991"},
},
)
data = res.json()["data"]
print(data["call"]["call_id"], "placed" if data["placed"] else "replayed")
{
"success": true,
"data": {
"placed": true,
"replayed": false,
"call": {
"call_id": "7d004ba2-565f-4ee2-9aa2-19523d8db7ae",
"reference_id": "qc2c-8f1c1e2a-4b77-4a51-9f3e-2c1d6f0a77b1",
"outbound_trunk_id": "00000000-0000-4000-8000-000000000000",
"did": "+917900000000",
"agent_number": "+919812345678",
"customer_number": "+919876543210",
"state": "initiated",
"call_done": false,
"terminal_event": null,
"hangup_cause": null,
"leg": null,
"error": null,
"error_detail": null,
"talk_seconds": null,
"total_seconds": null,
"start_time": null,
"answer_time": null,
"end_time": null,
"metadata": { "lead_id": "L-991", "campaign": "q3-renewals" },
"record_requested": false,
"recording_id": null,
"recording_status": null,
"recording_available": false,
"callback_url": "https://your-app.example.com/webhooks/qcall",
"callback_secret_last4": "oose",
"cost_usd": null,
"billing_status": null,
"placed_via": "api_key",
"created_by_user_id": 4021,
"api_key_id": 17,
"api_key_name": "crm-integration",
"created_at": "2026-09-23T14:11:27.134Z"
}
}
}
{
"success": true,
"data": {
"placed": false,
"replayed": true,
"call": {
"call_id": "7d004ba2-565f-4ee2-9aa2-19523d8db7ae",
"reference_id": "crm-task-91823",
"state": "initiated",
"call_done": false
}
}
}
{
"success": false,
"message": "That reference has already been used for a call that has finished. A reference is permanent — place the call again to get a new one.",
"code": "REFERENCE_ALREADY_USED"
}
Click-to-Call
Place Click-to-Call
Ring the agent on your own number, then the customer, then bridge them
POST
/
dialer
/
click2call
curl -X POST https://api.60db.ai/dialer/click2call \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"agent_number": "9812345678",
"customer_number": "9876543210",
"callback_url": "https://your-app.example.com/webhooks/qcall",
"callback_secret": "a-long-random-string-you-choose",
"agent_ring_timeout": 30,
"customer_ring_timeout": 45,
"max_duration": 3600,
"metadata": { "lead_id": "L-991", "campaign": "q3-renewals" }
}'
const res = await fetch('https://api.60db.ai/dialer/click2call', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SIXTYDB_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
agent_number: '9812345678',
customer_number: '9876543210',
callback_url: 'https://your-app.example.com/webhooks/qcall',
callback_secret: process.env.QCALL_WEBHOOK_SECRET,
metadata: { lead_id: 'L-991' },
}),
});
const { data } = await res.json();
if (data.replayed) console.log('already placed:', data.call.call_id);
else console.log('dialling:', data.call.call_id);
import os, requests
res = requests.post(
"https://api.60db.ai/dialer/click2call",
headers={"Authorization": f"Bearer {os.environ['SIXTYDB_API_KEY']}"},
json={
"agent_number": "9812345678",
"customer_number": "9876543210",
"callback_url": "https://your-app.example.com/webhooks/qcall",
"callback_secret": os.environ["QCALL_WEBHOOK_SECRET"],
"metadata": {"lead_id": "L-991"},
},
)
data = res.json()["data"]
print(data["call"]["call_id"], "placed" if data["placed"] else "replayed")
{
"success": true,
"data": {
"placed": true,
"replayed": false,
"call": {
"call_id": "7d004ba2-565f-4ee2-9aa2-19523d8db7ae",
"reference_id": "qc2c-8f1c1e2a-4b77-4a51-9f3e-2c1d6f0a77b1",
"outbound_trunk_id": "00000000-0000-4000-8000-000000000000",
"did": "+917900000000",
"agent_number": "+919812345678",
"customer_number": "+919876543210",
"state": "initiated",
"call_done": false,
"terminal_event": null,
"hangup_cause": null,
"leg": null,
"error": null,
"error_detail": null,
"talk_seconds": null,
"total_seconds": null,
"start_time": null,
"answer_time": null,
"end_time": null,
"metadata": { "lead_id": "L-991", "campaign": "q3-renewals" },
"record_requested": false,
"recording_id": null,
"recording_status": null,
"recording_available": false,
"callback_url": "https://your-app.example.com/webhooks/qcall",
"callback_secret_last4": "oose",
"cost_usd": null,
"billing_status": null,
"placed_via": "api_key",
"created_by_user_id": 4021,
"api_key_id": 17,
"api_key_name": "crm-integration",
"created_at": "2026-09-23T14:11:27.134Z"
}
}
}
{
"success": true,
"data": {
"placed": false,
"replayed": true,
"call": {
"call_id": "7d004ba2-565f-4ee2-9aa2-19523d8db7ae",
"reference_id": "crm-task-91823",
"state": "initiated",
"call_done": false
}
}
}
{
"success": false,
"message": "That reference has already been used for a call that has finished. A reference is permanent — place the call again to get a new one.",
"code": "REFERENCE_ALREADY_USED"
}
One request places a two-leg call: the agent is dialled first on your DID, and only when they answer is the customer dialled and the legs bridged. Both legs present your DID as the caller ID.
Requires a provisioned trunk (Gate T), a number assigned to that trunk, and a positive wallet balance. See the Click-to-Call guide for the full flow.
Failures return
A
reference_id is permanent, and a failed call still consumes it. Omit the field and one is minted per call, which is what you want unless you’re deliberately making a request repeatable. To retry after a call that rang out, place a new call with a new reference.Request
Headers
string
required
Bearer token with your API key
Body
string
required
The person dialled first. Indian mobile number as
+919812345678, 919812345678 or 9812345678. Spaces, hyphens, dots and brackets are stripped.string
required
Dialled once the agent answers. Must differ from
agent_number.string
The caller ID presented on both legs. Must be a number assigned to your workspace and to
outbound_trunk_id. Defaults to your trunk’s saved caller ID.string
UUID of the outbound trunk to place the call over. Defaults to your workspace’s active trunk.
string
Your idempotency key, unique per trunk. 1–128 characters of
A-Za-z0-9._:-. Minted for you (qc2c-<uuid>) when omitted.string
Public HTTPS URL that this call’s events are relayed to. Requires
callback_secret. Private, loopback, link-local and CGNAT addresses are rejected.string
16–256 characters you choose. Required whenever
callback_url is sent — it is the HMAC key your endpoint verifies x-qcall-signature against. Encrypted at rest and never returned; only its last four characters are readable afterwards.integer
default:"30"
Seconds to ring the agent.
5–60.integer
default:"45"
Seconds to ring the customer.
5–60.integer
default:"3600"
Maximum seconds of bridged talk time.
30–7200.boolean
default:"false"
Record the bridged conversation. Audio is uploaded after the call ends — poll the call with
?refresh=1, then mint a playback URL.object
Arbitrary JSON object echoed back on the call and in every webhook. At most 2048 bytes serialised, no NUL characters.
Response
201 Created — a call was admitted and the agent is being dialled. 200 OK — thisreference_id was already bound to a live call. The original call is returned and nothing new was dialled.
The bodies are identical, so branch on data.placed / data.replayed rather than on the shape.
boolean
true when a new call was dialled (201)boolean
true when an existing call was returned and nothing was dialled (200)object
Show properties
Show properties
string
Platform call id. Use it to poll, list events and mint recordings
string
Your idempotency key, or the one minted for you
string
The trunk this call rides
string
Caller ID presented on both legs,
+E.164string
+E.164string
+E.164string
pending, initiated, bridged, completed, failed, rejected or unknownboolean
true once the outcome is finalstring | null
call.completed, call.not_answered, call.failed or call.temporaryfailedstring | null
e.g.
NORMAL_CLEARING, NO_ANSWER, USER_BUSY, MAX_DURATIONstring | null
On failures:
agent or customer — which side failedstring | null
Machine-readable failure code, if any
string | null
Human-readable detail for that failure
integer | null
Bridged talk time. This is what gets billed
integer | null
Seconds from admission to the end of the call
string | null
ISO 8601
string | null
ISO 8601. When the legs were bridged;
null until thenstring | null
ISO 8601. Present once the call has ended
object | null
Exactly what you sent
boolean
Whether this call asked to be recorded
string | null
Opaque id, once the platform has produced one
string | null
e.g.
uploadedboolean
true when a playback URL can be mintedstring | null
Where this call’s events are relayed
string | null
Last four characters of the stored signing secret — enough to tell which secret we hold, never enough to sign with
number | null
What this call cost, in USD
string | null
billed, failed, or null when nothing was owedstring
api_key or sessioninteger | null
Which API key placed the call
string | null
That key’s name, snapshotted so a revoked key still explains its charges
integer | null
Who placed it
string
ISO 8601
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | INVALID_NUMBER | A number isn’t a valid Indian mobile number |
| 400 | SAME_NUMBER | agent_number and customer_number are the same |
| 400 | INVALID_RING_TIMEOUT | Ring timeout outside 5–60 s |
| 400 | INVALID_MAX_DURATION | max_duration outside 30–7200 s |
| 400 | INVALID_RECORD | record isn’t a boolean |
| 400 | INVALID_METADATA | Not an object, over 2 KiB, or contains NUL |
| 400 | INVALID_REFERENCE | reference_id fails the character/length rule |
| 400 | INVALID_CALLBACK_URL | Not HTTPS, or not publicly resolvable |
| 400 | INVALID_CALLBACK_SECRET | Missing, or outside 16–256 characters |
| 402 | RECHARGE_REQUIRED | Wallet balance is 0 or less |
| 403 | CALLER_ID_NOT_OWNED | did isn’t assigned to this workspace |
| 403 | destination_not_allowed | A number is outside your permitted prefixes |
| 404 | REFERENCE_NOT_OWNED | That reference belongs to another workspace |
| 409 | DIALER_NOT_PROVISIONED | Trunk not set up (Gate T) |
| 409 | REFERENCE_ALREADY_USED | The reference is spent — place a new call |
| 429 | C2C_RATE_LIMITED | More than 20 calls placed in the last minute |
| 429 | no_free_channel | Platform capacity exhausted. Honour Retry-After |
| 503 | CARRIER_LINE_DOWN | Carrier line is down; your number and balance are fine |
| 503 | C2C_NOT_CONFIGURED | Click-to-call isn’t set up on the server |
| 503 | C2C_CALLBACK_UNREACHABLE | The server’s results address isn’t reachable from the internet |
| 504 | C2C_OUTCOME_UNKNOWN | No answer from the platform — check the agent’s phone before retrying |
{ "success": false, "message": "...", "code": "..." }, plus request_id when the refusal came from the call platform (log it — it’s the handle support traces on) and retry_after in seconds on 429/503.
429 and 503 also carry a Retry-After header. A refused request never placed a call and never consumed its reference_id, so it is safe to retry with the same one.Example
curl -X POST https://api.60db.ai/dialer/click2call \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"agent_number": "9812345678",
"customer_number": "9876543210",
"callback_url": "https://your-app.example.com/webhooks/qcall",
"callback_secret": "a-long-random-string-you-choose",
"agent_ring_timeout": 30,
"customer_ring_timeout": 45,
"max_duration": 3600,
"metadata": { "lead_id": "L-991", "campaign": "q3-renewals" }
}'
const res = await fetch('https://api.60db.ai/dialer/click2call', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SIXTYDB_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
agent_number: '9812345678',
customer_number: '9876543210',
callback_url: 'https://your-app.example.com/webhooks/qcall',
callback_secret: process.env.QCALL_WEBHOOK_SECRET,
metadata: { lead_id: 'L-991' },
}),
});
const { data } = await res.json();
if (data.replayed) console.log('already placed:', data.call.call_id);
else console.log('dialling:', data.call.call_id);
import os, requests
res = requests.post(
"https://api.60db.ai/dialer/click2call",
headers={"Authorization": f"Bearer {os.environ['SIXTYDB_API_KEY']}"},
json={
"agent_number": "9812345678",
"customer_number": "9876543210",
"callback_url": "https://your-app.example.com/webhooks/qcall",
"callback_secret": os.environ["QCALL_WEBHOOK_SECRET"],
"metadata": {"lead_id": "L-991"},
},
)
data = res.json()["data"]
print(data["call"]["call_id"], "placed" if data["placed"] else "replayed")
{
"success": true,
"data": {
"placed": true,
"replayed": false,
"call": {
"call_id": "7d004ba2-565f-4ee2-9aa2-19523d8db7ae",
"reference_id": "qc2c-8f1c1e2a-4b77-4a51-9f3e-2c1d6f0a77b1",
"outbound_trunk_id": "00000000-0000-4000-8000-000000000000",
"did": "+917900000000",
"agent_number": "+919812345678",
"customer_number": "+919876543210",
"state": "initiated",
"call_done": false,
"terminal_event": null,
"hangup_cause": null,
"leg": null,
"error": null,
"error_detail": null,
"talk_seconds": null,
"total_seconds": null,
"start_time": null,
"answer_time": null,
"end_time": null,
"metadata": { "lead_id": "L-991", "campaign": "q3-renewals" },
"record_requested": false,
"recording_id": null,
"recording_status": null,
"recording_available": false,
"callback_url": "https://your-app.example.com/webhooks/qcall",
"callback_secret_last4": "oose",
"cost_usd": null,
"billing_status": null,
"placed_via": "api_key",
"created_by_user_id": 4021,
"api_key_id": 17,
"api_key_name": "crm-integration",
"created_at": "2026-09-23T14:11:27.134Z"
}
}
}
{
"success": true,
"data": {
"placed": false,
"replayed": true,
"call": {
"call_id": "7d004ba2-565f-4ee2-9aa2-19523d8db7ae",
"reference_id": "crm-task-91823",
"state": "initiated",
"call_done": false
}
}
}
{
"success": false,
"message": "That reference has already been used for a call that has finished. A reference is permanent — place the call again to get a new one.",
"code": "REFERENCE_ALREADY_USED"
}