Customers
The businesses you run calls for. {id} is always either the Kleos id or your own external_ref.
List your customers
GET/v1/customers
Returns the customers created with a key in the SAME mode. A test key never sees live customers and a live key never sees test ones.
Permission: customers:read
Parameters
| Name | In | Type | |
|---|---|---|---|
limit | query | integer | How many to return. Defaults to 100, capped at 200. |
Response
{
"object": "list",
"data": [
{
"object": "customer",
"id": "0f2a6c4e-96a2-4f2e-8f2b-6c1a0e73d8b1",
"external_ref": "crm-8842",
"name": "Vandeputte Plumbing",
"brand_name": "Vandeputte",
"website_url": "https://vandeputte.be",
"country": "BE",
"timezone": "Europe/Brussels",
"status": "prospect",
"mode": "test",
"created_at": "2026-08-04T09:12:44.000Z"
}
],
"has_more": false
}Create a customer
POST/v1/customers
Creates the account one of your customers will be run under. Creating it provisions nothing: no number is bought, no agent is published, nothing dials. Send `Idempotency-Key` and a retry after a timeout is safe. The reply carries the coverage of the customer's country, so you learn whether you can actually deliver at the moment you create them.
Permission: customers:write
Parameters
| Name | In | Type | |
|---|---|---|---|
Idempotency-Key | header | string | Your own unique string. Replaying it returns the first response for 24 hours. |
Body
| Field | Type | |
|---|---|---|
name * | string | The customer's legal or trading name. |
external_ref | string | Your own id for them. Unique per account, and usable in place of the Kleos id in every other call. |
brand_name | string | What the agent should call the business on a call, if it differs from `name`. |
website_url | string | Used for research before calls. |
country | string | ISO-3166 alpha-2. Decides the calling rules and the language the agent is certified in. |
timezone | string | IANA name, e.g. Europe/Brussels. Decides when calling is allowed. |
Response
{
"object": "customer",
"id": "0f2a6c4e-96a2-4f2e-8f2b-6c1a0e73d8b1",
"external_ref": "crm-8842",
"name": "Vandeputte Plumbing",
"brand_name": "Vandeputte",
"website_url": "https://vandeputte.be",
"country": "BE",
"timezone": "Europe/Brussels",
"status": "prospect",
"mode": "test",
"created_at": "2026-08-04T09:12:44.000Z",
"coverage": {
"object": "coverage",
"country": "BE",
"status": "blocked",
"sellable": false,
"calling_language": "nl-BE",
"number_route": "carrier",
"numbers_available": 0,
"waiting_on": "carrier",
"summary": "BE is closed on something outside Kleos, with no date. Do not sell it yet.",
"blockers": []
}
}Refusals
| Status | Code | When |
|---|---|---|
| 400 | name_required | `name` was missing or blank. |
| 400 | country_not_supported | `country` is not a market Kleos opens. `GET /v1/coverage` lists the ones it does. |
| 403 | customer_limit_reached | Your account has a customer ceiling and it is full. |
| 409 | idempotency_key_reused | The same `Idempotency-Key` arrived with a different body. |
Retrieve one customer
GET/v1/customers/{id}
`{id}` is either the Kleos id or your own `external_ref` — whichever you have to hand.
Permission: customers:read
Parameters
| Name | In | Type | |
|---|---|---|---|
id * | path | string | Kleos id or your `external_ref`. |
Response
{
"object": "customer",
"id": "0f2a6c4e-96a2-4f2e-8f2b-6c1a0e73d8b1",
"external_ref": "crm-8842",
"name": "Vandeputte Plumbing",
"brand_name": "Vandeputte",
"website_url": "https://vandeputte.be",
"country": "BE",
"timezone": "Europe/Brussels",
"status": "prospect",
"mode": "test",
"created_at": "2026-08-04T09:12:44.000Z"
}Refusals
| Status | Code | When |
|---|---|---|
| 404 | customer_not_found | No customer with that id in this mode. |
Can this customer take a live call yet?
GET/v1/customers/{id}/readiness
The honest answer, with machine-readable blockers. If `ready_for_live_calls` is false, the blocker list is exactly what the call path would refuse on — this endpoint never claims ready when a call would be turned away.
Permission: customers:read
Parameters
| Name | In | Type | |
|---|---|---|---|
id * | path | string | Kleos id or your `external_ref`. |
Response
{
"object": "readiness",
"customer_id": "0f2a6c4e-96a2-4f2e-8f2b-6c1a0e73d8b1",
"external_ref": "crm-8842",
"country": "BE",
"calling_language": "nl-BE",
"ready_for_live_calls": false,
"blockers": [
{
"code": "emporos_not_live",
"message": "This account is not approved for live calls yet."
},
{
"code": "no_number_in_country",
"message": "No Kleos number is available for this customer's country yet."
}
],
"checks": {
"account_live": false,
"business_verified": true,
"agreement_signed": true,
"balance_loaded": false,
"speech_certified_for_language": true,
"number_in_country": false,
"script_certified": false,
"customer_accepted_terms": false
}
}Refusals
| Status | Code | When |
|---|---|---|
| 404 | customer_not_found | No customer with that id in this mode. |
What is being set up, and who owes the next move
GET/v1/customers/{id}/provisioning
Readiness says whether a customer can call. This says what is being done about it. `waiting_on` is the field to build on: `kleos` means we owe you something, `customer` means your customer does, `emporos` means you do. The state is worked out fresh on every read from the same rows the call path reads, so it can never claim ready while a call would be refused.
Permission: customers:read
Parameters
| Name | In | Type | |
|---|---|---|---|
id * | path | string | Kleos id or your `external_ref`. |
Response
{
"object": "provisioning",
"customer_id": "0f2a6c4e-96a2-4f2e-8f2b-6c1a0e73d8b1",
"state": "awaiting_number",
"ready": false,
"waiting_on": "kleos",
"blockers": [
"no_number_in_country"
],
"steps": [
{
"step": "number",
"done": false,
"waiting_on": "kleos",
"blocker": "no_number_in_country"
},
{
"step": "script",
"done": true,
"waiting_on": "customer",
"blocker": null
},
{
"step": "agent",
"done": true,
"waiting_on": "kleos",
"blocker": null
},
{
"step": "calendar",
"done": false,
"waiting_on": "customer",
"blocker": "no_calendar_connected"
}
],
"mode": "test"
}Refusals
| Status | Code | When |
|---|---|---|
| 404 | customer_not_found | No customer with that id in this mode. |
Ask Kleos to start setting this customer up
POST/v1/customers/{id}/provisioning
Opens the work items and returns `202` — an acknowledgement, not a completion. Some of the work is a person at Kleos, so nothing here is instant and nothing here dials. Calling it again is safe: it returns the same open work rather than queueing a second set.
Permission: customers:write
Parameters
| Name | In | Type | |
|---|---|---|---|
id * | path | string | Kleos id or your `external_ref`. |
Idempotency-Key | header | string | Your own unique string. Replaying it returns the first response for 24 hours. |
Response
{
"object": "provisioning",
"customer_id": "0f2a6c4e-96a2-4f2e-8f2b-6c1a0e73d8b1",
"state": "awaiting_number",
"ready": false,
"waiting_on": "kleos",
"blockers": [
"no_number_in_country"
],
"steps": [
{
"step": "number",
"done": false,
"waiting_on": "kleos",
"blocker": "no_number_in_country"
}
],
"mode": "test",
"requested_steps": [
"number",
"calendar"
]
}Refusals
| Status | Code | When |
|---|---|---|
| 404 | customer_not_found | No customer with that id in this mode. |
| 500 | provisioning_not_recorded | Nothing was started. Retry safely. |
Mint the link your customer accepts calling terms through
POST/v1/customers/{id}/onboarding_link
The page it opens carries YOUR name, logo and colour, and does not mention Kleos. What it collects is the one thing you cannot supply for your customer: their own named confirmation that they may contact the businesses on their list and will honour do-not-call. Until they complete it, readiness reports `customer_not_attested`. The URL comes back exactly once — we store only a hash of it. Minting a new link revokes the previous one.
Permission: customers:write
Parameters
| Name | In | Type | |
|---|---|---|---|
id * | path | string | Kleos id or your `external_ref`. |
Response
{
"object": "onboarding_link",
"id": "a3c19f6e-88b1-4a6d-b0f2-9d3c5e71a204",
"prefix": "kQ7mZ2vA",
"status": "active",
"expires_at": "2026-09-03T09:12:44.000Z",
"completed_at": null,
"accepted_by": null,
"created_at": "2026-08-04T09:12:44.000Z",
"url": "https://app.acme.example/start/kQ7mZ2vA…",
"expires_in_days": 30
}Refusals
| Status | Code | When |
|---|---|---|
| 404 | customer_not_found | No customer with that id in this mode. |
| 500 | onboarding_link_not_created | Nothing was created or sent. Retry safely. |
See whether they accepted, and who accepted
GET/v1/customers/{id}/onboarding_link
The links minted for this customer, newest first, with who accepted and when. The link itself is never returned again — only its prefix, so you can tell two apart in a support conversation.
Permission: customers:read
Parameters
| Name | In | Type | |
|---|---|---|---|
id * | path | string | Kleos id or your `external_ref`. |
Response
{
"object": "list",
"data": [
{
"object": "onboarding_link",
"id": "a3c19f6e-88b1-4a6d-b0f2-9d3c5e71a204",
"prefix": "kQ7mZ2vA",
"status": "completed",
"expires_at": "2026-09-03T09:12:44.000Z",
"completed_at": "2026-08-05T14:02:11.000Z",
"accepted_by": "Alex Moreau",
"created_at": "2026-08-04T09:12:44.000Z"
}
],
"has_more": false
}Refusals
| Status | Code | When |
|---|---|---|
| 404 | customer_not_found | No customer with that id in this mode. |