Click to Call API

Click to Call API

Connect one of your team members and a customer with one request from your CRM or app. We ring your team member first, then the customer, and keep you posted as the call goes.

Base URL
https://voice.agentive.co.in/api/public/v1
Format
JSON over HTTPS
Auth
x-api-key + x-api-secret

Introduction

Click to call lets your CRM, helpdesk or app start a phone call between one of your team members and a customer with a single request. We ring your team member first, on the phone or app they already use with us. When they answer, we call the customer from your business number and put the two of them together.

You build no call screen. Your team member's phone, or our app or web panel, is the call screen: they hear the customer, see who it is, and use the same buttons they use for any call. Your system starts the call, follows it live, and can press keypad, hold, transfer and end if you want buttons of your own.

What you can do

  • List your team members, see who can be rung right now, and map your own users to them by email.
  • Start a call: your team member is rung first, then the customer.
  • Follow the call through signed webhooks, or read it when you like.
  • Play keypad tones, hold, resume, transfer to a colleague, a call menu or a number, and hang up.
  • Receive the outcome your team member saves for the call, and download the recording.
What your account needs
Click to call needs the Telephony API on your account. It is included with the Phone + CRM plan; on other plans, ask your account manager. Without it every route on this page answers HTTP 403 with feature_disabled, and the click_to_call.* events are not offered. GET /account tells you what your key can use: products.click_to_call is true when your account has the Telephony API. It does not say whether click to call is already switched on for your account: until it is, every route on this page answers HTTP 503 with service_unavailable and retryable: false (no Retry-After), before the Telephony API is checked. Do not retry that answer; ask your account manager. An account with the Telephony API reads and receives events for all its calls.
One base URL, one set of keys
This API shares its base URL, keys, response envelope and error codes with the Voice Broadcast API. Events arrive through the account webhooks described in Telephony Webhooks; the ones a click to call sends are on this page.
Billing
If your account's outgoing minutes for the day are used up and its balance cannot cover more, a new call is refused with HTTP 402 insufficient_balance.

Quick start

Four steps from keys to a call on your own phone.

  1. Get your keys
    In the Voice dashboard, open Developers > API Access and copy your publishable key and secret. The secret is shown in full only once. Put both in environment variables on your server, for example AGENTIVE_KEY and AGENTIVE_SECRET.
  2. Find a team member
    List the team members who can be rung right now, and note the id or email of yours.
    cURL
    curl "https://voice.agentive.co.in/api/public/v1/members?ringable=true" \
      -H "x-api-key: $AGENTIVE_KEY" \
      -H "x-api-secret: $AGENTIVE_SECRET"
  3. Start a call
    Use your own mobile number as to. Your team member's app or panel rings; answer it, and your mobile rings next.
    cURL
    curl https://voice.agentive.co.in/api/public/v1/click-to-call \
      -H "x-api-key: $AGENTIVE_KEY" \
      -H "x-api-secret: $AGENTIVE_SECRET" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: first-click-to-call-1" \
      -d '{
        "member_email": "neha@example.in",
        "to": "98XXXXXX21",
        "reference_id": "test-1"
      }'
    Keep the unique_id from the response and read the call while it runs:
    cURL
    curl https://voice.agentive.co.in/api/public/v1/click-to-call/c2c_9f1c4e7a2b6d48e0a3c5f1d29b7e6a40 \
      -H "x-api-key: $AGENTIVE_KEY" \
      -H "x-api-secret: $AGENTIVE_SECRET"
  4. Add a webhook
    Under Developers > Webhooks, add an https:// endpoint and tick the Click to call events, so the call's progress comes to you instead of you asking for it. See Webhooks.

Platform conventions

Agentive ships two API families: the Voice APIs (Voice Broadcast, AI Voice Agent, Voice OTP and telephony webhooks) and the WhatsApp Business API. They share a brand, a dashboard account and a set of habits, but they are separate products and they do not agree on everything. This section is the one place that says what is common and, where they differ, exactly how.

Base URLs

Voice APIsWhatsApp Business API
Base URLhttps://voice.agentive.co.in/api/public/v1https://app.agentive.co.in/api/v1
CoversVoice Broadcast, AI Voice Agent, Voice OTP and the endpoints webhook events point back at. All of them share one base, one credential pair and one envelope.Template sends for one API campaign. Issued and managed from the Chat dashboard.
TransportHTTPS only, JSON in and JSON out.HTTPS only, JSON in and JSON out.

Authentication

Voice APIsWhatsApp Business API
CredentialOne publishable key (pk_live_...) and one secret (sk_live_...) per account.One token (agcamp_...) per API campaign, so a leaked token can only send that campaign.
How to send itx-api-key and x-api-secret headers, or HTTP Basic with the publishable key as the username and the secret as the password.Authorization: Bearer <token>.
ScopeAccount-wide. Every endpoint on the Voice base accepts it.Campaign-wide. The template and number are fixed on the campaign.
Treat asBoth keys are confidential. The publishable key names your account to anyone who holds it, so keep it with the secret, on your server only.A secret. Keep it on your server only.
RotationFrom the API Access tab of the Voice dashboard. By default the old secret stops working at once; for a planned change you can keep it working for 24 hours beside the new one.From the campaign page in the Chat dashboard. The old token stops working at once.

Error envelope

Voice APIsWhatsApp Business API
Shape{ status, code: number, error_code: string, error, details, unique_id, reference_id }{ error, code: string, details: object }
What to branch onerror_code. The numeric code mirrors the HTTP status and stays for compatibility.code, which is itself the machine string.
Human sentencedetails, repeated in error so a handler written for the WhatsApp shape reads a Voice error too.error.
Extra contextNamed fields beside the envelope (lines, lines_in_use, attempts_left, retry_after_sec, balance_inr).A details object (missing, retryAfter, meta, validation).

401 and 403

Voice APIsWhatsApp Business API
401Credentials missing, unknown, or rejected. The status never confirms whether a key exists.Token missing or wrong.
403Credentials accepted, but the account is not active or the product is not enabled.Token accepted, but the campaign is not live or the number is not connected.
404Not found, or not yours. A foreign id is never a 403, because that would confirm it exists.Not found, or not yours. Same rule.

Idempotency

Voice APIsWhatsApp Business API
KeyIdempotency-Key request header, 1 to 128 visible characters. A longer key is refused with HTTP 400.externalId in the body (the Idempotency-Key header is accepted too), up to 160 characters.
Window24 hours per account.The life of the campaign.
A repeatReplays the stored response. The one exception is a 5xx raised after the call was already handed to the network: that reply is stored as call_state_unknown with a sentence appended telling you to check the call before retrying. A 400 or 404 is stored for the 24 hours too, so once you fix the request or the dashboard setting, send it with a new key. A 401 is never stored: the credentials are checked before the key is read.HTTP 200 with duplicate: true and the earlier delivery's current status.
Replay markerIdempotent-Replayed: true, plus the original idempotency-replayed: true header.duplicate: true in the body.
Same key, different bodyHTTP 422 with idempotency_conflict.The first send stands; the second is reported as a duplicate.
Still in flightHTTP 409 with idempotency_in_progress and Retry-After.Not applicable.

Rate limits and headers

Voice APIsWhatsApp Business API
BudgetPer account: 120 reads a minute and 120 writes a minute, counted separately, so polling can never starve call placement.Per campaign token: 300 requests a minute over a sliding window.
Limit headersRateLimit-Limit, RateLimit-Remaining and RateLimit-Reset, with X-RateLimit-* aliases of the same three values.None.
How long to waitRetry-After on every 429 and every 409, mirrored in the body as retry_after_sec.Retry-After on rate_limited; details.retryAfter (IST) on quiet_hours.
Capacity refusalHTTP 429 concurrency_limit when the account has no free line, with lines, lines_in_use and retry_after_sec in the body and a Retry-After of 5 seconds. Call-placing responses also carry X-Agentive-Lines and X-Agentive-Lines-In-Use.Not applicable; there are no lines to exhaust.

Webhook signing

Voice APIsWhatsApp Business API
Header to verifyX-Agentive-Signature-V2: t=<unix seconds>,v2=<hex>X-Agentive-Signature: <hex>, with no prefix.
What is signedHMAC-SHA256, with the signing secret, of t, a dot, then the exact raw body.HMAC-SHA256 of the exact raw body with the endpoint's signing secret.
FreshnessReject a t more than 300 seconds from your own clock. The timestamp is inside the signed material, so a captured delivery cannot be sent again later.Nothing time-bound is signed. De-duplicate on the event id for good, and accept deliveries over HTTPS only.
ComparisonConstant time, on bytes. Reject on any mismatch, and reject a delivery with no version 2 header.Constant time, on bytes. Reject on any mismatch.
Two different signatures
The Voice APIs sign the timestamp together with the body, and you verify only X-Agentive-Signature-V2. Voice deliveries also carry the older body-only X-Agentive-Signature for receivers built before version 2; do not accept it on its own, because it proves nothing about when a body was sent. The WhatsApp signature is the body alone with no prefix. A shared handler needs one verifier per product.

Webhook delivery headers

Voice APIsWhatsApp Business API
Event nameX-Agentive-EventX-Agentive-Event
Event idX-Agentive-Event-Id, the same value as id in the body. Shared by every endpoint that receives the event.X-Agentive-Delivery, the same value as id in the body. Despite the name it is the event id, not a per-endpoint delivery id.
Delivery idX-Agentive-Delivery-Id (dlv_...), one per event per endpoint, stable across every retry.None. X-Agentive-Delivery is the Chat product's name for the event id above.
Attempt numberX-Agentive-Attempt, 1-based, counting through the whole retry schedule.None.
Attempt timeX-Agentive-Timestamp, Unix seconds at the moment of this attempt, the same value as t in the version 2 signature. Check the age against the signed t, never against this header alone.X-Agentive-Timestamp, the same timestamp as the envelope.
User agentAgentive-Webhooks/2026-06-01Agentive-Webhooks/2026-05-08, the Chat payload version.

Webhook retries

Voice APIsWhatsApp Business API
AttemptsNine: three fast, then six spread out.Three.
Schedule0.5 s and 2 s between the first three, then 1 min, 5 min, 30 min, 2 h, 6 h and 12 h.0 s, 2 s and 8 s.
Total windowAbout 20 hours, after which the delivery is marked failed.About 10 seconds.
Timeout per attempt6 seconds.10 seconds.
What you seeThe dashboard lists the latest 50 deliveries per endpoint with the event, the latest result, the number of tries and the time in IST. Each delivery is kept for 30 days, then deleted.The campaign's Deliveries tab shows the message and its current status.

Timestamps and amounts

Voice APIsWhatsApp Business API
Time zoneIndian Standard Time, ISO 8601 with a +05:30 offset. The field is named per product: created_at_ist on a call, expires_at_ist on a verification, timestamp_ist on a webhook envelope.timestamp on the webhook envelope is UTC ISO 8601. Schedules and quiet hours are set and reported in IST.
UTC alongsidecreated_at_utc on a call, and occurred_at (UTC) plus created (Unix seconds) on a webhook envelope.The envelope timestamp is already UTC.
MoneyIndian Rupees, always. balance_inr is a number, never a formatted string.Indian Rupees, always. Amounts appear in the dashboard, not in the send API.

Versioning and field case

Voice APIsWhatsApp Business API
API versionIn the path: /api/public/v1.In the path: /api/v1.
Payload versionapi_version on every webhook event. Currently 2026-06-01.apiVersion on every webhook event. Currently 2026-05-08.
CompatibilityFields are added, never removed or redefined. Ignore fields you do not recognise.Fields are added, never removed or redefined. Ignore fields you do not recognise.
Field casesnake_case throughout, request and response.camelCase throughout, request and response.

Security

  • Every credential is a secret. That includes the Voice publishable key, whatever its name suggests: treat it exactly like the secret. Keep all keys on your server, in an environment variable or a secrets store, never in a browser, a mobile app, a spreadsheet or source control.
  • Send them only to the API. Never forward a key or a secret to any other host, and do not let an HTTP client carry them across a redirect to another host.
  • Rotate at once if one leaks. Rotate the Voice pair from the API Access tab of the Voice dashboard and a WhatsApp token from its campaign page. A leaked value must stop working immediately, so update your server in the same step. (For a planned Voice change, and only then, you can keep the old secret working for 24 hours.)
  • HTTPS only. Write https:// in the base URL in your code. A request sent over plain HTTP has already exposed its credentials before any redirect can protect it.
  • No address allow-listing today. Requests are not restricted to your server's IP addresses, and webhooks are not sent from a fixed list of addresses. Authenticate every webhook by its signature, never by where it came from.
Report a vulnerability
If you find a security problem in the APIs, the webhooks or the dashboard, write to hello@agentive.co.in with the steps to reproduce it. Please do not test against accounts that are not yours. Our contact details are also published at /.well-known/security.txt.
Reading this as one platform
The habits that hold across both products: HTTPS only, JSON both ways, a stable machine code on every error, an idempotency key on every request a retry could repeat, HMAC-SHA256 on every webhook, Indian Rupees and Indian Standard Time. The habits that do not: the credential header, the envelope key names, the field case, the signature scheme and the retry schedule. Write one transport layer per product, then share everything above it.

Authentication

Every path on this page is relative to this base URL. Use https://; a request over plain HTTP exposes your keys before anything can protect them.

Base URL
https://voice.agentive.co.in/api/public/v1

Every request carries two keys from Developers > API Access in the Voice dashboard, sent as two headers:

Headers

HeaderTypeRequiredDescription
x-api-keystringRequired
Your publishable key (pk_live_...). Treat it as confidential. See Authentication.
x-api-secretstringRequired
Your secret key (sk_live_...). Server-side only.
cURL
curl https://voice.agentive.co.in/api/public/v1/members \
  -H "x-api-key: $AGENTIVE_KEY" \
  -H "x-api-secret: $AGENTIVE_SECRET"

HTTP Basic authentication

You may send the same pair as HTTP Basic auth instead: the publishable key as the username and the secret as the password. When an x-api-key header is present it wins. Basic auth is what the recording download uses, because an HTTP client never carries an Authorization header to another host.

cURL
curl https://voice.agentive.co.in/api/public/v1/members \
  -u "$AGENTIVE_KEY:$AGENTIVE_SECRET"
Keys belong on your server
  • The keys can start calls from your team to any number, so keep both on your server: never in a web page, a mobile app, a browser extension or source control. A CRM page or an app asks your server, and your server asks us. See Integrating.
  • Treat the publishable key like the secret, despite its name. The keys work for the whole account.
  • Send the keys only to voice.agentive.co.in, never to any other host.
  • If a key may have leaked, rotate it from API Access with Stop the old secret now (this also gives you a new publishable key). See Rotating your secret.

Authentication failures

SituationHTTPerror_code
No publishable key sent401missing_credentials
Unknown key, or a wrong secret401invalid_credentials
The account is not active403account_inactive
API access is not enabled for the account403api_disabled
The Telephony API is not on the account403feature_disabled

401 always means the keys were rejected. 403 always means they were accepted and the account is not entitled. A refusal about the call itself (a blocked number, outgoing calls switched off, verification pending) is a 422, never a 403.

How the team member is rung

Your team member is always rung first, on the lane they chose in their own settings. The API never chooses the lane and never changes it. calling_mode on List team members tells you which lane their next call takes, and mode on the click to call says which one it took.

Over the internet (internet)Phone Bridge on (phone_bridge)
Rings firstTheir web Agent Panel (marked Click to call) and the Agentive app on their phone, like an incoming call.Their own mobile number, over the phone network.
When they answerWe call the customer into the call they are already in, exactly as if they had pressed Call themselves. They hear the customer's phone ringing.We call the customer and connect them, exactly as Phone Bridge works for any call they make.
How long they are rungring_timeout_sec: 30 seconds unless you send between 10 and 60.About 28 seconds, set by the phone network.
During the callThe normal outgoing call bar: hold, keypad, transfer, end. Listed in Call History, and recorded when call recording is on for your account, like any call they place.A normal phone call on their mobile. Listed in Call History, and recorded when call recording is on, like any Phone Bridge call.
Lines usedNot counted toward your account's line limit, like any call a team member places from the panel or app.One toward your line limit when it is placed. While it is live, an incoming call counts it as two (one to the team member, one to the customer) when we check whether the incoming call fits.

What your team member sees

  • The ring shows the customer's name from your contacts, or else the customer_name you sent, or else the number. A team member set to see customer numbers hidden sees the number hidden, as everywhere else.
  • If they decline, or do not answer in time, nothing more happens: the customer is never called, and the click to call ends as member_declined or member_no_answer.
  • After the call they can save an outcome, a note and a callback time, as for any call; you receive it as click_to_call.tagged.
  • When your system holds the call, the web panel shows it as Held.
A team member who cannot be rung is refused first
A team member who is Offline, on a break, on another call, or (over the internet) has no panel or app signed in is never rung. The request is refused straight away with member_unavailable or member_on_call, and nothing rings anywhere.

Integrating

The keys never leave your server, wherever the Call button is. Your page or app asks your server, your server asks us, and the progress comes back to your server as webhooks.

From a web CRM

  1. Map your users once
    Call List team members and match each of your users to a team member by email. Keep the member id, or send member_email each time.
  2. Add a Call button
    The button calls your own server with the record it belongs to. Your server looks up the customer's number in your own records and calls Start a click to call with the signed-in user's email, the number, your record id as reference_id and a fresh Idempotency-Key per click.
  3. Show the progress
    Push the click_to_call.* events from your webhook to the page, or poll your server, which reads the click to call every 3 seconds at most.
  4. Write the result back
    On click_to_call.ended, store the outcome and duration_sec on the record. On click_to_call.tagged, store your team member's outcome and note.

From your own mobile app

Your app never holds the keys: anything inside an app can be read by whoever installs it. The app calls your server, your server calls us, and your team member's phone rings.

Sequence
Your app            Your server              Agentive API             Team member
   |                     |                         |                        |
   | tap "Call"          |                         |                        |
   | (lead id) --------->|                         |                        |
   |                     | POST /click-to-call     |                        |
   |                     | (member_email, to) ---->|                        |
   |                     |                         | rings their phone,     |
   |                     |<--- 201 c2c_...         | app or web panel ----->|
   |<--- "Ringing Neha"  |     member_ringing      |                        |
   |                     |                         |<------------- answers  |
   |                     |<--- webhook events      | calls the customer     |
   |<--- live status     |     (signed)            | into their call        |
   |                     |                         |                        |
  • The app sends your own record id (a lead, a ticket), never a phone number, so a tampered app cannot make your team call anyone it likes.
  • Your server knows who is signed in to the app, and sends that person's email as member_email.
  • The app learns the state from your server: poll it, or push the webhook state to the app.
