> Источник: https://apipay.kz/guides/partner-connect-organization · Обновлено: 2026-09-27 · apipay.kz
> ApiPay — независимый сервис приёма платежей поверх вашего Kaspi Pay.

# Как партнёру подключить организацию мерчанта к ApiPay

**TL;DR.** Партнёр подключает организацию мерчанта (бизнес со своим Kaspi Pay: ТОО, ИП и другие формы) к приёму оплаты через [ApiPay Partner API](/partners/api) за четыре действия: создаёт организацию (`POST /organizations`), выдаёт клиенту ссылку на анкету о бизнесе (`POST /organizations/{id}/kyc/invite`) и ждёт одобрения, подключает кассира клиента по коду из SMS (`init` → `send-phone` → `verify-otp`) и выпускает для организации её `X-API-Key` с вебхуком — дальше ваша система выставляет счета от имени клиента. От мерчанта нужны три вещи: анкета, которую он заполняет сам, отдельный номер сотрудника с ролью «Кассир» в Kaspi Pay и код из SMS на этот номер. Деньги покупателей идут только на Kaspi-счёт организации клиента. Весь путь сначала прогоняется в песочнице — без реальных вызовов Kaspi, без SMS и без анкеты.

## Коротко

| Параметр | Значение |
|---|---|
| Хост Partner API (S2S) | `https://api.apipay.kz/api/partner` |
| Хост мерчантского API | `https://api.apipay.kz/api/v1` |
| Заголовок партнёра | `X-Partner-Key` (работает, когда доступ к Partner API одобрен; иначе `403 forbidden`) |
| Заголовок мерчанта | `X-API-Key` (выдаётся на каждую организацию) |
| Порядок | организация → анкета клиента → кассир → `X-API-Key` → первый счёт |
| Анкета | Заполняет сам клиент по вашей ссылке; до одобрения кассира не подключить (`403 kyc_required`) |
| Окно авторизации кассира | `process_id` живёт ~10 минут — уложите `send-phone` + `verify-otp` в него |
| Идемпотентность организации | По `external_id` — повтор вернёт ту же организацию |

## Что нужно от мерчанта?

Организация в ApiPay — это один бизнес со своим Kaspi Pay: ТОО, ИП или другая форма. ApiPay сверяет ИИН/БИН из анкеты с данными Kaspi Pay и проверяет анкету о бизнесе. От клиента нужно:

