---
title: "Place a Click2Call call"
method: POST
path: "/api/v1/click2call"
tags: ["Click2Call"]
---

# Place a Click2Call call

`POST /api/v1/click2call`

## Request

- Base URL: `https://app.didlogic.com`
- URL: `https://app.didlogic.com/api/v1/click2call`
- Auth: HTTP bearer

Places one call: the platform connects the agent leg first and, once it
answers, dials the destination number (the customer leg) and bridges the
two. The agent leg is one of your SIP accounts by default, or your own
WebSocket endpoint with `a_type: stream`.

The response is the call resource in its initial `accepted` state. Poll
`GET /api/v1/click2call/{id}` for progress.

> **`a_type: stream` requires an endpoint that speaks our protocol.** The
> message shapes, the audio format and what answers the call are described in
> [WebSocket Calls](https://docs.didlogic.com/docs/guides/apps-developer-tools/websocket-calls).

### States

`accepted` → `agent_ringing` → `agent_answered` → `customer_ringing` →
`bridged` → `completed`, with `failed` reachable from any of them.

> **`agent_ringing` is best-effort.** It is derived from a ringing
> indication on the agent leg, which some user agents never send, so it may
> be skipped entirely — a call can go straight from `accepted` to
> `agent_answered`. Do not require `agent_ringing` in client logic.

### Webhooks

Pass `callback_url` and we POST a JSON body to it on every transition of the
call. It must be an `https` URL on a public host, at most 255 characters;
anything else is refused with `422` and no call is placed. Delivery is per
call — there is no per-account setting, and calls placed without the field
get no webhooks.

| Event | Fires when |
|-------|-----------|
| `click2call.agent_ringing` | the agent leg starts ringing |
| `click2call.agent_answered` | the agent leg answers |
| `click2call.customer_ringing` | the destination is dialled |
| `click2call.bridged` | the two legs are bridged |
| `click2call.completed` | the call ends normally |
| `click2call.failed` | the call ends any other way |
| `click2call.finalized` | the billed record is committed |

The body is `{"event": …, "occurred_at": …, "event_seq": …, "data": {…}}`,
where `data` is the same call resource this endpoint returns. New fields may
appear in `data` and new names in the `click2call.*` namespace at any time:
ignore what you do not recognise.

> **`click2call.completed` and `.failed` fire immediately, with `cost` still
> `null`.** `click2call.finalized` follows once billing has settled and
> carries the priced document. The two can arrive out of order — `.finalized`
> does not mean nothing more is coming. Order is recoverable only from
> `event_seq`, which increases within a call.

Requests are **not signed**. Treat the call id as unguessable, not as proof
of origin, and confirm anything that matters with
`GET /api/v1/click2call/{id}`.

**Delivery.** Answer `2xx` within 15 seconds. A `408`, `429`, `5xx` or a
transport failure is retried up to five times with exponential backoff from
30 seconds, honouring `Retry-After`; any other `4xx` is treated as permanent
and never retried. Delivery is at-least-once, so the same `event_seq` can
arrive twice.

### Caller ID

While the agent's phone rings it shows the destination number as the
caller. Pass `a_cli` to show a number of your choice instead — for example
when your agents must not see where the call is going. It is an E.164
number of at most 15 digits, with or without the leading `+`; if the field
is present at all it must be one, and anything else — including `null` or
an empty string — is refused with `422 invalid_a_cli` and **no call is
placed**, so the destination is never shown by accident. `a_cli` does not
change the caller ID the destination sees.

`b_cli` is not supported in v1: sending it is refused with `422
caller_id_unsupported` and **no call is placed**. The caller ID presented on
the customer leg is the one configured for your account.

### Refusals

`403` means the account may not use this endpoint at all —
`click2call_api_disabled` or `account_not_verified`. `409` means the account
is in a state that forbids placing a call — `account_suspended`. `402` means
the call cannot be paid for — `insufficient_funds`. `422` means the request
itself was refused, and every violation is listed in `error.details`. In every
case no call is placed and nothing is charged.

`data.id` feeds click2call_show and click2call_destroy. `data.recording_url` feeds recordings_show.

## Request body

- object
  - `a_type` 'sip_account' | 'stream', required: Type of the agent leg. `sip_account` rings one of your SIP accounts; `stream` connects your own WebSocket endpoint first, where the account allows it.
  - `a_endpoint` string, required: For `sip_account`, the five-digit SIP account name from sipaccounts_index to ring first. For `stream`, the public `wss://` URL of your endpoint, at most 2048 characters, dialled exactly as given. Your endpoint is sent `start` as soon as the socket opens and the leg is answered; the destination is dialled after that, and its pickup arrives as `answered`.
  - `b_number` string, required: Destination number in E.164, with or without the leading `+`.
  - `callback_url` string, required: Optional `https` URL on a public host, at most 255 characters. Every transition of this call is POSTed to it — see the description above.
  - `client_call_id` string, required: Optional label of your own, echoed back on every surface that reports this call. At most 64 characters of `A-Z`, `a-z`, `0-9`, `_` or `-`. Not checked for uniqueness and never used to address the call.
  - `a_cli` string: Optional caller ID shown on the agent leg instead of `b_number`: E.164, at most 15 digits, with or without the leading `+`. When present it must be valid — `null` or an empty string is refused, never ignored.

## Response `201`

The call was placed. Poll the read operation for progress.

- object
  - `data` object, required: The Click2Call call resource
    - `id` string, required: Call ID (26-character ULID). Use it for the read and cancel operations.
    - `state` 'accepted' | 'agent_ringing' | 'agent_answered' | 'customer_ringing' | 'bridged' | 'completed' | 'failed', required: How far the call has got. `agent_ringing` may be skipped — see the description above.
    - `reason` 'no_answer' | 'busy' | 'unreachable' | 'canceled' | 'timed_out' | 'platform_error', nullable, required: Why the call ended. `null` while the call is live and on a normal end.
    - `a_type` string, required: Type of the agent leg: `sip_account` or `stream`.
    - `a_endpoint` string, required: The SIP account that was rung first, or for `stream` the endpoint URL exactly as you supplied it
    - `b_number` string, required: Destination number, digits only
    - `b_cli` string, nullable, required: Always `null` in v1
    - `created_at` string, required: When the call was placed. The 12-hour retention window runs from here.
    - `agent_ringing_at` string, nullable, required: When the agent leg started ringing. Best-effort: `null` when the phone never reported the ring, not only when it never rang.
    - `agent_answered_at` string, nullable, required: When the agent leg answered
    - `customer_ringing_at` string, nullable, required: When the destination was dialled
    - `bridged_at` string, nullable, required: When the two legs were bridged
    - `ended_at` string, nullable, required: When the call ended
    - `duration` integer, nullable, required: Whole life of the call in seconds, ringing included. Not the billed time.
    - `cost` string, nullable, required: Total cost in dollars, four decimals
    - `finalized_at` string, nullable, required: When the billed record was committed. `null` until then, and until it is set `cost` may still be `null`.
    - `recording_url` string, nullable, required: Where the recording of the call can be downloaded. `null` when the call was not recorded, and `null` on a call that has not settled yet. By default the link is signed and expires one hour after it was minted; each read of the call mints a fresh one, and `GET /api/v1/calls/recording/{callid}` also gives one. If the account has permanent recording links enabled (on request through your account manager or support), the field holds a permanent `/api/v1/recordings/{user_id}/{token}` URL instead. It does not expire while the recording is retained and the option stays on.
    - `client_call_id` string, nullable, required: The label you supplied when placing the call, echoed back unchanged. `null` when none was supplied. Carried on every webhook for this call.
    - `a_cli` string, nullable, required: The caller ID the agent leg was presented with, digits only: the `a_cli` you supplied, or `b_number` when you supplied none. Carried on every webhook for this call.

## Other responses

- `401`: The API token is missing or incorrect. This endpoint answers `401`; the rest of v1 answers `403` for the same refusal.
- `402`: The account has no funds available — `insufficient_funds`. The request was well formed and permitted; it simply cannot be paid for. No call is placed.
- `403`: The account may not use this endpoint: the request is refused with `click2call_api_disabled` or `account_not_verified`. No call is placed.
- `409`: The account is in a state that forbids placing a call — `account_suspended`. The request itself was well formed and permitted; no call is placed.
- `422`: The request failed validation. `error.code` is always `validation_failed`; each violation appears in `error.details` with its own `code` and the `field` it belongs to — `caller_id_unsupported` (`b_cli` is not supported in v1), `a_type_unsupported`, `invalid_a_endpoint`, `invalid_stream_url`, `invalid_stream_token`, `unknown_sip_account`, `invalid_b_number`, `invalid_a_cli`, `callback_url_insecure`, `invalid_callback_url` or `invalid_client_call_id`. No call is placed.

---

[API](https://skmtc.dev/didlogic/apis/get-started.md) · [All operations](https://skmtc.dev/didlogic/apis/get-started/llms.txt) · [OpenAPI document](https://skmtc.dev/didlogic/apis/get-started/revisions/dc972d592b34?raw)