Node.js (your server)
import express from "express";

const API = "https://voice.agentive.co.in/api/public/v1";
const KEY = process.env.AGENTIVE_KEY;
const SECRET = process.env.AGENTIVE_SECRET;
if (!KEY || !SECRET) throw new Error("AGENTIVE_KEY and AGENTIVE_SECRET must be set");

const app = express();
app.use(express.json());

async function agentive(path, { method = "GET", body, idempotencyKey } = {}) {
  const res = await fetch(`${API}${path}`, {
    method,
    headers: {
      "x-api-key": KEY,
      "x-api-secret": SECRET,
      ...(body ? { "Content-Type": "application/json" } : {}),
      ...(idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {}),
    },
    body: body ? JSON.stringify(body) : undefined,
  });
  return { status: res.status, data: await res.json() };
}

// requireAppUser and db are your own: your app's sign-in check and your records.
// The app sends a lead id, never a phone number: the number comes from your own
// records, so a tampered app cannot make your team call anyone it likes.
app.post("/calls", requireAppUser, async (req, res) => {
  const lead = await db.leads.findForUser(req.user.id, req.body.lead_id);
  if (!lead) return res.status(404).json({ error: "Lead not found" });

  const { status, data } = await agentive("/click-to-call", {
    method: "POST",
    // One key per tap, so a network retry of this request never rings twice.
    idempotencyKey: `lead-${lead.id}-tap-${req.body.tap_id}`,
    body: {
      member_email: req.user.email, // your user is the team member with this email
      to: lead.phone,
      customer_name: lead.name,
      reference_id: `lead-${lead.id}`,
    },
  });
  if (data.status !== "success") {
    // Show the sentence, branch on error_code, offer a retry only when retryable.
    return res.status(status).json({ error: data.details, code: data.error_code, retryable: data.retryable === true });
  }
  await db.calls.save({ userId: req.user.id, leadId: lead.id, clickToCallId: data.unique_id });
  res.status(201).json({ id: data.unique_id, state: data.click_to_call.state });
});

// The app polls this every 3 seconds at most, or you push the webhook state to it.
app.get("/calls/:id", requireAppUser, async (req, res) => {
  const call = await db.calls.findForUser(req.user.id, req.params.id);
  if (!call) return res.status(404).json({ error: "Not found" });
  const { data } = await agentive(`/click-to-call/${encodeURIComponent(call.clickToCallId)}`);
  const c = data.click_to_call;
  res.json({ state: c.state, active: c.active, on_hold: c.on_hold, outcome: c.outcome });
});

app.listen(3000);

Endpoints

EndpointPurpose
GET /membersYour team members, and whether each can be rung now.
GET /members/{member_id}One team member.
GET /call-menusYour call menus, as transfer targets.
POST /click-to-callRing a team member, then the customer.
GET /click-to-call/{id}Read one click to call.
GET /click-to-callPage through click to calls, or catch up after a missed webhook.
POST /click-to-call/{id}/hangupCancel it, or end the conversation.
POST /click-to-call/{id}/dtmfPlay keypad tones into the call.
POST /click-to-call/{id}/holdPut the customer on hold.
POST /click-to-call/{id}/unholdTake the customer off hold.
POST /click-to-call/{id}/transferHand the customer to a colleague, a call menu or a number.
GET /calls/{call_id}/recordingThe recording, streamed, and each of its parts.

List team members

GET/members

Your team members, with whether each can be rung through this API at all (can_click_to_call) and right now (ringable_now), the lane their next call takes and the numbers they can call from. Platform staff are never listed.

Headers

HeaderTypeRequiredDescription
x-api-keystringRequired
Your publishable key (pk_live_...). Treat it as confidential. See Authentication.
x-api-secretstringRequired
Your secret key (sk_live_...). Server-side only.

Query parameters

ParameterTypeRequiredDescription
qstringOptional
Part of a name or an email.
Example: neha
ringablebooleanOptional
true lists only the team members a click to call would ring now.
pagenumberOptional
The page of results to return. Default 1.
per_pagenumberOptional
Results per page. Default 50, maximum 100.

Request

curl "https://voice.agentive.co.in/api/public/v1/members?q=neha&per_page=20" \
  -H "x-api-key: $AGENTIVE_KEY" \
  -H "x-api-secret: $AGENTIVE_SECRET"

Response

JSON
{
  "status": "success",
  "code": 200,
  "data": [
    {
      "id": 1042,
      "object": "member",
      "name": "Neha Sharma",
      "email": "neha@example.in",
      "role": "agent",
      "calling_mode": "internet",
      "presence": "available",
      "on_call": false,
      "current_click_to_call_id": null,
      "can_click_to_call": true,
      "unavailable_reason": null,
      "ringable_now": true,
      "not_ringable_reason": null,
      "numbers": [
        {
          "number": "+9111XXXXXX45",
          "shared": true,
          "default": true
        }
      ],
      "mobile_last4": null
    },
    {
      "id": 1057,
      "object": "member",
      "name": "Arjun Mehta",
      "email": "arjun@example.in",
      "role": "agent",
      "calling_mode": "phone_bridge",
      "presence": "available",
      "on_call": true,
      "current_click_to_call_id": "c2c_4d7b1e9c3a5f42d8b6e0c2a1f9d37b58",
      "can_click_to_call": true,
      "unavailable_reason": null,
      "ringable_now": false,
      "not_ringable_reason": "on_call",
      "numbers": [
        {
          "number": "+9179XXXXXX12",
          "shared": false,
          "default": true
        }
      ],
      "mobile_last4": "4521"
    }
  ],
  "pagination": {
    "page": 1,
    "per_page": 50,
    "total": 2,
    "total_pages": 1
  }
}

Response fields

FieldTypeDescription
data[].idnumber
The member_id to send.
data[].objectstring
Always member.
data[].name, data[].emailstring
As set in Team Members. Map your own users to team members by this email.
data[].rolestring
org_admin (an account admin), manager, agent or viewer.
data[].calling_modestring
internet or phone_bridge: the lane the next call would take. The team member's own setting; see How the team member is rung.
data[].presencestring
available, break or offline, as in the Agent Panel. A member who went offline automatically while still signed in on a device reads available, as for incoming calls.
data[].on_callboolean
On a call right now, or being rung.
data[].current_click_to_call_idstring | null
The click to call they are on, when there is one.
data[].can_click_to_callboolean
Whether the member can be rung through this API at all.
data[].unavailable_reasonstring | null
Why not, when can_click_to_call is false: no_calling_number, app_agent, paused, mobile_not_verified or not_a_calling_role.
data[].ringable_nowboolean
Whether a click to call started now would ring them.
data[].not_ringable_reasonstring | null
Why not, when ringable_now is false: offline, on_break, no_device (no web panel or app signed in), on_call or cooldown.
data[].numbersobject[]
The numbers they can call from: number (E.164), shared (a team line) and default (used when you send no caller_id).
data[].mobile_last4string | null
The last four digits of their verified mobile number, or null. The full number is never returned.

Errors

StatusCodeerror_codeWhenWhat to do
403403feature_disabledYour account does not have the Telephony API, or (on a transfer) the kind of transfer you asked for: transfer to a call menu, or to an outside number.Contact your account manager.
429429rate_limitedYour request budget, the reading budget of a live click to call, or your account's calls a minute, is spent.Wait for Retry-After.

Get a team member

GET/members/{member_id}

One team member, in the same shape as the list, inside data.

Headers

HeaderTypeRequiredDescription
x-api-keystringRequired
Your publishable key (pk_live_...). Treat it as confidential. See Authentication.
x-api-secretstringRequired
Your secret key (sk_live_...). Server-side only.

Path parameters

ParameterTypeRequiredDescription
member_idnumberRequired
The team member's id.
Example: 1042

Request

cURL
curl https://voice.agentive.co.in/api/public/v1/members/1042 \
  -H "x-api-key: $AGENTIVE_KEY" \
  -H "x-api-secret: $AGENTIVE_SECRET"

Response

JSON
{
  "status": "success",
  "code": 200,
  "data": {
    "id": 1042,
    "object": "member",
    "name": "Neha Sharma",
    "email": "neha@example.in",
    "role": "agent",
    "calling_mode": "internet",
    "presence": "available",
    "on_call": false,
    "current_click_to_call_id": null,
    "can_click_to_call": true,
    "unavailable_reason": null,
    "ringable_now": true,
    "not_ringable_reason": null,
    "numbers": [
      {
        "number": "+9111XXXXXX45",
        "shared": true,
        "default": true
      }
    ],
    "mobile_last4": null
  }
}

Errors

StatusCodeerror_codeWhenWhat to do
404404member_not_foundNo team member with that id or email on your account. Also answered for a colleague named on a transfer.Check the id or email with List team members.
403403feature_disabledYour account does not have the Telephony API, or (on a transfer) the kind of transfer you asked for: transfer to a call menu, or to an outside number.Contact your account manager.
429429rate_limitedYour request budget, the reading budget of a live click to call, or your account's calls a minute, is spent.Wait for Retry-After.

List call menus

GET/call-menus

Your call menus, the targets of a transfer with to_menu_id. Only an account that can transfer calls to a call menu lists them; any other account gets an empty list and transfer_to_menu: false.

Headers

HeaderTypeRequiredDescription
x-api-keystringRequired
Your publishable key (pk_live_...). Treat it as confidential. See Authentication.
x-api-secretstringRequired
Your secret key (sk_live_...). Server-side only.

Request

cURL
curl https://voice.agentive.co.in/api/public/v1/call-menus \
  -H "x-api-key: $AGENTIVE_KEY" \
  -H "x-api-secret: $AGENTIVE_SECRET"

Response

JSON
{
  "status": "success",
  "code": 200,
  "transfer_to_menu": true,
  "data": [
    {
      "id": 12,
      "name": "Sales and support",
      "active": true,
      "open_now": true
    },
    {
      "id": 15,
      "name": "After hours",
      "active": true,
      "open_now": false
    }
  ]
}

Response fields

FieldTypeDescription
transfer_to_menuboolean
Whether your account can transfer a call to a call menu.
data[].idnumber
The to_menu_id to send.
data[].namestring
As named in the dashboard.
data[].activeboolean
The menu is switched on.
data[].open_nowboolean
The menu's working hours are open now.

Errors

StatusCodeerror_codeWhenWhat to do
403403feature_disabledYour account does not have the Telephony API, or (on a transfer) the kind of transfer you asked for: transfer to a call menu, or to an outside number.Contact your account manager.
429429rate_limitedYour request budget, the reading budget of a live click to call, or your account's calls a minute, is spent.Wait for Retry-After.

Start a click to call

POST/click-to-call

Ring a team member, then the customer. Every check runs before anything rings: the team member, their presence and their limits, the number, your account's settings and limits. If one fails, the request is refused, nothing rings, and no click to call is created. If they all pass, the answer is 201 with the click to call in member_ringing, and its id as unique_id. Unknown fields are refused, so a typo never silently does something else.

Headers

HeaderTypeRequiredDescription
x-api-keystringRequired
Your publishable key (pk_live_...). Treat it as confidential. See Authentication.
x-api-secretstringRequired
Your secret key (sk_live_...). Server-side only.
Content-TypestringRequired
Always application/json.
Idempotency-KeystringOptional
A key unique to this request, 1 to 128 visible ASCII characters. Send one on every POST: a retry with the same key returns the first answer and never rings anyone twice. See Idempotency.
Example: lead-2291-call-1

Request body

FieldTypeRequiredDescription
member_idnumberOptional
The team member to ring. Send exactly one of member_id and member_email.
Example: 1042
member_emailstringOptional
The team member's email in Team Members, instead of member_id.
Example: neha@example.in
tostringRequired
The customer: an Indian number in any common form, 98XXXXXX21, +91 98XXX XXX21, 098XXXXXX21, or a landline with its code. Never one of your own account's numbers.
Example: 98XXXXXX21
caller_idstringOptional
The number the customer sees: one of the team member's numbers, matched on its last ten digits. Leave it out to use their default.
Example: 011XXXXXX45
customer_namestringOptional
Shown on the team member's ring when your contacts have no name for the number. Up to 60 characters; control and text-direction characters are removed first.
Example: Ravi Kumar
ring_timeout_secnumberOptional
How long the team member's app and panel ring, 10 to 60. Default 30. A Phone Bridge mobile rings for about 28 seconds whatever you send.
Example: 30
reference_idstringOptional
Your own id for the call, up to 120 characters and no control characters. Echoed on every answer and every event. It never de-duplicates anything; use an Idempotency-Key for that.
Example: LEAD-2291
metadataobjectOptional
Your own data. Up to 10 keys of 1 to 40 letters, digits and _; each value a string or a number, up to 200 characters, no control characters. Echoed on every answer and event, never spoken. Do not put anything secret in it.
Example: { "deal": "D-77" }
dry_runbooleanOptional
true runs every check and answers 200 with the team member, the lane and the caller ID, without ringing anyone. See Dry run.

Request

curl https://voice.agentive.co.in/api/public/v1/click-to-call \
  -H "x-api-key: $AGENTIVE_KEY" \
  -H "x-api-secret: $AGENTIVE_SECRET" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: lead-2291-call-1" \
  -d '{
    "member_email": "neha@example.in",
    "to": "98XXXXXX21",
    "customer_name": "Ravi Kumar",
    "reference_id": "LEAD-2291",
    "metadata": { "deal": "D-77", "crm_owner": "neha" }
  }'

Response (201)

JSON
{
  "status": "success",
  "code": 201,
  "unique_id": "c2c_9f1c4e7a2b6d48e0a3c5f1d29b7e6a40",
  "reference_id": "LEAD-2291",
  "details": "Ringing the team member.",
  "click_to_call": {
    "id": "c2c_9f1c4e7a2b6d48e0a3c5f1d29b7e6a40",
    "object": "click_to_call",
    "state": "member_ringing",
    "active": true,
    "on_hold": false,
    "mode": "internet",
    "member": {
      "id": 1042,
      "name": "Neha Sharma"
    },
    "from_number": "+9111XXXXXX45",
    "to_number": "+9198XXXXXX21",
    "customer_name": "Ravi Kumar",
    "call_id": null,
    "reference_id": "LEAD-2291",
    "metadata": {
      "deal": "D-77",
      "crm_owner": "neha"
    },
    "created_at": "2026-10-12T05:31:02.000Z",
    "created_at_ist": "2026-10-12T11:01:02.000+05:30",
    "member_answered_at": null,
    "member_answered_at_ist": null,
    "customer_answered_at": null,
    "customer_answered_at_ist": null,
    "ended_at": null,
    "ended_at_ist": null,
    "duration_sec": 0,
    "outcome": null,
    "ended_by": null,
    "end_reason": null,
    "transferred_to": null,
    "recording_available": false,
    "recording_url": null,
    "disposition": null,
    "sequence": 1,
    "updated_at": "2026-10-12T05:31:02.000Z",
    "updated_at_ist": "2026-10-12T11:01:02.000+05:30",
    "dial_started_at": null,
    "dial_started_at_ist": null,
    "customer_ringing_at": null,
    "customer_ringing_at_ist": null,
    "member_ring_sec": null,
    "customer_ring_sec": null,
    "call_duration_sec": null,
    "recording": {
      "status": "none",
      "revision": 0,
      "url": null,
      "format": null,
      "size_bytes": null,
      "duration_sec": null,
      "channels": null,
      "channel_layout": null,
      "retention_days": 60,
      "expires_at": "2026-12-11T05:31:02.000Z",
      "parts": [],
      "parts_ordered": false
    }
  }
}

Response fields

FieldTypeDescription
unique_idstring
The click to call id. Keep it: every read and every event for this call carries it.
detailsstring
Ringing the team member.
click_to_callobject
The click to call object, in member_ringing.

Errors

StatusCodeerror_codeWhenWhat to do
400400validation_errorA field is missing, has the wrong type or breaks a rule, or the body carries a field the API does not take. field names it, for example agent_id in "Unknown field: agent_id".Fix the request, then send it with a new Idempotency-Key (a 400 is kept for the key).
400400invalid_numberto (or to_number on a transfer) is not a number we can dial in India, or it is one of your own account's numbers.Fix the number.
400400international_not_supportedThe number is outside India, or your account's filter for numbers outside India refuses it.Call only Indian numbers.
400400invalid_caller_idcaller_id is not one of the team member's numbers.Send one of the member's numbers, or leave it out to use their default.
404404member_not_foundNo team member with that id or email on your account. Also answered for a colleague named on a transfer.Check the id or email with List team members.
422422member_cannot_callThe team member cannot place calls at all. details names why: no calling number, an app-only member, a paused member, a Phone Bridge mobile number that is not verified, or a role that does not place calls (unavailable_reason on the member says the same).Fix it in Team Members, or pick another member.
409409member_unavailableThe team member is Offline or on a break, or (calls over the internet) has no web panel or app signed in. details says which. Nothing rang.Ask them to go Available or sign in, or call another member. Retry after Retry-After.
409409member_on_callThe team member is on a call, or is already being rung by another click to call or a campaign.Retry after Retry-After, when `ringable_now` on the member is true again.
429429member_cooldownThe team member missed or declined two click to calls in a row in the last 10 minutes.Wait for Retry-After (when the cooldown ends), or call another member.
429429member_limitThe team member was rung 60 times in the last hour, or 300 times today (IST).Wait for Retry-After, or call another member.
429429number_flood3 calls through the API to this number in 10 minutes, or 10 today (IST), counted across every API that places calls. Both limits answer this one code; details says which.Wait for Retry-After (up to 600 seconds, or until midnight IST). Do not retry sooner.
429429rate_limitedYour request budget, the reading budget of a live click to call, or your account's calls a minute, is spent.Wait for Retry-After.
429429concurrency_limitYour account's line limit is reached by the calls live right now. A Phone Bridge click to call counts as one line when it is placed; a click to call over the internet is not counted, like any call a team member places from the panel or app.Retry after Retry-After.
402402insufficient_balanceYour account's outgoing minutes for today are used up and its balance cannot cover more. No Retry-After is sent: it clears when you top up.Top up, then retry with the same Idempotency-Key.
422422number_blockedThe number is on the do not disturb list, or on your account's or the team member's block list.Do not call this number.
422422outbound_blockedOutgoing calls are switched off on your account, the account is paused, or its plan has ended.Contact your account manager.
422422verification_requiredYour account cannot place calls until its business verification is complete.Finish business verification in the dashboard.
403403feature_disabledYour account does not have the Telephony API, or (on a transfer) the kind of transfer you asked for: transfer to a call menu, or to an outside number.Contact your account manager.
409409idempotency_in_progressA request with the same Idempotency-Key is still running.Wait for Retry-After and send it again with the same key.
422422idempotency_conflictThe same Idempotency-Key was sent with a different body.Use a new key for a new request.
502502call_not_placedWe could not place the call, or carry out the control, on our side. On a start, a keypad or a transfer request the Idempotency-Key is then used up: sending it again replays call_state_unknown. A start refused this way carries no unique_id.Read the click to call (on a start, list by member_id or reference_id with created_from) before you try again, then use a new Idempotency-Key.
503503service_unavailableThe line for this caller ID cannot be reached right now, the call could not be reached to carry out a control, or the record could not be read. No Retry-After is sent. When the body says retryable: false (details "Click to call is switched off for this account."), click to call is not switched on for your account right now: every route answers this, before the Telephony API is checked.Wait about 30 seconds (add some jitter), then try again. After a keypad or transfer request we cannot be sure nothing was done: read the click to call first and use a new Idempotency-Key. If retryable is false, do not retry: contact your account manager.
400 for a field the API does not take
HTTP/1.1 400 Bad Request

