Calls
You hand Kleos a business to call. Kleos decides when to dial it, whether it is allowed to, and what to say — then hands you back what happened.
A request is not a call
POST /v1/calls queues a lead. It returns a call_request, and the phone rings later — sometimes minutes later, sometimes the next morning. That is deliberate. Dialling on the HTTP request would mean ringing a plumber at 3am because that is when your cron fired, and the calling window belongs to the person being called, not to your scheduler.
curl -X POST https://api.kleos.click/v1/calls \
-H "Authorization: Bearer kls_test_..." \
-H "Content-Type: application/json" \
-d '{
"customer_id": "crm-8842",
"phone": "+32 3 456 78 90",
"business_name": "Vandeputte Plumbing",
"external_ref": "lead-99213"
}'Between the request and the ring, the lead passes every gate a Kleos-run campaign passes: the customer's readiness, the country's calling hours, do-not-call, and the balance check. None of them can be skipped by an API parameter — the API is a way into the same machine, not a way around it.
Retries are safe. Duplicate calls are not.
A duplicate charge can be refunded. A business called twice in an hour by the same AI cannot be un-called. So while a request for a number is still open, a second request for the same number under the same customer returns the first one with 200 instead of queueing another. Send external_ref and you get the same protection across retries, restarts and redeploys.
Reading what happened
Poll GET /v1/calls?customer_id=… or fetch one by id. On a call, read finished rather than comparing status strings — an unfamiliar status is reported as not finished, so a status Kleos adds later can never make your integration think a live call ended.
What comes back is the outcome: whether it connected, how long it lasted, a summary, the recording and transcript, and the appointment if one was booked. What does not come back is how the decision was made — which prospects scored well, why the agent chose an objection path, what the research found. That is the product, and it stays ours. See Customers for what a customer can and cannot see of it.
The appointment
A call that books something sets appointment_id. Resolve it with GET /v1/appointments/{id}, or skip calls entirely and read GET /v1/appointments?from=…&to=… — for billing a customer, the meetings are the number you want, not the calls. The same calls:read key reads both.
Every appointment returns start_at and end_at as exact instants and timezone as the zone the meeting was agreed in. Use both. Formatting the instant in your own timezone is how a customer gets told to show up at the wrong hour.
Without polling
You do not have to ask repeatedly whether a call has finished. Point a webhook endpoint at your server and Kleos POSTs a signed call.completed — and an appointment.booked if one came of it — carrying the same object this page describes.
Cancelling
DELETE /v1/calls/{id} works while the request is still queued. Once the dialler has claimed it you get a 409, not a silent success. Being told “cancelled” while a phone is already ringing is the failure this endpoint exists to prevent.
status: expired, not a silent disappearance from the list.