Аутентификация
Этот документ описывает, как сервер мерчанта аутентифицируется в API ключом API, и связанные с ключом лимиты запросов, ротацию и сетевые правила. Прочитайте его до первого запроса.
Ключ API
Каждый запрос к маршрутам /v1 передаёт ключ API мерчанта в заголовке X-Api-Key. Единственное исключение — GET /v1/openapi.json, машиночитаемое описание API: ему ключ не нужен.
curl -s https://api.stons.io/v1/invoices/6f1d3c1e-2f7a-4f0e-9a51-0c8b3e2d7a44 -H "X-Api-Key: $API_KEY"
Ключ принадлежит мерчанту — в кабинете он называется проектом. Первый ключ владелец проекта получает при подаче заявки на проект в кабинете, следующие выпускает в разделе проекта «Ключи API» кнопкой «Выпустить новый ключ». Ключ имеет вид sk_<16 шестнадцатеричных символов>_<43 символа base64url>: первая часть — публичный идентификатор ключа, который кабинет показывает в списке ключей, вторая — секрет.
Сервис хранит не ключ, а хэш argon2id его секрета, и сравнивает хэши за постоянное время. Отсюда два следствия:
- утерянный ключ нельзя восстановить, только выпустить новый;
- ключ показывается один раз, в момент выпуска, — сохраните его в менеджер секретов.
Правила обращения с ключом:
- Ключ предназначен только для запросов сервер-сервер. Он не должен попадать в браузер, мобильное приложение, репозиторий или логи.
- Ключ видит только данные своего мерчанта: инвойса другого мерчанта для него не существует (
404). - Ключ работает с момента выпуска. Пока проект не одобрен после проверки,
POST /v1/invoicesиPOST /v1/addressesотвечают403 merchant_not_approved, а чтение инвойсов работает. - Успешную проверку ключа сервис помнит до 60 секунд. Поэтому отозванный ключ и ключ отключённого мерчанта отклоняются не позже чем через 60 секунд после изменения.
Ошибки
Ключ проверяется раньше тела запроса, поэтому запрос с неверным ключом получает 401, даже если и тело неверное.
| HTTP | error.code |
Когда |
|---|---|---|
| 401 | unauthorized |
Заголовка нет, ключ неверного формата, неизвестен, отозван или секрет не совпадает. Ответ не уточняет, что именно |
| 403 | merchant_disabled |
Ключ верный, но мерчант отключён — оператором или владельцем в кабинете |
| 429 | rate_limited |
Превышен лимит запросов — см. лимиты запросов |
Пример: запрос без ключа.
curl -s https://api.stons.io/v1/invoices/6f1d3c1e-2f7a-4f0e-9a51-0c8b3e2d7a44
401:
{"error":{"code":"unauthorized","message":"Missing or invalid X-Api-Key header"}}
Лимиты запросов
Запросы к маршрутам /v1 ограничены дважды, в окне 60 секунд: для IP-адреса клиента и для ключа API. Значения лимитов задаёт оператор установки.
- Лимит по IP проверяется первым, до ключа: в него засчитываются и запросы без ключа или с неверным ключом.
- Лимит по ключу проверяется после ключа: он считает запросы одного ключа с любых адресов.
GET /v1/openapi.jsonи несуществующие пути не ограничены.
Ответы ограниченных маршрутов содержат заголовки:
| Заголовок | Смысл |
|---|---|
x-ratelimit-limit |
Сколько запросов разрешено в окне. Запрос, прошедший оба лимита, сообщает лимит по ключу; запрос, отклонённый лимитом, — этот лимит |
x-ratelimit-remaining |
Сколько запросов осталось в окне |
x-ratelimit-reset |
Через сколько секунд окно начнётся заново |
retry-after |
Только в ответе 429: через сколько секунд повторить |
Условные чтения
Каждое простое чтение отвечает заголовком ETag — метку именно этого ответа дают GET /v1/assets, GET /v1/invoices, GET /v1/invoices/{id} и GET /v1/deposits/{tx_hash}. Пришлите метку обратно в If-None-Match, и ответ, который не изменился, придёт как 304 Not Modified без тела. Это нужно продукту, который опрашивает инвойс, пока покупатель платит, или проходит ленту изменений между вебхуками.
GET /v1/invoices/6f1d3c1e-2f7a-4f0e-9a51-0c8b3e2d7a44
X-Api-Key: <ваш ключ>
If-None-Match: "LXEWQrcmsEQBYnyp-6wy9chTD7GQPMTbAiWHF5IaSIE"
304приходит с той же меткой и без тела: то, что у вас есть, актуально.200приходит с новой меткой — храните её рядом с ответом и присылайте со следующим запросом.- Метка считается по всему телу, поэтому меняется от любого изменения ответа: пришёл перевод, сменился статус, пересчиталась сумма, тот же инвойс запросили вместе с QR-кодом. Это не номер версии инвойса, и у двух разных запросов об одном инвойсе метки разные.
- Метка непрозрачна: это строка в кавычках, которую надо сохранить и вернуть, читать из неё нечего.
If-None-Match: *совпадает с любым ответом, который есть у сервиса. - Условный запрос — это запрос: он считается в лимитах выше и стоит сервису той же работы. Экономия на вашей стороне — тело по сети и его разбор.
- Метку получает только
200. Ошибка метки не получает, и304её не заменяет: запрос, который ответил бы404, отвечает404.
Настройки проекта
Интеграция настраивает проект сама, своим ключом: указывает, куда присылать вебхуки, куда страница оплаты возвращает покупателя, и какие токены проект принимает. В кабинет заходить для этого не нужно (ADR-0067).
Секрет подписи вебхуков эти маршруты не возвращают. Он показывается один раз, при создании проекта; ключ API — не доступ к секрету.
GET /v1/project
{
"project_id": "7f2c1b94-3d5a-4e86-9a2f-6c0b1d8e4a37",
"name": "Shop",
"address_mode": "per_invoice",
"webhook_url": "https://shop.example/hooks",
"return_url": "https://shop.example/thanks",
"cancel_url": null,
"review_status": "approved"
}
address_modeрешает, что этот API принимает:POST /v1/addressesработает только вper_user, а счёт вper_userбезexternal_user_idотклоняется — см. адреса.review_status=pendingилиrejectedозначает, что счета и постоянные адреса отвечают403 merchant_not_approved: проект ещё не одобрен.
PATCH /v1/project
Меняет одно или несколько полей; пустое тело и любое другое поле — 400:
PATCH /v1/project
X-Api-Key: <ваш ключ>
Content-Type: application/json
{ "webhook_url": "https://shop.example/hooks", "return_url": "https://shop.example/thanks" }
webhook_urlможно задать и заменить, но не удалить:null—400. События, уже поставленные в очередь, должны куда-то уходить.return_urlиcancel_url: адрес задаёт,nullочищает, отсутствие поля оставляет как есть. Правила адресов — у страницы оплаты.- Режим адресов этим маршрутом не меняется: его разрешает оператор.
- Ответ — объект проекта после изменения. Изменение попадает в журнал аудита с именем ключа, которым сделано.
GET /v1/project/assets
Какие активы проект принимает, с двумя переключателями — платформы и своим:
{
"assets": [
{
"asset": "USDT_TRON",
"network": "tron",
"symbol": "USDT",
"decimals": 6,
"min_deposit": "1000000",
"platform_enabled": true,
"enabled": true,
"accepting": true
}
]
}
accepting — можно ли создать счёт в этом активе сейчас: платформа принимает
его и проект его не выключил.
PUT /v1/project/assets
Выбирает принимаемые активы. Перечислять можно только те, что сервис принимает к оплате; от одного до пятидесяти, каждый не больше одного раза:
PUT /v1/project/assets
X-Api-Key: <ваш ключ>
Content-Type: application/json
{ "assets": [{ "asset": "USDT_TRON", "enabled": true }, { "asset": "TRX_TRON", "enabled": false }] }
Актив, которого сервис не принимает, — 422 asset_not_supported, и из запроса
не записывается ничего. Ответ — весь список после изменения. Переключатель,
который ничего не меняет, аудита не создаёт.
Ротация
У мерчанта может быть несколько действующих ключей одновременно, поэтому ключ заменяется без простоя:
- Владелец проекта выпускает новый ключ в разделе проекта «Ключи API» в кабинете.
- Продукт переключается на новый ключ.
- Владелец там же отзывает старый ключ. API отклоняет отозванный ключ не позже чем через 60 секунд после отзыва. Отозванный ключ не восстанавливается.
Сеть
API мерчантов отвечает по адресу https://api.stons.io/v1. В боевой установке перед ним стоит HTTPS-прокси:
- из интернета доступны только пути, начинающиеся с
/v1/; любой другой путь отвечает404с пустым телом; - запрос по
http://перенаправляется наhttps://кодом308. К моменту перенаправления заголовок с ключом уже ушёл по сети открытым текстом, поэтому в клиенте указывайте адрес сhttps://.
Маршруты /admin/v1, /cabinet/v1 и /checkout/v1 обслуживают админку сотрудников, приложение кабинета и сервер страницы оплаты STONS. Они не часть интеграции мерчанта и из интернета недоступны.