{
  "status": "error",
  "code": 400,
  "unique_id": null,
  "reference_id": "LEAD-2291",
  "details": "Unknown field: agent_id",
  "error_code": "validation_error",
  "error": "Unknown field: agent_id",
  "retryable": false,
  "field": "agent_id"
}
429 after two missed rings in a row
HTTP/1.1 429 Too Many Requests
Retry-After: 412

{
  "status": "error",
  "code": 429,
  "unique_id": null,
  "reference_id": "LEAD-2291",
  "details": "This team member missed or declined two calls in a row. Try again in a few minutes, or call another member.",
  "error_code": "member_cooldown",
  "error": "This team member missed or declined two calls in a row. Try again in a few minutes, or call another member.",
  "retryable": true,
  "retry_after_sec": 412
}
422 for a blocked number
HTTP/1.1 422 Unprocessable Entity

{
  "status": "error",
  "code": 422,
  "unique_id": null,
  "reference_id": "LEAD-2291",
  "details": "This number cannot be called.",
  "error_code": "number_blocked",
  "error": "This number cannot be called.",
  "retryable": false
}
Is the team member free?
You do not have to check first: a team member who cannot be rung is refused straight away. To show it in your own screen before the click, read ringable_now on Get a team member.

Get a click to call

GET/click-to-call/{id}

Read one click to call, always from our current record. Poll every 3 seconds at most, and stop once active is false. A live click to call has its own reading budget, so polling it does not use up your account's reads; see Limits and polling. Webhooks are the better way to wait.

Headers

HeaderTypeRequiredDescription
x-api-keystringRequired
Your publishable key (pk_live_...). Treat it as confidential. See Authentication.
x-api-secretstringRequired
Your secret key (sk_live_...). Server-side only.

Path parameters

ParameterTypeRequiredDescription
idstringRequired
The click to call id, c2c_ and 32 hex characters, as returned in unique_id when you started it. Another account's id answers 404.
Example: c2c_9f1c4e7a2b6d48e0a3c5f1d29b7e6a40

Request

curl https://voice.agentive.co.in/api/public/v1/click-to-call/c2c_9f1c4e7a2b6d48e0a3c5f1d29b7e6a40 \
  -H "x-api-key: $AGENTIVE_KEY" \
  -H "x-api-secret: $AGENTIVE_SECRET"

Response

JSON
{
  "status": "success",
  "code": 200,
  "unique_id": "c2c_9f1c4e7a2b6d48e0a3c5f1d29b7e6a40",
  "reference_id": "LEAD-2291",
  "details": "in_progress",
  "click_to_call": {
    "id": "c2c_9f1c4e7a2b6d48e0a3c5f1d29b7e6a40",
    "object": "click_to_call",
    "state": "in_progress",
    "active": true,
    "on_hold": false,
    "mode": "internet",
    "member": {
      "id": 1042,
      "name": "Neha Sharma"
    },
    "from_number": "+9111XXXXXX45",
    "to_number": "+9198XXXXXX21",
    "customer_name": "Ravi Kumar",
    "call_id": "6b1e2f3a-4c5d-4e6f-8a7b-9c0d1e2f3a4b",
    "reference_id": "LEAD-2291",
    "metadata": {
      "deal": "D-77",
      "crm_owner": "neha"
    },
    "created_at": "2026-10-12T05:31:02.000Z",
    "created_at_ist": "2026-10-12T11:01:02.000+05:30",
    "member_answered_at": "2026-10-12T05:31:09.204Z",
    "member_answered_at_ist": "2026-10-12T11:01:09.204+05:30",
    "customer_answered_at": "2026-10-12T05:31:19.516Z",
    "customer_answered_at_ist": "2026-10-12T11:01:19.516+05:30",
    "ended_at": null,
    "ended_at_ist": null,
    "duration_sec": 42,
    "outcome": null,
    "ended_by": null,
    "end_reason": null,
    "transferred_to": null,
    "recording_available": false,
    "recording_url": null,
    "disposition": null,
    "sequence": 4,
    "updated_at": "2026-10-12T05:31:19.516Z",
    "updated_at_ist": "2026-10-12T11:01:19.516+05:30",
    "dial_started_at": "2026-10-12T05:31:10.000Z",
    "dial_started_at_ist": "2026-10-12T11:01:10.000+05:30",
    "customer_ringing_at": "2026-10-12T05:31:10.400Z",
    "customer_ringing_at_ist": "2026-10-12T11:01:10.400+05:30",
    "member_ring_sec": 7,
    "customer_ring_sec": 9,
    "call_duration_sec": null,
    "recording": {
      "status": "processing",
      "revision": 0,
      "url": null,
      "format": null,
      "size_bytes": null,
      "duration_sec": null,
      "channels": null,
      "channel_layout": null,
      "retention_days": 60,
      "expires_at": "2026-12-11T05:31:10.000Z",
      "parts": [],
      "parts_ordered": false
    }
  }
}

Response fields

FieldTypeDescription
unique_idstring
The click to call id.
detailsstring
The state, as a word.
click_to_callobject

Errors