- **Анкета о бизнесе** — что продаёт, где продаёт (ссылка на витрину и скриншоты) и подтверждение, что не торгует запрещённым. Заполняет её сам клиент по вашей ссылке, аккаунт в ApiPay ему не нужен.
- **Номер сотрудника с ролью «Кассир»** в Kaspi Pay, в формате `7XXXXXXXXXX`. Кассира добавляют в приложении Kaspi Pay: Настройки → Сотрудники, роль «Кассир» — права роли описаны в справке Kaspi «[Что может делать кассир в приложении Kaspi Pay?](https://guide.kaspi.kz/partner/ru/profile/user/cashier/q3450)». Какой номер подходит — в статье «[Требования к номеру кассира](/guides/trebovaniya-k-nomeru-kassira)».
- **Код из SMS** на этот номер в момент подключения — без кода клиента организацию не подключить.

Номер кассира держите только для ApiPay: в ApiPay он работает в одной организации, а любой другой вход в Kaspi Pay под этим номером обрывает сессию ApiPay, и счета перестают выставляться. Предупредите клиента заранее.

От вас — партнёрский `X-Partner-Key`. Он выпускается в веб-кабинете партнёра: ключ для песочницы доступен сразу, рабочий режим открывается после ручного одобрения. Ключ показывается один раз. Работает он только у партнёра с одобренным доступом к Partner API: без одобрения или при отозванном доступе — `403 forbidden`.

> **Два разных телефона в двух форматах — не перепутайте.** Телефон **кассира** (через чей Kaspi Pay проходит оплата) — `7XXXXXXXXXX`, regex `^7\d{10}$`, идёт в `send-phone → cashier_phone`. Телефон **плательщика** (кому выставляете счёт) — `8XXXXXXXXXX`, regex `^8\d{10}$`, идёт в `/api/v1/invoices → phone_number`.

## Куда слать запросы и как выглядят ошибки?

Каждый S2S-запрос идёт на `https://api.apipay.kz/api/partner` с заголовком `X-Partner-Key: <ключ>`. Это **не** `apipay.kz` — там сайт и кабинет, а не S2S-хост.

Конверт ответа: успех — `{ "success": true, ... }`; ошибка обработки запроса — `{ "success": false, "error": "<code>", "error_code": "<code>", "message": "<ru>", "errors"?: {...} }` (`error` дублирует `error_code` для обратной совместимости; `errors` — только на части ответов 422 с машинным кодом). Ошибки аутентификации и доступа — сокращённые: `{ "success": false, "error": "<code>" }`. Ошибки валидации тела — стандартная форма `{ "message", "errors" }` без `success`. Машинные коды в `error`/`error_code` стабильны — по ним и локализуйте тексты на стороне CRM.

**Ошибки, общие для всех шагов:**

| HTTP | `error` | Когда |
|---|---|---|
| 401 | `partner_key_missing` | нет заголовка `X-Partner-Key` |
| 401 | `invalid_partner_key` | ключ неверный или партнёр отключён |
| 401 | `partner_user_missing` | у партнёра не привязан пользователь |
| 403 | `forbidden` | доступ к Partner API не одобрен или отозван |
| 404 | `organization_not_found` | организация не принадлежит партнёру или не существует |
| 422 | `{ message, errors }` | ошибка валидации тела |
| 429 | `Too Many Attempts.` | превышен лимит группы (заголовок `Retry-After`) |

## Как прогнать подключение в песочнице?

Песочница детерминирована: не делает реальных вызовов Kaspi и не шлёт SMS, поэтому её можно покрыть автотестами CRM. Режим песочницы — на уровне партнёра: один `X-Partner-Key` и переключатель `sandbox ⟷ production`. Тестовая организация остаётся `sandbox_mode: true` даже после `verify-otp`, подключение к Kaspi у неё не создаётся. Анкета на тестовой организации не заводится — шаг 2 в песочнице пропускается. Сквозной сценарий с магическими значениями — ниже, в разделе «Как выглядит полный прогон в песочнице?».

## Шаг 1. Как создать организацию клиента?

`POST /organizations`. Тело (все поля опциональны): `{ "has_catalog": false, "external_id": "crm-client-42", "name": "ТОО Example" }`.
- `external_id` — ваш референс клиента в CRM и **ключ идемпотентности**: повторный POST с тем же `external_id` вернёт уже существующую организацию (`200`), дубль не создастся.
- `name` (`max:255`) — если пустой, генерируется автоматически и позже может подмениться именем из Kaspi на `verify-otp`.
- `has_catalog` — для большинства интеграций `false` (простые счета `amount` + `description`).

Ответ `201` (создана) / `200` (идемпотентный повтор): `{ "success": true, "organization": { "id": 501, "status": "pending", "sandbox_mode": true, "external_id": "crm-client-42", "origin": "created", ... } }`. Лимит — 10 запросов в минуту на партнёра; при достижении лимита тестовых организаций (до 20) — `429 test_org_limit`.

## Шаг 2. Как провести клиента через анкету о бизнесе?

На боевой организации кассира можно подключить только после одобрения анкеты: до этого `init` и `send-phone` отвечают `403 kyc_required`, SMS кассиру не уходит, повтор не поможет.

1. `POST /organizations/{id}/kyc/invite` → `201` с персональной ссылкой `invite_url` и сроком её действия `expires_at`. Ссылка показывается один раз, новая выдача отзывает предыдущую.
2. Передайте ссылку клиенту лично. Он заполняет и подтверждает анкету сам — подать анкету за клиента нельзя, такой ручки нет.
3. Решение модератора приходит на ваш `webhook_url` событием `kyc.status_changed`. Текущее состояние — в `GET /organizations/{id}/kyc`: ветвитесь по полю `required_action`, а замечание модератора (`comment`) передавайте клиенту дословно.

Отказы выдачи: `409 test_organization` (на тестовой организации анкета не заводится), `409 kyc_already_submitted` (анкета уже подана или проверена — смотрите статус). Подробно, со всеми статусами и что видит клиент, — в статье «[KYC клиента через Partner API](/guides/kyc-klienta-cherez-partner-api)».

## Шаг 3. Как начать подключение кассира (init)?

`POST /organizations/{id}/kaspi-auth/init`. Тело: `{}` или `{ "force": true }` (переавторизация поверх активной сессии — смена кассира или переподключение). Ответ: `{ "success": true, "process_id": "...", "process_status": "phone_required" }`. `process_id` живёт ~10 минут — уложите следующие два шага в это окно.

Ошибки:
- `409 already_connected` — организация уже подключена к Kaspi. Чтобы переподключить, повторите `init` с `"force": true`.
- `403 kyc_required` — анкета организации ещё не одобрена (шаг 2).
- `403 production_access_required` — боевая организация у партнёра в режиме песочницы (см. «Как перейти в рабочий режим?»).

## Шаг 4. Как отправить номер кассира (send-phone)?

`POST /organizations/{id}/kaspi-auth/send-phone`. Тело: `{ "cashier_phone": "7XXXXXXXXXX" }` (формат `^7\d{10}$` — это телефон **кассира**, не плательщика). Успех: `{ "success": true, "process_status": "otp_required" }` — Kaspi отправляет SMS-код на номер кассира. Это самый «ошибкоёмкий» шаг, разберите каждую ветку:

| HTTP | `error` | Смысл | Что делать |
|---|---|---|---|
| 422 | `invalid_phone` | неверный формат номера | исправить формат на `7XXXXXXXXXX` |
| 422 | `not_cashier` | у номера нет роли «Кассир» в Kaspi | уточнить у мерчанта корректный номер кассира |
| 422 | `not_registered` | номер не зарегистрирован кассиром в Kaspi | **сессия закрыта**: мерчанту добавить номер в Kaspi Pay → Настройки → Сотрудники с ролью «Кассир», затем начинать заново с `init`. Повторный `send-phone` вернёт `409 no_process` |
| 403 | `kyc_required` | анкета организации не одобрена | довести клиента до одобрения анкеты (шаг 2), затем новый `init` |
| 409 | `no_process` | авторизация не начата | вызвать `init` |
| 409 | `context_expired` | Kaspi-контекст протух (`process_id` ~10 мин) | вызвать `init` заново, повторить `send-phone` |
| 409 | `cashier_unavailable` | этот номер подключить нельзя; причина не раскрывается | повтор не поможет — направьте мерчанта в поддержку ApiPay |
| 429 | `rate_limited` | за сутки с аккаунта пробовали слишком много разных номеров кассиров | окно суточное: выждите время из `Retry-After` / `retry_after_seconds` — обычно это часы, а не минута. Уже подключённые кассиры в счётчик не входят, переавторизация рабочей точки лимитом не блокируется |
| 502 | `sms_failed` | Kaspi не вернул экран ввода кода | повторить позже |
| 503 | `kaspi_busy` | Kaspi временно ограничил попытки | **сессия закрыта**: пауза ~60 секунд собственным бэкоффом, затем новый `init` |

Лимит на шаги подключения кассира — 10 запросов в минуту (боевая организация) / 60 (тестовая — мок не шлёт SMS) на партнёра и организацию.

**Терминальные исходы.** После `not_registered`, `context_expired` и `kaspi_busy` сессия авторизации закрыта: повторный `send-phone` вернёт `409 no_process`, дальше только новый `init`. Заголовок `Retry-After` приходит только с `429 rate_limited` и тарифными лимитами; на `kaspi_busy` и прочих его нет — используйте собственный бэкофф с ориентиром 60 секунд.

Два разных `429` на этой ручке различаются по телу: поминутный лимит группы отдаёт `{"message": "Too Many Attempts."}` без `error_code`, суточный — `error_code: "rate_limited"` и `retry_after_seconds`. Суточную паузу применяйте только ко второму.

Исходы `cashier_unavailable` и `rate_limited` в песочнице не воспроизводятся: тестовая организация идёт мок-путём, и сессию мок не ведёт — после `not_registered` в песочнице следующий `send-phone` ответит успехом, а на боевой организации тот же цикл упрётся в `409 no_process`. Заложите обработку заранее.

## Шаг 5. Как подтвердить код из SMS (verify-otp)?

`POST /organizations/{id}/kaspi-auth/verify-otp`. Тело: `{ "otp": "1234" }` (4–6 цифр, `^\d{4,6}$`). Успех `200`: `{ "success": true, "mode": "self", "organization": <card>, "process_status": "active" }` — организация финализируется (`status: "verified"`).

> **Неверный код — это тоже HTTP 200, и он повторяем.** `{ "success": false, "error": "invalid_otp", "process_status": "otp_required" }`. Сессия НЕ сбрасывается — попросите код заново и повторите `verify-otp`. Не трактуйте `invalid_otp` как транспортную ошибку и не начинайте процесс заново.

Другие ошибки:
- `422 not_registered` — Kaspi принял код, но номер не заведён кассиром. Сессия закрыта: после добавления номера кассиром в Kaspi Pay — новый `init`.
- `409 cashier_unavailable` — то же, что на `send-phone`: повтор не поможет, мерчанта в поддержку ApiPay.
- `409 no_process` — авторизация не начата или истекла (вернитесь к `init`).
- `409 organization_identity_conflict` — организацию Kaspi этого кассира нельзя закрепить за этой организацией: либо она уже закреплена за другим юрлицом, либо организация Kaspi уже закреплена за другой организацией в ApiPay. Повтор не поможет. Кассира другого юрлица подключайте в его организацию; если сменился владелец бизнеса — в поддержку ApiPay.
- `502` — Kaspi-сессия не финализировалась или Kaspi не вернул данные организации (`organization_identity_unavailable`); повторите с нового `init`, при повторе — в поддержку с `organization_id` и временем запроса.

После успешного подключения под номером кассира нельзя входить в приложение Kaspi Pay: подключение разорвётся, и счета перестанут выставляться. Скажите об этом клиенту до подключения.

## Шаг 6. Как проверить готовность (status)?

`GET /organizations/{id}/kaspi-auth/status`. Ответ: `{ "success": true, "status": "none|pending|active|expired", "process_status": "idle|phone_required|otp_required|active|failed", "kaspi_connected": bool, "expires_at": "...|null" }`. По нему видно ход авторизации и нужно ли переподключение кассира.

## Шаг 7. Как выдать мерчанту его X-API-Key?

`POST /organizations/{id}/api-key`. Тело: `{ "name": "CRM key", "webhook_url": "https://...", "webhook_secret": "..." }`. `webhook_url` **обязателен** и проходит SSRF-валидацию (приватные и внутренние адреса → `422`); `name` и `webhook_secret` опциональны (`webhook_secret` сгенерируется автоматически).

Указывайте постоянный домен своего сервиса — не шортенер, не IP и не временный адрес-перехватчик. Проверяйте в ответе `webhook_review_status`: `approved` — уведомления идут; `pending_review` — адрес на ручной проверке, уведомлений до одобрения не будет, статус счёта узнавайте через `GET /api/v1/invoices/{id}`; `rejected` — укажите другой адрес.

Ответ `200`:

```json
{
  "success": true,
  "key": "<X-API-Key в открытом виде — показывается ОДИН РАЗ>",
  "key_id": 200,
  "webhook_url": "https://crm.example.kz/sub/501/webhook",
  "webhook_secret": "<секрет в открытом виде — показывается ОДИН РАЗ>",
  "webhook_review_status": "approved",
  "is_org_default": true,
  "regenerated": false
}
```

> **`key` и `webhook_secret` возвращаются в открытом виде ровно один раз** — сохраните их сразу. Повторный вызов перевыпускает ключ той же записи (`regenerated: true`): прежние `X-API-Key` и `webhook_secret` перестают действовать сразу, поэтому не ретрайте этот запрос вслепую — только при плановой ротации. Как ключ соотносится с секретом подписи вебхука — в разборе [API-ключ и вебхук-секрет](/guides/api-klyuch-i-webhook-secret).

## Шаг 8. Как выставить первый счёт от имени мерчанта?

Дальше работаете **выданным `X-API-Key`** (не партнёрским ключом) против `https://api.apipay.kz/api/v1`:
- Счёт по номеру — `POST /api/v1/invoices`, тело `{ "phone_number": "8XXXXXXXXXX", "amount": 5000, "description": "Заказ №123" }` → `201`. Финальный статус счёта приходит вебхуком `invoice.status_changed` или через `GET /invoices/{id}`.
- QR-счёт — `POST /api/v1/invoices/qr`, тело `{ "amount": 5000, "description": "..." }` → `201` + `qr_token_url`, `qr_image_url`, `qr_expires_at`. Срок жизни берите из `qr_expires_at`, а не из константы в коде — разбор в статье [QR-счёт: окно и лимиты](/guides/qr-schet-ttl-i-limity). В песочнице опциональное поле `"simulate": "paid|cancelled|expired"` сразу финализирует QR.

Полный платёжный API (поля, статусы, отмены, возвраты, вебхуки) — в документации [apipay.kz/docs](/docs), а про счета по номеру — в разборе [счёт Kaspi по номеру](/guides/kak-sozdat-schet-kaspi-po-nomeru).

## Как выглядит полный прогон в песочнице?

Связный copy-paste сценарий от создания организации до симуляции оплаты. Анкеты в нём нет — на тестовой организации она не заводится. Плейсхолдеры — `YOUR_PARTNER_KEY` и `YOUR_API_KEY`:

```bash
# 1. Создать тестовую организацию
curl -X POST https://api.apipay.kz/api/partner/organizations \
  -H 'X-Partner-Key: YOUR_PARTNER_KEY' -H 'Content-Type: application/json' \
  -d '{"has_catalog":false,"external_id":"crm-client-42"}'
# 201 {"success":true,"organization":{"id":501,"status":"pending","sandbox_mode":true,...}}

# 2. init — начать авторизацию кассира
curl -X POST https://api.apipay.kz/api/partner/organizations/501/kaspi-auth/init \
  -H 'X-Partner-Key: YOUR_PARTNER_KEY' -H 'Content-Type: application/json' -d '{}'
# 200 {"success":true,"process_id":"SANDBOX-...","process_status":"phone_required"}

# 3. send-phone — магический номер кассира = успех
curl -X POST https://api.apipay.kz/api/partner/organizations/501/kaspi-auth/send-phone \
  -H 'X-Partner-Key: YOUR_PARTNER_KEY' -H 'Content-Type: application/json' \
  -d '{"cashier_phone":"77770000010"}'
# 200 {"success":true,"process_status":"otp_required"}

# 4. verify-otp — сначала неверный код (повторяемо), потом магический 0000
curl -X POST .../kaspi-auth/verify-otp -H 'X-Partner-Key: YOUR_PARTNER_KEY' \
  -H 'Content-Type: application/json' -d '{"otp":"1234"}'
# 200 {"success":false,"error":"invalid_otp","process_status":"otp_required"}
curl -X POST .../kaspi-auth/verify-otp -H 'X-Partner-Key: YOUR_PARTNER_KEY' \
  -H 'Content-Type: application/json' -d '{"otp":"0000"}'
# 200 {"success":true,"organization":{"status":"verified",...}}

# 5. Выдать мерчанту X-API-Key
curl -X POST https://api.apipay.kz/api/partner/organizations/501/api-key \
  -H 'X-Partner-Key: YOUR_PARTNER_KEY' -H 'Content-Type: application/json' \
  -d '{"name":"CRM key","webhook_url":"https://crm.example.kz/sub/501/webhook"}'
# 200 {"success":true,"key":"<X-API-Key один раз>","webhook_secret":"<один раз>","is_org_default":true,...}

# 6. Первый счёт — уже выданным X-API-Key, плательщик = 8XXXXXXXXXX
curl -X POST https://api.apipay.kz/api/v1/invoices \
  -H 'X-API-Key: YOUR_API_KEY' -H 'Content-Type: application/json' \
  -d '{"phone_number":"87770001122","amount":5000,"description":"Заказ №123"}'
# 201, is_sandbox=true

# 7. Симулировать оплату → на webhook_url прилетит invoice.status_changed
curl -X POST https://api.apipay.kz/api/v1/invoices/1001/simulate-status \
  -H 'X-API-Key: YOUR_API_KEY' -H 'Content-Type: application/json' -d '{"status":"paid"}'
# 200 payload вебхука идентичен боевому
```

Магические значения песочницы: OTP `0000` (успех); кассир-телефоны `77770000010` (успех), `…011` (`not_cashier`), `…012` (`sms_failed`), `…013` (`not_registered`), `…014` (`context_expired`), `…015` (`kaspi_busy`); lookup-номера `87770000001` (есть Kaspi, «Иван И.»), `87770000002` (нет Kaspi).

## Как перейти в рабочий режим?

Рабочие операции (реальное подключение кассира и боевые счета) открываются после ручного одобрения партнёрского доступа. Пока партнёр в режиме песочницы, любой S2S-вызов по **боевой** организации (`init`/`send-phone`/`verify-otp`/`status`/`tariff/pay`) отбивается `403 production_access_required`.

Переключение `sandbox ⟷ production` делается в веб-кабинете партнёра; переход в рабочий режим удаляет все тестовые организации партнёра. В рабочем режиме добавляется анкета клиента (шаг 2), а вместо `777…`/`0000` — реальные телефоны и коды из SMS; остальной порядок и коды ошибок те же. Программа и условия для партнёров — на странице [Partner API](/partners/api).

## Кто платит тариф мерчанта?

Тариф мерчанта — фиксированная подписка ApiPay, а не процент с оборота. Оплатить её партнёр может сам через Partner API (`tariff/pay`). Разбор тарифных эндпоинтов — в статье [Partner API white-label](/guides/partner-api-white-label), сетка тарифов — в [Тарифы и комиссия ApiPay](/guides/tarify-i-komissiya-apipay).

## Как узнать, что кассира клиента нужно переподключить?

`GET /api/partner/health` возвращает сводку по всем организациям партнёра (разбор полей — в статье [Partner API white-label](/guides/partner-api-white-label)). Если `needs_reauth > 0` — требуется переподключение кассира: сверьтесь по конкретной организации через `GET /organizations/{id}/kaspi-auth/status` (`kaspi_connected`/`status`).

Переподключение кассира — повтор шагов с `init` + `"force": true` → `send-phone` → `verify-otp`. Отдельного вебхука об этом нет — отслеживайте опросом `/health` и `/status`.

## Частые вопросы

**Что нужно от мерчанта, чтобы подключить его через Partner API?**
Своя организация в Kaspi Pay, отдельный номер сотрудника с ролью «Кассир» и код из SMS на этот номер в момент подключения. Плюс анкета о бизнесе, которую клиент заполняет сам по вашей ссылке: до её одобрения кассира не подключить.

**Может ли партнёр заполнить анкету о бизнесе за клиента?**
Нет. В анкете есть подтверждение о неторговле запрещённым, и давать его должен сам клиент; ручки для подачи анкеты за клиента нет. Партнёр выдаёт ссылку `POST /organizations/{id}/kyc/invite` и получает решение вебхуком `kyc.status_changed`.

**Можно ли один номер кассира дать нескольким партнёрам или сервисам?**
Держите номер кассира только для ApiPay: в ApiPay он работает в одной организации, а любой другой вход в Kaspi Pay под этим номером обрывает сессию ApiPay. Каждой организации клиента — свой номер.

**Куда идут деньги покупателей мерчанта?**
Только на Kaspi-счёт организации клиента, от имени которой выставлен счёт. Партнёр денежный поток не трогает.

**Чем `X-Partner-Key` отличается от `X-API-Key`?**
`X-Partner-Key` — партнёрский ключ для S2S Partner API (`/api/partner`): подключение организаций, анкета, тариф, health. `X-API-Key` — ключ каждой организации мерчанта для платёжного API (`/api/v1`): счета, вебхуки, возвраты. Перепутать их — главная причина `401`.

Смотрите также: [Partner API](/partners/api) · [Подключение кассира](/connect-cashier) · [KYC клиента через Partner API](/guides/kyc-klienta-cherez-partner-api) · [Partner API white-label](/guides/partner-api-white-label) · [Несколько организаций и партнёрам](/guides/multiorganizatsii-i-partnyoram) · [Требования к номеру кассира](/guides/trebovaniya-k-nomeru-kassira)

---

ApiPay — независимый сервис и не аффилирован с АО «Kaspi Bank». Kaspi и Kaspi Pay — товарные знаки их правообладателя.
База знаний: https://apipay.kz/guides
