Ваши клиенты и их кредиты
Добавляйте своих клиентов, ведите их счета кредитов, пополняйте, списывайте и корректируйте их кредиты, блокируйте клиента и задавайте, сколько кредитов стоит операция.
Клиент здесь — это клиент вашего собственного продукта, названный идентификатором, который ему даёт ваша система. Slen ведёт счета кредитов ваших клиентов, берёт с них кредиты, когда флоу выполняется для них, и отклоняет запуск, за который они не могут заплатить. Операции этой страницы добавляют и удаляют клиентов, настраивают их счета, двигают их кредиты, блокируют клиента и задают, сколько кредитов стоит операция. Консоль показывает тех же клиентов на экране Клиенты.
Клиенты и счета
Клиента называет
external_user_id— тот же идентификатор, который запрос, запускающий флоу, отправляет вcontext.user_id. Это непрозрачный идентификатор вашей системы, а не адрес почты и не имя.PUT /v1/customers/{externalUserId}добавляет клиента без кредитов и без счёта. Он отвечает201, когда создал клиента, и200, когда клиент уже был, поэтому повтору не нужен ключ.Счёт принадлежит одному клиенту и одному типу кредитов. Тип кредитов — имя, которое вы выбираете сами; если его не указать, это
default. У клиента с двумя типами кредитов два счёта.Счёт создаёт первое пополнение его типа кредитов или его настройки. Списание и корректировка ничего не создают.
Счёт несёт
balance,reservedиavailable.reserved— это то, что держат идущие вызовы, аavailable— этоbalanceминусreserved.Для клиента, которого проект никогда не видел, ответ —
404с кодомcustomer_not_found. Для клиента без счетовaccountsв ответе пустой.
Чтение требует customers:read, а каждое изменение — customers:write, кроме задания цены, которое требует management:write. Ключ вашего приложения с gateway:write и customers:write запускает флоу и ведёт своих клиентов и больше ничего не меняет.
Настройки счёта
PUT /v1/customers/{externalUserId}/accounts/{creditType} задаёт обе настройки счёта сразу. Он создаёт клиента и счёт, когда их нет, и отвечает 201, а иначе меняет настройки и отвечает 200.
allow_negativeразрешает счёту уходить ниже нуля. По умолчанию разрешение выключено, и тогда холд или списание, которые счёт не может покрыть, отклоняются сinsufficient_credits. Его можно выключить, пока баланс ниже нуля: защита действует со следующего движения.low_balance_thresholdзадаёт уровень события вебхукаcustomer.balance_low. Событие отправляется, когда движение переводитavailableс этого уровня или выше на значение ниже него. Ноль, значение по умолчанию, отключает событие.Пополнение и корректировка не меняют настройки: тело, в котором названо любое из этих полей, отклоняется с
422.
Как запуск тратит кредиты
Запуск тратит кредиты, когда он называет клиента в context.user_id и у проекта есть цена в кредитах для его операции. context.credit_type называет тип кредитов, а без него тип — default. Тип кредитов решает, какой счёт платит и какая цена применяется.
Операция без цены для этого типа кредитов или с нулевой ценой не берёт кредитов.
Каждый вызов модели в запуске резервирует цену на счёте до того, как модель вызвана. Когда вызов завершён и рассчитан, холд становится расходом, а вызов, который не удался, возвращает его.
Счёт, который не может покрыть цену, отклоняет вызов с кодом
insufficient_credits. Запуск для клиента, у которого нет счёта этого типа кредитов, отклоняется так же.Запуск для клиента, которого проект не знает, отклоняется с кодом
customer_not_found.Запуск для заблокированного клиента отклоняется с кодом
customer_blocked— Заблокировать клиента.
Цена задаётся для кода операции и типа кредитов, и следующий холд уже использует её. Повторное задание цены заменяет её, а уже взятый холд сохраняет цену, по которой он взят.
Движения кредитов
Кредиты двигают три операции. Каждая требует заголовок Idempotency-Key длиной не больше 190 символов, а запрос без него отвечает 400 с кодом idempotency_key_required.
Пополнение добавляет кредиты. Оно создаёт клиента и счёт, когда их нет.
Списание (charge) снимает кредиты за то, что ваше приложение продаёт само, — выгрузку, место. Выписка показывает его как
charge. Для клиента, которого проект не знает, ответ —404с кодомcustomer_not_found, а для клиента без счёта этого типа кредитов —402с кодомinsufficient_credits.Корректировка исправляет баланс. Её
creditsимеет знак, а ноль отклоняется. Если счёта нет, ответ —404с кодомcustomer_not_found.
Что у трёх общего:
У
creditsне больше четырёх знаков после точки. Пятый знак отклоняется с422, а не округляется.note, до 255 символов, сохраняется в записи и виден в выписке: номер счёта на оплату, причина.Списание или корректировка не могут опустить баланс ниже того, что держит счёт, если счёт не допускает отрицательный баланс. Такой запрос отвечает
402с кодомinsufficient_credits. Пополнение так не отклоняется никогда, в том числе на счёте ниже нуля.Заблокированного клиента можно пополнять, с него можно списывать, и его можно корректировать.
Списание — не расход: отчёт маржи сопоставляет кредиты вызова с тем, сколько вызов стоил, а за списанием не стоит никакой вызов.
Повтор с ключом ничего не двигает.
Повтор того же запроса с его ключом отвечает
200. В течение 24 часов, если сервис не настроен иначе, ответом служит первый ответ, с балансом того момента. Потом — счёт в его нынешнем виде.Если ключ уже называет другое пополнение, списание или корректировку проекта, ответ —
409с кодомidempotency_conflict. В эти 24 часа любое другое тело под этим ключом — другой запрос, в том числе другойnote. Потом другой клиент, другой тип кредитов, другой вид движения или другая сумма — это другое движение. Новому движению нужен новый ключ.Отклонённое движение не сохраняется: тот же запрос можно отправить снова с его ключом.
Заблокировать клиента
POST /v1/customers/{externalUserId}/block блокирует клиента, а POST /v1/customers/{externalUserId}/unblock снова его пускает.
Новый запуск заблокированного клиента отклоняется с
403и кодомcustomer_blockedдо того, как что-либо создано.Его запуск, который уже идёт, — в очереди или в ожидании согласования, — отклоняется на следующем холде кредитов вызова модели. Узел модели выходит через Отказ, а без ветки там выполнение заканчивается со статусом
rejectedи кодомcustomer_blocked. Запуск операции без цены в кредитах не резервирует кредиты и доходит до конца.Повтор запроса с ключом, с которым он был отправлен до блокировки, получает в ответ то выполнение.
Кредиты по-прежнему двигаются, настройки меняются, а выписка читается, поэтому с заблокированным клиентом можно рассчитаться.
blocked_at— момент первой блокировки. Повторная блокировка его сохраняет и заменяетblock_reason, только когда переданreason. Разблокировка очищает оба поля.
Удалить клиента
DELETE /v1/customers/{externalUserId} удаляет клиента и его счета, только когда с ним ничего не происходило: каждый счёт на нуле и ничего не зарезервировано, а в выписке нет ни одной записи. Иначе клиент остаётся, а ответ объясняет почему.
{
"error": {
"code": "customer_has_history",
"message": "The customer has credits or a credit history and cannot be deleted; block it instead",
"request_id": "5d8a3f1c-2b6e-4c9a-8f17-3e0b9d4c6a25"
}
}Выписка объясняет баланс, поэтому она хранится. Такого клиента заблокируйте. Удаление и движение одного и того же клиента никогда не обходят друг друга: удаление, которое встретило идущее пополнение, ждёт и затем отказывает, а пополнение, которое встретило идущее удаление, ждёт и затем создаёт клиента заново.
Читать клиентов и выписки
Список делится на страницы, самый новый клиент — первым, и каждый клиент приходит со всеми своими счетами.
statusоставляет активных или заблокированных клиентов,q— клиентов, чейexternal_user_idего содержит, аcredit_type— тех, у кого есть счёт этого типа кредитов.low_balance— это число, а не флаг: он оставляет клиентов, у которых есть счёт сavailableниже него, — счёт типаcredit_type, когда переданы оба. Фильтры выбирают клиентов и никогда не укорачивают список их счетов.Выписка клиента перечисляет каждое движение его кредитов, самое новое — первым. Запись несёт счёт таким, каким его оставило движение, в
balance_afterиavailable_after, иnoteзапроса.entry_typeоставляет один вид движений, аcredit_type— движения одного счёта. У клиента без счетов выписка пустая.
entry_type | Что сдвинулось | credits |
|---|---|---|
topup | Пополнение | Положительное |
charge | Списание вашего приложения | Отрицательное |
adjust | Корректировка | С любым знаком |
hold | Вызов модели зарезервировал свою цену из available | Отрицательное |
commit | Вызов рассчитан, и зарезервированная цена ушла из balance | Отрицательное |
release | Холд вернулся в available | Положительное |
Не складывайте credits, чтобы узнать, сколько потратил клиент. У вызова, который потратил кредиты, две отрицательные записи на одну и ту же цену — его hold и его commit, и расход — это только записи commit.
Операции
Описания операций и полей приведены на английском.
Клиенты и их счета
GET /v1/customers
- Разрешение:
customers:read - Доступно в SDK для PHP и Node.js
One cursor page of the project's end customers, newest first, each with every credit account it holds (accounts, by credit_type; empty for a customer created by PUT /v1/customers/{externalUserId} that holds none yet). The filters choose customers: credit_type — those holding an account of that type; low_balance — those holding an account with fewer credits still spendable (balance - reserved) than the threshold, of credit_type when it is given too; status, q. The nested list is never narrowed: a customer that matches comes with all its accounts.
Параметры
| Параметр | Описание |
|---|---|
cursor необязательноquery | The |
view необязательноquery |
|
limit необязательноquery | Page size.
|
status необязательноquery | Only customers in this status.
|
credit_type необязательноquery | Customers holding an account of this credit type.
|
low_balance необязательноquery | A threshold, not a flag: |
q необязательноquery | Narrows the list to the customers whose
|
Ответы
One cursor page of customers
{
"data": [
{
"customer_id": "74438200973594624",
"external_user_id": "user_8421",
"status": "active",
"blocked_at": "2026-10-02T09:30:00Z",
"block_reason": "string",
"accounts": [
{
"customer_account_id": "74438200973594624",
"customer_id": "74438200973594624",
"project_id": "74438200973594624",
"external_user_id": "user_8421",
"credit_type": "default",
"balance": "12.5000",
"reserved": "12.5000",
"available": "12.5000",
"allow_negative": true,
"low_balance_threshold": "12.5000",
"created_at": "2026-10-02T09:30:00Z",
"updated_at": "2026-10-02T09:30:00Z"
}
],
"created_at": "2026-10-02T09:30:00Z",
"updated_at": "2026-10-02T09:30:00Z"
}
],
"meta": {
"limit": 1,
"next_cursor": "string"
}
}Ошибки
| Статус | Коды | Когда |
|---|---|---|
| 401 | No credential, or one that is not valid | |
| 403 | Authenticated, but not allowed: scope ( | |
| 422 | Validation failure or a semantically impossible request. Field-level validation from the framework answers |
Заголовки
X-Request-Id
Тело каждой ошибки — конверт ошибки.
Клиент и его счета
GET /v1/customers/{externalUserId}
- Разрешение:
customers:read - Доступно в SDK для PHP и Node.js
The customer and every credit account it holds, by credit_type. A customer with no account yet answers with accounts: []; an external_user_id the project has never seen is 404 customer_not_found, and so is one of another project. The external_user_id is the caller's opaque identifier — not an email address or a name.
Параметры
| Параметр | Описание |
|---|---|
externalUserId обязательноpath | The end customer's identifier in the caller's own system.
|
Ответы
The customer
{
"data": {
"customer_id": "74438200973594624",
"external_user_id": "user_8421",
"status": "active",
"blocked_at": "2026-10-02T09:30:00Z",
"block_reason": "string",
"accounts": [
{
"customer_account_id": "74438200973594624",
"customer_id": "74438200973594624",
"project_id": "74438200973594624",
"external_user_id": "user_8421",
"credit_type": "default",
"balance": "12.5000",
"reserved": "12.5000",
"available": "12.5000",
"allow_negative": true,
"low_balance_threshold": "12.5000",
"created_at": "2026-10-02T09:30:00Z",
"updated_at": "2026-10-02T09:30:00Z"
}
],
"created_at": "2026-10-02T09:30:00Z",
"updated_at": "2026-10-02T09:30:00Z"
}
}Ошибки
| Статус | Коды | Когда |
|---|---|---|
| 401 | No credential, or one that is not valid | |
| 403 | Authenticated, but not allowed: scope ( | |
| 404 | No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable. |
Заголовки
X-Request-Id
Тело каждой ошибки — конверт ошибки.
Создать клиента, если его ещё нет
PUT /v1/customers/{externalUserId}
- Разрешение:
customers:write - Доступно в SDK для PHP и Node.js
Creates the customer when the project has none by this external_user_id (201) and answers the one it has otherwise (200) — idempotent by nature, so no Idempotency-Key: a repeat, or two first calls at once, end with one customer. No credit account is created: a customer without accounts is a legal state, and the first top-up or account setting creates the account of its credit type. The body is an empty object; any field is 422.
Параметры
| Параметр | Описание |
|---|---|
externalUserId обязательноpath | The end customer's identifier in the caller's own system.
|
Тело запроса необязательно
{}- Максимум ключей:
0
Ответы
The customer already existed
{
"data": {
"customer_id": "74438200973594624",
"external_user_id": "user_8421",
"status": "active",
"blocked_at": "2026-10-02T09:30:00Z",
"block_reason": "string",
"accounts": [
{
"customer_account_id": "74438200973594624",
"customer_id": "74438200973594624",
"project_id": "74438200973594624",
"external_user_id": "user_8421",
"credit_type": "default",
"balance": "12.5000",
"reserved": "12.5000",
"available": "12.5000",
"allow_negative": true,
"low_balance_threshold": "12.5000",
"created_at": "2026-10-02T09:30:00Z",
"updated_at": "2026-10-02T09:30:00Z"
}
],
"created_at": "2026-10-02T09:30:00Z",
"updated_at": "2026-10-02T09:30:00Z"
}
}The customer was created
{
"data": {
"customer_id": "74438200973594624",
"external_user_id": "user_8421",
"status": "active",
"blocked_at": "2026-10-02T09:30:00Z",
"block_reason": "string",
"accounts": [
{
"customer_account_id": "74438200973594624",
"customer_id": "74438200973594624",
"project_id": "74438200973594624",
"external_user_id": "user_8421",
"credit_type": "default",
"balance": "12.5000",
"reserved": "12.5000",
"available": "12.5000",
"allow_negative": true,
"low_balance_threshold": "12.5000",
"created_at": "2026-10-02T09:30:00Z",
"updated_at": "2026-10-02T09:30:00Z"
}
],
"created_at": "2026-10-02T09:30:00Z",
"updated_at": "2026-10-02T09:30:00Z"
}
}Ошибки
| Статус | Коды | Когда |
|---|---|---|
| 401 | No credential, or one that is not valid | |
| 403 | Authenticated, but not allowed: scope ( | |
| 422 | Validation failure or a semantically impossible request. Field-level validation from the framework answers |
Заголовки
X-Request-Id
Тело каждой ошибки — конверт ошибки.
Удалить клиента без истории
DELETE /v1/customers/{externalUserId}
- Разрешение:
customers:write - Доступно в SDK для PHP и Node.js
Removes the customer and its credit accounts — only when nothing ever happened to them: every account at a zero balance with nothing reserved, and not one entry in its statement. Otherwise 409 customer_has_history: the journal is append-only, its entries explain the balance, so a customer with history is blocked (POST /v1/customers/{externalUserId}/block) rather than deleted. A delete that meets a top-up or a charge of the same customer under way waits for it, and then refuses; a top-up that meets a delete under way waits, and then creates the customer again. An external_user_id the project has never seen is 404 customer_not_found. The external_user_id is the caller's opaque identifier — not an email address or a name: erasing one with history is not offered. Audited.
Параметры
| Параметр | Описание |
|---|---|
externalUserId обязательноpath | The end customer's identifier in the caller's own system.
|
Ответы
The customer and its accounts are gone
Без тела
Ошибки
| Статус | Коды | Когда |
|---|---|---|
| 401 | No credential, or one that is not valid | |
| 403 | Authenticated, but not allowed: scope ( | |
| 404 | No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable. | |
| 409 | State conflict: idempotency clash, stale draft, illegal transition; the last owner of an organization ( |
Заголовки
X-Request-Id
Тело каждой ошибки — конверт ошибки.
Задать настройки счёта
PUT /v1/customers/{externalUserId}/accounts/{creditType}
- Разрешение:
customers:write - Доступно в SDK для PHP и Node.js
The settings of the customer's account of this credit_type: whether it may go below zero (allow_negative) and the level under which a movement sends customer.balance_low (low_balance_threshold, 0 switches the warning off). Both fields are required — the body is the whole of the settings. Creates the customer and the account when they do not exist yet (201); otherwise changes the settings and answers 200. Not a movement of credits, so no Idempotency-Key: the same settings put twice are the same settings. allow_negative: false on an account already below zero is allowed — the guard holds from the next movement on. A blocked customer's settings can be changed.
This is the one place the settings change: a top-up or an adjustment that names either field is 422.
Параметры
| Параметр | Описание |
|---|---|
externalUserId обязательноpath | The end customer's identifier in the caller's own system.
|
creditType обязательноpath | The credit type of the account — tenant-defined text,
|
Тело запроса
{
"allow_negative": true,
"low_balance_threshold": "12.5000"
}Ответы
The settings were changed
{
"data": {
"customer_account_id": "74438200973594624",
"customer_id": "74438200973594624",
"project_id": "74438200973594624",
"external_user_id": "user_8421",
"credit_type": "default",
"balance": "12.5000",
"reserved": "12.5000",
"available": "12.5000",
"allow_negative": true,
"low_balance_threshold": "12.5000",
"created_at": "2026-10-02T09:30:00Z",
"updated_at": "2026-10-02T09:30:00Z"
}
}The account was created with these settings
{
"data": {
"customer_account_id": "74438200973594624",
"customer_id": "74438200973594624",
"project_id": "74438200973594624",
"external_user_id": "user_8421",
"credit_type": "default",
"balance": "12.5000",
"reserved": "12.5000",
"available": "12.5000",
"allow_negative": true,
"low_balance_threshold": "12.5000",
"created_at": "2026-10-02T09:30:00Z",
"updated_at": "2026-10-02T09:30:00Z"
}
}Ошибки
| Статус | Коды | Когда |
|---|---|---|
| 401 | No credential, or one that is not valid | |
| 403 | Authenticated, but not allowed: scope ( | |
| 422 | Validation failure or a semantically impossible request. Field-level validation from the framework answers |
Заголовки
X-Request-Id
Тело каждой ошибки — конверт ошибки.
Заблокировать клиента
POST /v1/customers/{externalUserId}/block
- Разрешение:
customers:write - Доступно в SDK для PHP и Node.js
A blocked customer's new runs are refused 403 customer_blocked before anything is created (POST /v1/operations/execute with its context.user_id), and a run of theirs already under way is refused at its next hold of credits. The balance can still be moved — top-up, charge, adjustment and the account settings stay open, so the customer can be settled with — and the statement still reads. blocked_at is set the first time; blocking again answers 200 without moving it, and replaces block_reason only when a reason is given. An external_user_id the project has never seen is 404 customer_not_found. Audited.
Параметры
| Параметр | Описание |
|---|---|
externalUserId обязательноpath | The end customer's identifier in the caller's own system.
|
Тело запроса необязательно
{
"reason": "string"
}Ответы
The customer, blocked
{
"data": {
"customer_id": "74438200973594624",
"external_user_id": "user_8421",
"status": "active",
"blocked_at": "2026-10-02T09:30:00Z",
"block_reason": "string",
"accounts": [
{
"customer_account_id": "74438200973594624",
"customer_id": "74438200973594624",
"project_id": "74438200973594624",
"external_user_id": "user_8421",
"credit_type": "default",
"balance": "12.5000",
"reserved": "12.5000",
"available": "12.5000",
"allow_negative": true,
"low_balance_threshold": "12.5000",
"created_at": "2026-10-02T09:30:00Z",
"updated_at": "2026-10-02T09:30:00Z"
}
],
"created_at": "2026-10-02T09:30:00Z",
"updated_at": "2026-10-02T09:30:00Z"
}
}Ошибки
| Статус | Коды | Когда |
|---|---|---|
| 401 | No credential, or one that is not valid | |
| 403 | Authenticated, but not allowed: scope ( | |
| 404 | No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable. | |
| 422 | Validation failure or a semantically impossible request. Field-level validation from the framework answers |
Заголовки
X-Request-Id
Тело каждой ошибки — конверт ошибки.
Разблокировать клиента
POST /v1/customers/{externalUserId}/unblock
- Разрешение:
customers:write - Доступно в SDK для PHP и Node.js
The customer is active again: blocked_at and block_reason are cleared and new runs are accepted. Unblocking a customer that is not blocked answers 200 with the customer as it is. The body is empty; any field is 422. An external_user_id the project has never seen is 404 customer_not_found. Audited.
Параметры
| Параметр | Описание |
|---|---|
externalUserId обязательноpath | The end customer's identifier in the caller's own system.
|
Тело запроса необязательно
{}- Максимум ключей:
0
Ответы
The customer, active
{
"data": {
"customer_id": "74438200973594624",
"external_user_id": "user_8421",
"status": "active",
"blocked_at": "2026-10-02T09:30:00Z",
"block_reason": "string",
"accounts": [
{
"customer_account_id": "74438200973594624",
"customer_id": "74438200973594624",
"project_id": "74438200973594624",
"external_user_id": "user_8421",
"credit_type": "default",
"balance": "12.5000",
"reserved": "12.5000",
"available": "12.5000",
"allow_negative": true,
"low_balance_threshold": "12.5000",
"created_at": "2026-10-02T09:30:00Z",
"updated_at": "2026-10-02T09:30:00Z"
}
],
"created_at": "2026-10-02T09:30:00Z",
"updated_at": "2026-10-02T09:30:00Z"
}
}Ошибки
| Статус | Коды | Когда |
|---|---|---|
| 401 | No credential, or one that is not valid | |
| 403 | Authenticated, but not allowed: scope ( | |
| 404 | No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable. | |
| 422 | Validation failure or a semantically impossible request. Field-level validation from the framework answers |
Заголовки
X-Request-Id
Тело каждой ошибки — конверт ошибки.
Выписка по кредитам клиента
GET /v1/customers/{externalUserId}/entries
- Разрешение:
customers:read - Доступно в SDK для PHP и Node.js
Every movement of the customer's credits, newest first, each carrying the balance it left behind — so a reader never has to replay the feed to know where the customer stood — and the note its caller gave. Cursor-paged, no date window: a statement is read from the top. An external_user_id the project has never seen is 404 customer_not_found; a customer without an account (or without one of the credit_type asked for) has an empty statement.
Параметры
| Параметр | Описание |
|---|---|
externalUserId обязательноpath | The end customer's identifier in the caller's own system.
|
cursor необязательноquery | The |
limit необязательноquery | Page size (default 30).
|
credit_type необязательноquery | Narrows a customer who holds several pots down to one.
|
entry_type необязательноquery | Only the movements of this type — the top-ups, say, or the charges.
|
Ответы
One cursor page of credit movements
{
"data": [
{
"entry_id": "74438200973594624",
"customer_account_id": "74438200973594624",
"external_user_id": "user_8421",
"credit_type": "default",
"entry_type": "hold",
"credits": "12.5000",
"balance_after": "12.5000",
"available_after": "12.5000",
"reservation_id": "74438200973594624",
"note": "string",
"created_at": "2026-10-02T09:30:00Z"
}
],
"meta": {
"limit": 1,
"next_cursor": "string"
}
}Ошибки
| Статус | Коды | Когда |
|---|---|---|
| 401 | No credential, or one that is not valid | |
| 403 | Authenticated, but not allowed: scope ( | |
| 404 | No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable. | |
| 422 | Validation failure or a semantically impossible request. Field-level validation from the framework answers |
Заголовки
X-Request-Id
Тело каждой ошибки — конверт ошибки.
Пополнить кредиты клиента
POST /v1/customers/{externalUserId}/topup
- Разрешение:
customers:write - Нужен Idempotency-Key
- Доступно в SDK для PHP и Node.js
Creates the account when it does not exist yet. Moves money, so it is behind the idempotency middleware.
The Idempotency-Key is also written on the journal entry, where it is unique within the project for as long as the entry exists. A key that already names this top-up — same customer, same credit_type, same credits — answers 200 with the account as it stands now and credits nothing, however long ago the first call was and even while it is still running. A key that names any other top-up, adjustment or charge of the project answers 409 idempotency_conflict and moves nothing. The note does not take part in that comparison; while the stored answer of the first call lasts, any other body under its key — another note included — is 409 idempotency_conflict as for every financial write.
An account the top-up creates starts with allow_negative: false and no low-balance warning. The settings are PUT /v1/customers/{externalUserId}/accounts/{creditType}'s: a body that names allow_negative or low_balance_threshold is 422, as is any field this body does not describe.
Параметры
| Параметр | Описание |
|---|---|
externalUserId обязательноpath | The end customer's identifier in the caller's own system.
|
Idempotency-Key обязательноheader | Replay guard for a financial write. The same key returns the original result with the original status and never charges twice; a different body under a used key answers
|
Тело запроса
{
"credits": "12.5000",
"credit_type": "default",
"note": "string"
}Ответы
The account after the top-up
{
"data": {
"customer_account_id": "74438200973594624",
"customer_id": "74438200973594624",
"project_id": "74438200973594624",
"external_user_id": "user_8421",
"credit_type": "default",
"balance": "12.5000",
"reserved": "12.5000",
"available": "12.5000",
"allow_negative": true,
"low_balance_threshold": "12.5000",
"created_at": "2026-10-02T09:30:00Z",
"updated_at": "2026-10-02T09:30:00Z"
}
}Ошибки
| Статус | Коды | Когда |
|---|---|---|
| 400 | Malformed request — e.g. the | |
| 401 | No credential, or one that is not valid | |
| 403 | Authenticated, but not allowed: scope ( | |
| 409 | State conflict: idempotency clash, stale draft, illegal transition; the last owner of an organization ( | |
| 422 | Validation failure or a semantically impossible request. Field-level validation from the framework answers |
Заголовки
X-Request-Id
Тело каждой ошибки — конверт ошибки.
Скорректировать баланс клиента
POST /v1/customers/{externalUserId}/adjust
- Разрешение:
customers:write - Нужен Idempotency-Key
- Доступно в SDK для PHP и Node.js
Signed: an adjustment may take credits away. Zero is refused — it would be an audit row saying nothing.
The Idempotency-Key is unique within the project on the journal entry, exactly as for a top-up: repeating the same adjustment under its key answers 200 with the account as it stands now and moves nothing — also when the balance could no longer cover it a second time — and a key that names any other top-up, adjustment or charge answers 409 idempotency_conflict. The note takes no part in that comparison, exactly as for a top-up.
Параметры
| Параметр | Описание |
|---|---|
externalUserId обязательноpath | The end customer's identifier in the caller's own system.
|
Idempotency-Key обязательноheader | Replay guard for a financial write. The same key returns the original result with the original status and never charges twice; a different body under a used key answers
|
Тело запроса
{
"credits": "12.5000",
"credit_type": "default",
"note": "string"
}Ответы
The account after the adjustment
{
"data": {
"customer_account_id": "74438200973594624",
"customer_id": "74438200973594624",
"project_id": "74438200973594624",
"external_user_id": "user_8421",
"credit_type": "default",
"balance": "12.5000",
"reserved": "12.5000",
"available": "12.5000",
"allow_negative": true,
"low_balance_threshold": "12.5000",
"created_at": "2026-10-02T09:30:00Z",
"updated_at": "2026-10-02T09:30:00Z"
}
}Ошибки
| Статус | Коды | Когда |
|---|---|---|
| 400 | Malformed request — e.g. the | |
| 401 | No credential, or one that is not valid | |
| 402 | Refused because of money: | |
| 403 | Authenticated, but not allowed: scope ( | |
| 404 | No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable. | |
| 409 | State conflict: idempotency clash, stale draft, illegal transition; the last owner of an organization ( | |
| 422 | Validation failure or a semantically impossible request. Field-level validation from the framework answers |
Заголовки
X-Request-Id
Тело каждой ошибки — конверт ошибки.
Списать кредиты клиента за услугу приложения
POST /v1/customers/{externalUserId}/charge
- Разрешение:
customers:write - Нужен Idempotency-Key
- Доступно в SDK для PHP и Node.js
The application's own charge — a PDF export, a seat, anything it sells outside an operation. credits is the positive amount to take; the journal entry is a charge with the amount negative and the note the caller gave. Guarded like every debit: an account with allow_negative off cannot go below what is held on it (402 insufficient_credits). A charge is not spend: the margin report sets credits against provider dollars, and no provider stands behind it. Crossing the account's low_balance_threshold sends customer.balance_low like any movement.
An external_user_id the project has never seen is 404 customer_not_found — nothing is created; a known customer without an account of the credit_type holds no such credits: 402 insufficient_credits. A blocked customer can be charged. Moves money, so it is behind the idempotency middleware, and the Idempotency-Key is unique within the project on the journal entry exactly as for a top-up: the same charge repeated under its key answers 200 with the account as it stands now and takes nothing — also when the balance could no longer cover it a second time — and a key that names any other top-up, adjustment or charge answers 409 idempotency_conflict.
Параметры
| Параметр | Описание |
|---|---|
externalUserId обязательноpath | The end customer's identifier in the caller's own system.
|
Idempotency-Key обязательноheader | Replay guard for a financial write. The same key returns the original result with the original status and never charges twice; a different body under a used key answers
|
Тело запроса
{
"credits": "12.5000",
"credit_type": "default",
"note": "string"
}Ответы
The account after the charge
{
"data": {
"customer_account_id": "74438200973594624",
"customer_id": "74438200973594624",
"project_id": "74438200973594624",
"external_user_id": "user_8421",
"credit_type": "default",
"balance": "12.5000",
"reserved": "12.5000",
"available": "12.5000",
"allow_negative": true,
"low_balance_threshold": "12.5000",
"created_at": "2026-10-02T09:30:00Z",
"updated_at": "2026-10-02T09:30:00Z"
}
}Ошибки
| Статус | Коды | Когда |
|---|---|---|
| 400 | Malformed request — e.g. the | |
| 401 | No credential, or one that is not valid | |
| 402 | Refused because of money: | |
| 403 | Authenticated, but not allowed: scope ( | |
| 404 | No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable. | |
| 409 | State conflict: idempotency clash, stale draft, illegal transition; the last owner of an organization ( | |
| 422 | Validation failure or a semantically impossible request. Field-level validation from the framework answers |
Заголовки
X-Request-Id
Тело каждой ошибки — конверт ошибки.
Цены операций в кредитах
GET /v1/credit-prices
- Разрешение:
customers:read - Доступно в SDK для PHP и Node.js
The project's price list: how many credits of which type one run of an operation holds from the customer named by context.user_id. An operation without a price does not touch credits at all. A handful of rows per project, so no cursor; the filters are equality.
Параметры
| Параметр | Описание |
|---|---|
operation_code необязательноquery | Only the prices of this operation code (equality).
|
credit_type необязательноquery | Only the prices in this credit type (equality).
|
Ответы
Credit prices of the project, by operation code and credit type
{
"data": [
{
"credit_price_id": "74438200973594624",
"project_id": "74438200973594624",
"operation_code": "generate_email",
"credit_type": "default",
"credits": "12.5000",
"created_at": "2026-10-02T09:30:00Z",
"updated_at": "2026-10-02T09:30:00Z"
}
]
}Ошибки
| Статус | Коды | Когда |
|---|---|---|
| 401 | No credential, or one that is not valid | |
| 403 | Authenticated, but not allowed: scope ( | |
| 422 | Validation failure or a semantically impossible request. Field-level validation from the framework answers |
Заголовки
X-Request-Id
Тело каждой ошибки — конверт ошибки.
Задать цену операции в кредитах
PUT /v1/credit-prices
- Разрешение:
management:write
Upsert by (operation_code, credit_type). Zero is allowed — it marks an operation free without deleting the row. A new price takes effect on the next hold; holds already taken keep theirs.
Тело запроса
{
"operation_code": "generate_email",
"credits": "12.5000",
"credit_type": "default"
}Ответы
The stored price
{
"data": {
"credit_price_id": "74438200973594624",
"project_id": "74438200973594624",
"operation_code": "generate_email",
"credit_type": "default",
"credits": "12.5000",
"created_at": "2026-10-02T09:30:00Z",
"updated_at": "2026-10-02T09:30:00Z"
}
}Ошибки
| Статус | Коды | Когда |
|---|---|---|
| 401 | No credential, or one that is not valid | |
| 403 | Authenticated, but not allowed: scope ( | |
| 422 | Validation failure or a semantically impossible request. Field-level validation from the framework answers |
Заголовки
X-Request-Id
Тело каждой ошибки — конверт ошибки.
Примеры
Найти клиентов, у которых заканчиваются кредиты
low_balance оставляет клиентов, у которых есть счёт, где доступно меньше 20 кредитов. У этого клиента на балансе 18, и 4 из них зарезервированы.
curl "https://api.slen.ai/api/v1/customers?low_balance=20" \
-H "Authorization: Bearer $SLEN_API_KEY"{
"data": [
{
"customer_id": "74439840149372928",
"external_user_id": "user_3107",
"status": "active",
"blocked_at": null,
"block_reason": null,
"accounts": [
{
"customer_account_id": "74439840153567232",
"customer_id": "74439840149372928",
"project_id": "74438200973594624",
"external_user_id": "user_3107",
"credit_type": "default",
"balance": "18.0000",
"reserved": "4.0000",
"available": "14.0000",
"allow_negative": false,
"low_balance_threshold": "0.0000",
"created_at": "2026-09-28T11:02:17+00:00",
"updated_at": "2026-10-02T08:39:51+00:00"
}
],
"created_at": "2026-09-28T11:02:17+00:00",
"updated_at": "2026-10-02T08:39:51+00:00"
}
],
"meta": {
"limit": 50,
"next_cursor": null
}
}Прочитать клиента
У клиента один счёт, типа кредитов default. Движение, которое опускает его available ниже 10, отправляет customer.balance_low.
curl https://api.slen.ai/api/v1/customers/user_8421 \
-H "Authorization: Bearer $SLEN_API_KEY"{
"data": {
"customer_id": "74439117522108416",
"external_user_id": "user_8421",
"status": "active",
"blocked_at": null,
"block_reason": null,
"accounts": [
{
"customer_account_id": "74439117526302720",
"customer_id": "74439117522108416",
"project_id": "74438200973594624",
"external_user_id": "user_8421",
"credit_type": "default",
"balance": "82.5000",
"reserved": "0.0000",
"available": "82.5000",
"allow_negative": false,
"low_balance_threshold": "10.0000",
"created_at": "2026-10-02T08:40:30+00:00",
"updated_at": "2026-10-02T09:20:05+00:00"
}
],
"created_at": "2026-10-02T08:40:00+00:00",
"updated_at": "2026-10-02T08:40:00+00:00"
}
}Добавить клиента
Тело пустое. Клиент новый, поэтому ответ — 201, а accounts пуст, пока пополнение или настройки не создадут счёт.
curl -X PUT https://api.slen.ai/api/v1/customers/user_8421 \
-H "Authorization: Bearer $SLEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'{
"data": {
"customer_id": "74439117522108416",
"external_user_id": "user_8421",
"status": "active",
"blocked_at": null,
"block_reason": null,
"accounts": [],
"created_at": "2026-10-02T08:40:00+00:00",
"updated_at": "2026-10-02T08:40:00+00:00"
}
}Удалить клиента
Этого клиента добавили и ни разу не пополнили, поэтому клиент удаляется. У ответа нет тела.
curl -X DELETE https://api.slen.ai/api/v1/customers/user_9050 \
-H "Authorization: Bearer $SLEN_API_KEY"204 No Content
Настроить счёт
Настройки создают счёт типа кредитов default с предупреждением ниже 10 кредитов. Счёт не может уйти ниже нуля.
curl -X PUT https://api.slen.ai/api/v1/customers/user_8421/accounts/default \
-H "Authorization: Bearer $SLEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"allow_negative": false,
"low_balance_threshold": "10"
}'{
"data": {
"customer_account_id": "74439117526302720",
"customer_id": "74439117522108416",
"project_id": "74438200973594624",
"external_user_id": "user_8421",
"credit_type": "default",
"balance": "0.0000",
"reserved": "0.0000",
"available": "0.0000",
"allow_negative": false,
"low_balance_threshold": "10.0000",
"created_at": "2026-10-02T08:40:30+00:00",
"updated_at": "2026-10-02T08:40:30+00:00"
}
}Заблокировать клиента
Причина видна как block_reason. Счета сохраняют свои кредиты.
curl https://api.slen.ai/api/v1/customers/user_8421/block \
-H "Authorization: Bearer $SLEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"reason": "Chargeback on invoice 1042"
}'{
"data": {
"customer_id": "74439117522108416",
"external_user_id": "user_8421",
"status": "blocked",
"blocked_at": "2026-10-02T09:31:10+00:00",
"block_reason": "Chargeback on invoice 1042",
"accounts": [
{
"customer_account_id": "74439117526302720",
"customer_id": "74439117522108416",
"project_id": "74438200973594624",
"external_user_id": "user_8421",
"credit_type": "default",
"balance": "82.5000",
"reserved": "0.0000",
"available": "82.5000",
"allow_negative": false,
"low_balance_threshold": "10.0000",
"created_at": "2026-10-02T08:40:30+00:00",
"updated_at": "2026-10-02T09:20:05+00:00"
}
],
"created_at": "2026-10-02T08:40:00+00:00",
"updated_at": "2026-10-02T09:31:10+00:00"
}
}Разблокировать клиента
У запроса нет тела. Клиент снова active, а blocked_at и block_reason очищены.
curl -X POST https://api.slen.ai/api/v1/customers/user_8421/unblock \
-H "Authorization: Bearer $SLEN_API_KEY"{
"data": {
"customer_id": "74439117522108416",
"external_user_id": "user_8421",
"status": "active",
"blocked_at": null,
"block_reason": null,
"accounts": [
{
"customer_account_id": "74439117526302720",
"customer_id": "74439117522108416",
"project_id": "74438200973594624",
"external_user_id": "user_8421",
"credit_type": "default",
"balance": "82.5000",
"reserved": "0.0000",
"available": "82.5000",
"allow_negative": false,
"low_balance_threshold": "10.0000",
"created_at": "2026-10-02T08:40:30+00:00",
"updated_at": "2026-10-02T09:20:05+00:00"
}
],
"created_at": "2026-10-02T08:40:00+00:00",
"updated_at": "2026-10-02T10:02:00+00:00"
}
}Прочитать выписку клиента
В ответе три движения, самое новое — первым: списание за выгрузку, корректировка, которая сняла 12.5 кредита, и пополнение перед ними.
curl https://api.slen.ai/api/v1/customers/user_8421/entries \
-H "Authorization: Bearer $SLEN_API_KEY"{
"data": [
{
"entry_id": "74439673397166080",
"customer_account_id": "74439117526302720",
"external_user_id": "user_8421",
"credit_type": "default",
"entry_type": "charge",
"credits": "-5.0000",
"balance_after": "82.5000",
"available_after": "82.5000",
"reservation_id": null,
"note": "PDF export 311",
"created_at": "2026-10-02T09:20:05+00:00"
},
{
"entry_id": "74439652312547328",
"customer_account_id": "74439117526302720",
"external_user_id": "user_8421",
"credit_type": "default",
"entry_type": "adjust",
"credits": "-12.5000",
"balance_after": "87.5000",
"available_after": "87.5000",
"reservation_id": null,
"note": null,
"created_at": "2026-10-02T09:15:42+00:00"
},
{
"entry_id": "74439117530497024",
"customer_account_id": "74439117526302720",
"external_user_id": "user_8421",
"credit_type": "default",
"entry_type": "topup",
"credits": "100.0000",
"balance_after": "100.0000",
"available_after": "100.0000",
"reservation_id": null,
"note": "Invoice 1042",
"created_at": "2026-10-02T08:41:00+00:00"
}
],
"meta": {
"limit": 30,
"next_cursor": null
}
}Пополнить клиента
Ключ называет это пополнение, и повтор с тем же ключом ничего не добавляет. Примечание называет счёт на оплату, по которому оно оплачено.
curl https://api.slen.ai/api/v1/customers/user_8421/topup \
-H "Authorization: Bearer $SLEN_API_KEY" \
-H "Idempotency-Key: topup-user_8421-invoice-1042" \
-H "Content-Type: application/json" \
-d '{
"credits": "100",
"note": "Invoice 1042"
}'{
"data": {
"customer_account_id": "74439117526302720",
"customer_id": "74439117522108416",
"project_id": "74438200973594624",
"external_user_id": "user_8421",
"credit_type": "default",
"balance": "100.0000",
"reserved": "0.0000",
"available": "100.0000",
"allow_negative": false,
"low_balance_threshold": "10.0000",
"created_at": "2026-10-02T08:40:30+00:00",
"updated_at": "2026-10-02T08:41:00+00:00"
}
}Скорректировать баланс
Отрицательная сумма снимает кредиты.
curl https://api.slen.ai/api/v1/customers/user_8421/adjust \
-H "Authorization: Bearer $SLEN_API_KEY" \
-H "Idempotency-Key: adjust-user_8421-refund-77" \
-H "Content-Type: application/json" \
-d '{
"credits": "-12.5"
}'{
"data": {
"customer_account_id": "74439117526302720",
"customer_id": "74439117522108416",
"project_id": "74438200973594624",
"external_user_id": "user_8421",
"credit_type": "default",
"balance": "87.5000",
"reserved": "0.0000",
"available": "87.5000",
"allow_negative": false,
"low_balance_threshold": "10.0000",
"created_at": "2026-10-02T08:40:30+00:00",
"updated_at": "2026-10-02T09:15:42+00:00"
}
}Списать с клиента
Ваше приложение снимает 5 кредитов за сделанную выгрузку. Ключ называет выгрузку, поэтому повтор больше ничего не снимает.
curl https://api.slen.ai/api/v1/customers/user_8421/charge \
-H "Authorization: Bearer $SLEN_API_KEY" \
-H "Idempotency-Key: charge-user_8421-export-311" \
-H "Content-Type: application/json" \
-d '{
"credits": "5",
"note": "PDF export 311"
}'{
"data": {
"customer_account_id": "74439117526302720",
"customer_id": "74439117522108416",
"project_id": "74438200973594624",
"external_user_id": "user_8421",
"credit_type": "default",
"balance": "82.5000",
"reserved": "0.0000",
"available": "82.5000",
"allow_negative": false,
"low_balance_threshold": "10.0000",
"created_at": "2026-10-02T08:40:30+00:00",
"updated_at": "2026-10-02T09:20:05+00:00"
}
}Получить список цен в кредитах
У проекта одна цена.
curl https://api.slen.ai/api/v1/credit-prices \
-H "Authorization: Bearer $SLEN_API_KEY"{
"data": [
{
"credit_price_id": "74440031913676800",
"project_id": "74438200973594624",
"operation_code": "generate_email",
"credit_type": "default",
"credits": "2.0000",
"created_at": "2026-10-02T08:30:00+00:00",
"updated_at": "2026-10-02T08:30:00+00:00"
}
]
}Задать цену в кредитах
Начиная со следующего холда один вызов модели в этой операции стоит клиенту два кредита.
curl -X PUT https://api.slen.ai/api/v1/credit-prices \
-H "Authorization: Bearer $SLEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operation_code": "generate_email",
"credits": "2"
}'{
"data": {
"credit_price_id": "74440031913676800",
"project_id": "74438200973594624",
"operation_code": "generate_email",
"credit_type": "default",
"credits": "2.0000",
"created_at": "2026-10-02T08:30:00+00:00",
"updated_at": "2026-10-02T08:30:00+00:00"
}
}Что читать дальше
Кредиты клиентов — те же клиенты в консоли.
Клиенты и кредиты — те же операции из SDK.
Аналитика — кредиты, списанные с ваших клиентов, против того, сколько стоили их вызовы.
Аутентификация — разрешения, которые нужны ключу, чтобы читать и менять клиентов.
Обзор — как кредиты записываются в запросе и в ответе.
Коды ошибок — каждый код API: что он значит и что с ним делать.
Вебхуки — как приходит
customer.balance_low.
Собрано из openapi b070e1f, sdk contract 5cc7055, php ae4a53b, node 84bc62d