StatusCodeerror_codeWhenWhat to do
404404not_foundNo click to call with that id on your account.Check the id.
403403feature_disabledYour account does not have the Telephony API, or (on a transfer) the kind of transfer you asked for: transfer to a call menu, or to an outside number.Contact your account manager.
429429rate_limitedYour request budget, the reading budget of a live click to call, or your account's calls a minute, is spent.Wait for Retry-After.
The call record of the same call
GET /calls/{id} also accepts a c2c_ id and answers the call record. Its answered_at is usually the call record's own, to the second, after a transfer too, while the click to call's customer_answered_at carries milliseconds, so the two can differ by up to a second. Only before the call record exists is its answered_at the click to call's customer_answered_at itself. The click to call's ended_at usually carries milliseconds too (since 14 October 2026), so it can read up to a second later than the call record's ended_at. A call that ends in the same second as the customer's answer (or, on Phone Bridge, the team member's) never reads an end earlier than that answer.

List click to calls

GET/click-to-call

Your click to calls, newest first. With updated_since they are ordered by when they last changed instead, oldest change first, to catch up on anything a webhook did not bring you: pass pagination.next_cursor as cursor with the same filters until next_cursor is null. A click to call that changes during your walk moves to the end and is read again, never skipped. For the next catch-up, pass the highest updated_at you read as updated_since, and keep the copy with the highest sequence. A query parameter we do not know is reported in warnings and the X-Agentive-Warning header, and will later be refused.

Headers

HeaderTypeRequiredDescription
x-api-keystringRequired
Your publishable key (pk_live_...). Treat it as confidential. See Authentication.
x-api-secretstringRequired
Your secret key (sk_live_...). Server-side only.

Query parameters

ParameterTypeRequiredDescription
statestringOptional
Only this state.
activebooleanOptional
true for calls still going, false for ended ones.
member_idnumberOptional
Only this team member's.
reference_idstringOptional
Only those with this reference_id.
created_from, created_tostringOptional
ISO 8601. Started at or after created_from, and at or before created_to (both ends included).
Example: 2026-10-12T00:00:00+05:30
updated_sincestringOptional
ISO 8601. Only those that changed at or after this time, oldest change first.
Example: 2026-10-12T05:00:00Z
cursorstringOptional
The pagination.next_cursor of the previous page, sent with the same filters. A page read with a cursor carries null page, total and total_pages.
pagenumberOptional
The page of results to return. Default 1.
per_pagenumberOptional
Results per page. Default 50, maximum 100.

Request

# Everything that changed since your last successful sync, to catch a missed webhook.
curl "https://voice.agentive.co.in/api/public/v1/click-to-call?updated_since=2026-10-12T05:00:00Z&per_page=100" \
  -H "x-api-key: $AGENTIVE_KEY" \
  -H "x-api-secret: $AGENTIVE_SECRET"

Response

JSON
{
  "status": "success",
  "code": 200,
  "data": [
    {
      "id": "c2c_9f1c4e7a2b6d48e0a3c5f1d29b7e6a40",
      "object": "click_to_call",
      "state": "completed",
      "active": false,
      "on_hold": false,
      "mode": "internet",
      "member": {
        "id": 1042,
        "name": "Neha Sharma"
      },
      "from_number": "+9111XXXXXX45",
      "to_number": "+9198XXXXXX21",
      "customer_name": "Ravi Kumar",
      "call_id": "6b1e2f3a-4c5d-4e6f-8a7b-9c0d1e2f3a4b",
      "reference_id": "LEAD-2291",
      "metadata": {
        "deal": "D-77",
        "crm_owner": "neha"
      },
      "created_at": "2026-10-12T05:31:02.000Z",
      "created_at_ist": "2026-10-12T11:01:02.000+05:30",
      "member_answered_at": "2026-10-12T05:31:09.204Z",
      "member_answered_at_ist": "2026-10-12T11:01:09.204+05:30",
      "customer_answered_at": "2026-10-12T05:31:19.516Z",
      "customer_answered_at_ist": "2026-10-12T11:01:19.516+05:30",
      "ended_at": "2026-10-12T05:32:53.000Z",
      "ended_at_ist": "2026-10-12T11:02:53.000+05:30",
      "duration_sec": 94,
      "outcome": "completed",
      "ended_by": "customer",
      "end_reason": "customer_ended",
      "transferred_to": null,
      "recording_available": true,
      "recording_url": "https://voice.agentive.co.in/api/public/v1/calls/6b1e2f3a-4c5d-4e6f-8a7b-9c0d1e2f3a4b/recording",
      "disposition": {
        "outcome": "callback",
        "note": "Wants the Gold plan quote on email first.",
        "callback_at": "2026-10-14T05:30:00.000Z",
        "tagged_at": "2026-10-12T05:34:05.000Z"
      },
      "sequence": 8,
      "updated_at": "2026-10-12T05:34:05.000Z",
      "updated_at_ist": "2026-10-12T11:04:05.000+05:30",
      "dial_started_at": "2026-10-12T05:31:10.000Z",
      "dial_started_at_ist": "2026-10-12T11:01:10.000+05:30",
      "customer_ringing_at": "2026-10-12T05:31:10.400Z",
      "customer_ringing_at_ist": "2026-10-12T11:01:10.400+05:30",
      "member_ring_sec": 7,
      "customer_ring_sec": 9,
      "call_duration_sec": 94,
      "recording": {
        "status": "available",
        "revision": 1,
        "url": "https://voice.agentive.co.in/api/public/v1/calls/6b1e2f3a-4c5d-4e6f-8a7b-9c0d1e2f3a4b/recording",
        "format": "ogg",
        "size_bytes": 381952,
        "duration_sec": 94.6,
        "channels": 2,
        "channel_layout": {
          "left": "customer",
          "right": "member"
        },
        "retention_days": 60,
        "expires_at": "2026-12-11T05:31:10.000Z",
        "parts": [
          {
            "part": 1,
            "primary": true,
            "segment": null,
            "url": "https://voice.agentive.co.in/api/public/v1/calls/6b1e2f3a-4c5d-4e6f-8a7b-9c0d1e2f3a4b/recording",
            "format": "ogg",
            "size_bytes": 381952,
            "duration_sec": 94.6,
            "channels": 2,
            "started_at": "2026-10-12T05:31:19.516Z",
            "member": null
          }
        ],
        "parts_ordered": true
      }
    },
    {
      "id": "c2c_4d7b1e9c3a5f42d8b6e0c2a1f9d37b58",
      "object": "click_to_call",
      "state": "transferred",
      "active": false,
      "on_hold": false,
      "mode": "phone_bridge",
      "member": {
        "id": 1057,
        "name": "Arjun Mehta"
      },
      "from_number": "+9179XXXXXX12",
      "to_number": "+9197XXXXXX08",
      "customer_name": "Meera Iyer",
      "call_id": "8d3f4b5c-6e7a-4b8c-9d0e-1f2a3b4c5d6e",
      "reference_id": "LEAD-2304",
      "metadata": {
        "deal": "D-81"
      },
      "created_at": "2026-10-12T06:02:14.000Z",
      "created_at_ist": "2026-10-12T11:32:14.000+05:30",
      "member_answered_at": "2026-10-12T06:02:20.418Z",
      "member_answered_at_ist": "2026-10-12T11:32:20.418+05:30",
      "customer_answered_at": "2026-10-12T06:02:31.612Z",
      "customer_answered_at_ist": "2026-10-12T11:32:31.612+05:30",
      "ended_at": "2026-10-12T06:03:32.000Z",
      "ended_at_ist": "2026-10-12T11:33:32.000+05:30",
      "duration_sec": 61,
      "outcome": "transferred",
      "ended_by": "api",
      "end_reason": "transferred",
      "transferred_to": {
        "kind": "member",
        "member_id": 1042,
        "member_name": "Neha Sharma",
        "menu_id": null,
        "menu_name": null,
        "number": null
      },
      "recording_available": false,
      "recording_url": null,
      "disposition": null,
      "sequence": 5,
      "updated_at": "2026-10-12T06:03:32.000Z",
      "updated_at_ist": "2026-10-12T11:33:32.000+05:30",
      "dial_started_at": null,
      "dial_started_at_ist": null,
      "customer_ringing_at": "2026-10-12T06:02:20.418Z",
      "customer_ringing_at_ist": "2026-10-12T11:32:20.418+05:30",
      "member_ring_sec": 6,
      "customer_ring_sec": 11,
      "call_duration_sec": 248,
      "recording": {
        "status": "processing",
        "revision": 0,
        "url": null,
        "format": null,
        "size_bytes": null,
        "duration_sec": null,
        "channels": null,
        "channel_layout": null,
        "retention_days": 60,
        "expires_at": "2026-12-11T06:02:14.000Z",
        "parts": [],
        "parts_ordered": false
      }
    }
  ],
  "pagination": {
    "page": 1,
    "per_page": 50,
    "total": 2,
    "total_pages": 1
  }
}

Errors

StatusCodeerror_codeWhenWhat to do
400400validation_errorA field is missing, has the wrong type or breaks a rule, or the body carries a field the API does not take. field names it, for example agent_id in "Unknown field: agent_id".Fix the request, then send it with a new Idempotency-Key (a 400 is kept for the key).
403403feature_disabledYour account does not have the Telephony API, or (on a transfer) the kind of transfer you asked for: transfer to a call menu, or to an outside number.Contact your account manager.
429429rate_limitedYour request budget, the reading budget of a live click to call, or your account's calls a minute, is spent.Wait for Retry-After.
A catch-up page read with a cursor
{
  "status": "success",
  "code": 200,
  "data": [
    {
      "id": "c2c_9f1c4e7a2b6d48e0a3c5f1d29b7e6a40",
      "object": "click_to_call",
      "state": "completed",
      "active": false,
      "on_hold": false,
      "mode": "internet",
      "member": {
        "id": 1042,
        "name": "Neha Sharma"
      },
      "from_number": "+9111XXXXXX45",
      "to_number": "+9198XXXXXX21",
      "customer_name": "Ravi Kumar",
      "call_id": "6b1e2f3a-4c5d-4e6f-8a7b-9c0d1e2f3a4b",
      "reference_id": "LEAD-2291",
      "metadata": {
        "deal": "D-77",
        "crm_owner": "neha"
      },
      "created_at": "2026-10-12T05:31:02.000Z",
      "created_at_ist": "2026-10-12T11:01:02.000+05:30",
      "member_answered_at": "2026-10-12T05:31:09.204Z",
      "member_answered_at_ist": "2026-10-12T11:01:09.204+05:30",
      "customer_answered_at": "2026-10-12T05:31:19.516Z",
      "customer_answered_at_ist": "2026-10-12T11:01:19.516+05:30",
      "ended_at": "2026-10-12T05:32:53.000Z",
      "ended_at_ist": "2026-10-12T11:02:53.000+05:30",
      "duration_sec": 94,
      "outcome": "completed",
      "ended_by": "customer",
      "end_reason": "customer_ended",
      "transferred_to": null,
      "recording_available": true,
      "recording_url": "https://voice.agentive.co.in/api/public/v1/calls/6b1e2f3a-4c5d-4e6f-8a7b-9c0d1e2f3a4b/recording",
      "disposition": {
        "outcome": "callback",
        "note": "Wants the Gold plan quote on email first.",
        "callback_at": "2026-10-14T05:30:00.000Z",
        "tagged_at": "2026-10-12T05:34:05.000Z"
      },
      "sequence": 8,
      "updated_at": "2026-10-12T05:34:05.000Z",
      "updated_at_ist": "2026-10-12T11:04:05.000+05:30",
      "dial_started_at": "2026-10-12T05:31:10.000Z",
      "dial_started_at_ist": "2026-10-12T11:01:10.000+05:30",
      "customer_ringing_at": "2026-10-12T05:31:10.400Z",
      "customer_ringing_at_ist": "2026-10-12T11:01:10.400+05:30",
      "member_ring_sec": 7,
      "customer_ring_sec": 9,
      "call_duration_sec": 94,
      "recording": {
        "status": "available",
        "revision": 1,
        "url": "https://voice.agentive.co.in/api/public/v1/calls/6b1e2f3a-4c5d-4e6f-8a7b-9c0d1e2f3a4b/recording",
        "format": "ogg",
        "size_bytes": 381952,
        "duration_sec": 94.6,
        "channels": 2,
        "channel_layout": {
          "left": "customer",
          "right": "member"
        },
        "retention_days": 60,
        "expires_at": "2026-12-11T05:31:10.000Z",
        "parts": [
          {
            "part": 1,
            "primary": true,
            "segment": null,
            "url": "https://voice.agentive.co.in/api/public/v1/calls/6b1e2f3a-4c5d-4e6f-8a7b-9c0d1e2f3a4b/recording",
            "format": "ogg",
            "size_bytes": 381952,
            "duration_sec": 94.6,
            "channels": 2,
            "started_at": "2026-10-12T05:31:19.516Z",
            "member": null
          }
        ],
        "parts_ordered": true
      }
    },
    {
      "id": "c2c_4d7b1e9c3a5f42d8b6e0c2a1f9d37b58",
      "object": "click_to_call",
      "state": "transferred",
      "active": false,
      "on_hold": false,
      "mode": "phone_bridge",
      "member": {
        "id": 1057,
        "name": "Arjun Mehta"
      },
      "from_number": "+9179XXXXXX12",
      "to_number": "+9197XXXXXX08",
      "customer_name": "Meera Iyer",
      "call_id": "8d3f4b5c-6e7a-4b8c-9d0e-1f2a3b4c5d6e",
      "reference_id": "LEAD-2304",
      "metadata": {
        "deal": "D-81"
      },
      "created_at": "2026-10-12T06:02:14.000Z",
      "created_at_ist": "2026-10-12T11:32:14.000+05:30",
      "member_answered_at": "2026-10-12T06:02:20.418Z",
      "member_answered_at_ist": "2026-10-12T11:32:20.418+05:30",
      "customer_answered_at": "2026-10-12T06:02:31.612Z",
      "customer_answered_at_ist": "2026-10-12T11:32:31.612+05:30",
      "ended_at": "2026-10-12T06:03:32.000Z",
      "ended_at_ist": "2026-10-12T11:33:32.000+05:30",
      "duration_sec": 61,
      "outcome": "transferred",
      "ended_by": "api",
      "end_reason": "transferred",
      "transferred_to": {
        "kind": "member",
        "member_id": 1042,
        "member_name": "Neha Sharma",
        "menu_id": null,
        "menu_name": null,
        "number": null
      },
      "recording_available": false,
      "recording_url": null,
      "disposition": null,
      "sequence": 5,
      "updated_at": "2026-10-12T06:03:32.000Z",
      "updated_at_ist": "2026-10-12T11:33:32.000+05:30",
      "dial_started_at": null,
      "dial_started_at_ist": null,
      "customer_ringing_at": "2026-10-12T06:02:20.418Z",
      "customer_ringing_at_ist": "2026-10-12T11:32:20.418+05:30",
      "member_ring_sec": 6,
      "customer_ring_sec": 11,
      "call_duration_sec": 248,
      "recording": {
        "status": "processing",
        "revision": 0,
        "url": null,
        "format": null,
        "size_bytes": null,
        "duration_sec": null,
        "channels": null,
        "channel_layout": null,
        "retention_days": 60,
        "expires_at": "2026-12-11T06:02:14.000Z",
        "parts": [],
        "parts_ordered": false
      }
    }
  ],
  "pagination": {
    "page": null,
    "per_page": 2,
    "total": null,
    "total_pages": null,
    "next_cursor": "eyJrIjoiMTc5MTc4MjQ0NTAwMCJ9.c2Kp7w"
  }
}

Hang up

POST/click-to-call/{id}/hangup

Before the customer answers, this cancels the click to call (canceled): a team member still being rung stops ringing, and a customer still being called is not connected. After the customer answers, it ends the conversation (completed). Either way ended_by is api. A click to call that has already ended answers 200, unchanged (it keeps the side that ended it, even when that was a moment before your request), so it is safe to send twice.

Headers

HeaderTypeRequiredDescription
x-api-keystringRequired
Your publishable key (pk_live_...). Treat it as confidential. See Authentication.
x-api-secretstringRequired
Your secret key (sk_live_...). Server-side only.
Idempotency-KeystringOptional
A key unique to this request, 1 to 128 visible ASCII characters. Send one on every POST: a retry with the same key returns the first answer and never rings anyone twice. See Idempotency.
Example: lead-2291-call-1

Path parameters

ParameterTypeRequiredDescription
idstringRequired
The click to call id, c2c_ and 32 hex characters, as returned in unique_id when you started it. Another account's id answers 404.
Example: c2c_9f1c4e7a2b6d48e0a3c5f1d29b7e6a40

Request

curl -X POST https://voice.agentive.co.in/api/public/v1/click-to-call/c2c_7a2e5c9d1b3f46a8c0e4d2b6f8a1c3e5/hangup \
  -H "x-api-key: $AGENTIVE_KEY" \
  -H "x-api-secret: $AGENTIVE_SECRET"

Response (hung up while the team member was ringing)

JSON
{
  "status": "success",
  "code": 200,
  "unique_id": "c2c_7a2e5c9d1b3f46a8c0e4d2b6f8a1c3e5",
  "reference_id": "LEAD-2310",
  "details": "canceled",
  "click_to_call": {
    "id": "c2c_7a2e5c9d1b3f46a8c0e4d2b6f8a1c3e5",
    "object": "click_to_call",
    "state": "canceled",
    "active": false,
    "on_hold": false,
    "mode": "internet",
    "member": {
      "id": 1042,
      "name": "Neha Sharma"
    },
    "from_number": "+9111XXXXXX45",
    "to_number": "+9198XXXXXX21",
    "customer_name": null,
    "call_id": null,
    "reference_id": "LEAD-2310",
    "metadata": null,
    "created_at": "2026-10-12T06:40:00.000Z",
    "created_at_ist": "2026-10-12T12:10:00.000+05:30",
    "member_answered_at": null,
    "member_answered_at_ist": null,
    "customer_answered_at": null,
    "customer_answered_at_ist": null,
    "ended_at": "2026-10-12T06:40:07.000Z",
    "ended_at_ist": "2026-10-12T12:10:07.000+05:30",
    "duration_sec": 0,
    "outcome": "canceled",
    "ended_by": "api",
    "end_reason": "cancelled",
    "transferred_to": null,
    "recording_available": false,
    "recording_url": null,
    "disposition": null,
    "sequence": 2,
    "updated_at": "2026-10-12T06:40:07.000Z",
    "updated_at_ist": "2026-10-12T12:10:07.000+05:30",
    "dial_started_at": null,
    "dial_started_at_ist": null,
    "customer_ringing_at": null,
    "customer_ringing_at_ist": null,
    "member_ring_sec": 7,
    "customer_ring_sec": null,
    "call_duration_sec": null,
    "recording": {
      "status": "none",
      "revision": 0,
      "url": null,
      "format": null,
      "size_bytes": null,
      "duration_sec": null,
      "channels": null,
      "channel_layout": null,
      "retention_days": 60,
      "expires_at": "2026-12-11T06:40:00.000Z",
      "parts": [],
      "parts_ordered": false
    }
  }
}

Errors

StatusCodeerror_codeWhenWhat to do
404404not_foundNo click to call with that id on your account.Check the id.
503503service_unavailableThe line for this caller ID cannot be reached right now, the call could not be reached to carry out a control, or the record could not be read. No Retry-After is sent. When the body says retryable: false (details "Click to call is switched off for this account."), click to call is not switched on for your account right now: every route answers this, before the Telephony API is checked.Wait about 30 seconds (add some jitter), then try again. After a keypad or transfer request we cannot be sure nothing was done: read the click to call first and use a new Idempotency-Key. If retryable is false, do not retry: contact your account manager.

Over the internet, a hang up before the customer is called reaches its outcome at once. Once a call is on the line, the answer comes back at once with the call as it stands (still active, ended_by api), and the outcome and click_to_call.ended follow about five seconds after the call ends, like call.completed. Until then a new click to call for the same team member answers member_on_call (retryable): try again a few seconds later.

Send keypad tones

POST/click-to-call/{id}/dtmf

Play keypad tones into the call, in order, for example to pick an option in a phone menu the customer's side runs. Only while in_progress and not on hold. Send an Idempotency-Key: a blind retry without one would press the keys twice. The digits are never stored in call records, events or API logs.

Headers

HeaderTypeRequiredDescription
x-api-keystringRequired
Your publishable key (pk_live_...). Treat it as confidential. See Authentication.
x-api-secretstringRequired
Your secret key (sk_live_...). Server-side only.
Content-TypestringRequired
Always application/json.
Idempotency-KeystringOptional
A key unique to this request, 1 to 128 visible ASCII characters. Send one on every POST: a retry with the same key returns the first answer and never rings anyone twice. See Idempotency.
Example: lead-2291-call-1

Path parameters

ParameterTypeRequiredDescription
idstringRequired
The click to call id, c2c_ and 32 hex characters, as returned in unique_id when you started it. Another account's id answers 404.
Example: c2c_9f1c4e7a2b6d48e0a3c5f1d29b7e6a40

Request body

FieldTypeRequiredDescription
digitsstringRequired
1 to 32 of 0 to 9, *, # and A to D.
Example: 1234#

Request

curl -X POST https://voice.agentive.co.in/api/public/v1/click-to-call/c2c_9f1c4e7a2b6d48e0a3c5f1d29b7e6a40/dtmf \
  -H "x-api-key: $AGENTIVE_KEY" \
  -H "x-api-secret: $AGENTIVE_SECRET" \
  -H "Idempotency-Key: lead-2291-dtmf-1" \
  -H "Content-Type: application/json" \
  -d '{ "digits": "1234#" }'

Response

JSON
{
  "status": "success",
  "code": 200,
  "unique_id": "c2c_9f1c4e7a2b6d48e0a3c5f1d29b7e6a40",
  "reference_id": "LEAD-2291",
  "details": "in_progress",
  "click_to_call": {
    "id": "c2c_9f1c4e7a2b6d48e0a3c5f1d29b7e6a40",
    "object": "click_to_call",
    "state": "in_progress",
    "active": true,
    "on_hold": false,
    "mode": "internet",
    "member": {
      "id": 1042,
      "name": "Neha Sharma"
    },
    "from_number": "+9111XXXXXX45",
    "to_number": "+9198XXXXXX21",
    "customer_name": "Ravi Kumar",
    "call_id": "6b1e2f3a-4c5d-4e6f-8a7b-9c0d1e2f3a4b",
    "reference_id": "LEAD-2291",
    "metadata": {
      "deal": "D-77",
      "crm_owner": "neha"
    },
    "created_at": "2026-10-12T05:31:02.000Z",
    "created_at_ist": "2026-10-12T11:01:02.000+05:30",
    "member_answered_at": "2026-10-12T05:31:09.204Z",
    "member_answered_at_ist": "2026-10-12T11:01:09.204+05:30",
    "customer_answered_at": "2026-10-12T05:31:19.516Z",
    "customer_answered_at_ist": "2026-10-12T11:01:19.516+05:30",
    "ended_at": null,
    "ended_at_ist": null,
    "duration_sec": 42,
    "outcome": null,
    "ended_by": null,
    "end_reason": null,
    "transferred_to": null,
    "recording_available": false,
    "recording_url": null,
    "disposition": null,
    "sequence": 4,
    "updated_at": "2026-10-12T05:31:19.516Z",
    "updated_at_ist": "2026-10-12T11:01:19.516+05:30",
    "dial_started_at": "2026-10-12T05:31:10.000Z",
    "dial_started_at_ist": "2026-10-12T11:01:10.000+05:30",
    "customer_ringing_at": "2026-10-12T05:31:10.400Z",
    "customer_ringing_at_ist": "2026-10-12T11:01:10.400+05:30",
    "member_ring_sec": 7,
    "customer_ring_sec": 9,
    "call_duration_sec": null,
    "recording": {
      "status": "processing",
      "revision": 0,
      "url": null,
      "format": null,
      "size_bytes": null,
      "duration_sec": null,
      "channels": null,
      "channel_layout": null,
      "retention_days": 60,
      "expires_at": "2026-12-11T05:31:10.000Z",
      "parts": [],
      "parts_ordered": false
    }
  }
}

Errors

StatusCodeerror_codeWhenWhat to do
400400validation_errorA field is missing, has the wrong type or breaks a rule, or the body carries a field the API does not take. field names it, for example agent_id in "Unknown field: agent_id".Fix the request, then send it with a new Idempotency-Key (a 400 is kept for the key).
409409call_not_connectedKeypad, hold or transfer was sent before the customer answered. No leg was touched.Wait for click_to_call.customer_answered, then send it again.
409409call_on_holdKeypad tones were sent while the call is on hold through this API (a hold the team member made from their panel or app is not known here).Resume the call first.
410410call_endedThe click to call has ended.Nothing to do. Hang up on an ended call answers 200 instead.
410410call_transferredThe click to call was transferred: the customer is now with a colleague, a call menu or another number.Follow the rest of the call through the account's call events.
404404not_foundNo click to call with that id on your account.Check the id.
503503service_unavailableThe line for this caller ID cannot be reached right now, the call could not be reached to carry out a control, or the record could not be read. No Retry-After is sent. When the body says retryable: false (details "Click to call is switched off for this account."), click to call is not switched on for your account right now: every route answers this, before the Telephony API is checked.Wait about 30 seconds (add some jitter), then try again. After a keypad or transfer request we cannot be sure nothing was done: read the click to call first and use a new Idempotency-Key. If retryable is false, do not retry: contact your account manager.

Hold

POST/click-to-call/{id}/hold

Put the customer on hold: they hear hold music, and the team member's web panel shows the call as Held. Only while in_progress. Holding a call already on hold answers 200 and sends no second event.

Headers

HeaderTypeRequiredDescription
x-api-keystringRequired
Your publishable key (pk_live_...). Treat it as confidential. See Authentication.
x-api-secretstringRequired
Your secret key (sk_live_...). Server-side only.
Idempotency-KeystringOptional
A key unique to this request, 1 to 128 visible ASCII characters. Send one on every POST: a retry with the same key returns the first answer and never rings anyone twice. See Idempotency.
Example: lead-2291-call-1

Path parameters

ParameterTypeRequiredDescription
idstringRequired
The click to call id, c2c_ and 32 hex characters, as returned in unique_id when you started it. Another account's id answers 404.
Example: c2c_9f1c4e7a2b6d48e0a3c5f1d29b7e6a40

Request

curl -X POST https://voice.agentive.co.in/api/public/v1/click-to-call/c2c_9f1c4e7a2b6d48e0a3c5f1d29b7e6a40/hold \
  -H "x-api-key: $AGENTIVE_KEY" \
  -H "x-api-secret: $AGENTIVE_SECRET"

Response

JSON
{
  "status": "success",
  "code": 200,
  "unique_id": "c2c_9f1c4e7a2b6d48e0a3c5f1d29b7e6a40",
  "reference_id": "LEAD-2291",
  "details": "in_progress",
  "click_to_call": {
    "id": "c2c_9f1c4e7a2b6d48e0a3c5f1d29b7e6a40",
    "object": "click_to_call",
    "state": "in_progress",
    "active": true,
    "on_hold": true,
    "mode": "internet",
    "member": {
      "id": 1042,
      "name": "Neha Sharma"
    },
    "from_number": "+9111XXXXXX45",
    "to_number": "+9198XXXXXX21",
    "customer_name": "Ravi Kumar",
    "call_id": "6b1e2f3a-4c5d-4e6f-8a7b-9c0d1e2f3a4b",
    "reference_id": "LEAD-2291",
    "metadata": {
      "deal": "D-77",
      "crm_owner": "neha"
    },
    "created_at": "2026-10-12T05:31:02.000Z",
    "created_at_ist": "2026-10-12T11:01:02.000+05:30",
    "member_answered_at": "2026-10-12T05:31:09.204Z",
    "member_answered_at_ist": "2026-10-12T11:01:09.204+05:30",
    "customer_answered_at": "2026-10-12T05:31:19.516Z",
    "customer_answered_at_ist": "2026-10-12T11:01:19.516+05:30",
    "ended_at": null,
    "ended_at_ist": null,
    "duration_sec": 51,
    "outcome": null,
    "ended_by": null,
    "end_reason": null,
    "transferred_to": null,
    "recording_available": false,
    "recording_url": null,
    "disposition": null,
    "sequence": 5,
    "updated_at": "2026-10-12T05:31:19.516Z",
    "updated_at_ist": "2026-10-12T11:01:19.516+05:30",
    "dial_started_at": "2026-10-12T05:31:10.000Z",
    "dial_started_at_ist": "2026-10-12T11:01:10.000+05:30",
    "customer_ringing_at": "2026-10-12T05:31:10.400Z",
    "customer_ringing_at_ist": "2026-10-12T11:01:10.400+05:30",
    "member_ring_sec": 7,
    "customer_ring_sec": 9,
    "call_duration_sec": null,
    "recording": {
      "status": "processing",
      "revision": 0,
      "url": null,
      "format": null,
      "size_bytes": null,
      "duration_sec": null,
      "channels": null,
      "channel_layout": null,
      "retention_days": 60,
      "expires_at": "2026-12-11T05:31:10.000Z",
      "parts": [],
      "parts_ordered": false
    }
  }
}

Errors

StatusCodeerror_codeWhenWhat to do
409409call_not_connectedKeypad, hold or transfer was sent before the customer answered. No leg was touched.Wait for click_to_call.customer_answered, then send it again.
410410call_endedThe click to call has ended.Nothing to do. Hang up on an ended call answers 200 instead.
410410call_transferredThe click to call was transferred: the customer is now with a colleague, a call menu or another number.Follow the rest of the call through the account's call events.
404404not_foundNo click to call with that id on your account.Check the id.
503503service_unavailableThe line for this caller ID cannot be reached right now, the call could not be reached to carry out a control, or the record could not be read. No Retry-After is sent. When the body says retryable: false (details "Click to call is switched off for this account."), click to call is not switched on for your account right now: every route answers this, before the Telephony API is checked.Wait about 30 seconds (add some jitter), then try again. After a keypad or transfer request we cannot be sure nothing was done: read the click to call first and use a new Idempotency-Key. If retryable is false, do not retry: contact your account manager.
409 while the customer has not answered
{
  "status": "error",
  "code": 409,
  "unique_id": null,
  "reference_id": "LEAD-2291",
  "details": "The customer has not answered yet.",
  "error_code": "call_not_connected",
  "error": "The customer has not answered yet.",
  "retryable": true
}

Resume

POST/click-to-call/{id}/unhold

Take the customer off hold. Resuming a call that is not on hold answers 200 and sends no event. When the team member takes a hold you made through the API off from their own panel, you receive click_to_call.resumed just the same. A hold the team member makes from their panel or app is not reported.

Headers

HeaderTypeRequiredDescription
x-api-keystringRequired
Your publishable key (pk_live_...). Treat it as confidential. See Authentication.
x-api-secretstringRequired
Your secret key (sk_live_...). Server-side only.
Idempotency-KeystringOptional
A key unique to this request, 1 to 128 visible ASCII characters. Send one on every POST: a retry with the same key returns the first answer and never rings anyone twice. See Idempotency.
Example: lead-2291-call-1

Path parameters

ParameterTypeRequiredDescription
idstringRequired
The click to call id, c2c_ and 32 hex characters, as returned in unique_id when you started it. Another account's id answers 404.
Example: c2c_9f1c4e7a2b6d48e0a3c5f1d29b7e6a40

Request

curl -X POST https://voice.agentive.co.in/api/public/v1/click-to-call/c2c_9f1c4e7a2b6d48e0a3c5f1d29b7e6a40/unhold \
  -H "x-api-key: $AGENTIVE_KEY" \
  -H "x-api-secret: $AGENTIVE_SECRET"

Response

JSON
{
  "status": "success",
  "code": 200,
  "unique_id": "c2c_9f1c4e7a2b6d48e0a3c5f1d29b7e6a40",
  "reference_id": "LEAD-2291",
  "details": "in_progress",
  "click_to_call": {
    "id": "c2c_9f1c4e7a2b6d48e0a3c5f1d29b7e6a40",
    "object": "click_to_call",
    "state": "in_progress",
    "active": true,
    "on_hold": false,
    "mode": "internet",
    "member": {
      "id": 1042,
      "name": "Neha Sharma"
    },
    "from_number": "+9111XXXXXX45",
    "to_number": "+9198XXXXXX21",
    "customer_name": "Ravi Kumar",
    "call_id": "6b1e2f3a-4c5d-4e6f-8a7b-9c0d1e2f3a4b",
    "reference_id": "LEAD-2291",
    "metadata": {
      "deal": "D-77",
      "crm_owner": "neha"
    },
    "created_at": "2026-10-12T05:31:02.000Z",
    "created_at_ist": "2026-10-12T11:01:02.000+05:30",
    "member_answered_at": "2026-10-12T05:31:09.204Z",
    "member_answered_at_ist": "2026-10-12T11:01:09.204+05:30",
    "customer_answered_at": "2026-10-12T05:31:19.516Z",
    "customer_answered_at_ist": "2026-10-12T11:01:19.516+05:30",
    "ended_at": null,
    "ended_at_ist": null,
    "duration_sec": 81,
    "outcome": null,
    "ended_by": null,
    "end_reason": null,
    "transferred_to": null,
    "recording_available": false,
    "recording_url": null,
    "disposition": null,
    "sequence": 6,
    "updated_at": "2026-10-12T05:31:19.516Z",
    "updated_at_ist": "2026-10-12T11:01:19.516+05:30",
    "dial_started_at": "2026-10-12T05:31:10.000Z",
    "dial_started_at_ist": "2026-10-12T11:01:10.000+05:30",
    "customer_ringing_at": "2026-10-12T05:31:10.400Z",
    "customer_ringing_at_ist": "2026-10-12T11:01:10.400+05:30",
    "member_ring_sec": 7,
    "customer_ring_sec": 9,
    "call_duration_sec": null,
    "recording": {
      "status": "processing",
      "revision": 0,
      "url": null,
      "format": null,
      "size_bytes": null,
      "duration_sec": null,
      "channels": null,
      "channel_layout": null,
      "retention_days": 60,
      "expires_at": "2026-12-11T05:31:10.000Z",
      "parts": [],
      "parts_ordered": false
    }
  }
}

Errors

StatusCodeerror_codeWhenWhat to do
409409call_not_connectedKeypad, hold or transfer was sent before the customer answered. No leg was touched.Wait for click_to_call.customer_answered, then send it again.
410410call_endedThe click to call has ended.Nothing to do. Hang up on an ended call answers 200 instead.
410410call_transferredThe click to call was transferred: the customer is now with a colleague, a call menu or another number.Follow the rest of the call through the account's call events.
404404not_foundNo click to call with that id on your account.Check the id.
503503service_unavailableThe line for this caller ID cannot be reached right now, the call could not be reached to carry out a control, or the record could not be read. No Retry-After is sent. When the body says retryable: false (details "Click to call is switched off for this account."), click to call is not switched on for your account right now: every route answers this, before the Telephony API is checked.Wait about 30 seconds (add some jitter), then try again. After a keypad or transfer request we cannot be sure nothing was done: read the click to call first and use a new Idempotency-Key. If retryable is false, do not retry: contact your account manager.

Transfer

POST/click-to-call/{id}/transfer

Hand the customer on, while in_progress: to a colleague (rung on their own lane, like any transfer), to a call menu, or to an outside number. The click to call then ends as transferred, with ended_by api and transferred_to naming the target; the rest of the customer's call is reported by your account's normal call events. The call itself goes on: once a colleague answers it, the call's record names that colleague as its member, and its ended_by tells how the call then ended (null when that is not known). On a move to a call menu its recording ends at the move; the call menu part is a call of its own, lane menu.

Headers

HeaderTypeRequiredDescription
x-api-keystringRequired
Your publishable key (pk_live_...). Treat it as confidential. See Authentication.
x-api-secretstringRequired
Your secret key (sk_live_...). Server-side only.
Content-TypestringRequired
Always application/json.
Idempotency-KeystringOptional
A key unique to this request, 1 to 128 visible ASCII characters. Send one on every POST: a retry with the same key returns the first answer and never rings anyone twice. See Idempotency.
Example: lead-2291-call-1

Path parameters

ParameterTypeRequiredDescription
idstringRequired
The click to call id, c2c_ and 32 hex characters, as returned in unique_id when you started it. Another account's id answers 404.
Example: c2c_9f1c4e7a2b6d48e0a3c5f1d29b7e6a40

Request body

FieldTypeRequiredDescription
to_member_idnumberOptional
A colleague, by id. Send exactly one of the four.
Example: 1042
to_member_emailstringOptional
A colleague, by email.
Example: neha@example.in
to_menu_idnumberOptional
A call menu from List call menus. Needs transfer to a call menu on your account.
Example: 12
to_numberstringOptional
An outside Indian number. Needs call forwarding to outside numbers on your account, and every check of Start a click to call applies to it, the per-number limits included.
Example: 98XXXXXX34

Request

curl -X POST https://voice.agentive.co.in/api/public/v1/click-to-call/c2c_4d7b1e9c3a5f42d8b6e0c2a1f9d37b58/transfer \
  -H "x-api-key: $AGENTIVE_KEY" \
  -H "x-api-secret: $AGENTIVE_SECRET" \
  -H "Idempotency-Key: lead-2304-transfer-1" \
  -H "Content-Type: application/json" \
  -d '{ "to_member_email": "neha@example.in" }'

Response

JSON
{
  "status": "success",
  "code": 200,
  "unique_id": "c2c_4d7b1e9c3a5f42d8b6e0c2a1f9d37b58",
  "reference_id": "LEAD-2304",
  "details": "transferred",
  "click_to_call": {
    "id": "c2c_4d7b1e9c3a5f42d8b6e0c2a1f9d37b58",
    "object": "click_to_call",
    "state": "transferred",
    "active": false,
    "on_hold": false,
    "mode": "phone_bridge",
    "member": {
      "id": 1057,
      "name": "Arjun Mehta"
    },
    "from_number": "+9179XXXXXX12",
    "to_number": "+9197XXXXXX08",
    "customer_name": "Meera Iyer",
    "call_id": "8d3f4b5c-6e7a-4b8c-9d0e-1f2a3b4c5d6e",
    "reference_id": "LEAD-2304",
    "metadata": {
      "deal": "D-81"
    },
    "created_at": "2026-10-12T06:02:14.000Z",
    "created_at_ist": "2026-10-12T11:32:14.000+05:30",
    "member_answered_at": "2026-10-12T06:02:20.418Z",
    "member_answered_at_ist": "2026-10-12T11:32:20.418+05:30",
    "customer_answered_at": "2026-10-12T06:02:31.612Z",
    "customer_answered_at_ist": "2026-10-12T11:32:31.612+05:30",
    "ended_at": "2026-10-12T06:03:32.000Z",
    "ended_at_ist": "2026-10-12T11:33:32.000+05:30",
    "duration_sec": 61,
    "outcome": "transferred",
    "ended_by": "api",
    "end_reason": "transferred",
    "transferred_to": {
      "kind": "member",
      "member_id": 1042,
      "member_name": "Neha Sharma",
      "menu_id": null,
      "menu_name": null,
      "number": null
    },
    "recording_available": false,
    "recording_url": null,
    "disposition": null,
    "sequence": 5,
    "updated_at": "2026-10-12T06:03:32.000Z",
    "updated_at_ist": "2026-10-12T11:33:32.000+05:30",
    "dial_started_at": null,
    "dial_started_at_ist": null,
    "customer_ringing_at": "2026-10-12T06:02:20.418Z",
    "customer_ringing_at_ist": "2026-10-12T11:32:20.418+05:30",
    "member_ring_sec": 6,
    "customer_ring_sec": 11,
    "call_duration_sec": 248,
    "recording": {
      "status": "processing",
      "revision": 0,
      "url": null,
      "format": null,
      "size_bytes": null,
      "duration_sec": null,
      "channels": null,
      "channel_layout": null,
      "retention_days": 60,
      "expires_at": "2026-12-11T06:02:14.000Z",
      "parts": [],
      "parts_ordered": false
    }
  }
}

Errors

StatusCodeerror_codeWhenWhat to do
400400validation_errorA field is missing, has the wrong type or breaks a rule, or the body carries a field the API does not take. field names it, for example agent_id in "Unknown field: agent_id".Fix the request, then send it with a new Idempotency-Key (a 400 is kept for the key).
409409call_not_connectedKeypad, hold or transfer was sent before the customer answered. No leg was touched.Wait for click_to_call.customer_answered, then send it again.
410410call_endedThe click to call has ended.Nothing to do. Hang up on an ended call answers 200 instead.
410410call_transferredThe click to call was transferred: the customer is now with a colleague, a call menu or another number.Follow the rest of the call through the account's call events.
404404member_not_foundNo team member with that id or email on your account. Also answered for a colleague named on a transfer.Check the id or email with List team members.
404404menu_not_foundNo call menu with that id on your account.Check the id with List call menus.
409409target_on_callThe colleague you are transferring to is on another call.Try again in a moment, or pick someone else.
409409target_unavailableThe colleague is Offline, on a break, or cannot be rung right now.Pick someone else, or a call menu.
403403feature_disabledYour account does not have the Telephony API, or (on a transfer) the kind of transfer you asked for: transfer to a call menu, or to an outside number.Contact your account manager.
400400invalid_numberto (or to_number on a transfer) is not a number we can dial in India, or it is one of your own account's numbers.Fix the number.
400400international_not_supportedThe number is outside India, or your account's filter for numbers outside India refuses it.Call only Indian numbers.
422422number_blockedThe number is on the do not disturb list, or on your account's or the team member's block list.Do not call this number.
429429number_flood3 calls through the API to this number in 10 minutes, or 10 today (IST), counted across every API that places calls. Both limits answer this one code; details says which.Wait for Retry-After (up to 600 seconds, or until midnight IST). Do not retry sooner.
404404not_foundNo click to call with that id on your account.Check the id.
503503service_unavailableThe line for this caller ID cannot be reached right now, the call could not be reached to carry out a control, or the record could not be read. No Retry-After is sent. When the body says retryable: false (details "Click to call is switched off for this account."), click to call is not switched on for your account right now: every route answers this, before the Telephony API is checked.Wait about 30 seconds (add some jitter), then try again. After a keypad or transfer request we cannot be sure nothing was done: read the click to call first and use a new Idempotency-Key. If retryable is false, do not retry: contact your account manager.
The four kinds of body
{ "to_member_id": 1042 }
{ "to_member_email": "neha@example.in" }
{ "to_menu_id": 12 }
{ "to_number": "98XXXXXX34" }

Download the recording

GET/calls/{call_id}/recording

The call's audio, once recording_available is true (its recording_url is this address). The path takes the call_id or the click to call id itself (c2c_...); add ?segment=N for an earlier part listed in recording.parts. The API streams the file itself: HTTP 200 with the audio as the body, Content-Type audio/ogg (or audio/wav) and Accept-Ranges: bytes, never a redirect. A Range header gets HTTP 206; a range it cannot serve, HTTP 416 (a range past the end carries Content-Range: bytes */<file size>). Needs call recording on your account. GET /calls/{unique_id} also accepts the c2c_ id and answers the call record, as described in the Voice Broadcast API.

Headers

HeaderTypeRequiredDescription
x-api-keystringRequired
Your publishable key (pk_live_...). Treat it as confidential. See Authentication.
x-api-secretstringRequired
Your secret key (sk_live_...). Server-side only.

Path parameters

ParameterTypeRequiredDescription
call_idstringRequired
The click to call's call_id, or its c2c_ id.
Example: 6b1e2f3a-4c5d-4e6f-8a7b-9c0d1e2f3a4b

Request

# The API streams the audio itself. Basic auth: key as user, secret as password.
curl "https://voice.agentive.co.in/api/public/v1/calls/6b1e2f3a-4c5d-4e6f-8a7b-9c0d1e2f3a4b/recording" \
  -u "$AGENTIVE_KEY:$AGENTIVE_SECRET" \
  --output call.ogg

Errors

StatusCodeerror_codeWhenWhat to do
404404not_foundNo recording exists for the call, or not yet (details "No recording is available for this call."), or no part has that segment ("Segment not found.").Wait for recording.available, or for recording_available to be true.
404404recording_expiredThe recording is past its recording.expires_at (your plan's retention) and is no longer available.Copy recordings before expires_at.
403403feature_disabledCall recording is not enabled for your account.
416416validation_errorThe Range header is malformed, names several ranges, or starts past the end of the file. A range past the end carries Content-Range: bytes */<file size> (since 14 October 2026) whenever we know the file's size. The body's unique_id names the call: the same unique_id that GET /calls/{id} answers for the id in your path (since 14 October 2026; before, null). Every other error here keeps unique_id null.
502502upstream_errorThe audio could not be fetched just now.Retry after a short pause.
Never send your secret to another host
The samples use HTTP Basic, which HTTP clients never forward to a different host. If you send x-api-key and x-api-secret instead, switch off redirect following for this request.

The click to call object

Every answer on this page and every click_to_call.* event carries the same object. Phone numbers are full E.164 (+91 and ten digits). Every time comes twice: in UTC with a Z, and the same instant at +05:30 in the _ist twin.

Fields

FieldTypeDescription
idstring
c2c_ and 32 hex characters. The unique_id of every answer and event for this call.
objectstring
Always click_to_call.
statestring
Where the call is. See States and outcomes. Equal to outcome once active is false.
activeboolean
true while the call is still going (one of the four live states).
on_holdboolean
true while the customer is on hold through this API (Hold). A hold the team member makes from their own panel or app is not reflected here. If they take an API hold off from their panel, on_hold goes back to false and click_to_call.resumed is sent.
modestring
internet or phone_bridge: how the team member was rung.
memberobject
{ id, name } of the team member.
from_numberstring
The number the customer sees, in E.164.
to_numberstring
The customer, in E.164.
customer_namestring | null
What you sent in customer_name, cleaned.
call_idstring | null
The call record behind it. Use it for the recording. Over the internet it is set once the customer is being called, and is null before that and on a call that never got that far. On Phone Bridge it is set from member_ringing, because the call record starts when the team member's mobile is rung.
reference_idstring | null
What you sent.
metadataobject | null
What you sent. Echoed, never spoken. A number comes back as text.
created_atstring
When you started it, ISO 8601 UTC.
created_at_iststring
The same instant at +05:30.
member_answered_atstring | null
When the team member answered, ISO 8601 UTC with milliseconds. On Phone Bridge before 14 October 2026, to the second.
member_answered_at_iststring | null
The same instant at +05:30.
customer_answered_atstring | null
When the customer answered, ISO 8601 UTC with milliseconds (before 14 October 2026, to the second). The call record's answered_at for the same call is to the second, so it can read up to one second earlier.
customer_answered_at_iststring | null
The same instant at +05:30.
ended_atstring | null
When it ended (or was transferred), ISO 8601 UTC. null while active. Since 14 October 2026 it usually carries milliseconds: the moment our server recorded the end. It can still read to the second, for example when the end was recorded another way or after a restart of our service. Ends that do not come from the call record (over the internet, a cancel before the customer is called; a ring on the team member's web panel or app that runs out or that they decline; a call that could not be placed; a transfer you request) carried milliseconds before too. The call record's ended_at for the same call is to the second, so it can read up to one second earlier. A call that ends in the same second as customer_answered_at (or, on Phone Bridge, member_answered_at) never reads an end earlier than that answer.
ended_at_iststring | null
The same instant at +05:30.
duration_secnumber
Your team member's talk time in seconds, counted from the customer's answer. 0 when the customer never answered. After a transfer it covers your team member's part only; the whole conversation is call_duration_sec.
outcomestring | null
How it ended; null while active. See Outcomes. Branch on this.
ended_bystring | null
member, customer, api, system or null. See Who ended the call.
end_reasonstring | null
A short code for your logs about why it ended: the call's reason (for example customer_ended, member_ended, busy, no_answer, transferred or cancelled) or, when the call could not be placed, the error code it was refused with. On a connected call that was not transferred it is null unless ended_by is member or customer. Branch on outcome, not on this.
transferred_toobject | null
Who the call was handed to: { "kind", "member_id", "member_name", "menu_id", "menu_name", "number" }. kind is member (a colleague, member_id and member_name), menu (a call menu, menu_id and menu_name) or number (an outside number, number in E.164). The keys that do not apply to the kind are null. Always set when you transferred the call through the API. When the team member transferred it from their panel or app, it names the colleague or the number when we know it (a call menu is not named), and is null otherwise.
recording_availableboolean
The recording is complete and can be downloaded now. It turns true about a minute after the last part is stored, together with recording.available.
recording_urlstring | null
The authenticated GET /calls/{call_id}/recording address of the main file once the recording is ready, else null. It needs your keys. Earlier parts are in recording.parts.
dispositionobject | null
Set when the team member saves an outcome for the call in the panel or app: { outcome, note, callback_at, tagged_at }. outcome is the code they picked, for example interested, callback or not_interested; treat one you do not recognise as other. As saved: it does not follow a callback later removed or moved to a newer reminder (the call's own disposition does).
sequencenumber
Goes up with every change. Keep the copy with the highest sequence.
updated_atstring
When the click to call last changed, ISO 8601 UTC. Use the latest one you hold for your next catch-up.
updated_at_iststring
The same instant at +05:30.
dial_started_atstring | null
When we started placing the customer's call over the internet. null until then, and on Phone Bridge (see customer_ringing_at).
dial_started_at_iststring | null
The same instant at +05:30.
customer_ringing_atstring | null
When the customer's call was placed (the customer_ringing state). null until then. On Phone Bridge it is the moment the team member's mobile answered, because we call the customer then (since 14 October 2026; before, the moment we noticed that answer, up to a second later). null when the customer was never called: on Phone Bridge, a team member who did not answer (before 14 October 2026 such a click to call carried a time a few seconds after its end).
customer_ringing_at_iststring | null
The same instant at +05:30.
member_ring_secnumber | null
How long your team member was rung: from the start to their answer, or to the end. Whole seconds, rounded down from the times above. null while they are still being rung.
customer_ring_secnumber | null
How long the customer was called: from customer_ringing_at to their answer, or to the end. Whole seconds, rounded down from the times above. null while the customer is still being called (and before), and when it is not known (some Phone Bridge click to calls). Since 14 October 2026 the answer times carry milliseconds, so a ring of 1.4 seconds reads 1: before, both ring lengths read 0 on most answered calls and on every Phone Bridge call.
call_duration_secnumber | null
The talk time of the whole call record, after a transfer included (the talk_sec of the call). null while the call is live and when the customer was never called.
recordingobject
The call's recording block: status, every part with its own download address, the stereo layout and expires_at, as on the call object. status is none while there is no call record.

States and outcomes

A click to call moves forward through up to four live states and ends in one outcome. It never moves backwards. Every state it passes through is reported once and in order, even one that went by very quickly.

member_ringingmember_answeredcustomer_ringingin_progresscompleted
From in_progress it can instead end astransferred; before that, as one of the outcomes below.
Live stateWhat it meansEvent
member_ringingYour team member is being rung.click_to_call.member_ringing
member_answeredThey answered. Over the internet we place the customer's call a moment later; on Phone Bridge at once.click_to_call.member_answered
customer_ringingWe are calling the customer. Their phone may not have started ringing yet: the network does not tell us when it does.click_to_call.customer_ringing
in_progressThe customer answered and they are talking. on_hold changes while it is in progress.click_to_call.customer_answered, then .held and .resumed

Outcomes

Once active is false, state and outcome hold the same value, one of these:

outcomeWhat happenedWorth another try?
completedThey talked, and the call ended (whoever ended it).Not needed.
transferredThe team member handed the customer to a colleague, a call menu or a number.Not needed.
member_no_answerYour team member did not answer in time. The customer was never called.Later, or ring someone else.
member_declinedYour team member declined. The customer was never called.Ask them, or ring someone else.
customer_no_answerThe customer's phone rang and nobody answered.Later.
customer_busyThe customer's line was busy.In a few minutes.
customer_rejectedThe customer declined the call.Not soon.
customer_unreachableThe customer's phone was switched off, out of coverage, or cannot take calls.Later.
invalid_numberThe number does not exist.No: fix the number.
canceledEnded before the customer answered: by your system's hang up, or by the team member.If you meant to.
failedThe call could not be completed on the network or on our side.Yes, in a minute.

Who ended the call

ended_by is for your reports; branch on outcome.

ended_byMeans
memberYour team member hung up (on their own mobile too, with Phone Bridge), or ended the call before the customer answered, or someone on your team (an admin or manager) ended it from Live Monitor.
customerThe customer hung up, or their side refused the call before it connected (busy or declined).
apiYour system, through Hang up or Transfer.
systemWe ended it, for example when the line dropped, or our support team ended the call from Live Monitor.
nullNobody hung up, or we could not tell which side did. For example: nobody answered, the customer's phone could not be reached, the call was transferred from the panel or app, or a connected call whose ending side was not reported (common when the call rings your team member's own mobile). A connected call can be completed with ended_by null: use outcome to know whether they talked. Since 15 October 2026 a Phone Bridge click to call your team member hung up on their own mobile reads member (end_reason member_ended), and a click to call someone on your team (an admin or manager) ended from Live Monitor reads member and one our support team ended reads system, as the call itself reads; before, these read null, or customer on a click to call over the internet.

Webhooks

Rather than polling, let the call come to you. Click to call sends events to the account webhook endpoints you register under Developers > Webhooks in the Voice dashboard, where they have their own Click to call group. They are offered only to an account with the Telephony API. Each delivery is signed, retried for about 20 hours until your endpoint answers with a 2xx, and carries an event id that repeats across retries. The envelope, headers and retry schedule are in Telephony Webhooks. Send test event works for every one of them.

Registering an endpoint
An endpoint must be an https:// address on port 443 or 8443, checked on every delivery. A custom signing secret must be at least 32 characters, and an account can register up to 10 endpoints. There is no per-call callback address: events go only to the endpoints registered on your account.

The events

Every event carries the whole click to call object in data (data.object is click_to_call), as it was at that event's step, with that event's own sequence, and the envelope's reference_id is yours.

EventSent when
click_to_call.member_ringingYour team member is being rung.
click_to_call.member_answeredYour team member answered.
click_to_call.customer_ringingWe are calling the customer.
click_to_call.customer_answeredThe customer answered: they are talking.
click_to_call.heldThe customer was put on hold through this API.
click_to_call.resumedThe customer was taken off hold.
click_to_call.transferredThe customer was handed on, by your system or the team member.
click_to_call.endedThe click to call is over. Sent exactly once, with the outcome.
click_to_call.taggedThe team member saved an outcome, a note or a callback for the call.
Webhook event

click_to_call.member_ringing

Fires whenYour team member is being rung. Sent at once, as you get the 201.
Sample event
{
  "id": "evt_0a1b2c3d4e5f40718293a4b5c6d7e8f9",
  "object": "event",
  "api_version": "2026-06-01",
  "type": "click_to_call.member_ringing",
  "created": 1791783062,
  "occurred_at": "2026-10-12T05:31:02.000Z",
  "timestamp_ist": "2026-10-12T11:01:02.000+05:30",
  "org_id": 42,
  "reference_id": "LEAD-2291",
  "livemode": true,
  "data": {
    "id": "c2c_9f1c4e7a2b6d48e0a3c5f1d29b7e6a40",
    "object": "click_to_call",
    "state": "member_ringing",
    "active": true,
    "on_hold": false,
    "mode": "internet",
    "member": {
      "id": 1042,
      "name": "Neha Sharma"
    },
    "from_number": "+9111XXXXXX45",
    "to_number": "+9198XXXXXX21",
    "customer_name": "Ravi Kumar",
    "call_id": null,
    "reference_id": "LEAD-2291",
    "metadata": {
      "deal": "D-77",
      "crm_owner": "neha"
    },
    "created_at": "2026-10-12T05:31:02.000Z",
    "created_at_ist": "2026-10-12T11:01:02.000+05:30",
    "member_answered_at": null,
    "member_answered_at_ist": null,
    "customer_answered_at": null,
    "customer_answered_at_ist": null,
    "ended_at": null,
    "ended_at_ist": null,
    "duration_sec": 0,
    "outcome": null,
    "ended_by": null,
    "end_reason": null,
    "transferred_to": null,
    "recording_available": false,
    "recording_url": null,
    "disposition": null,
    "sequence": 1,
    "updated_at": "2026-10-12T05:31:02.000Z",
    "updated_at_ist": "2026-10-12T11:01:02.000+05:30",
    "dial_started_at": null,
    "dial_started_at_ist": null,
    "customer_ringing_at": null,
    "customer_ringing_at_ist": null,
    "member_ring_sec": null,
    "customer_ring_sec": null,
    "call_duration_sec": null,
    "recording": {
      "status": "none",
      "revision": 0,
      "url": null,
      "format": null,
      "size_bytes": null,
      "duration_sec": null,
      "channels": null,
      "channel_layout": null,
      "retention_days": 60,
      "expires_at": "2026-12-11T05:31:02.000Z",
      "parts": [],
      "parts_ordered": false
    }
  },
  "event": "click_to_call.member_ringing",
  "event_id": "evt_0a1b2c3d4e5f40718293a4b5c6d7e8f9"
}
Webhook event

click_to_call.member_answered

Fires whenYour team member answered. The customer is called next.
Sample event
{
  "id": "evt_1b2c3d4e5f60418293a4b5c6d7e8f90a",
  "object": "event",
  "api_version": "2026-06-01",
  "type": "click_to_call.member_answered",
  "created": 1791783069,
  "occurred_at": "2026-10-12T05:31:09.204Z",
  "timestamp_ist": "2026-10-12T11:01:09.204+05:30",
  "org_id": 42,
  "reference_id": "LEAD-2291",
  "livemode": true,
  "data": {
    "id": "c2c_9f1c4e7a2b6d48e0a3c5f1d29b7e6a40",
    "object": "click_to_call",
    "state": "member_answered",
    "active": true,
    "on_hold": false,
    "mode": "internet",
    "member": {
      "id": 1042,
      "name": "Neha Sharma"
    },
    "from_number": "+9111XXXXXX45",
    "to_number": "+9198XXXXXX21",
    "customer_name": "Ravi Kumar",
    "call_id": null,
    "reference_id": "LEAD-2291",
    "metadata": {
      "deal": "D-77",
      "crm_owner": "neha"
    },
    "created_at": "2026-10-12T05:31:02.000Z",
    "created_at_ist": "2026-10-12T11:01:02.000+05:30",
    "member_answered_at": "2026-10-12T05:31:09.204Z",
    "member_answered_at_ist": "2026-10-12T11:01:09.204+05:30",
    "customer_answered_at": null,
    "customer_answered_at_ist": null,
    "ended_at": null,
    "ended_at_ist": null,
    "duration_sec": 0,
    "outcome": null,
    "ended_by": null,
    "end_reason": null,
    "transferred_to": null,
    "recording_available": false,
    "recording_url": null,
    "disposition": null,
    "sequence": 2,
    "updated_at": "2026-10-12T05:31:09.204Z",
    "updated_at_ist": "2026-10-12T11:01:09.204+05:30",
    "dial_started_at": null,
    "dial_started_at_ist": null,
    "customer_ringing_at": null,
    "customer_ringing_at_ist": null,
    "member_ring_sec": 7,
    "customer_ring_sec": null,
    "call_duration_sec": null,
    "recording": {
      "status": "none",
      "revision": 0,
      "url": null,
      "format": null,
      "size_bytes": null,
      "duration_sec": null,
      "channels": null,
      "channel_layout": null,
      "retention_days": 60,
      "expires_at": "2026-12-11T05:31:02.000Z",
      "parts": [],
      "parts_ordered": false
    }
  },
  "event": "click_to_call.member_answered",
  "event_id": "evt_1b2c3d4e5f60418293a4b5c6d7e8f90a"
}
Webhook event

click_to_call.customer_ringing

Fires whenWe are calling the customer. call_id is set from here on (on Phone Bridge it was already set at member_ringing).
Sample event
{
  "id": "evt_2c3d4e5f607142938a4b5c6d7e8f90a1",
  "object": "event",
  "api_version": "2026-06-01",
  "type": "click_to_call.customer_ringing",
  "created": 1791783071,
  "occurred_at": "2026-10-12T05:31:11.000Z",
  "timestamp_ist": "2026-10-12T11:01:11.000+05:30",
  "org_id": 42,
  "reference_id": "LEAD-2291",
  "livemode": true,
  "data": {
    "id": "c2c_9f1c4e7a2b6d48e0a3c5f1d29b7e6a40",
    "object": "click_to_call",
    "state": "customer_ringing",
    "active": true,
    "on_hold": false,
    "mode": "internet",
    "member": {
      "id": 1042,
      "name": "Neha Sharma"
    },
    "from_number": "+9111XXXXXX45",
    "to_number": "+9198XXXXXX21",
    "customer_name": "Ravi Kumar",
    "call_id": "6b1e2f3a-4c5d-4e6f-8a7b-9c0d1e2f3a4b",
    "reference_id": "LEAD-2291",
    "metadata": {
      "deal": "D-77",
      "crm_owner": "neha"
    },
    "created_at": "2026-10-12T05:31:02.000Z",
    "created_at_ist": "2026-10-12T11:01:02.000+05:30",
    "member_answered_at": "2026-10-12T05:31:09.204Z",
    "member_answered_at_ist": "2026-10-12T11:01:09.204+05:30",
    "customer_answered_at": null,
    "customer_answered_at_ist": null,
    "ended_at": null,
    "ended_at_ist": null,
    "duration_sec": 0,
    "outcome": null,
    "ended_by": null,
    "end_reason": null,
    "transferred_to": null,
    "recording_available": false,
    "recording_url": null,
    "disposition": null,
    "sequence": 3,
    "updated_at": "2026-10-12T05:31:09.204Z",
    "updated_at_ist": "2026-10-12T11:01:09.204+05:30",
    "dial_started_at": "2026-10-12T05:31:10.000Z",
    "dial_started_at_ist": "2026-10-12T11:01:10.000+05:30",
    "customer_ringing_at": "2026-10-12T05:31:10.400Z",
    "customer_ringing_at_ist": "2026-10-12T11:01:10.400+05:30",
    "member_ring_sec": 7,
    "customer_ring_sec": null,
    "call_duration_sec": null,
    "recording": {
      "status": "processing",
      "revision": 0,
      "url": null,
      "format": null,
      "size_bytes": null,
      "duration_sec": null,
      "channels": null,
      "channel_layout": null,
      "retention_days": 60,
      "expires_at": "2026-12-11T05:31:10.000Z",
      "parts": [],
      "parts_ordered": false
    }
  },
  "event": "click_to_call.customer_ringing",
  "event_id": "evt_2c3d4e5f607142938a4b5c6d7e8f90a1"
}
Webhook event

click_to_call.customer_answered

Fires whenThe customer answered: the state is in_progress.
Sample event
{
  "id": "evt_3d4e5f6071824a93b4c5d6e7f80a1b2c",
  "object": "event",
  "api_version": "2026-06-01",
  "type": "click_to_call.customer_answered",
  "created": 1791783079,
  "occurred_at": "2026-10-12T05:31:19.516Z",
  "timestamp_ist": "2026-10-12T11:01:19.516+05:30",
  "org_id": 42,
  "reference_id": "LEAD-2291",
  "livemode": true,
  "data": {
    "id": "c2c_9f1c4e7a2b6d48e0a3c5f1d29b7e6a40",
    "object": "click_to_call",
    "state": "in_progress",
    "active": true,
    "on_hold": false,
    "mode": "internet",
    "member": {
      "id": 1042,
      "name": "Neha Sharma"
    },
    "from_number": "+9111XXXXXX45",
    "to_number": "+9198XXXXXX21",
    "customer_name": "Ravi Kumar",
    "call_id": "6b1e2f3a-4c5d-4e6f-8a7b-9c0d1e2f3a4b",
    "reference_id": "LEAD-2291",
    "metadata": {
      "deal": "D-77",
      "crm_owner": "neha"
    },
    "created_at": "2026-10-12T05:31:02.000Z",
    "created_at_ist": "2026-10-12T11:01:02.000+05:30",
    "member_answered_at": "2026-10-12T05:31:09.204Z",
    "member_answered_at_ist": "2026-10-12T11:01:09.204+05:30",
    "customer_answered_at": "2026-10-12T05:31:19.516Z",
    "customer_answered_at_ist": "2026-10-12T11:01:19.516+05:30",
    "ended_at": null,
    "ended_at_ist": null,
    "duration_sec": 0,
    "outcome": null,
    "ended_by": null,
    "end_reason": null,
    "transferred_to": null,
    "recording_available": false,
    "recording_url": null,
    "disposition": null,
    "sequence": 4,
    "updated_at": "2026-10-12T05:31:19.516Z",
    "updated_at_ist": "2026-10-12T11:01:19.516+05:30",
    "dial_started_at": "2026-10-12T05:31:10.000Z",
    "dial_started_at_ist": "2026-10-12T11:01:10.000+05:30",
    "customer_ringing_at": "2026-10-12T05:31:10.400Z",
    "customer_ringing_at_ist": "2026-10-12T11:01:10.400+05:30",
    "member_ring_sec": 7,
    "customer_ring_sec": 9,
    "call_duration_sec": null,
    "recording": {
      "status": "processing",
      "revision": 0,
      "url": null,
      "format": null,
      "size_bytes": null,
      "duration_sec": null,
      "channels": null,
      "channel_layout": null,
      "retention_days": 60,
      "expires_at": "2026-12-11T05:31:10.000Z",
      "parts": [],
      "parts_ordered": false
    }
  },
  "event": "click_to_call.customer_answered",
  "event_id": "evt_3d4e5f6071824a93b4c5d6e7f80a1b2c"
}
Webhook event

click_to_call.held

Fires whenThe customer was put on hold through this API. on_hold is true. A hold the team member makes from their panel or app does not send this event.
Sample event
{
  "id": "evt_4e5f60718293a4b5c6d7e8f90a1b2c3d",
  "object": "event",
  "api_version": "2026-06-01",
  "type": "click_to_call.held",
  "created": 1791783130,
  "occurred_at": "2026-10-12T05:32:10.000Z",
  "timestamp_ist": "2026-10-12T11:02:10.000+05:30",
  "org_id": 42,
  "reference_id": "LEAD-2291",
  "livemode": true,
  "data": {
    "id": "c2c_9f1c4e7a2b6d48e0a3c5f1d29b7e6a40",
    "object": "click_to_call",
    "state": "in_progress",
    "active": true,
    "on_hold": true,
    "mode": "internet",
    "member": {
      "id": 1042,
      "name": "Neha Sharma"
    },
    "from_number": "+9111XXXXXX45",
    "to_number": "+9198XXXXXX21",
    "customer_name": "Ravi Kumar",
    "call_id": "6b1e2f3a-4c5d-4e6f-8a7b-9c0d1e2f3a4b",
    "reference_id": "LEAD-2291",
    "metadata": {
      "deal": "D-77",
      "crm_owner": "neha"
    },
    "created_at": "2026-10-12T05:31:02.000Z",
    "created_at_ist": "2026-10-12T11:01:02.000+05:30",
    "member_answered_at": "2026-10-12T05:31:09.204Z",
    "member_answered_at_ist": "2026-10-12T11:01:09.204+05:30",
    "customer_answered_at": "2026-10-12T05:31:19.516Z",
    "customer_answered_at_ist": "2026-10-12T11:01:19.516+05:30",
    "ended_at": null,
    "ended_at_ist": null,
    "duration_sec": 51,
    "outcome": null,
    "ended_by": null,
    "end_reason": null,
    "transferred_to": null,
    "recording_available": false,
    "recording_url": null,
    "disposition": null,
    "sequence": 5,
    "updated_at": "2026-10-12T05:31:19.516Z",
    "updated_at_ist": "2026-10-12T11:01:19.516+05:30",
    "dial_started_at": "2026-10-12T05:31:10.000Z",
    "dial_started_at_ist": "2026-10-12T11:01:10.000+05:30",
    "customer_ringing_at": "2026-10-12T05:31:10.400Z",
    "customer_ringing_at_ist": "2026-10-12T11:01:10.400+05:30",
    "member_ring_sec": 7,
    "customer_ring_sec": 9,
    "call_duration_sec": null,
    "recording": {
      "status": "processing",
      "revision": 0,
      "url": null,
      "format": null,
      "size_bytes": null,
      "duration_sec": null,
      "channels": null,
      "channel_layout": null,
      "retention_days": 60,
      "expires_at": "2026-12-11T05:31:10.000Z",
      "parts": [],
      "parts_ordered": false
    }
  },
  "event": "click_to_call.held",
  "event_id": "evt_4e5f60718293a4b5c6d7e8f90a1b2c3d"
}
Webhook event

click_to_call.resumed

Fires whenThe customer was taken off an API hold, through the API or by the team member from their panel. on_hold is false.
Sample event
{
  "id": "evt_5f60718293a4b5c6d7e8f90a1b2c3d4e",
  "object": "event",
  "api_version": "2026-06-01",
  "type": "click_to_call.resumed",
  "created": 1791783160,
  "occurred_at": "2026-10-12T05:32:40.000Z",
  "timestamp_ist": "2026-10-12T11:02:40.000+05:30",
  "org_id": 42,
  "reference_id": "LEAD-2291",
  "livemode": true,
  "data": {
    "id": "c2c_9f1c4e7a2b6d48e0a3c5f1d29b7e6a40",
    "object": "click_to_call",
    "state": "in_progress",
    "active": true,
    "on_hold": false,
    "mode": "internet",
    "member": {
      "id": 1042,
      "name": "Neha Sharma"
    },
    "from_number": "+9111XXXXXX45",
    "to_number": "+9198XXXXXX21",
    "customer_name": "Ravi Kumar",
    "call_id": "6b1e2f3a-4c5d-4e6f-8a7b-9c0d1e2f3a4b",
    "reference_id": "LEAD-2291",
    "metadata": {
      "deal": "D-77",
      "crm_owner": "neha"
    },
    "created_at": "2026-10-12T05:31:02.000Z",
    "created_at_ist": "2026-10-12T11:01:02.000+05:30",
    "member_answered_at": "2026-10-12T05:31:09.204Z",
    "member_answered_at_ist": "2026-10-12T11:01:09.204+05:30",
    "customer_answered_at": "2026-10-12T05:31:19.516Z",
    "customer_answered_at_ist": "2026-10-12T11:01:19.516+05:30",
    "ended_at": null,
    "ended_at_ist": null,
    "duration_sec": 81,
    "outcome": null,
    "ended_by": null,
    "end_reason": null,
    "transferred_to": null,
    "recording_available": false,
    "recording_url": null,
    "disposition": null,
    "sequence": 6,
    "updated_at": "2026-10-12T05:31:19.516Z",
    "updated_at_ist": "2026-10-12T11:01:19.516+05:30",
    "dial_started_at": "2026-10-12T05:31:10.000Z",
    "dial_started_at_ist": "2026-10-12T11:01:10.000+05:30",
    "customer_ringing_at": "2026-10-12T05:31:10.400Z",
    "customer_ringing_at_ist": "2026-10-12T11:01:10.400+05:30",
    "member_ring_sec": 7,
    "customer_ring_sec": 9,
    "call_duration_sec": null,
    "recording": {
      "status": "processing",
      "revision": 0,
      "url": null,
      "format": null,
      "size_bytes": null,
      "duration_sec": null,
      "channels": null,
      "channel_layout": null,
      "retention_days": 60,
      "expires_at": "2026-12-11T05:31:10.000Z",
      "parts": [],
      "parts_ordered": false
    }
  },
  "event": "click_to_call.resumed",
  "event_id": "evt_5f60718293a4b5c6d7e8f90a1b2c3d4e"
}
Webhook event

click_to_call.transferred

Fires whenThe team member (or your system) handed the customer on. transferred_to says to whom: always when your system transferred it, and when the team member did, whenever we know the colleague or number. Followed at once by click_to_call.ended with the same object, one sequence higher. On a move to a call menu its recording ends at the move; the call menu part is a call of its own, lane menu.
Sample event
{
  "id": "evt_60718293a4b5c6d7e8f90a1b2c3d4e5f",
  "object": "event",
  "api_version": "2026-06-01",
  "type": "click_to_call.transferred",
  "created": 1791785012,
  "occurred_at": "2026-10-12T06:03:32.000Z",
  "timestamp_ist": "2026-10-12T11:33:32.000+05:30",
  "org_id": 42,
  "reference_id": "LEAD-2304",
  "livemode": true,
  "data": {
    "id": "c2c_4d7b1e9c3a5f42d8b6e0c2a1f9d37b58",
    "object": "click_to_call",
    "state": "transferred",
    "active": false,
    "on_hold": false,
    "mode": "phone_bridge",
    "member": {
      "id": 1057,
      "name": "Arjun Mehta"
    },
    "from_number": "+9179XXXXXX12",
    "to_number": "+9197XXXXXX08",
    "customer_name": "Meera Iyer",
    "call_id": "8d3f4b5c-6e7a-4b8c-9d0e-1f2a3b4c5d6e",
    "reference_id": "LEAD-2304",
    "metadata": {
      "deal": "D-81"
    },
    "created_at": "2026-10-12T06:02:14.000Z",
    "created_at_ist": "2026-10-12T11:32:14.000+05:30",
    "member_answered_at": "2026-10-12T06:02:20.418Z",
    "member_answered_at_ist": "2026-10-12T11:32:20.418+05:30",
    "customer_answered_at": "2026-10-12T06:02:31.612Z",
    "customer_answered_at_ist": "2026-10-12T11:32:31.612+05:30",
    "ended_at": "2026-10-12T06:03:32.000Z",
    "ended_at_ist": "2026-10-12T11:33:32.000+05:30",
    "duration_sec": 61,
    "outcome": "transferred",
    "ended_by": "api",
    "end_reason": "transferred",
    "transferred_to": {
      "kind": "member",
      "member_id": 1042,
      "member_name": "Neha Sharma",
      "menu_id": null,
      "menu_name": null,
      "number": null
    },
    "recording_available": false,
    "recording_url": null,
    "disposition": null,
    "sequence": 5,
    "updated_at": "2026-10-12T06:03:32.000Z",
    "updated_at_ist": "2026-10-12T11:33:32.000+05:30",
    "dial_started_at": null,
    "dial_started_at_ist": null,
    "customer_ringing_at": "2026-10-12T06:02:20.418Z",
    "customer_ringing_at_ist": "2026-10-12T11:32:20.418+05:30",
    "member_ring_sec": 6,
    "customer_ring_sec": 11,
    "call_duration_sec": 248,
    "recording": {
      "status": "processing",
      "revision": 0,
      "url": null,
      "format": null,
      "size_bytes": null,
      "duration_sec": null,
      "channels": null,
      "channel_layout": null,
      "retention_days": 60,
      "expires_at": "2026-12-11T06:02:14.000Z",
      "parts": [],
      "parts_ordered": false
    }
  },
  "event": "click_to_call.transferred",
  "event_id": "evt_60718293a4b5c6d7e8f90a1b2c3d4e5f"
}
Webhook event

click_to_call.ended

Fires whenThe click to call is over, however it ended. Sent exactly once per click to call, also after a transfer, with the outcome, ended_by and duration_sec. This is the event to build on. When the call itself ends (either side hangs up, or your hang up request once a call is on the line), the outcome and this event come about five seconds after the end, like call.completed: we wait for the call's last facts, such as who hung up. Until then the click to call stays active. A transfer you request, and over the internet a cancel before the customer is called or a ring the team member does not answer or declines, reach their outcome at once.
Sample event
{
  "id": "evt_718293a4b5c6d7e8f90a1b2c3d4e5f60",
  "object": "event",
  "api_version": "2026-06-01",
  "type": "click_to_call.ended",
  "created": 1791783173,
  "occurred_at": "2026-10-12T05:32:53.000Z",
  "timestamp_ist": "2026-10-12T11:02:53.000+05:30",
  "org_id": 42,
  "reference_id": "LEAD-2291",
  "livemode": true,
  "data": {
    "id": "c2c_9f1c4e7a2b6d48e0a3c5f1d29b7e6a40",
    "object": "click_to_call",
    "state": "completed",
    "active": false,
    "on_hold": false,
    "mode": "internet",
    "member": {
      "id": 1042,
      "name": "Neha Sharma"
    },
    "from_number": "+9111XXXXXX45",
    "to_number": "+9198XXXXXX21",
    "customer_name": "Ravi Kumar",
    "call_id": "6b1e2f3a-4c5d-4e6f-8a7b-9c0d1e2f3a4b",
    "reference_id": "LEAD-2291",
    "metadata": {
      "deal": "D-77",
      "crm_owner": "neha"
    },
    "created_at": "2026-10-12T05:31:02.000Z",
    "created_at_ist": "2026-10-12T11:01:02.000+05:30",
    "member_answered_at": "2026-10-12T05:31:09.204Z",
    "member_answered_at_ist": "2026-10-12T11:01:09.204+05:30",
    "customer_answered_at": "2026-10-12T05:31:19.516Z",
    "customer_answered_at_ist": "2026-10-12T11:01:19.516+05:30",
    "ended_at": "2026-10-12T05:32:53.000Z",
    "ended_at_ist": "2026-10-12T11:02:53.000+05:30",
    "duration_sec": 94,
    "outcome": "completed",
    "ended_by": "customer",
    "end_reason": "customer_ended",
    "transferred_to": null,
    "recording_available": false,
    "recording_url": null,
    "disposition": null,
    "sequence": 7,
    "updated_at": "2026-10-12T05:32:53.000Z",
    "updated_at_ist": "2026-10-12T11:02:53.000+05:30",
    "dial_started_at": "2026-10-12T05:31:10.000Z",
    "dial_started_at_ist": "2026-10-12T11:01:10.000+05:30",
    "customer_ringing_at": "2026-10-12T05:31:10.400Z",
    "customer_ringing_at_ist": "2026-10-12T11:01:10.400+05:30",
    "member_ring_sec": 7,
    "customer_ring_sec": 9,
    "call_duration_sec": 94,
    "recording": {
      "status": "processing",
      "revision": 0,
      "url": null,
      "format": null,
      "size_bytes": null,
      "duration_sec": null,
      "channels": null,
      "channel_layout": null,
      "retention_days": 60,
      "expires_at": "2026-12-11T05:31:10.000Z",
      "parts": [],
      "parts_ordered": false
    }
  },
  "event": "click_to_call.ended",
  "event_id": "evt_718293a4b5c6d7e8f90a1b2c3d4e5f60"
}
Webhook event

click_to_call.tagged

Fires whenThe team member saved an outcome, a note or a callback for the call in the panel or app; disposition carries it. Usually after click_to_call.ended, and again if they change it.
Sample event
{
  "id": "evt_8293a4b5c6d7e8f90a1b2c3d4e5f6071",
  "object": "event",
  "api_version": "2026-06-01",
  "type": "click_to_call.tagged",
  "created": 1791783245,
  "occurred_at": "2026-10-12T05:34:05.000Z",
  "timestamp_ist": "2026-10-12T11:04:05.000+05:30",
  "org_id": 42,
  "reference_id": "LEAD-2291",
  "livemode": true,
  "data": {
    "id": "c2c_9f1c4e7a2b6d48e0a3c5f1d29b7e6a40",
    "object": "click_to_call",
    "state": "completed",
    "active": false,
    "on_hold": false,
    "mode": "internet",
    "member": {
      "id": 1042,
      "name": "Neha Sharma"
    },
    "from_number": "+9111XXXXXX45",
    "to_number": "+9198XXXXXX21",
    "customer_name": "Ravi Kumar",
    "call_id": "6b1e2f3a-4c5d-4e6f-8a7b-9c0d1e2f3a4b",
    "reference_id": "LEAD-2291",
    "metadata": {
      "deal": "D-77",
      "crm_owner": "neha"
    },
    "created_at": "2026-10-12T05:31:02.000Z",
    "created_at_ist": "2026-10-12T11:01:02.000+05:30",
    "member_answered_at": "2026-10-12T05:31:09.204Z",
    "member_answered_at_ist": "2026-10-12T11:01:09.204+05:30",
    "customer_answered_at": "2026-10-12T05:31:19.516Z",
    "customer_answered_at_ist": "2026-10-12T11:01:19.516+05:30",
    "ended_at": "2026-10-12T05:32:53.000Z",
    "ended_at_ist": "2026-10-12T11:02:53.000+05:30",
    "duration_sec": 94,
    "outcome": "completed",
    "ended_by": "customer",
    "end_reason": "customer_ended",
    "transferred_to": null,
    "recording_available": true,
    "recording_url": "https://voice.agentive.co.in/api/public/v1/calls/6b1e2f3a-4c5d-4e6f-8a7b-9c0d1e2f3a4b/recording",
    "disposition": {
      "outcome": "callback",
      "note": "Wants the Gold plan quote on email first.",
      "callback_at": "2026-10-14T05:30:00.000Z",
      "tagged_at": "2026-10-12T05:34:05.000Z"
    },
    "sequence": 8,
    "updated_at": "2026-10-12T05:34:05.000Z",
    "updated_at_ist": "2026-10-12T11:04:05.000+05:30",
    "dial_started_at": "2026-10-12T05:31:10.000Z",
    "dial_started_at_ist": "2026-10-12T11:01:10.000+05:30",
    "customer_ringing_at": "2026-10-12T05:31:10.400Z",
    "customer_ringing_at_ist": "2026-10-12T11:01:10.400+05:30",
    "member_ring_sec": 7,
    "customer_ring_sec": 9,
    "call_duration_sec": 94,
    "recording": {
      "status": "available",
      "revision": 1,
      "url": "https://voice.agentive.co.in/api/public/v1/calls/6b1e2f3a-4c5d-4e6f-8a7b-9c0d1e2f3a4b/recording",
      "format": "ogg",
      "size_bytes": 381952,
      "duration_sec": 94.6,
      "channels": 2,
      "channel_layout": {
        "left": "customer",
        "right": "member"
      },
      "retention_days": 60,
      "expires_at": "2026-12-11T05:31:10.000Z",
      "parts": [
        {
          "part": 1,
          "primary": true,
          "segment": null,
          "url": "https://voice.agentive.co.in/api/public/v1/calls/6b1e2f3a-4c5d-4e6f-8a7b-9c0d1e2f3a4b/recording",
          "format": "ogg",
          "size_bytes": 381952,
          "duration_sec": 94.6,
          "channels": 2,
          "started_at": "2026-10-12T05:31:19.516Z",
          "member": null
        }
      ],
      "parts_ordered": true
    }
  },
  "event": "click_to_call.tagged",
  "event_id": "evt_8293a4b5c6d7e8f90a1b2c3d4e5f6071"
}

Ordering and de-duplication

  • Events can arrive out of order, and a retry can arrive after a newer event. Keep a click to call's state only from the event with the highest data.sequence you have seen.
  • De-duplicate on the event id: our retries repeat it.
  • The order of the events is member_ringing, member_answered, customer_ringing, customer_answered, then any number of held and resumed, then transferred if it happened, then ended. A call that never got far is just member_ringing then ended. tagged can come at any time once we have started calling the customer, usually after ended.
  • When a call moves through several states at once (a very short call, or one we caught up with late), you still receive every event, in that order. Each event has its own sequence. It goes up with every change to the call, and some changes send no event (the recording becoming ready, for one), so the numbers can skip: a gap is not a missed event. Each event's object shows the call as it was at that step, so a step's later facts (answer times, outcome, ended_at) are not in it yet. The event with the highest sequence holds the latest picture.
  • click_to_call.ended is sent exactly once. Missed one? Read the click to call, or list with updated_since.

Call and recording events

From the moment we call the customer, a click to call has a call record (its call_id), and that record appears in your account's other events like any call on your account: the call.* events, call.tagged, recording.available and the others you tick. On the click to call's own record, data.unique_id is the click to call id, and the call object carries click_to_call_id, your reference_id and your metadata. Follow the click to call with the click_to_call.* events; use recording.available to learn when the recording is complete. After a transfer, the call record goes on until the customer's whole call is over, and its single terminal event comes then, with the whole call's times.

A transfer into a call menu is the one exception. If your team member moves the customer into a call menu, the rest of the call is a record of its own (lane menu, its own call_id and unique_id) with its own call.* events and recording; the click to call's record names it in transfers[].to_call_id, and click_to_call.ended stays the click to call's outcome. That record sends no call.ringing, and its call.answered comes only when a team member picks up from the menu or the number a menu key forwards the call to answers. One that a WhatsApp key, or a key that hands the call to an AI voice agent, connected ends call.completed with the move as its answered_at and no call.answered before it; one that nobody answered ends call.no_answer. The click to call's own record then usually sends its terminal call.* event a few seconds after the move, at the latest when the customer's call ends (since 14 October 2026; before, always when the customer's call ended).

Since 17 October 2026 the call menu record of a click to call (named in its transfers[].to_call_id) also carries the click to call's click_to_call_id, reference_id and metadata, and via_api true, on its events, its recording.available and every read (before, it carried none of them; a call menu record from before then can show them on a read too, at its unchanged version, and its events are not sent again). One click to call can then have two records with the same click_to_call_id, so an event with a set click_to_call_id can be the call menu record's: the click to call's own record is the one whose unique_id is the click to call id. Match the click to call's own record by unique_id, and take the click to call's outcome from click_to_call.ended, never from the call menu record's terminal event. The call menu record's recording is the call menu part of the call: keep it beside the click to call's own recording, never in place of it.

Over the internet, a click to call that ended before the customer was called has no call record and sends no call.* event. On Phone Bridge the call record starts when the team member's mobile is rung: if they decline or do not answer, that record still sends a final call.* event whose to_number is the customer, even though the customer was never called. Use the click_to_call.* outcome (member_declined or member_no_answer) to tell who did not answer. A Phone Bridge call your team member answered and the customer did not is call.no_answer, never call.completed.

Your whole call history

The Telephony API reads every call on your account, not only the click to calls: the calls your team makes from the panel, the app, their own SIM or Phone Bridge, and every inbound call, menu calls included. Each one is a complete call record: the team member, the answer and end times, talk, wait and hold time, the outcome detail, who ended the call, the recording with every part, the outcome and note your team saved, voicemail, the call menu key, who it rang and every transfer.

  • Keep it in sync with the change feed (GET /calls?updated_since=...) and the call events.
  • Read one call with GET /calls/{id} using the click to call id, a call_id or any id an event gave you.
  • Filter the calls of one team member with member_id (a call they handed on to a colleague who answered it is listed under that colleague), of one of your records with reference_id, or of one click to call with click_to_call_id. Since 17 October 2026 both also return a click to call's call menu record (for reference_id, of the first 50 click to calls placed with it); tell the two records apart by unique_id.
  • Download recordings and voicemails with your keys, before their expires_at.

Verifying a webhook

Verify every delivery before you trust it. Read the X-Agentive-Signature-V2 header, which looks like t=1791783173,v2=5f0c...:

  1. Read the raw body
    Take the body as bytes, before any JSON parsing.
  2. Check the time
    Reject the delivery if t is more than 300 seconds from your own clock.
  3. Compute the signature
    HMAC-SHA256 with the endpoint's signing secret over t, a dot, then the raw body, hex-encoded.
  4. Compare safely
    Compare it with v2 in constant time, after checking that the two are the same length in bytes. Answer 401 on any mismatch, and on a delivery with no X-Agentive-Signature-V2 at all.
  5. De-duplicate, then keep the newest
    Record the event id and ignore one you have already handled, then keep the click to call only when its sequence is newer than the one you hold.
import crypto from "node:crypto";
import express from "express";

const SECRET = process.env.AGENTIVE_WEBHOOK_SECRET;
if (!SECRET) throw new Error("AGENTIVE_WEBHOOK_SECRET is not set"); // never verify with an empty key
const TOLERANCE_SEC = 300; // five minutes

const app = express();
// The RAW body: the signature covers the exact bytes we sent.
app.use("/webhooks/agentive", express.raw({ type: "application/json" }));

function verifyV2(rawBody, header) {
  const parts = {};
  for (const piece of String(header || "").split(",")) {
    const i = piece.indexOf("=");
    if (i > 0) parts[piece.slice(0, i).trim()] = piece.slice(i + 1).trim();
  }
  const t = Number(parts.t);
  if (!Number.isFinite(t) || !parts.v2) return false;
  if (Math.abs(Math.floor(Date.now() / 1000) - t) > TOLERANCE_SEC) return false;
  const expected = crypto
    .createHmac("sha256", SECRET)
    .update(String(t) + ".")
    .update(rawBody)
    .digest("hex");
  const a = Buffer.from(parts.v2, "utf8");
  const b = Buffer.from(expected, "utf8");
  return a.length === b.length && crypto.timingSafeEqual(a, b); // compare byte lengths first
}

app.post("/webhooks/agentive", async (req, res) => {
  if (!verifyV2(req.body, req.header("X-Agentive-Signature-V2"))) {
    return res.status(401).send("bad signature");
  }
  const evt = JSON.parse(req.body.toString("utf8"));
  // alreadyHandled / remember / saveIfNewer: your own store, keyed as noted.
  if (await alreadyHandled(evt.id)) return res.sendStatus(200); // our retries repeat the id
  await remember(evt.id);

  if (evt.type.startsWith("click_to_call.")) {
    const c = evt.data;
    // Events can arrive out of order: keep a call's state only when its sequence is newer.
    await saveIfNewer(c.id, c.sequence, c);
    if (evt.type === "click_to_call.ended") console.log(c.reference_id, c.outcome, c.duration_sec);
  }
  res.sendStatus(200);
});

app.listen(3000);
Only version 2
Deliveries also carry an older X-Agentive-Signature that signs the body alone. Do not accept it on its own: it cannot tell a fresh delivery from one captured and sent again later. Never run a verifier with an empty secret; the samples refuse to start without one.

To check your code by hand, compute a signature from the shell:

Shell
# AGENTIVE_WEBHOOK_SECRET must already be set in your environment.
T=1791783173
BODY='{"id":"evt_abc","type":"click_to_call.ended","data":{}}'
printf '%s.%s' "$T" "$BODY" | python3 -c '
import hashlib, hmac, os, sys
key = os.environb[b"AGENTIVE_WEBHOOK_SECRET"]
print(hmac.new(key, sys.stdin.buffer.read(), hashlib.sha256).hexdigest())'
# Compare the result with the v2= part of X-Agentive-Signature-V2 (t=$T).

Errors

Every error has the same envelope as the rest of the Voice APIs: the HTTP status, a numeric code that mirrors it, a stable error_code to branch on, and a sentence in details (repeated in error) for your logs. Every refusal on these routes, except a refusal of the keys themselves, also carries retryable: true when the same request can work later, false when it will not until something changes. A Retry-After header (and retry_after_sec) comes only with a retryable refusal, and not with every one: insufficient_balance, call_not_placed and service_unavailable carry none, so choose your own pause. A refusal for a bad field adds field.

error_codeHTTPretryableMeansWhat to do
validation_error400NoA field is missing, has the wrong type or breaks a rule, or the body carries a field the API does not take. field names it, for example agent_id in "Unknown field: agent_id".Fix the request, then send it with a new Idempotency-Key (a 400 is kept for the key).
invalid_number400Noto (or to_number on a transfer) is not a number we can dial in India, or it is one of your own account's numbers.Fix the number.
international_not_supported400NoThe number is outside India, or your account's filter for numbers outside India refuses it.Call only Indian numbers.
invalid_caller_id400Nocaller_id is not one of the team member's numbers.Send one of the member's numbers, or leave it out to use their default.
member_not_found404NoNo team member with that id or email on your account. Also answered for a colleague named on a transfer.Check the id or email with List team members.
member_cannot_call422NoThe team member cannot place calls at all. details names why: no calling number, an app-only member, a paused member, a Phone Bridge mobile number that is not verified, or a role that does not place calls (unavailable_reason on the member says the same).Fix it in Team Members, or pick another member.
member_unavailable409YesThe team member is Offline or on a break, or (calls over the internet) has no web panel or app signed in. details says which. Nothing rang.Ask them to go Available or sign in, or call another member. Retry after Retry-After.
member_on_call409YesThe team member is on a call, or is already being rung by another click to call or a campaign.Retry after Retry-After, when `ringable_now` on the member is true again.
member_cooldown429YesThe team member missed or declined two click to calls in a row in the last 10 minutes.Wait for Retry-After (when the cooldown ends), or call another member.
member_limit429YesThe team member was rung 60 times in the last hour, or 300 times today (IST).Wait for Retry-After, or call another member.
number_flood429Yes3 calls through the API to this number in 10 minutes, or 10 today (IST), counted across every API that places calls. Both limits answer this one code; details says which.Wait for Retry-After (up to 600 seconds, or until midnight IST). Do not retry sooner.
rate_limited429YesYour request budget, the reading budget of a live click to call, or your account's calls a minute, is spent.Wait for Retry-After.
concurrency_limit429YesYour account's line limit is reached by the calls live right now. A Phone Bridge click to call counts as one line when it is placed; a click to call over the internet is not counted, like any call a team member places from the panel or app.Retry after Retry-After.
insufficient_balance402YesYour account's outgoing minutes for today are used up and its balance cannot cover more. No Retry-After is sent: it clears when you top up.Top up, then retry with the same Idempotency-Key.
number_blocked422NoThe number is on the do not disturb list, or on your account's or the team member's block list.Do not call this number.
outbound_blocked422NoOutgoing calls are switched off on your account, the account is paused, or its plan has ended.Contact your account manager.
verification_required422NoYour account cannot place calls until its business verification is complete.Finish business verification in the dashboard.
service_unavailable503YesThe line for this caller ID cannot be reached right now, the call could not be reached to carry out a control, or the record could not be read. No Retry-After is sent. When the body says retryable: false (details "Click to call is switched off for this account."), click to call is not switched on for your account right now: every route answers this, before the Telephony API is checked.Wait about 30 seconds (add some jitter), then try again. After a keypad or transfer request we cannot be sure nothing was done: read the click to call first and use a new Idempotency-Key. If retryable is false, do not retry: contact your account manager.
call_not_placed502YesWe could not place the call, or carry out the control, on our side. On a start, a keypad or a transfer request the Idempotency-Key is then used up: sending it again replays call_state_unknown. A start refused this way carries no unique_id.Read the click to call (on a start, list by member_id or reference_id with created_from) before you try again, then use a new Idempotency-Key.
call_not_connected409YesKeypad, hold or transfer was sent before the customer answered. No leg was touched.Wait for click_to_call.customer_answered, then send it again.
call_on_hold409YesKeypad tones were sent while the call is on hold through this API (a hold the team member made from their panel or app is not known here).Resume the call first.
call_ended410NoThe click to call has ended.Nothing to do. Hang up on an ended call answers 200 instead.
call_transferred410NoThe click to call was transferred: the customer is now with a colleague, a call menu or another number.Follow the rest of the call through the account's call events.
target_on_call409YesThe colleague you are transferring to is on another call.Try again in a moment, or pick someone else.
target_unavailable409YesThe colleague is Offline, on a break, or cannot be rung right now.Pick someone else, or a call menu.
menu_not_found404NoNo call menu with that id on your account.Check the id with List call menus.
feature_disabled403NoYour account does not have the Telephony API, or (on a transfer) the kind of transfer you asked for: transfer to a call menu, or to an outside number.Contact your account manager.
not_found404NoNo click to call with that id on your account.Check the id.
idempotency_in_progress409YesA request with the same Idempotency-Key is still running.Wait for Retry-After and send it again with the same key.
idempotency_conflict422NoThe same Idempotency-Key was sent with a different body.Use a new key for a new request.
call_state_unknown502NoThe replay of a request that failed on our side after the work had started: we cannot say whether it took effect. Sending the same key again only replays it, for 24 hours.Read the click to call (or list by reference_id) before you try again with a new key.
missing_credentials401Not setNo publishable key was sent.Send both keys.
invalid_credentials401Not setUnknown key, or a wrong secret.Check the keys; rotate if unsure.
account_inactive403Not setThe account is not active.Contact your account manager.
api_disabled403Not setAPI access is not enabled for the account.Contact your account manager.

Not set means the keys were refused, so the body carries no retryable: fix the keys or the account before you try again. Treat an error_code you do not recognise as a generic failure of its HTTP class: 409 and 429 are worth retrying after Retry-After, a 503 after a pause of your own of about 30 seconds, the rest are not.

Limits and polling

Limits protect your customers, your team and the phone network. Each one refuses with a clear error_code, and all but insufficient_balance also carry a Retry-After, because they can work later (top up first, then retry, for insufficient_balance).

LimitScopeerror_code
One click to call at a time. A team member on any call, or being rung by a campaign, is refused too.Team membermember_on_call
60 rings an hour, and 300 a day (IST)Team membermember_limit
A 10 minute pause after 2 missed or declined click to calls in a rowTeam membermember_cooldown
3 calls through the API to one number in 10 minutes, and 10 in a day (IST), across every API that places callsAccount and numbernumber_flood
Your account's line limit: calls live at the same time. A Phone Bridge click to call counts as one when it is placed; a click to call over the internet is not counted (each team member can have only one at a time).Accountconcurrency_limit
Your account's calls a minute, and 120 requests a minute each for reads and for writesAccountrate_limited
Your plan's outgoing minutes for the day, then your balanceAccountinsufficient_balance

Polling

Read a live click to call every 3 seconds at most, and stop once active is false. A call that ends on the line stays active for about five seconds before it reaches its outcome (see Hang up). Reading a live click to call has its own budget of 2 requests a second for each call, counted apart from your account's 120 reads a minute, so a CRM with many calls in progress does not run out. Reading an ended one counts as a normal read. Webhooks are the better way to wait.

To show who is free to take a call, read GET /members (its presence, on_call and ringable_now). Once every 15 to 30 seconds is enough, and stays well inside your 120 reads a minute.

Dry run

Send "dry_run": true with a start request to run every check without ringing anyone. If the call would go ahead you get 200 with the team member, the lane they would be rung on and the caller ID the customer would see. If it would be refused you get the same refusal a real request would. No click to call is created and no event is sent. Use it to test your integration, or to check a team member and a number before showing a Call button.

Request body
{
  "member_email": "neha@example.in",
  "to": "98XXXXXX21",
  "reference_id": "LEAD-2291",
  "dry_run": true
}
Response (200)
{
  "status": "success",
  "code": 200,
  "unique_id": null,
  "reference_id": "LEAD-2291",
  "details": "Every check passed. Nothing was rung.",
  "dry_run": {
    "member": {
      "id": 1042,
      "name": "Neha Sharma"
    },
    "mode": "internet",
    "caller_id": "+9111XXXXXX45"
  }
}

Security

  • Keys on your server only. A key can start calls from your team to any number. Keep both keys in environment variables or a secrets store on your server, and put a server between every page or app and this API.
  • Verify every webhook with the version 2 signature, within five minutes, and refuse anything else. See Verifying a webhook.
  • Decide the number on your server. Take the number to call from your own records, not from whatever the page or app sends.

Rotating your secret

Rotate the secret from Developers > API Access. You choose what happens to the old one:

ChoiceWhen to use it
Stop the old secret nowThe default, and the only safe choice if the secret may have leaked. You get a new publishable key and a new secret, and the old ones stop working at once: update both in your integration.
Keep it working for 24 hoursA planned change. Your publishable key stays the same and only the secret changes. The old and the new secret both work for 24 hours, so you can roll the new one out with no failed requests. End it early with Stop it now.

Only one old secret is ever kept: a second rotation ends the first one's 24 hours. API Access shows when your keys were last used, so you can see that the old secret has gone quiet before you end it.

Idempotency

Send an Idempotency-Key header on every POST. If the network drops and you are not sure whether the request went through, send it again with the same key: you get the first answer back, with Idempotent-Replayed: true, and nobody is rung twice, even if our server restarted in between.

  • The key is 1 to 128 visible ASCII characters, kept for 24 hours per account.
  • The same key with a different body is refused with HTTP 422 and idempotency_conflict.
  • A 2xx, 400, 404 or 422 answer is kept for the key and replayed however often you resend. After fixing the request, send it with a new key.
  • A 401 is never kept for the key: the keys are checked before the key is looked at, so fix the credentials and resend with the same key.
  • A 402, 403, 409, 410 or 429 does not use the key up: fix the cause, then resend with the same key.
  • A 5xx raised after we started placing the call is kept for the key and replayed as call_state_unknown, with retryable: false; so is any 5xx answering a keypad or transfer request, which may have been partly done. Read the click to call (list by member_id or reference_id when you have no id) before trying again with a new key. Any other 5xx does not use the key up.
  • A second click to call with a different key, for a team member who is already being rung, is refused with member_on_call; it never rings them twice.

The full rules are in the Voice Broadcast API; they are the same on every Voice endpoint.

What we send and keep

  • A team member's full mobile number is never returned by these routes or in a team call's record, only its last four digits (an AI voice agent call keeps the numbers the AI API returns). Platform staff are never listed.
  • Keypad digits you send are played, and never stored in call records, events or API logs.
  • Your reference_id and metadata are echoed on every event; keep secrets and personal data out of them.
  • The recording is reached only through the API with your keys, never through a public link.
  • A click to call's record is kept for 400 days, then deleted. Its recording follows your plan's recording retention: recording.expires_at says when, so copy it before then.

Compliance

  • One person, one call. Click to call connects a team member, who answers first, with one customer. It is not for bulk calling or recorded messages: there is no list upload and no bulk endpoint, and the limits above stop anything that looks like one. For recorded messages use the Voice Broadcast API.
  • Consent. Call people who expect to hear from your business, and stop at once when someone asks you to.
  • Do not disturb. Numbers on the platform's do not disturb list, and on your account's or the team member's block list, are never called, whatever you send.
  • Calling hours. Click to call has no set calling window, because your team member answers first, as when they press Call in the panel. Call customers at times they expect.
  • Complaints. Complaints from people you call can lead to outgoing calls being switched off on your account.

Go-live checklist

Before your first real customer

  • Both keys live in environment variables on your server, and no page or app ever holds them.
  • Your users are mapped to team members by email, and the number to call comes from your own records.
  • Every POST sends an Idempotency-Key, and a call_state_unknown answer is checked before any new attempt.
  • Your code branches on error_code, offers a retry only when retryable is true, and waits for Retry-After (or about 30 seconds when there is none).
  • A webhook endpoint on https:// verifies X-Agentive-Signature-V2 with a five minute window, de-duplicates on the event id, keeps the highest sequence, and answers 2xx quickly.
  • You store the outcome from click_to_call.ended, and catch up with updated_since after any outage.
  • You tried dry_run, then a real call from a test team member to your own number, and saw every event arrive.

Next steps