curl --request POST \
--url https://app.thinnest.ai/api/v1/calls \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data @- <<EOF
{
"to": "+91 98765 43210",
"purpose": "I'm calling from Skyline Homes about your enquiry for Sky Towers.",
"agent": "ag_5c4a5f93-2b1e-4d7a-9f60-8e2d1c3b4a71",
"name": "Asha Rao",
"source": "99acres enquiry form",
"reference": "LSQ-42",
"variables": {
"lead_name": "Asha",
"project": "Sky Towers",
"budget_asked": "2BHK"
},
"callingHours": {
"start": "10:00",
"end": "19:00",
"days": [
"mon",
"tue",
"wed",
"thu",
"fri",
"sat"
]
},
"extract": [
{
"name": "budget",
"type": "number",
"description": "Budget in rupees"
},
{
"name": "interest",
"choices": [
"Hot",
"Warm",
"Cold"
]
},
{
"name": "site_visit",
"type": "boolean"
}
],
"summary": true,
"metadata": {
"deal_id": "D-19",
"owner": "Ravi"
},
"retry": {
"count": 2,
"noAnswerMinutes": 60,
"busyMinutes": 15
},
"from": "918045678901",
"overrides": {
"voice": "priya",
"maxCallSeconds": 300
}
}
EOFconst options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
to: '+91 98765 43210',
purpose: 'I\'m calling from Skyline Homes about your enquiry for Sky Towers.',
agent: 'ag_5c4a5f93-2b1e-4d7a-9f60-8e2d1c3b4a71',
name: 'Asha Rao',
source: '99acres enquiry form',
reference: 'LSQ-42',
variables: {lead_name: 'Asha', project: 'Sky Towers', budget_asked: '2BHK'},
callingHours: {start: '10:00', end: '19:00', days: ['mon', 'tue', 'wed', 'thu', 'fri', 'sat']},
extract: [
{name: 'budget', type: 'number', description: 'Budget in rupees'},
{name: 'interest', choices: ['Hot', 'Warm', 'Cold']},
{name: 'site_visit', type: 'boolean'}
],
summary: true,
metadata: {deal_id: 'D-19', owner: 'Ravi'},
retry: {count: 2, noAnswerMinutes: 60, busyMinutes: 15},
from: '918045678901',
overrides: {voice: 'priya', maxCallSeconds: 300}
})
};
fetch('https://app.thinnest.ai/api/v1/calls', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://app.thinnest.ai/api/v1/calls"
payload = {
"to": "+91 98765 43210",
"purpose": "I'm calling from Skyline Homes about your enquiry for Sky Towers.",
"agent": "ag_5c4a5f93-2b1e-4d7a-9f60-8e2d1c3b4a71",
"name": "Asha Rao",
"source": "99acres enquiry form",
"reference": "LSQ-42",
"variables": {
"lead_name": "Asha",
"project": "Sky Towers",
"budget_asked": "2BHK"
},
"callingHours": {
"start": "10:00",
"end": "19:00",
"days": ["mon", "tue", "wed", "thu", "fri", "sat"]
},
"extract": [
{
"name": "budget",
"type": "number",
"description": "Budget in rupees"
},
{
"name": "interest",
"choices": ["Hot", "Warm", "Cold"]
},
{
"name": "site_visit",
"type": "boolean"
}
],
"summary": True,
"metadata": {
"deal_id": "D-19",
"owner": "Ravi"
},
"retry": {
"count": 2,
"noAnswerMinutes": 60,
"busyMinutes": 15
},
"from": "918045678901",
"overrides": {
"voice": "priya",
"maxCallSeconds": 300
}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"id": "out_9f2c1a44-6b0e-4c1d-8a7f-2e5b3c9d1f60",
"to": "919876543210",
"from": "918045678901",
"status": "ringing",
"reference": "LSQ-42"
}Place Call
Your agent rings a customer, opens with your purpose (spoken aloud, so under 300 characters), then holds a normal conversation. A number that is not a contact yet becomes one with the name and source you send; someone who opted out or is on your do-not-call list is refused. Outside the calling hours — never wider than 9am–9pm in the customer’s own time — or with scheduledAt, the call is queued and answers status: "scheduled" with a sch_… id; otherwise it rings now with an out_… id. Each call is billed from your balance, refused with 402 when it cannot pay, held to your plan’s concurrent lines and 60 requests a minute; what it learned arrives as call.analysed or from Get Call. Needs a full key.
curl --request POST \
--url https://app.thinnest.ai/api/v1/calls \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data @- <<EOF
{
"to": "+91 98765 43210",
"purpose": "I'm calling from Skyline Homes about your enquiry for Sky Towers.",
"agent": "ag_5c4a5f93-2b1e-4d7a-9f60-8e2d1c3b4a71",
"name": "Asha Rao",
"source": "99acres enquiry form",
"reference": "LSQ-42",
"variables": {
"lead_name": "Asha",
"project": "Sky Towers",
"budget_asked": "2BHK"
},
"callingHours": {
"start": "10:00",
"end": "19:00",
"days": [
"mon",
"tue",
"wed",
"thu",
"fri",
"sat"
]
},
"extract": [
{
"name": "budget",
"type": "number",
"description": "Budget in rupees"
},
{
"name": "interest",
"choices": [
"Hot",
"Warm",
"Cold"
]
},
{
"name": "site_visit",
"type": "boolean"
}
],
"summary": true,
"metadata": {
"deal_id": "D-19",
"owner": "Ravi"
},
"retry": {
"count": 2,
"noAnswerMinutes": 60,
"busyMinutes": 15
},
"from": "918045678901",
"overrides": {
"voice": "priya",
"maxCallSeconds": 300
}
}
EOFconst options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
to: '+91 98765 43210',
purpose: 'I\'m calling from Skyline Homes about your enquiry for Sky Towers.',
agent: 'ag_5c4a5f93-2b1e-4d7a-9f60-8e2d1c3b4a71',
name: 'Asha Rao',
source: '99acres enquiry form',
reference: 'LSQ-42',
variables: {lead_name: 'Asha', project: 'Sky Towers', budget_asked: '2BHK'},
callingHours: {start: '10:00', end: '19:00', days: ['mon', 'tue', 'wed', 'thu', 'fri', 'sat']},
extract: [
{name: 'budget', type: 'number', description: 'Budget in rupees'},
{name: 'interest', choices: ['Hot', 'Warm', 'Cold']},
{name: 'site_visit', type: 'boolean'}
],
summary: true,
metadata: {deal_id: 'D-19', owner: 'Ravi'},
retry: {count: 2, noAnswerMinutes: 60, busyMinutes: 15},
from: '918045678901',
overrides: {voice: 'priya', maxCallSeconds: 300}
})
};
fetch('https://app.thinnest.ai/api/v1/calls', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://app.thinnest.ai/api/v1/calls"
payload = {
"to": "+91 98765 43210",
"purpose": "I'm calling from Skyline Homes about your enquiry for Sky Towers.",
"agent": "ag_5c4a5f93-2b1e-4d7a-9f60-8e2d1c3b4a71",
"name": "Asha Rao",
"source": "99acres enquiry form",
"reference": "LSQ-42",
"variables": {
"lead_name": "Asha",
"project": "Sky Towers",
"budget_asked": "2BHK"
},
"callingHours": {
"start": "10:00",
"end": "19:00",
"days": ["mon", "tue", "wed", "thu", "fri", "sat"]
},
"extract": [
{
"name": "budget",
"type": "number",
"description": "Budget in rupees"
},
{
"name": "interest",
"choices": ["Hot", "Warm", "Cold"]
},
{
"name": "site_visit",
"type": "boolean"
}
],
"summary": True,
"metadata": {
"deal_id": "D-19",
"owner": "Ravi"
},
"retry": {
"count": 2,
"noAnswerMinutes": 60,
"busyMinutes": 15
},
"from": "918045678901",
"overrides": {
"voice": "priya",
"maxCallSeconds": 300
}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"id": "out_9f2c1a44-6b0e-4c1d-8a7f-2e5b3c9d1f60",
"to": "919876543210",
"from": "918045678901",
"status": "ringing",
"reference": "LSQ-42"
}Authorizations
Your API key (ta_live_…) from Settings → API keys, sent as Authorization: Bearer <key>. Keep it on a server: it can message every customer you have. A key is full, build or read-only; a request its level does not allow is refused with 403.
Headers
Any unique string. A retry with the same key within 24 hours returns the first request's answer instead of acting twice.
255Developers only: the customer workspace this request acts in — its org_… id from POST /customers. Leave it out to act in your own workspace.
"org_3fKq9TzQ1mN8vB2xR7cLpA"
Body
One call to place.
The customer's number, with or without +; spaces are fine. Read as Indian (91) when it has no country code.
"+91 98765 43210"
Why you are calling, as you would say it — the first thing the customer hears. Under 300 characters.
1 - 299"I'm calling from Skyline Homes about your enquiry for Sky Towers."
Which agent calls — its ag_… id or its name. Needed only when more than one agent answers the phone.
"ag_5c4a5f93-2b1e-4d7a-9f60-8e2d1c3b4a71"
The same as the Idempotency-Key header, for clients that cannot set headers. The header wins.
The lead's name. A number that is not a contact yet becomes one with this name; an existing contact's name is never overwritten.
120Where the lead came from, e.g. 99acres enquiry form — recorded on a new contact.
120Your own id for this lead, never interpreted. It comes back on the response, every report and both call webhooks.
200Values the agent's instructions use as {{name}}. Up to 20; names are lower-cased with spaces turned into _, values cut to 150 characters.
Show child attributes
Show child attributes
When this person may be rung, in their own time. Leave it out for any day, 09:00 to 21:00.
Show child attributes
Show child attributes
Outside the calling hours right now: schedule queues the call for the minute they open; refuse answers 409 with nextOpening and does nothing. Does not apply to a call with scheduledAt, nor to a batch (which always queues).
schedule, refuse The details to fill from the call, keyed back exactly as named. A value that does not fit its type is left out rather than sent wrong. Leave it out for the agent's own collected fields.
30Show child attributes
Show child attributes
true writes two or three sentences about the call, false skips it. Leave it out to follow the agent's own switch.
Your own flat data, returned exactly as sent on every report and webhook. Up to 20 keys of 1–60 characters, 4,000 characters serialised; values are strings, numbers, booleans or null — a nested object or list is refused.
Show child attributes
Show child attributes
Don't ring before this instant, written with its offset (2026-10-07T10:00:00+05:30). Up to 30 days ahead; queued, then rung at this time or the next opening of the calling hours after it. A time already past means now.
Try again when the person was not reached (no answer, busy, an answering machine, or a pick-up the agent never reached). A declined call, a dead number and a call somebody answered are never retried. A retry keeps the same id, rings only inside the calling hours, and is checked again against opt-outs and the do-not-call list.
Show child attributes
Show child attributes
{
"count": 2,
"noAnswerMinutes": 60,
"busyMinutes": 15
}
Which of the agent's numbers rings — its own, or one lent to it for calling (List Phone Numbers). Leave it out for the agent's own line. On an agent with several numbers, a chosen number places at most 200 calls a day.
Changes to the agent for this call only; the agent itself is not edited. Any other key is refused by name.
Show child attributes
Show child attributes
{
"voice": "priya",
"language": "Hindi",
"maxCallSeconds": 300
}
Response
Accepted: ringing now (out_…), or queued for later (sch_…, with scheduledFor).
The call's id — out_… when ringing now, sch_… when queued. Every later report and webhook carries it.
"out_9f2c1a44-6b0e-4c1d-8a7f-2e5b3c9d1f60"
The number as it will be dialled.
"919876543210"
The number it rings from. Null on a queued call that will use the agent's own line.
"918045678901"
Ringing now, or queued.
ringing, scheduled Your reference, echoed.
When a queued call will ring. Only on scheduled.