ДОКУМЕНТАЦИЯ РАЗРАБОТЧИКА
Документация API
Подключай счета, оплату и платёжные уведомления.
Результаты поиска
Ничего не найдено. Попробуй название эндпоинта, поля или руководства.
Быстрый старт
Создай первый счёт.
- Подготовь магазин
Включи способы оплаты, настрой провайдеров и сделай резервные копии кошельков проекта.
- Создай API-ключ
В консоли открой Настройки → Доступ к API, выбери чтение и запись и назначь проект.
- Отправь запрос
Используй свой хост API и скопируй ID проекта и магазина. Передавай десятичные суммы строками.
- Открой оплату
Перенаправь на
links.checkoutиз ответа. Перед выполнением заказа проверь окончательное зачисление.
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new invoice.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: order-1042-attempt-1' \
--header 'Content-Type: application/json' \
--data-raw '{
"amount": "49.90",
"currency": "USD",
"order_id": "order-1042",
"exchange_rate_spread_percent": "0.5"
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new invoice.
const body = `{
"amount": "49.90",
"currency": "USD",
"order_id": "order-1042",
"exchange_rate_spread_percent": "0.5"
}`;
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "order-1042-attempt-1",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new invoice.
$body = <<<'JSON'
{
"amount": "49.90",
"currency": "USD",
"order_id": "order-1042",
"exchange_rate_spread_percent": "0.5"
}
JSON;
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: order-1042-attempt-1", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new invoice.
headers = {
"Idempotency-Key": "order-1042-attempt-1",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"amount": "49.90",
"currency": "USD",
"order_id": "order-1042",
"exchange_rate_spread_percent": "0.5"
}""".encode("utf-8")
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))В примерах используются заполнители; эта страница не отправляет запросы. Все поля счёта и формат ответа →
ID проекта и магазина
Где найти YOUR_PROJECT_ID и YOUR_STORE_ID.
Используй UUID из консоли, а не названия проектов, магазинов или их читаемые идентификаторы.
| Заполнитель | Где найти | Для чего |
|---|---|---|
| YOUR_PROJECT_ID | Проект → Настройки → API ID → API ID проекта → Копировать. Также показан во вкладке «Основное» магазина. | Запросы на уровне проекта и магазина. |
| YOUR_STORE_ID | Проект → Магазины → выбери магазин → Основное → API ID → API ID магазина → Копировать. | Создание счетов и запросы способов оплаты магазина. |
- Для создания счёта нужны оба ID, даже для магазина по умолчанию. Магазин должен принадлежать проекту, а API-ключ - иметь доступ к нему.
- Создание, список, подробности счёта и страница оплаты возвращают invoice_id: тот же UUID, который приходит в IPN/вебхуках. Используй его в путях счетов, а не внутренний id или order_id. С версии продавца 4.0.0 старое поле ответа public_id удалено; обнови интеграции до обновления версии.
- В REST API нет маршрутов списка проектов и магазинов. Скопируй ID в консоли или используй MCP-инструменты list_projects и list_stores с ограниченной областью доступа в версии продавца 5.0.0+.
- Магазин → Основное → Домены магазина позволяет выбрать активные хосты merchant, pay и API. Ссылки на оплату в ответах и ссылки новых уведомлений выбираются по приоритету: этот магазин, магазин по умолчанию, система. Удалённые или неактивированные имена не выбираются. Настрой SDK на нужный хост API; смена предпочтения не перенаправляет другие активные псевдонимы.
Авторизация и область доступа
Храни ключи на сервере и выдавай только нужные права.
| Хост по умолчанию | Назначение |
|---|---|
| merchant.example.com | Консоль продавца и настройки |
| pay.example.com | Оплата покупателя |
| api.example.com | Запросы API продавца |
Замени example.com своим доменом. Существующие установки сохраняют настроенные имена; управляй псевдонимами в Настройки → Система.
Authorization: Bearer YOUR_MERCHANT_API_TOKEN| Настройка | Как это работает |
|---|---|
| Уровень доступа | Ключи только для чтения позволяют получать списки и записи. Ключи с чтением и записью также создают счета и меняют документированные политики активов. |
| Проекты | Назначь проекты, доступные ключу. ID магазинов и счетов должны принадлежать назначенному проекту. |
| Ограничения по IP | При желании разреши точные публичные исходящие IPv4- или IPv6-адреса в Настройки → Доступ к API. |
| Хранение ключей доступа | Храни токены в конфигурации серверной части. Никогда не добавляй bearer-ключ в браузер или ссылку оплаты. |
Публичные маршруты оплаты используют публичный ID счёта и раскрывают только безопасные для оплаты данные. Сеансы консоли и административные функции отделены от ключей API продавца.
Активы и кошельки
Выбирай способы оплаты отдельно для каждого магазина.
- Получи платёжные активы проекта и сведения об их готовности.
- Включи нативную сеть, настрой её кошелёк и провайдеров.
- Посмотри кандидатов в токены и проверь контракт или mint перед включением токена.
- Выбери упорядоченный список способов оплатымагазина. Новые счета используют готовые варианты.
Токены используют кошелёк своей нативной сети. Балансы кошельков возвращают точные суммы в минимальных единицах и ориентировочную стоимость в фиате. По полям готовности определяй, какие способы могут принимать платежи.
Проверенные ERC-20 работают в поддерживаемых EVM-сетях, проверенные SPL - в Solana. Нативные способы оплаты доступны в 30 интегрированных сетях. Monero использует внешнее view-only подключение кошелька, привязанное к проекту.
API приёма и поддержка монет и токенов
| Способ | Поддержка | Доказательства | Требования |
|---|---|---|---|
| Приём нативных монет | поддерживается | Сканирование транзакций | BTC, SOL, ETH (Ethereum/Base/Arbitrum/OP), BNB, HYPE, AVAX и POL; доказательствами оплаты служат выходы Bitcoin, канонические транзакции и квитанции EVM, разобранные переводы Solana. |
| Приём токенов ERC-20 | поддерживается | Сканирование транзакций | Ethereum, Base, BNB Chain, HyperEVM, Avalanche, Polygon, Arbitrum и Optimism требуют ончейн-проверки; платежи сопоставляются по индексированным логам Transfer. |
| Приём токенов SPL | поддерживается | Сканирование транзакций | Кандидатам Solana нужны основная сеть и проверка mint; платежи сопоставляются по точным изменениям токен-балансов в разобранных транзакциях. |
| Дополнительные нативные UTXO-сети | поддерживается | Сканирование транзакций | BCH/LTC/DOGE используют Esplora; BCH/DOGE также принимают Bitcore, LTC/DOGE/DASH - BlockCypher, Dash - Insight, прозрачный ZEC - zcash-explorer. Все также поддерживают полные сохранённые блоки Core-совместимого node-rpc. Прямой режим требует 1–48 подтверждений, без обнаружения в мемпуле. Защищённые переводы Zcash не поддерживаются. |
| Индексированные сети с аккаунтами | поддерживается | Сканирование транзакций | TRON использует tron-indexer или solidified node-rpc; XRP - xrpl-jsonrpc; Stellar - stellar-horizon или сохранённые реестры Stellar node-rpc; Cosmos Hub - cometbft-jsonrpc; Algorand - algorand-indexer или algod node-rpc; Hedera требует hedera-mirror, не EVM-relay. |
| Нативные платежи по реестру | поддерживается | Сканирование транзакций | Aptos использует aptos-rest; Sui - sui-graphql; NEAR - near-jsonrpc; Kaspa - kaspa-rest. Polkadot Asset Hub принимает substrate-rest или финализированный node-rpc с поддержкой метаданных; Tezos - tezos-tzkt или полные операции Octez node-rpc. Только поступления нативной монеты; для старых счетов нужна сохранённая архивная история. |
| Нативные платежи Cardano и TON | поддерживается | Сканирование транзакций | Cardano требует cardano-koios, TON - toncenter-v3. Теги XRP, memo ID Stellar и комментарии счетов TON возвращаются как destination_tag и должны передаваться точно. |
| Надёжность окончательного зачисления | поддерживается | Независимая проверка | По умолчанию для окончательного зачисления два независимых провайдера должны подтвердить точную транзакцию или событие, сумму, канонический блок или слот и финальность. Прямое сканирование и общие окна EVM также проверяют полноту данных. Администратор может явно выбрать для сети одного доверенного провайдера; это убирает независимую сверку, но не проверки идентичности, полноты или финальности. |
| Нативный приём Monero | поддерживается | View-only wallet RPC, привязанный к проекту | Отдельный внешний watch-only wallet-RPC за HTTPS-шлюзом со списком разрешённых методов создаёт субадреса account-0. Доказательства зачисления дают mainnet-демоны с настроенным порогом: по умолчанию 2 независимых источника, опционально 1. Нативный --restricted-rpc несовместим с create_address; оператор явно подтверждает резервную копию и отсутствие spend key. Ключи в Wholly Crypto не передаются. |
Балансы бирж и выбор кошелька либо биржи для сбора каждого актива доступны в консоли, но не в публичном API v1. Настройка бирж.
Жизненный цикл счёта
Доказательства платежа, зачисление и выполнение заказа.
| Статус | Значение |
|---|---|
| new | Ожидание платежа |
| processing | Платёж обнаружен; ожидается нужная сумма или финальность |
| settled | Принят по политике зачисления счёта либо вручную |
| expired | Срок истёк; отслеживание поздней оплаты может продолжаться |
| invalid | Платёж нельзя принять автоматически |
| cancelled | Отменён; повторное открытие возможно только явной сверкой |
amount_status записывает none, partial, paid или overpaid. timing_status различает своевременные и поздние платежи. Правила магазина задают число подтверждений и допустимую недоплату.
Используй поле счёта invoice_id с маршрутом подробностей счёта. Перенаправление с платёжной страницы само по себе не доказывает оплату. Проверяй исключения через сверку.
Безопасные повторы
Для создания счёта нужен Idempotency-Key. После тайм-аута повторяй с теми же данными доступа, ключом и точным телом запроса. Новый ключ используй только для нового счёта.
Сканирование платежей EVM
Общий поиск нативных блоков и ERC-20 группирует свежие счета отдельно от догоняющего сканирования старых. Каждый счёт хранит постоянный курсор истории. Запросы токенов охватывают не более 100 блоков и уменьшаются при более строгих лимитах провайдера. По умолчанию каждое окно проверяют два независимых провайдера. В Настройки → Подключения к сетям → Подробности можно выбрать один доверенный источник без независимой сверки; проверки каноничности транзакции, суммы и подтверждений сохраняются. Подробности отличают задержки сканера, ограничения истории и паузы из-за квоты от базового состояния узла. Ресурсы публичного RPC не гарантированы.
IPN и вебхуки
Получай и проверяй события платежей.
IPN получает каждое созданное событие счёта по действующему ipn_url этого счёта. Вебхуки получают только события, выбранные для каждого включённого эндпоинта магазина. Оба отправляют один JSON-снимок через POST, но работают независимо: включение обоих может уведомить приложение дважды.
Укажи ipn_url при создании счёта или используй значение магазина. IPN использует секрет Магазин → IPN ; каждый Магазин → Вебхуки эндпоинт имеет свой секрет. Ни один из них не является API-ключом.
Когда выполнять заказ?
При обработке по событиям используй event_type = invoice.settled вместе со status = settled как повод проверить заказ. Запроси текущее состояние счёта и выполняй каждый заказ только один раз.
status - состояние счёта в момент создания события. event_type показывает, что произошло. payment.received может иметь processing или settled; это не второй платёж и не отдельное основание выполнять заказ.
Какие события и статусы отправляются?
| Событие в настройках и истории | Статус в теле | Значение |
|---|---|---|
| invoice.created | new | Счёт создан и ожидает оплаты. Также используется при контролируемом повторном открытии со статусом new. |
| payment.received | Resulting invoice status | Платёж записан или полученная сумма увеличилась. Обычно processing или settled; само событие не доказывает окончательного зачисления. |
| invoice.processing | processing | Платёж обнаружен, но нужная сумма или финальность ещё не достигнуты. Включает частичные платежи. |
| invoice.settled | settled | Политика зачисления выполнена либо платёж принят вручную. Проверь resolution и заказ перед выполнением. |
| invoice.expired | expired | Срок оплаты истёк. Поздний платёж может изменить статус, пока отслеживание продолжается. |
| invoice.invalid | invalid | Автоматическое принятие невозможно, доказательства платежа потеряны либо продавец его отклонил. Проверь счёт. |
| invoice.cancelled | cancelled | Счёт отменён. Не выполняй заказ; отмена не возвращает ончейн-платёж. |
Почему последовательности событий Ethereum и Solana могут различаться
Подтверждения приходят позже: пример Ethereum
| Последовательность | event_type | status |
|---|---|---|
| 1 | invoice.created | new |
| 2 | payment.received | processing |
| 2 | invoice.processing | processing |
| 3 | invoice.settled | settled |
Уже финален при обнаружении: пример Solana
| Последовательность | event_type | status |
|---|---|---|
| 1 | invoice.created | new |
| 2 | payment.received | settled |
| 2 | invoice.settled | settled |
Это порядок создания событий, а не гарантированный порядок доставки. Оба сценария возможны и в других сетях в зависимости от момента обнаружения и политики зачисления. Не требуй processing перед settled.
Выполняй один раз: пример приёмника и защита от дублей
| Подход | Как обрабатывать |
|---|---|
| Приёмник отдельных событий | Храни разные события по подписанному event_id, затем выбирай invoice.settled со status = settled. Не отбрасывай событие, если раньше пришёл payment.received с тем же sequence. |
| Входящая очередь состояния заказов SDK | Готовые примеры приёмников PHP, Python и Node объединяют project + invoice_id + sequence. Обрабатывай сохранённое состояние независимо от event_type, запрашивай текущий счёт и выполняй заказ один раз, если он settled. Не добавляй после такого объединения фильтр только на invoice.settled. |
Повтор сохраняет event_id и исходное тело. Разные события могут иметь один sequence, но разные event_id. При обработке по событиям убирай дубли по подписанному event_id; отдельно защищай выполнение заказа по настроенной установке/проекту + invoice_id и своему заказу. Повторное зачисление не должно оплачивать заказ дважды.
HTTP receiver:
Verify raw-body signature, timestamp and configured project/store scope.
Save to a durable inbox; deduplicate the signed event_id.
Return HTTP 2xx only after persistence succeeds.
Event-based background worker:
Other events go to status/reconciliation handling, not fulfilment.
Continue here only for event_type = invoice.settled and status = settled.
Fetch the current invoice from your configured API origin.
Check settled status, project/store, order, amount, currency and review policy.
In one database transaction:
Lock the order and check the scoped invoice has not been fulfilled.
Credit/complete once and save the fulfilment record.
Queue any external fulfilment with the same business idempotency key.
SDK order-state worker:
Use the same current-invoice checks and fulfil-once transaction.
Do not filter event_type after collapsing events by invoice revision.Псевдокод, не готовый приёмник.
Все состояния счетов и исключения платежей
| Поле | Значения | Значение |
|---|---|---|
| status | new, processing, settled, expired, invalid, cancelled | Состояние счёта при создании события; к моменту доставки оно может измениться. |
| amount_status | none, partial, paid, overpaid | Полученная сумма с учётом допустимой недоплаты. paid не означает финальность подтверждений. |
| timing_status | on_time, late | Уложился ли платёж в срок счёта. |
| resolution | automatic, manually_settled, manually_invalidated | Определён ли результат обычными правилами или ручным принятием/отклонением. |
| requires_review | false, true | Признак исключения, а не ещё один статус счёта и не автоматическое разрешение выполнить заказ или возврат. |
| Ситуация | Обработка |
|---|---|
| Недоплата и допуск | По автоматическим правилам partial не завершается оплатой. paid может учитывать допустимую недоплату, но финальность всё равно нужна. Используй статус счёта, а не только сравнение сумм. |
| Переплата | overpaid может сочетаться с settled и requires_review = true. Применяй свою политику переплат; не зачисляй заказ дважды и не возвращай средства автоматически на непроверенный адрес. |
| Поздняя оплата | expired может позже измениться, пока идёт отслеживание. timing_status = late требует проверки; не открывай отменённый заказ заново и не отправляй его автоматически. |
| Принятие вручную | invoice.settled может иметь resolution = manually_settled без подходящих ончейн-средств. Реши, принимает ли интеграция такое ручное решение; сводные поля платежа могут быть null. |
| Реорганизация и отмена достоверности | Новая ревизия может признать прежние доказательства платежа недействительными. Получи текущее состояние заново и обработай отмену через сверку. Не игнорируй её лишь потому, что заказ когда-то был оплачен. |
| Ноль подтверждений или нулевая сумма | Зачисление с нулём подтверждений возможно при обнаружении и несёт риск реорганизации. Явно разрешённый счёт на нулевую сумму завершается без платежа. В обоих случаях предварительный payment.received не обязателен. |
Для выполнения заказа используй status = settled, не amount_status = paid и не перенаправление с оплаты. При нуле требуемых подтверждений зачисление возможно сразу при обнаружении; это несёт риск реорганизации.
Недоплата - amount_status = partial, переплата - overpaid. paid означает получение допустимого минимума с учётом допуска недоплаты счёта. Это состояния суммы, не статусы счёта. late - значение timing_status, не отдельное событие.
Обычная последовательность: new → processing → settled, но промежуточные состояния могут пропускаться. Явно разрешённый счёт на нулевую сумму завершается без платежа и сохраняет amount_status = none. Ручное принятие отмечается manually_settled.
Уведомления - неизменяемые снимки, а не ответы о текущем статусе. Они могут опоздать, прийти не по порядку или повториться. События платежа и статуса могут иметь один sequence счёта и одинаковые поля его состояния, но разные подписанные event_id и event_type. Число подтверждений не гарантирует уведомление на каждый блок.
Что ты получаешь
{
"invoice_id": "11111111-2222-4333-8444-555555555555",
"status": "settled",
"amount_status": "paid",
"timing_status": "on_time",
"resolution": "automatic",
"sequence": 3,
"amount": "49.9",
"currency": "EUR",
"order_id": "order-1042",
"payload_version": 2,
"event_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"event_type": "invoice.settled",
"occurred_at": "2026-09-14T12:05:00Z",
"project_id": "11111111-1111-4111-8111-111111111111",
"store_id": "22222222-2222-4222-8222-222222222222",
"description": "Annual plan",
"email": "ada@example.test",
"customer": {
"firstname": "Ada",
"lastname": "Lovelace",
"countryiso2": "GB"
},
"metadata": {
"firstname": "Ada",
"lastname": "Lovelace",
"countryiso2": "GB",
"cart_id": "cart-681"
},
"created_at": "2026-09-14T12:00:00Z",
"updated_at": "2026-09-14T12:05:00Z",
"expires_at": "2026-09-14T12:15:00Z",
"monitoring_expires_at": "2026-09-21T12:15:00Z",
"settled_at": "2026-09-14T12:05:00Z",
"paid_chain": "ethereum",
"paid_asset": "USDC",
"paid_asset_amount": "58.17342",
"paid_asset_amount_received": "58.17342",
"paid_payment_method_id": "33333333-3333-4333-8333-333333333333",
"settlement_exchange_rate": {
"rate": "1.17",
"units": "asset_per_invoice_currency",
"currency": "EUR",
"symbol": "USDC",
"observed_at": "2026-09-14T12:05:00Z",
"as_of": "2026-09-14T12:04:30Z",
"pricing_provider": "kraken",
"asset_provider": "kraken",
"pricing_fetched_at": "2026-09-14T12:04:30Z",
"asset_fetched_at": "2026-09-14T12:04:30Z",
"stale": false,
"is_fixed": false,
"reference_currency": "USD",
"uses_reference_proxy": false
},
"cancelled_at": null,
"exchange_rate_spread_percent": "0.5",
"underpayment_tolerance_percent": "1",
"reason_code": "payment_confirmed",
"requires_review": false,
"links": {
"checkout": "https://pay.example.com/invoice/11111111-2222-4333-8444-555555555555",
"invoice": "https://api.example.com/v1/projects/11111111-1111-4111-8111-111111111111/invoices/11111111-2222-4333-8444-555555555555",
"payments": "https://api.example.com/v1/projects/11111111-1111-4111-8111-111111111111/invoices/11111111-2222-4333-8444-555555555555/payments"
},
"payment_info": {
"active_payment_method_id": "33333333-3333-4333-8333-333333333333",
"method_count": 1,
"methods_truncated": false,
"methods": [
{
"payment_method_id": "33333333-3333-4333-8333-333333333333",
"payment_rail": "onchain",
"chain_slug": "ethereum",
"network": "mainnet",
"caip_network_id": "eip155:1",
"asset_id": "44444444-4444-4444-8444-444444444444",
"asset_key": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"caip_asset_id": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"asset_name": "USD Coin",
"symbol": "USDC",
"asset_kind": "token",
"asset_decimals": 6,
"contract_address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"token_standard": "erc20",
"destination_address": "0x1111111111111111111111111111111111111111",
"destination_tag": null,
"status": "paid",
"amounts": {
"expected_amount": "58.17342",
"expected_amount_atomic": "58173420",
"received_amount": "58.17342",
"received_amount_atomic": "58173420",
"confirmed_amount": "58.17342",
"confirmed_amount_atomic": "58173420",
"unconfirmed_amount": "0",
"unconfirmed_amount_atomic": "0",
"minimum_payment_amount": "57.591686",
"minimum_payment_amount_atomic": "57591686",
"remaining_amount": "0",
"remaining_amount_atomic": "0",
"remaining_to_full_amount": "0",
"remaining_to_full_amount_atomic": "0",
"overpaid_amount": "0",
"overpaid_amount_atomic": "0"
},
"acceptance": {
"finality_mode": "confirmations",
"required_confirmations": 2,
"observed_confirmations": 2,
"underpayment_tolerance_percent": "1"
},
"quote": {
"effective_rate": "1.1658",
"reference_rate": "1.16",
"units": "asset_per_invoice_currency",
"currency": "EUR",
"symbol": "USDC",
"exchange_rate_spread_percent": "0.5",
"quote_expires_at": "2026-09-14T12:15:00Z",
"provenance_available": true,
"rounding": "up",
"unrounded_payment_amount": "58.17342",
"rounding_adjustment": "0",
"pricing_provider": "kraken",
"asset_provider": "kraken",
"pricing_fetched_at": "2026-09-14T11:59:30Z",
"asset_fetched_at": "2026-09-14T11:59:30Z"
},
"market_rate_at_event": {
"rate": "1.17",
"units": "asset_per_invoice_currency",
"currency": "EUR",
"symbol": "USDC",
"observed_at": "2026-09-14T12:05:00Z",
"pricing_provider": "kraken",
"asset_provider": "kraken",
"pricing_fetched_at": "2026-09-14T12:04:30Z",
"asset_fetched_at": "2026-09-14T12:04:30Z",
"as_of": "2026-09-14T12:04:30Z",
"stale": false,
"is_fixed": false,
"reference_currency": "USD",
"uses_reference_proxy": false
},
"payment_count": 1,
"payments_truncated": false,
"payments": [
{
"payment_id": "55555555-5555-4555-8555-555555555555",
"payment_method_id": "33333333-3333-4333-8333-333333333333",
"transaction_id": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"payment_hash": null,
"event_index": 0,
"payment_rail": "onchain",
"chain_slug": "ethereum",
"network": "mainnet",
"asset_id": "44444444-4444-4444-8444-444444444444",
"asset_key": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"caip_asset_id": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"symbol": "USDC",
"asset_decimals": 6,
"amount": "58.17342",
"amount_atomic": "58173420",
"status": "final",
"counts_towards_received": true,
"confirmations": 2,
"block_height": 26000000,
"observed_at": "2026-09-14T12:04:30Z",
"chain_time": "2026-09-14T12:04:20Z",
"finalized_at": "2026-09-14T12:05:00Z",
"explorer_name": "Etherscan",
"explorer_url": "https://etherscan.io/tx/0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
],
"links": {
"payments": "https://api.example.com/v1/projects/11111111-1111-4111-8111-111111111111/invoices/11111111-2222-4333-8444-555555555555/payments?payment_method_id=33333333-3333-4333-8333-333333333333"
}
}
]
}
}amount - исходная итоговая сумма счёта. payment_info описывает обнаруженные криптопереводы, недостающие суммы и зафиксированные курсы. Версия 2 также подписывает имя и ID события и область проекта и магазина.
Все поля уведомления и дополнительные данные счёта
| Поле | Тип | Значение |
|---|---|---|
| invoice_id | UUID | Публичный UUID счёта для авторизованного маршрута подробностей |
| status | string | Статус счёта в снимке: new, processing, settled, expired, invalid, cancelled |
| amount_status | string | none, partial, paid или overpaid; paid учитывает допустимую недоплату, но не финальность подтверждений |
| timing_status | string | on_time или late |
| resolution | string | automatic, manually_settled или manually_invalidated |
| sequence | integer | Растущая ревизия счёта; разные события могут иметь одну ревизию. Сравнивай без потери точности целых чисел |
| amount | decimal string | Исходная сумма счёта, а не полученная криптовалюта; сохраняй десятичную точность |
| currency | string | Валюта amount, например EUR для счёта в евро, оплаченного USDC |
| order_id | string | null | Номер заказа продавца |
| payload_version | integer | 2 для новых событий версии 4.1.0+; отсутствует в сохранённых старых событиях |
| event_id | UUID | Подписанный идентификатор события, не меняется при повторах и ручной повторной доставке |
| event_type | string | Одно из семи событий подписки |
| occurred_at | timestamp | Время создания неизменяемого события, не время доставки |
| project_id | UUID | Область проекта продавца; сверяй с настройкой приёмника |
| store_id | UUID | Область магазина продавца; сверяй с настройкой приёмника |
| description | string | null | Исходное описание счёта |
| string | null | Необязательный email покупателя в момент события | |
| customer | object | Распознанные необязательные поля данных покупателя; без догадок и добавленных извне персональных данных |
| metadata | object | Исходные метаданные продавца на момент события |
| created_at | timestamp | Время создания счёта |
| updated_at | timestamp | Время обновления состояния счёта |
| expires_at | timestamp | Срок оплаты счёта |
| monitoring_expires_at | timestamp | Срок отслеживания поздних платежей |
| settled_at | timestamp | null | Время окончательного зачисления |
| paid_chain | string | null | 4.1.2+: slug сети доказанного способа оплаты, например ethereum; null без сохранённого подходящего зачисления |
| paid_asset | string | null | 4.1.2+: тикер монеты или токена, например BTC, ETH или USDC; метка для отображения, не уникальный ID актива |
| paid_asset_amount | decimal string | null | 5.0.1+: полная зафиксированная сумма запроса в единицах paid_asset до вычета допуска; сохраняется при зачислении |
| paid_asset_amount_received | decimal string | null | 5.0.1+: вся действительная сумма, полученная победившим способом к моменту зачисления, включая допустимую недоплату или переплату; фиксированный снимок, не текущий баланс |
| paid_payment_method_id | UUID | null | 4.1.2+: ID платёжного намерения, завершившего оплату; совпадает с payment_info.methods[].payment_method_id и его точной сетью и контрактом |
| settlement_exchange_rate | object | null | 4.1.2+: рыночный снимок до наценки, сохранённый при зачислении, с единицами, валютой, временем источников и признаками качества; при доставке не пересчитывается |
| cancelled_at | timestamp | null | Время отмены |
| exchange_rate_spread_percent | decimal string | Зафиксированная наценка, не текущая настройка магазина |
| underpayment_tolerance_percent | decimal string | Зафиксированный допуск счёта; каждый способ также сообщает фактический допуск |
| reason_code | string | null | Машиночитаемая причина перехода состояния |
| requires_review | boolean | Признак исключения платежа; не разрешение автоматически выполнить заказ или возврат |
| links | object | URL оплаты, авторизованных подробностей счёта и платежей в момент события. Приоритет: домены из Магазин → Основное, затем магазин по умолчанию, затем основной системный домен; только активные домены нужной роли. Повторы сохраняют исходные подписанные ссылки; null, если активного хоста нет. |
| payment_info | object | Фактически обнаруженные способы, точные суммы, зафиксированная котировка, справочный рыночный снимок и ограниченный список наблюдений; группы полей описаны ниже |
Сводка зачисления: settlement_exchange_rate
| Поле | Тип | Значение |
|---|---|---|
| rate / units / currency / symbol | strings | Единицы актива на одну единицу валюты счёта до наценки. Десятичная строка, не сумма платежа и не исполненная сделка. |
| observed_at / as_of | timestamps | Время фиксации зачисления и более раннее время источника. Не считай кешированные данные текущей биржевой котировкой. |
| pricing_provider / asset_provider / pricing_fetched_at / asset_fetched_at | strings / timestamps | Источники фиатной цены и цены актива со временем получения, сохранённые при зачислении. |
| stale / is_fixed / uses_reference_proxy / reference_currency | booleans / string | Те же признаки качества, что в market_rate_at_event. Фиксированные цены проекта отмечены; базовая валюта - USD. |
| Missing snapshot or price | null | Исторический курс не угадывается. До зачисления все сводные поля null; одно лишь отсутствие цены не убирает доказанные идентификаторы paid_*. |
Способы оплаты: payment_info
| Поле | Тип | Значение |
|---|---|---|
| active_payment_method_id | UUID | null | Победивший или выбранный обнаруженный способ. Null до обнаружения или после признания недействительным; способ по умолчанию не подставляется. |
| method_count / methods_truncated | integer / boolean | Общее число обнаруженных способов и признак неполноты встроенного списка. |
| methods[] | object[] | Не более восьми обнаруженных способов, активный первым. Суммы разных активов не объединяются. |
| payment_method_id / payment_rail | UUID / string | Идентификатор платёжного намерения счёта и способ передачи onchain или lightning. |
| chain_slug / network / caip_network_id | string | Идентификатор сети. Всегда связывай токен с его сетью. |
| asset_id / asset_key / caip_asset_id | UUID / string / nullable string | Проверенный идентификатор реестра; символ сам по себе не уникален. |
| asset_name / symbol / asset_kind | string | Отображаемое имя, тикер и вид актива: native или token. |
| contract_address / token_standard | string | null | Контракт токена или mint и стандарт; null для нативных активов. |
| asset_decimals | integer | Точность минимальных единиц; у BTC Lightning - 11. |
| destination_address / destination_tag | string | null | Публичный адрес приёма и обязательный memo/tag. У Lightning адрес null; закрытого ключа здесь никогда нет. |
| status | string | Состояние способа: pending, partial, paid, overpaid, expired или invalid. Само paid не означает окончательную оплату счёта. |
| payment_count / payments_truncated / payments[] | integer / boolean / object[] | Общее число наблюдений и не более пяти последних. Каждое описано ниже. |
| links.payments | HTTPS URL | null | Авторизованная постраничная история этого способа на настроенном API-origin. |
Точные суммы: methods[].amounts
| Поле | Тип | Значение |
|---|---|---|
| expected_amount | decimal string | Полная зафиксированная котировка после наценки и округления вверх. |
| received_amount / confirmed_amount | decimal strings | Действительные обнаруженные средства / средства, выполнившие политику подтверждений или финальности этого способа. |
| unconfirmed_amount | decimal string | max(received - confirmed, 0). Это не дополнительная сумма к отправке. |
| minimum_payment_amount | decimal string | Допустимый порог после учёта недоплаты. Может быть ниже полной котировки. |
| remaining_amount | decimal string | max(minimum accepted - received, 0). Сколько ещё нужно для допустимого порога, а не прогресс подтверждений. |
| remaining_to_full_amount | decimal string | max(full quote - received, 0), без учёта допуска. |
| overpaid_amount | decimal string | max(received - full quote, 0). Не разрешает автоматический возврат. |
| Every amount's *_atomic companion | integer string | Точное представление в минимальных единицах. Используй библиотеки десятичных или целых чисел, не float и не JavaScript Number для денег. |
Политика подтверждений: methods[].acceptance
| Поле | Тип | Значение |
|---|---|---|
| finality_mode / required_confirmations | string / integer | Зафиксированное число подтверждений или политика finalized. Ноль подтверждений явно разрешается политикой продавца, это не общая финальность сети. |
| observed_confirmations | integer | null | Минимум среди действительных наблюдений, не только последнего перевода. Null для Lightning или при отсутствии действительных наблюдений. |
| underpayment_tolerance_percent | decimal string | Фактический допуск способа. Lightning использует ноль, даже если у счёта ненулевой допуск для ончейн-платежей. |
Курсы: methods[].quote и market_rate_at_event
| Поле | Тип | Значение |
|---|---|---|
| quote.effective_rate / units / currency / symbol | strings | Зафиксированный курс asset_per_invoice_currency с наценкой; currency и symbol явно задают направление. |
| quote.exchange_rate_spread_percent / quote_expires_at | decimal string / timestamp | Зафиксированные наценка и срок котировки. Не заменяются текущими настройками магазина. |
| quote.reference_rate / unrounded_payment_amount / rounding_adjustment | decimal string | null | Базовый курс до наценки, сумма до округления и прибавка округления вверх в единицах актива. |
| quote.pricing_provider / asset_provider / pricing_fetched_at / asset_fetched_at | string or timestamp | null | Исходные источники и время цен валюты и актива. Без API-ключей и данных доступа к провайдерам. |
| quote.provenance_available / rounding | boolean / string | False для старых счетов без сохранённого снимка источников; округление вверх. |
| market_rate_at_event | object | null | Справочный кешированный снимок рынка в момент события. Отсутствующие данные остаются null; он не меняет суммы счёта и не ждёт сетевого запроса. |
| market_rate_at_event.rate / units / currency / symbol | strings | Рыночный курс до наценки с тем же явным направлением, что и quote. |
| market_rate_at_event.observed_at / as_of / pricing_fetched_at / asset_fetched_at | timestamps | Время снимка события / более раннее из двух времён источников / время каждого источника. |
| market_rate_at_event.pricing_provider / asset_provider | strings | Кешированные источники валюты и актива, включая настроенные цены пользовательских токенов. |
| market_rate_at_event.stale / is_fixed / uses_reference_proxy / reference_currency | booleans / string | Устарел ли кеш, фиксирована ли цена токена, использует ли ориентир USD стейблкоин. Базовая валюта - USD. Stale - справочный признак, не свежая котировка. |
Записи переводов: methods[].payments[] и GET …/payments
| Поле | Тип | Значение |
|---|---|---|
| payment_id / payment_method_id | UUID | ID наблюдения / ID родительского платёжного намерения. Для удаления дублей истории используй payment_id. |
| transaction_id / payment_hash / event_index | string | null / integer | Ончейн-хеш и индекс перевода, лога или выхода либо хеш Lightning. У Lightning нет транзакции или ссылки на обозреватель. |
| payment_rail / chain_slug / network / asset_id / asset_key / caip_asset_id / symbol / asset_decimals | strings / UUID / integer | Те же идентификаторы актива и сети, что у содержащего их способа. |
| amount / amount_atomic | decimal / integer strings | Точная сумма этого перевода, никогда не пересчёт в фиат. |
| status / counts_towards_received | string / boolean | detected, confirming и final учитываются; reorged, replaced и invalid - нет. Сохраняй недействительную историю для сверки. |
| confirmations / block_height | integer | null | Данные блока наблюдения; подтверждения null для Lightning. |
| observed_at / chain_time / finalized_at | timestamp | null | Первое локальное обнаружение, доверенное время сети при наличии и время достижения финальности по политике. |
| explorer_name / explorer_url | string | null | Проверенная ссылка на публичный обозреватель блоков, если поддерживается. |
Версия продавца 5.13.3 исключает проверенные внутренние переводы на газ из суммы платежей покупателей, payment_info, API платежей счёта, лимитов возврата и событий payment.received. Записи сети и казначейства сохраняются для учёта кошельков. Обычные переводы и реальные переплаты по-прежнему учитываются. Уже подписанные тела уведомлений не переписываются. Если историческое зачисление зависело от внутреннего пополнения, а не средств покупателя, сверка создаёт invoice.invalid с reason_code internal_gas_funding_excluded; проверь его, а не выполняй заказ повторно.
Версия продавца 4.1.0 добавляет payload_version 2, не перемещая и не меняя прежние девять полей. Уже стоящие в очереди события сохраняют исходное тело и могут не иметь payload_version. event_id, event_type и ID проекта и магазина теперь входят в подписанное тело; транспортные заголовки события и доставки остаются неподписанными.
payment_info описывает обнаруженные платежи, а не все предложенные варианты оплаты. До обнаружения active_payment_method_id равен null, а methods пуст. Реорганизованные или недействительные наблюдения могут оставаться в methods даже после обнуления активного способа. Никогда не складывай суммы разных активов или сетей.
Все суммы, целые значения минимальных единиц, курсы и проценты - строки. received_amount включает действительные средства, ожидающие подтверждения; confirmed_amount соответствует политике финальности способа. remaining_amount - max(minimum_payment_amount минус received_amount, 0); remaining_to_full_amount - max(expected_amount минус received_amount, 0). Пример: ожидается 100 USDC, получено 99, допуск 1%: remaining_amount равен 0, remaining_to_full_amount - 1. Финальность всё ещё обязательна.
quote - зафиксированный расчёт счёта: единицы актива на одну единицу валюты счёта. Наценка применяется до округления вверх. Для точного сравнения используй expected_amount_atomic; одного отображаемого курса может быть недостаточно для воспроизведения округления. Старые счета без сохранённых источников показывают null в полях источника, базового курса и округления, а provenance_available - false; сегодняшние данные не выдаются за историческую котировку.
market_rate_at_event - справочные кешированные данные до наценки, зафиксированные при создании события. Есть время источников, признаки устаревания и использования прокси-ориентира; без пригодной кешированной пары значение null. Запрос курса в реальном времени не блокирует уведомление, а рыночный снимок никогда не меняет сумму к оплате. Фиксированные пользовательские токены отмечены is_fixed; DEX-токены используют источник своего проекта, а не другого токена с тем же символом.
Верхнеуровневые paid_chain, paid_asset, paid_payment_method_id и settlement_exchange_rate (4.1.2+) обозначают доказанный победивший способ после зачисления, а не выбор на странице оплаты или сумму разных способов. До зачисления, после признания недействительным, для старых зачислений без снимка или ручного принятия без средств с нужной финальностью сводные поля null. Символы - метки отображения: используй ID способа для точной сети, актива и контракта.
Версия продавца 5.0.1 добавляет paid_asset_amount и paid_asset_amount_received как точные десятичные строки в единицах paid_asset; payload_version остаётся 2. paid_asset_amount - полная зафиксированная котировка с наценкой и округлением вверх, не порог допуска и не остаток к оплате. paid_asset_amount_received - все действительные поступления победившего способа на момент зачисления, включая ожидающие подтверждения и допустимую недоплату или переплату. Пример: запрошено 100 USDC, получено и принято с допуском 99 - значения будут 100 и 99, не 99 и 99. Оба фиксируются со снимком зачисления; для поступлений на момент события используй payment_info.methods[].amounts, для текущих записей - API платежей. Без подходящего снимка и для снимков до 5.0.1 они null; тела старых событий в очереди не меняются. Никогда не преобразуй точные десятичные строки в float для учёта.
settlement_exchange_rate - кешированный рыночный снимок до наценки, записанный при зачислении, не зафиксированная котировка счёта и не исполненная биржевая сделка. Формат совпадает с market_rate_at_event; 1.17 asset_per_invoice_currency для EUR/USDC означает 1 EUR = 1.17 USDC. Время источников и признаки stale/fixed/proxy описывают качество. При отсутствии пары курс null, но у доказанного способа сохраняются paid_*. Он не меняет сумму к оплате и не ждёт вызова провайдера. Поздние платежи тем же способом и повторные доставки не заменяют снимок, включая сохранённый null-курс. Настоящее повторное зачисление или смена завершившего способа создаёт новый снимок; observed_at показывает время фиксации, а settled_at может сохранять первое зачисление. Старые тела событий не меняются.
Включаются не более восьми обнаруженных способов и пяти последних наблюдений на способ, с общим числом и признаками усечения. Ограничение размера может ещё уменьшить массивы. Наблюдение - один перевод, лог или UTXO-выход, не обязательно уникальный хеш транзакции. Для полной текущей истории используй GET /v1/projects/YOUR_PROJECT_ID/invoices/{invoice_id}/payments с payment_method_id, limit и offset. Подробности счёта сохраняют все котированные способы и их quote_details. API-ссылки требуют настроенного хоста и ключа; никогда не пересылай bearer-токен на произвольный URL из уведомления.
Lightning использует payment_hash вместо transaction_id; адрес приёма, обозреватель и наблюдаемые подтверждения - null. Точная сумма BTC имеет 11 десятичных знаков - миллисатоши, эффективный допуск равен нулю. Preimage BOLT11, ключи кошелька, секреты подписи и доступы провайдеров не включаются. Данные покупателя и metadata допустимы только в ответах продавцу и подписанных уведомлениях, не на публичной странице оплаты; не помещай секреты в metadata.
Безопасный приём
- Проверяй точное исходное тело подходящим секретом до разбора. Магазин → IPN даёт IPN-секрет, в том числе для доставок по индивидуальному ipn_url. Каждый эндпоинт Магазин → Вебхуки имеет собственный секрет. Ни один не является API-токеном; смена одного не меняет остальные.
- Проверь подписанное время - по умолчанию SDK допускает пять минут в обе стороны - и сверяй подписанные ID проекта и магазина с настройками приёмника, если они есть. Надёжно поставь сообщение в очередь до ответа HTTP 2xx. Для обработки отдельных событий v2 event_id подписан; одни ID заголовков не защищают от повтора, потому что заголовки не подписаны. Для очереди состояний убирай дубли по invoice_id и sequence и сравнивай исходные поля состояния, а не всё тело v2: разные типы и ID событий могут иметь одну ревизию.
- В фоновой задаче получи текущий счёт с настроенного API-origin, а не по произвольной ссылке уведомления. Сверь сохранённый заказ, проект, магазин, сумму и валюту, потребуй текущий статус settled и примени свою политику ручного принятия и исключений. Заблокируй заказ и выполни его один раз в транзакции базы, независимо от удаления дублей событий.
- Никогда не применяй старый sequence поверх нового. Разные события могут иметь одну ревизию; не сочетай удаление дублей по ревизии с фильтром только invoice.settled. Повторное открытие и сверка могут менять статус; порядок задаёт sequence, а не фиксированный ранг статусов. Записывай отмены для проверки, не выполняй заказ повторно.
Примеры приёмников: PHP · Python · Node.js / TypeScript.
Проверка подписи и правила доставки
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyWhollySignature(rawBody, header, signingSecret, toleranceSeconds = 300) {
const match = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header || "");
if (!match) return false;
const timestamp = Number(match[1]);
if (!Number.isSafeInteger(timestamp)) return false;
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - timestamp) > toleranceSeconds) return false;
// rawBody must be the exact request Buffer, before JSON parsing.
const expected = createHmac("sha256", signingSecret)
.update(String(timestamp))
.update(".")
.update(rawBody)
.digest();
const presented = Buffer.from(match[2], "hex");
return timingSafeEqual(expected, presented);
}| Правило доставки | Подробности |
|---|---|
| Заголовки | Wholly-Signature, Wholly-Event-Id и Wholly-Delivery-Id; Content-Type - application/json. |
| Подпись | HMAC-SHA256 от <unix timestamp>.<exact raw body>; формат заголовка t=<timestamp>,v1=<64 lowercase hex>. |
| Успех | Любой ответ HTTP 2xx. Перенаправления не выполняются; остальные ответы считаются ошибками. |
| Тайм-ауты | 5 секунд на соединение и 10 секунд на весь запрос. |
| Расписание повторов | До 8 попыток для повторяемых ошибок: сразу, затем через 10 с, 1 мин, 5 мин, 15 мин, 1 ч, 6 ч и 24 ч после завершения предыдущей попытки. IPN повторяется автоматически; автоматические повторы вебхука можно отключить для отдельного эндпоинта. |
| Безопасность адресата | Только публичный HTTPS. DNS повторно проверяется и закрепляется для доставки; локальные, частные и зарезервированные адреса отклоняются. |
| Хранение событий | Тела уведомлений и история доставки хранятся 90 дней; старые подробности удаляются ограниченными партиями. |
| Защита от дублей | Сохраняй подписанные invoice_id и sequence в рамках настроенного проекта. Wholly-Event-Id идентифицирует событие, Wholly-Delivery-Id - запись доставки: повторы используют её снова, ручная доставка создаёт новую. Оба ID в заголовках не подписаны. |
| Имена событий | Версия 2 подписывает event_id и event_type в теле. В старых событиях очереди их нет. Разные типы могут иметь один sequence счёта; сверяй состояние по ревизии либо убирай дубли отдельных событий по подписанному event_id. |
| Смена секрета | При смене нет периода перекрытия или заголовка версии: подписи событий в очереди, повторов и ручных доставок меняются сразу. |
| Приостановленная доставка | Недостаточный баланс оплаты приостанавливает IPN и вебхуки, включая повторы. Входящие платежи продолжаются; уведомления возобновляются после пополнения в пределах срока хранения тела. |
ИИ-ассистенты · MCP
Подключи ассистента к установке продавца.
Версия продавца 5.0.0 включает опциональный MCP-сервер на настроенном API-домене. Он работает внутри установки, а не через общий relay Wholly Crypto.
- Открой Настройки → Доступ к API. Создай отдельный ключ, назначь только нужные ассистенту проекты и начни с чтения. Для аккаунта у оператора сначала оператор включает MCP всей установки; ты управляешь только своими ключами и разрешениями.
- В разделе ИИ-подключения · MCP включи MCP, выбери ключ и сохрани его MCP-доступ. Существующие ключи не имеют MCP-доступа без явного включения.
- Скопируй URL MCP-сервера в настройки удалённого HTTP-сервера клиента. Для OAuth войди в консоль продавца, проверь имя клиента и адрес возврата, выбери ключ и подтверди. Существующие защиты Basic Auth и TOTP сохраняются.
- Для создания счетов дополнительно нужны ключ с чтением и записью, режим «Чтение и создание счетов» в политике MCP, OAuth scope mcp:invoice:create и явное подтверждение. OAuth-подключение не получает проекты, добавленные к ключу после разрешения.
{
"mcpServers": {
"whollycrypto": {
"url": "https://api.example.com/mcp"
}
}
}| Инструмент | Доступ | Назначение |
|---|---|---|
| list_projects | Получи | Включённые проекты, назначенные подключению; пагинация limit/offset. |
| list_stores | Получи | Магазины, ID и статус включения внутри project_id; пагинация limit/offset. |
| list_payment_methods | Получи | Настроенные сети, токены и Lightning для project_id + store_id. |
| get_wallet_balances | Получи | Адреса приёма и кешированные балансы с полями актуальности и доступности; без секретов кошельков. |
| list_invoices | Получи | Счета проекта с фильтрами магазина, статуса и поиска; пагинация limit/offset. |
| get_invoice | Получи | Полные подробности счёта и ссылка оплаты по project_id + invoice_id. |
| get_delivery_history | Получи | Статусы IPN/вебхуков магазина, попытки и HTTP-результаты. Необязательные фильтры invoice_id/kind; без секретов и тел уведомлений. |
| convert_amount | Получи | Справочный кешированный пересчёт по from, to и десятичной строке amount; не котировка счёта. |
| create_invoice | Явное разрешение записи | project_id, store_id, idempotency_key и invoice - существующее тело создания счёта. invoice.payment_methods фильтрует включённые способы магазина; 5.4.0+ игнорирует неактивные и неразрешённые варианты и возвращается к настройкам магазина, если совпадений нет. Одна сеть без активов выбирает все её активные разрешённые активы. asset_tickers в рамках сети поддерживаются с 5.3.0. Возвращается обычный ответ счёта. |
Протокол, OAuth и безопасность
Используй Streamable HTTP по HTTPS. Согласуй объявленную версию протокола и добавляй MCP-Protocol-Version к последующим POST. Отправляй Content-Type: application/json и Accept: application/json, text/event-stream. Ответы - конечный JSON; при переподключении ID сеанса MCP не нужен.
OAuth использует короткие токены доступа на 15 минут, одноразовые коды S256 PKCE на 5 минут и обновляемые refresh-токены с жизнью подключения 30 дней. Повтор использованного refresh-токена отзывает подключение. Подключись заново после истечения, смены ключа, политики или основного API-домена.
Обнаружение OAuth публично только при включённом MCP. Параметр resource должен точно совпадать с основным URL из обнаружения, включая /mcp. Динамическая регистрация поддерживается; удалённые документы метаданных client-ID и секреты клиентов - нет.
Клиенты с пользовательскими Authorization-заголовками могут вместо OAuth использовать API-токен продавца с включённым MCP как Bearer. Он сохраняет отдельные REST-права; для доступа только к MCP лучше OAuth. Никогда не вставляй ключи в чат, URL, аргументы инструментов или систему контроля версий.
MCP делит с ключом минутную квоту REST и точные ограничения исходящего IP; действуют и IP-ограничения API-хоста. OAuth не обходит белый список. Для удалённого ИИ-клиента разреши документированные исходящие IP либо осознанно оставь ограничение выключенным. На маршрутах MCP/OAuth не должно быть браузерных проверок или кеширования.
HTTP-ошибки: 401 требует авторизации, 403 запрещает origin/IP/права, 404 означает выключенный MCP или неверный хост, 405 требует POST, 413 - превышение тела 32 КиБ, 429 содержит Retry-After. Ошибки JSON-RPC используют error.code; ошибки инструмента - result.isError=true даже при HTTP 200. Успешный ответ включает content и structuredContent.
Списки по умолчанию содержат 25 строк, максимум 100; offset ограничен 1000000. Ответ инструмента ограничен 2 МиБ. Истёкшие разрешения, запросы авторизации и счётчики квот чистятся автоматически; в настройках отображается до 100 активных OAuth-подключений.
MCP не работает с отключёнными проектами и магазинами. Подключение может показать статус включения магазина, но для чтения способов оплаты, истории доставки и создания счетов магазин должен быть включён. Обычные пользователи проектов не могут администрировать MCP.
Для нового счёта используй новый idempotency_key; после тайм-аута повторяй тот же ключ доступа, ключ идемпотентности и идентичный объект invoice. Десятичные суммы, наценка, допуск, подтверждения и оформление следуют REST-контракту счёта. MCP не обходит платёжную политику и правила баланса продавца.
Начальные инструменты не раскрывают закрытые ключи и сид-фразы, не отправляют и не собирают средства, не делают возвраты, не повторяют уведомления, не меняют способы оплаты, аккаунты, домены или оплату сервиса. Считай описания счетов, поля покупателей и метаданные недоверенными данными, а не инструкциями агенту. Подключённый ИИ-провайдер получает разрешённые для чтения данные.
| Метод | Путь | Контракт |
|---|---|---|
| POST | /mcp | Авторизованный JSON-RPC: initialize, ping, tools/list, tools/call. Уведомления возвращают 202; пакетные запросы отклоняются. |
| GET / DELETE | /mcp | Авторизованный 405: конечные JSON-ответы, без отдельного SSE-потока и серверного сеанса MCP. |
| GET | /.well-known/oauth-protected-resource/mcp | Основной URL ресурса и обнаружение сервера авторизации; также доступно по /.well-known/oauth-protected-resource. |
| GET | /.well-known/oauth-authorization-server | OAuth-эндпоинты, authorization_code/refresh_token, S256 PKCE и поддерживаемые области доступа. |
| POST | /mcp/oauth/register | Регистрация публичного клиента: client_name и точные redirect_uris. Только HTTPS или loopback HTTP. Без секрета клиента и загрузки удалённых метаданных. |
| GET | /mcp/oauth/authorize | client_id, redirect_uri, response_type=code, resource, code_challenge, code_challenge_method=S256, необязательные scope/state; перенаправляет на подтверждение в консоли. |
| POST | /mcp/oauth/token | Данные формы: authorization_code + code + code_verifier + redirect_uri либо refresh_token + refresh_token. Всегда добавляй client_id и resource. |
| POST | /mcp/oauth/revoke | Данные формы: client_id и token. Отзывает соответствующее подключение токена доступа или обновления. |
Пример прямого запроса инструмента
Сначала инициализируй и согласуй протокол через MCP-клиент. Здесь показан последующий запрос.
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/mcp" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'MCP-Protocol-Version: 2025-11-25' \
--header 'Accept: application/json, text/event-stream' \
--header 'Content-Type: application/json' \
--data-raw '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_invoices",
"arguments": {
"project_id": "YOUR_PROJECT_ID",
"limit": 10
}
}
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const body = `{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_invoices",
"arguments": {
"project_id": "YOUR_PROJECT_ID",
"limit": 10
}
}
}`;
const response = await fetch("https://api.example.com/mcp", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"MCP-Protocol-Version": "2025-11-25",
"Accept": "application/json, text/event-stream",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$body = <<<'JSON'
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_invoices",
"arguments": {
"project_id": "YOUR_PROJECT_ID",
"limit": 10
}
}
}
JSON;
$ch = curl_init("https://api.example.com/mcp");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "MCP-Protocol-Version: 2025-11-25", "Accept: application/json, text/event-stream", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"MCP-Protocol-Version": "2025-11-25",
"Accept": "application/json, text/event-stream",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_invoices",
"arguments": {
"project_id": "YOUR_PROJECT_ID",
"limit": 10
}
}
}""".encode("utf-8")
request = Request("https://api.example.com/mcp",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))API оператора
Создавай размещённых продавцов отдельными серверными ключами с ограниченными правами.
Размещай несколько бизнесов и автоматизируй настройку через api.example.com/v1/operator. Доступно с 7.4.0 только в режиме оператора. Обычный API продавца не меняется.
- Открой Оператор → Настройки → API оператора и включи его: по умолчанию он выключен. Создай отдельный ключ только с нужными правами и доступными продавцами.
- Храни ключ wc_operator_ на сервере. Используй хост API, не хост панели оператора и не ключ продавца.
- До каждого POST оператора сохраняй Idempotency-Key и точное тело запроса. При неясном результате прочитай состояние аккаунта; не меняй ключ только ради повтора.
- Создай продавца с onboarding: direct и паролем либо onboarding: invitation без пароля. Затем создай проекты и магазины и выдай ключ продавца с доступом к проекту для платёжной интеграции.
| Область доступа | Доступ |
|---|---|
| merchants.read / merchants.write | Список, чтение, создание и изменение размещённых продавцов. |
| users.read / users.write / users.security | Чтение, создание и изменение пользователей; отдельные права на пароли и отзыв сеансов. Никогда не создаёт администратора оператора. |
| invitations.read / invitations.write | Список, чтение, создание, замена и отзыв одноразовых ссылок приглашения и сброса. Для новых пользователей также нужно users.write; для сброса - users.security. |
| credits.read / credits.write / fees.write | Чтение балансов и журнала, начисление или корректировка локального баланса, будущие комиссии. Ненулевой стартовый баланс требует credits.write. |
| topups.read / topups.write | Чтение или создание запросов оплаты для пополнения размещённого продавца. API не может отметить их оплаченными. |
| projects.read / projects.write / reports.read | Создание проектов, магазинов, оформления и платёжных настроек продавца; чтение счетов, балансов кошельков и финансовых отчётов. |
| merchant_credentials.read / merchant_credentials.write | Управление обычными ограниченными ключами продавца. Сильное разрешение: после выдачи ключи действуют независимо. |
| events.read / webhooks.write / audit.read / health.read | Чтение истории жизненного цикла, настройка подписанных уведомлений, аудит, возможности и состояние узлов. |
Регистрация, балансы, права и безопасные повторы
| Тема | Правило |
|---|---|
| Ключи доступа | Необязательный срок действия и список точных IPv4/IPv6; по умолчанию 60 запросов в минуту, настройка от 1 до 600. Каждый запрос проверяет выдавшего администратора и текущие права. HTTP 429 содержит Retry-After. |
| Изоляция | Ключ видит только назначенных размещённых продавцов. Создание продавцов и общие отчёты требуют доступа ко всем продавцам. Собственный бизнес оператора исключён. |
| Первый вход | Прямые аккаунты подтверждают, что владелец хоста имеет доступ к ключам их кошельков. require_password_change добавляет смену пароля при первом входе. Принятие приглашения требует явного согласия с условиями доступа к ключам, затем обычного входа. Basic Auth и существующая TOTP-защита сохраняются. |
| Приглашения | Ссылки нового пользователя действуют 48 часов, сброса пароля - один час. Токены одноразовые. Перевыпуск отзывает старую ссылку. Приём SMTP не гарантирует доставку во входящие; проверь email_delivery. |
| Безопасные повторы | Каждый POST оператора требует ключ длиной 16–128 символов: буквы, цифры, -, _ или точка. Тот же ключ и точные URL/тело возвращают сохранённый результат. Другие байты дают 409. При повторе секреты и ссылки не возвращаются; при необходимости явно смени ключ или перевыпусти ссылку новой операцией. |
| Неопределённый результат | operator_request_in_progress означает, что операция выполняется или прервалась до записи результата. Проверь ресурс и аудит; не отправляй вслепую новый ключ. Завершённые квитанции уплотняются через 30 дней; старые ключи всё равно не могут выполнить операцию повторно. |
| Балансы и комиссии | Десятичные строки, не более шести знаков после запятой. starting_credit - разовое локальное начисление. Корректировке нужны сумма со знаком, заметка и request_id плюс HTTP-ключ повтора. fee_bps=100 означает 1%; изменения действуют на будущие счета. Начисление не пополняет собственный предоплаченный баланс установки. |
| Приостановка | enabled=false отключает размещённый аккаунт и отзывает сеансы консоли. payments_paused=true останавливает новые счета. Существующие платежи продолжают отслеживаться. Создание проектов и магазинов и автоматизация сохраняют правила баланса установки. |
| Недоступно через API | Нет секретов кошельков, подписи, отправки, возвратов, безвозвратного удаления, сброса TOTP, изменения доменов и серверной конфигурации. Обычные запросы счетов по-прежнему используют ключ продавца и API продавца. |
Вебхуки жизненного цикла оператора
| Событие | Данные |
|---|---|
| merchant.created / merchant.updated | merchant_id, enabled, payments_paused, fee_bps. |
| user.created / user.updated | merchant_id, user_id, enabled. Событие обновления охватывает email, включение аккаунта и изменения роли администратора. |
| invitation.accepted / password_reset.completed | merchant_id, user_id, invitation_id. |
| topup.settled / credit.balance_changed | merchant_id, ledger_id, kind, amount и balance. Для сверки прочитай валюту баланса продавца или подробности записи журнала. |
События оператора отделены от IPN счетов и вебхуков магазинов. Подписка принадлежит создавшему её ключу оператора, до 10 эндпоинтов на ключ. В очередь попадают только будущие подходящие события; для сохранённой истории используй GET /events.
Тело содержит event_id, event_type, merchant_id, occurred_at и data. Проверяй Wholly-Signature по точному исходному телу с помощью выдаваемого один раз signing_secret эндпоинта: HMAC-SHA256(secret, timestamp + '.' + raw_body), заголовок t=...,v1=.... Ограничивай допустимое отклонение времени небольшим интервалом.
Используй общий проверяющий подпись метод SDK, а не парсер уведомлений счёта. Затем проверь merchant_id и event_type, транзакционно сохрани и убери дубли event_id и возвращай 2xx только после надёжного принятия. Wholly-Event-Id должен совпадать с подписанным телом. Не считай неподписанные заголовки бизнес-данными.
Доставка выполняется как минимум один раз, может нарушать порядок и повторяется до 8 попыток. Для сверки читай текущие ресурсы; occurred_at не является монотонной последовательностью. Права, включение и срок действия проверяются перед доставкой. Отключённая подписка приостанавливает уже поставленные задачи, но не добавляет новые события.
События и история доставки хранятся 30 дней. Политика автоматизации установки может приостановить доставку. GET /webhooks/{id}/deliveries показывает результат и неизменяемое тело; публичный API не выполняет принудительную доставку истёкшей записи.
{
"event_id": "55555555-5555-4555-8555-555555555555",
"event_type": "merchant.created",
"merchant_id": "11111111-1111-4111-8111-111111111111",
"occurred_at": "2026-10-01T12:00:00Z",
"data": {
"merchant_id": "11111111-1111-4111-8111-111111111111",
"enabled": true,
"payments_paused": false,
"fee_bps": 300
}
}Ошибки и лимиты
Предсказуемая обработка валидации, квот и повторов.
Проверь HTTP-статус и Content-Type до разбора ответа. При 429подожди не менее Retry-After указанного интервала перед повтором.
| Лимит | Подробности |
|---|---|
| Частота запросов | Квота на ключ: по умолчанию 120 запросов за минуту UTC, настройка от 1 до 6000 в Настройки → API. Все авторизованные чтения и записи v1, включая идемпотентные повторы и ошибки доступа или валидации после аутентификации, делят квоту между доменами, проектами и процессами. Недействительные ключи, маршруты консоли и публичная оплата её не расходуют. |
| Заголовки лимитов | Авторизованные ответы v1 включают X-RateLimit-Limit, X-RateLimit-Remaining и X-RateLimit-Reset - Unix-секунды следующей границы минуты UTC. Превышение возвращает JSON 429 rate_limit_exceeded и Retry-After в целых секундах. Жди не меньше этого времени и добавляй случайную задержку повтора. Фиксированные окна допускают всплески на границах минут; это не гарантия запросов в секунду. |
| Тело запроса продавца | Максимум 32 КиБ в маршрутизаторе приложения. Внешний сервер может отклонить слишком большой запрос до формирования JSON-ошибки. |
| Список счетов | limit по умолчанию 50, допустимо 1–100; offset - 0–1 000 000. Поиск до 100 символов. Результаты от новых к старым, с метаданными total/has_more. |
| Способы магазина | До 64 выбранных активов на магазин: хватит для 30 нативных сетей и ограниченного каталога проверенных токенов. Создание счёта также зависит от политики проекта, возможностей сканера и готового кошелька сети с резервной копией. |
| Поиск токенов | Лимит кандидатов по умолчанию 50, допустимо 1–100. Результаты поиска не становятся платёжными активами до успешной ончейн-проверки. |
| Зарегистрированные токены проекта | До 20 постоянных токен-активов на проект. Уже зарегистрированный актив можно использовать повторно без нового места. |
| Идемпотентность | Обязательна при создании счёта. 1–128 видимых ASCII-символов без пробелов; ключи уникальны в рамках магазина, повтор требует исходного ключа доступа и точного исходного тела. |
| Метаданные | Только JSON-объект: не более 4 096 байт в закодированном виде и пяти уровней вложенности. |
| Уведомления | Публичный HTTPS URL до 2 048 байт. Тела запросов уведомлений ограничены 256 КиБ; сохраняемые тела событий счёта - 64 КиБ с ограниченными историями платежей. |
| Материалы платёжной страницы | QR SVG имеют private и no-store, потому что недоплата меняет точный остаток. Версионные PNG-логотипы публично кешируются на год и неизменяемы. |
| Внешний сервер API | Управляемые запросы к API имеют тайм-аут чтения 30 секунд. Задавай вызывающим приложениям явные тайм-ауты короче общего лимита их задачи. |
| Ошибки без JSON | Неверные UUID или параметры запроса, методы и ограничение 32 КиБ могут вернуть текст фреймворка или пустой ответ. Неизвестные пути /v1 сейчас возвращают 404 с HTML консоли; проверяй статус и Content-Type до разбора. |
Справочник ошибок
| HTTP | Код ошибки | Значение |
|---|---|---|
| 400 | invalid_reconciliation_action | Недопустимый статус исключения, причина, поисковый запрос или фильтр страницы истории. |
| 500 | reconciliation_unavailable | Не удалось загрузить очередь исключений или доказательства. Повтори чтение с увеличивающейся задержкой. |
| 402 | billing_required | Для каждого нового счёта нужны проверенный связанный аккаунт оплаты и действующее разрешение. Недостаток предоплаченного баланса не блокирует создание и входящие платежи: вместо этого приостанавливаются IPN, вебхуки и Sweep, а комиссии продолжают начисляться. Создание блокируется для приостановленных аккаунтов, истёкшей или неверной проверки оплаты, недоступного сервиса баланса или неразрешённой фиатной базы счёта. Комиссия считается от исходной фиатной суммы, не полученной криптовалюты, наценки, переплаты или сетевых комиссий. Эта сумма и независимый пересчёт регистрируются до создания оплаты. Во время сбоев продолжаются мониторинг и получение существующих счетов. После пополнения очередь уведомлений возобновляется в пределах обычного срока хранения, а включённые правила сбора запускаются снова. Проверь Настройки → Комиссии и повтори неудачное создание с тем же Idempotency-Key. |
| 400 | invalid_json | Некорректный JSON, неизвестное поле или тело, не соответствующее документированному запросу. |
| 400 | idempotency_key_required | При создании счёта не передан Idempotency-Key. |
| 400 | invalid_idempotency_key | Ключ пуст, длиннее 128 байт, не ASCII, содержит пробельный или управляющий символ. |
| 400 | invalid_payment_request | Не прошла проверка поля или выбранного активного способа. Точную причину ищи в error.message и error.details.payment_methods (PaymentMethodIssue[]). SDK 2.4.0+ даёт безопасные полезные описания исключений и методы разбора причин; старые PHP SDK предоставляют getApiMessage(). |
| 400 | invalid_invoice_status | Фильтр списка содержит статус вне шести документированных состояний счёта. |
| 400 | invalid_callback_url | Действующий IPN-адрес не прошёл проверку HTTPS, публичного адреса, DNS или SSRF. |
| 400 | invalid_wallet_request | Некорректные данные подготовки кошелька или адреса. |
| 400 | invalid_token_asset | Некорректна сеть токена, запрос кандидатов, CoinGecko ID, метаданные каталога или контракт/mint. |
| 401 | authentication_required | Bearer-токен отсутствует, неверно оформлен, отключён, заменён или неизвестен. |
| 403 | source_ip_denied | IP-ограничение ключа не включает точный публичный адрес источника запроса. |
| 403 | source_ip_not_allowed | Ограничение IP хоста запрещает этого клиента. Администратор управляет списками активных хостов в Настройки → Система; они действуют вместе с IP-ограничениями ключа. |
| 503 | source_access_unavailable | Проверка доступа к хосту временно недоступна. Повтори позже; при сбое ограничения закрывают доступ. |
| 403 / 409 / 500 | merchant_api_access_denied | Ошибка авторизации: права или область проекта могут дать 403, отключённый проект/магазин - 409, сбой системы авторизации - 500. Кошельки приёма оператора доступны только панели оператора, не API-ключам продавца или MCP, даже при старом явном разрешении проекта. |
| 403 | project_access_denied | Повторная проверка в транзакции создания обнаружила, что ключ больше не имеет доступа к проекту. |
| 404 | invoice_not_found | В разрешённом проекте нет счёта с этим публичным ID либо страница оплаты не может его показать. |
| 404 | payment_resource_not_found | Проект, магазин, актив или кошелёк, нужный для подготовки счёта, больше не существует. |
| 404 | token_candidate_not_found | Проект недоступен или токен больше не присутствует в текущем сопоставленном каталоге поиска. |
| 409 | idempotency_conflict | Ключ уже существует в магазине, а данные доступа или точные байты тела запроса отличаются. |
| 409 | store_unavailable | Проект или магазин отключён либо недоступен. |
| 409 | no_ready_payment_methods | Нет готового способа оплаты магазина. Читай error.message и error.details.payment_methods: chain_slug, asset_ticker и reason_code. Должны быть корректны резервная копия и активация кошелька, установленный адаптер и цена. С 6.0.6 паузы сканера, неудачные или устаревшие проверки узлов и отсутствие кворума провайдеров не блокируют создание. |
| 409 | payment_method_unavailable | Выбранный способ стал недоступен при атомарной повторной проверке создания. |
| 409 | store_payment_method_not_selected | Переопределение подтверждений магазина запрошено для актива, который сейчас не выбран этим магазином. |
| 409 | wallet_unavailable | Платёжный кошелёк стал недоступен при атомарной повторной проверке создания. |
| 409 | ipn_secret_required | Действующий IPN URL есть, но у магазина нет секрета подписи IPN. |
| 409 | payment_resource_not_ready | Нужный актив или кошелёк отключён, не имеет резервной копии, ждёт доказательства активации общего аккаунта, исчерпан или иначе не готов. |
| 409 | account_activation_unverified | Не удалось доказать активацию XRP Ledger или Stellar через настроенное число исправных mainnet-эндпоинтов: по умолчанию 2, опционально 1. Пополни именно этот аккаунт и повтори проверку. |
| 400 | invalid_monero_wallet_rpc | Некорректен HTTPS-эндпоинт, точный основной mainnet-адрес, метка или полный набор данных Digest/Basic/заголовков авторизации. |
| 404 | monero_wallet_rpc_not_found | Привязка Monero wallet-RPC к проекту не существует. |
| 409 | monero_wallet_rpc_not_ready | Не готов актив Monero, кворум двух демонов, неизменяемая привязка либо явное подтверждение резервной копии и режима view-only. |
| 409 | monero_wallet_rpc_unavailable | Создание счёта требует активной, проверенной и подтверждённой привязки Monero wallet-RPC проекта с действующими серверными данными доступа. |
| 503 | lightning_unavailable | Единственный готовый способ магазина - Lightning, но его кошелёк или котировку проверить не удалось. Повтори с тем же ключом идемпотентности. Если есть другой готовый ончейн-способ, недоступный Lightning просто исключается. |
| 422 | monero_wallet_rpc_verification_failed | Не прошла проверка точного кошелька, закрепления HTTPS, синхронизации, кворума mainnet-демонов или доказательства запрета методов шлюзом. |
| 503 | monero_wallet_rpc_failed | Внешний watch-only wallet-RPC не смог безопасно создать и повторно прочитать субадрес счёта; запасной адрес не выдумывается. |
| 409 | token_chain_not_ready | Нативный актив сети отключён, привязка каталога изменилась во время проверки либо в проекте уже зарегистрирован максимум 20 токен-активов. |
| 503 | dex_price_unavailable | DEX-провайдер недоступен, занят, ограничивает запросы либо вернул устаревшие или некорректные данные. Повтори через минуту; фиксированная цена остаётся доступной. |
| 422 | invalid_dex_price | Неверное сочетание режима цены или выбранный пул не даёт подходящую цену для точного контракта. Выбери другой пул или фиксированную цену USD. |
| 422 | token_verification_failed | Все подходящие узлы не прошли проверку сети, кода контракта, десятичных знаков, запроса баланса или mint. |
| 422 | invalid_store_confirmation_policy | Переопределение магазина недоступно для этого режима финальности, выходит за возвращённые границы сети или запрашивает неподдерживаемый приём с нулём подтверждений. |
| 409 | invoice_not_payable | Счёт на странице оплаты завершён или срок оплаты истёк. |
| 409 | invoice_payment_method_locked | Действительный платёж уже выбрал другой актив; продолжай с active_payment_method_id. |
| 409 | payment_method_not_payable | Выбранный способ завершён или больше не принимает платежи. |
| 422 | payment_qr_unavailable | Платёжный запрос слишком велик для SVG QR-кода. |
| 503 | payment_rates_unavailable | Нет свежей надёжной котировки ни для одного готового способа оплаты. |
| 500 | authentication_unavailable | Bearer-аутентификация не смогла безопасно прочитать или проверить сохранённый ключ. |
| 429 | rate_limit_exceeded | Ключ исчерпал квоту текущей минуты UTC. Подожди не менее Retry-After секунд; повторяй создание счёта с тем же ключом идемпотентности. |
| 500 | database_error / internal_error | Временная серверная ошибка; безопасно повтори с тем же ключом идемпотентности. |
Обзор API
Выбери эндпоинт, чтобы увидеть поля, примеры и ответ.
Счета
POSTСоздать счёт/v1/projects/{project_id}/stores/{store_id}/invoicesGETСписок счетов/v1/projects/{project_id}/invoicesGETПолучить счёт/v1/projects/{project_id}/invoices/{invoice_id}GETСписок платежей счёта/v1/projects/{project_id}/invoices/{invoice_id}/paymentsСпособы оплаты
GETСписок платёжных активов проекта/v1/projects/{project_id}/payment-assetsPUTОбновить политику активов проекта/v1/projects/{project_id}/payment-assets/{asset_id}GETПосмотреть кандидатов в платёжные токены/v1/projects/{project_id}/payment-token-candidatesPOSTПроверить и зарегистрировать токен/v1/projects/{project_id}/payment-token-assetsGETНайти DEX-пулы пользовательского токена/v1/projects/{project_id}/payment-token-dex-poolsPOSTДобавить пользовательский токен или изменить цену/v1/projects/{project_id}/payment-token-assets/customGETСписок способов оплаты магазина/v1/projects/{project_id}/stores/{store_id}/payment-assetsPUTЗаменить способы оплаты магазина/v1/projects/{project_id}/stores/{store_id}/payment-assetsPUTНастроить подтверждения магазина/v1/projects/{project_id}/stores/{store_id}/payment-assets/{asset_id}/confirmation-policyКошельки
GETСписок кошельков и балансов проекта/v1/projects/{project_id}/walletsСверка платежей
GETСписок исключений платежей/v1/projects/{project_id}/reconciliationGETПолучить данные для сверки/v1/projects/{project_id}/reconciliation/{invoice_id}API оператора
GETВозможности/v1/operator/capabilitiesGETСостояние/v1/operator/healthGETСписок продавцов/v1/operator/merchantsPOSTСоздать продавца/v1/operator/merchantsGETПолучить продавца/v1/operator/merchants/{merchant_id}POSTОбновить продавца/v1/operator/merchants/{merchant_id}GETСписок пользователей/v1/operator/merchants/{merchant_id}/usersPOSTСоздать пользователя/v1/operator/merchants/{merchant_id}/usersGETПолучить пользователя/v1/operator/merchants/{merchant_id}/users/{user_id}POSTОбновить пользователя/v1/operator/merchants/{merchant_id}/users/{user_id}POSTУстановить пароль пользователя/v1/operator/merchants/{merchant_id}/users/{user_id}/passwordPOSTОтозвать сеансы пользователя/v1/operator/merchants/{merchant_id}/users/{user_id}/revoke-sessionsGETСписок приглашений/v1/operator/merchants/{merchant_id}/invitationsPOSTСоздать приглашение/v1/operator/merchants/{merchant_id}/invitationsGETПолучить приглашение/v1/operator/invitations/{invitation_id}POSTПовторить приглашение/v1/operator/invitations/{invitation_id}/resendPOSTОтозвать приглашение/v1/operator/invitations/{invitation_id}/revokeGETПолучить баланс оплаты/v1/operator/merchants/{merchant_id}/creditsGETСписок операций баланса/v1/operator/merchants/{merchant_id}/credits/ledgerPOSTСкорректировать баланс/v1/operator/merchants/{merchant_id}/credits/adjustmentsGETСписок пополнений/v1/operator/merchants/{merchant_id}/topupsPOSTСоздать пополнение/v1/operator/merchants/{merchant_id}/topupsGETПолучить пополнение/v1/operator/merchants/{merchant_id}/topups/{topup_id}GETОтчёты/v1/operator/reportsGETЖурнал аудита/v1/operator/auditGETСписок событий/v1/operator/eventsGETСписок вебхуков/v1/operator/webhooksPOSTСоздать вебхук/v1/operator/webhooksPOSTОбновить вебхук/v1/operator/webhooks/{webhook_id}POSTСменить секрет вебхука/v1/operator/webhooks/{webhook_id}/rotateGETСписок доставок вебхуков/v1/operator/webhooks/{webhook_id}/deliveriesGETСписок проектов/v1/operator/merchants/{merchant_id}/projectsPOSTСоздать проект/v1/operator/merchants/{merchant_id}/projectsGETПолучить проект/v1/operator/merchants/{merchant_id}/projects/{project_id}POSTОбновить проект/v1/operator/merchants/{merchant_id}/projects/{project_id}GETСписок магазинов/v1/operator/merchants/{merchant_id}/projects/{project_id}/storesPOSTСоздать магазин/v1/operator/merchants/{merchant_id}/projects/{project_id}/storesGETПолучить магазин/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}POSTОбновить магазин/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}GETПолучить оформление магазина/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/checkout-appearancePOSTОбновить оформление магазина/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/checkout-appearanceGETСписок платёжных активов магазина/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/payment-assetsPOSTОбновить платёжные активы магазина/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/payment-assetsGETСписок вебхуков магазина/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/webhooksPOSTСоздать вебхук магазина/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/webhooksPOSTОбновить вебхук магазина/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/webhooks/{webhook_id}GETСписок счетов/v1/operator/merchants/{merchant_id}/projects/{project_id}/invoicesGETПолучить счёт/v1/operator/merchants/{merchant_id}/projects/{project_id}/invoices/{invoice_id}GETСписок кошельков/v1/operator/merchants/{merchant_id}/projects/{project_id}/walletsGETСписок адресов кошелька/v1/operator/merchants/{merchant_id}/projects/{project_id}/wallets/{wallet_id}/addressesGETСписок ключей API продавца/v1/operator/merchants/{merchant_id}/api-credentialsPOSTСоздать ключ API продавца/v1/operator/merchants/{merchant_id}/api-credentialsPOSTОбновить ключ API продавца/v1/operator/merchants/{merchant_id}/api-credentials/{credential_id}POSTСменить ключ API продавца/v1/operator/merchants/{merchant_id}/api-credentials/{credential_id}/rotatePOSTОтозвать ключ API продавца/v1/operator/merchants/{merchant_id}/api-credentials/{credential_id}/revokePOSTПроверить токен приглашения/v1/onboarding/invitations/checkPOSTПринять приглашение или сброс пароля/v1/onboarding/invitations/acceptСтраница оплаты
GETОболочка платёжной страницы/GETРазмещённая платёжная страница/invoice/{invoice_id}GETБезопасные данные счёта для оплаты/checkout-api/invoices/{invoice_id}GETПредпросмотр оплаты магазина/invoice/preview/{project_id}GETДанные предпросмотра оплаты/checkout-api/previews/{project_id}GETИзображение платёжной страницы магазина/checkout-api/invoices/{invoice_id}/appearance-images/{kind}/{revision}/image.pngGETИзображение предпросмотра магазина/checkout-api/previews/{project_id}/stores/{store_id}/appearance-images/{kind}/{revision}/image.pngGETЛоготип предпросмотра с номером версии/checkout-api/previews/{project_id}/logo/{revision}/image.pngGETQR-код оплаты/checkout-api/invoices/{invoice_id}/payment-methods/{intent_id}/qr.svgGETЛоготип оплаты с номером версии/checkout-api/invoices/{invoice_id}/logo/{revision}/image.pngСервис
GETОбнаружение сервиса API/GETСостояние сервиса/healthzGETВозможности/v1/operator/capabilitiesТолько чтение
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно health.read; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/capabilities" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/capabilities", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/capabilities");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/capabilities",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"api_version": "v1",
"operator_version": "7.4.0",
"scopes": [
"health.read"
],
"all_merchants": false,
"merchant_ids": [
"11111111-1111-4111-8111-111111111111"
],
"onboarding": [
"direct",
"invitation"
],
"write_methods": [
"POST"
],
"idempotency_required": true
}GETСостояние/v1/operator/healthТолько чтение
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно health.read; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/health" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/health", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/health");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/health",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"version": "7.4.0",
"nodes": []
}GETСписок продавцов/v1/operator/merchantsТолько чтение
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно merchants.read; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| page, search | query · optional | Страницы с 1, по 25 записей. Поиск поддерживается для продавцов, пользователей, проектов, магазинов, кошельков, ключей и вебхуков; списки событий используют свои фильтры. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POSTСоздать продавца/v1/operator/merchantsЧтение и запись
Атомарно создаёт размещённого продавца и первого администратора: напрямую с паролем или по приглашению.
- Нужно merchants.write; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- До отправки сохрани уникальный Idempotency-Key и точное тело. Повторы не выполняют уже зафиксированное действие. Секреты при повторе не возвращаются; если ответ с секретом потерян, проверь созданный ресурс и явно смени или перевыпусти секрет. 409 operator_request_in_progress может означать прерванный запрос с неизвестным результатом: проверь ресурс и аудит, не повторяй вслепую с новым ключом.
- Нужен доступ ко всем продавцам. Явное изменение комиссии также требует fees.write; ненулевой starting_credit - credits.write; регистрация по приглашению - invitations.write. Без автоматического входа, обхода Basic Auth или повторного начисления при повторе.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | обязательно | 16–128 букв, цифр, -, _ или точек; сохраняется для этой операции |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| name, email | string · required | Название продавца и глобально уникальный email первого администратора. |
| onboarding | direct | invitation · required | direct требует password и не отправляет приглашение по email. invitation не передаёт password. |
| password | string · direct only | 12–128 символов, не более 512 байт UTF-8; не возвращается и не отправляется по email. Для временного пароля используй require_password_change. |
| require_password_change | boolean · default false | Требует новый пароль при первом входе. Каждый созданный напрямую аккаунт должен подтвердить условия хранения кошельков у оператора. |
| currency | fiat code · optional | Валюта предоплаченного аккаунта; по умолчанию региональная, позже не меняется. |
| fee_bps | integer · optional | 0–10000; 100 означает 1%. Если не указано, используется значение оператора. Нужно fees.write. |
| starting_credit | decimal string · default 0 | Точное разовое локальное начисление. Ненулевое требует credits.write. Не пополняет баланс установки оператора. |
| external_id | string · optional | Уникальная ссылка интеграции, 1–120 символов. |
| default_timezone | IANA timezone · optional | По умолчанию региональный часовой пояс установки. |
| send_invitation_email | boolean · default false | Только для приглашений. Нужен настроенный SMTP; ответ отличает приём почтовым relay от создания аккаунта. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Example shop",
"email": "admin@example.test",
"onboarding": "direct",
"password": "REPLACE_WITH_A_UNIQUE_TEMPORARY_PASSWORD",
"require_password_change": true,
"currency": "EUR",
"starting_credit": "0",
"external_id": "customer-1042"
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"name": "Example shop",
"email": "admin@example.test",
"onboarding": "direct",
"password": "REPLACE_WITH_A_UNIQUE_TEMPORARY_PASSWORD",
"require_password_change": true,
"currency": "EUR",
"starting_credit": "0",
"external_id": "customer-1042"
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"name": "Example shop",
"email": "admin@example.test",
"onboarding": "direct",
"password": "REPLACE_WITH_A_UNIQUE_TEMPORARY_PASSWORD",
"require_password_change": true,
"currency": "EUR",
"starting_credit": "0",
"external_id": "customer-1042"
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"name": "Example shop",
"email": "admin@example.test",
"onboarding": "direct",
"password": "REPLACE_WITH_A_UNIQUE_TEMPORARY_PASSWORD",
"require_password_change": true,
"currency": "EUR",
"starting_credit": "0",
"external_id": "customer-1042"
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 201 или 200 application/json
{
"merchant_id": "11111111-1111-4111-8111-111111111111",
"user_id": "22222222-2222-4222-8222-222222222222",
"merchant": {
"id": "11111111-1111-4111-8111-111111111111",
"name": "Example shop",
"currency": "EUR",
"balance": "0"
},
"onboarding": "direct",
"access_link": null,
"email_delivery": {
"status": "not_requested"
},
"custody_acceptance_required": true
}GETПолучить продавца/v1/operator/merchants/{merchant_id}Только чтение
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно merchants.read; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"id": "11111111-1111-4111-8111-111111111111",
"name": "Example shop",
"currency": "EUR",
"balance": "10",
"fee_bps": 300,
"enabled": true,
"payments_paused": false,
"external_id": "customer-1042"
}POSTОбновить продавца/v1/operator/merchants/{merchant_id}Чтение и запись
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно merchants.write; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- До отправки сохрани уникальный Idempotency-Key и точное тело. Повторы не выполняют уже зафиксированное действие. Секреты при повторе не возвращаются; если ответ с секретом потерян, проверь созданный ресурс и явно смени или перевыпусти секрет. 409 operator_request_in_progress может означать прерванный запрос с неизвестным результатом: проверь ресурс и аудит, не повторяй вслепую с новым ключом.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | обязательно | 16–128 букв, цифр, -, _ или точек; сохраняется для этой операции |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| name, enabled, payments_paused, fee_bps, external_id | optional fields | Отключение отзывает сеансы. payments_paused блокирует новые счета, не сканирование существующих платежей. Изменение комиссии требует fees.write и действует на будущие счета; валюту аккаунта менять нельзя. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"payments_paused": true
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"payments_paused": true
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"payments_paused": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"payments_paused": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"id": "11111111-1111-4111-8111-111111111111",
"name": "Example shop",
"currency": "EUR",
"balance": "10",
"fee_bps": 300,
"enabled": true,
"payments_paused": false
}GETСписок пользователей/v1/operator/merchants/{merchant_id}/usersТолько чтение
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно users.read; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| page, search | query · optional | Страницы с 1, по 25 записей. Поиск поддерживается для продавцов, пользователей, проектов, магазинов, кошельков, ключей и вебхуков; списки событий используют свои фильтры. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POSTСоздать пользователя/v1/operator/merchants/{merchant_id}/usersЧтение и запись
Добавь администратора продавца или пользователя с доступом к выбранным проектам.
- Нужно users.write; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- До отправки сохрани уникальный Idempotency-Key и точное тело. Повторы не выполняют уже зафиксированное действие. Секреты при повторе не возвращаются; если ответ с секретом потерян, проверь созданный ресурс и явно смени или перевыпусти секрет. 409 operator_request_in_progress может означать прерванный запрос с неизвестным результатом: проверь ресурс и аудит, не повторяй вслепую с новым ключом.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | обязательно | 16–128 букв, цифр, -, _ или точек; сохраняется для этой операции |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| email, display_name | strings · required | Email уникален в пределах установки. |
| onboarding, password, require_password_change, send_invitation_email | same as merchant creation | Создание приглашения дополнительно требует invitations.write. |
| access_level | admin | projects · default admin | admin - администратор только этого продавца, не установки или оператора. |
| project_ids | UUID[] | Только проекты этого продавца. Для ограниченного доступа нужно выбрать проекты; доступ между арендаторами невозможен. |
| default_timezone | IANA timezone · optional | Если не указано, используется региональное значение. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"email": "staff@example.test",
"display_name": "Store team",
"onboarding": "invitation",
"access_level": "projects",
"project_ids": [
"11111111-1111-4111-8111-111111111111"
],
"send_invitation_email": false
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"email": "staff@example.test",
"display_name": "Store team",
"onboarding": "invitation",
"access_level": "projects",
"project_ids": [
"11111111-1111-4111-8111-111111111111"
],
"send_invitation_email": false
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"email": "staff@example.test",
"display_name": "Store team",
"onboarding": "invitation",
"access_level": "projects",
"project_ids": [
"11111111-1111-4111-8111-111111111111"
],
"send_invitation_email": false
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"email": "staff@example.test",
"display_name": "Store team",
"onboarding": "invitation",
"access_level": "projects",
"project_ids": [
"11111111-1111-4111-8111-111111111111"
],
"send_invitation_email": false
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 201 или 200 application/json
{
"user": {
"id": "22222222-2222-4222-8222-222222222222",
"merchant_id": "11111111-1111-4111-8111-111111111111",
"email": "admin@example.test",
"display_name": "Shop administrator",
"access_level": "admin",
"project_ids": [],
"enabled": true,
"password_setup_required": false,
"custody_acceptance_required": true,
"password_change_required": true
},
"user_id": "22222222-2222-4222-8222-222222222222",
"access_link": null,
"email_delivery": {
"status": "not_requested"
},
"merchant_id": "11111111-1111-4111-8111-111111111111"
}GETПолучить пользователя/v1/operator/merchants/{merchant_id}/users/{user_id}Только чтение
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно users.read; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| user_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"id": "22222222-2222-4222-8222-222222222222",
"merchant_id": "11111111-1111-4111-8111-111111111111",
"email": "admin@example.test",
"display_name": "Shop administrator",
"access_level": "admin",
"project_ids": [],
"enabled": true,
"password_setup_required": false,
"custody_acceptance_required": true,
"password_change_required": true
}POSTОбновить пользователя/v1/operator/merchants/{merchant_id}/users/{user_id}Чтение и запись
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно users.write; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- До отправки сохрани уникальный Idempotency-Key и точное тело. Повторы не выполняют уже зафиксированное действие. Секреты при повторе не возвращаются; если ответ с секретом потерян, проверь созданный ресурс и явно смени или перевыпусти секрет. 409 operator_request_in_progress может означать прерванный запрос с неизвестным результатом: проверь ресурс и аудит, не повторяй вслепую с новым ключом.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | обязательно | 16–128 букв, цифр, -, _ или точек; сохраняется для этой операции |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| user_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| email, display_name, enabled, access_level, project_ids, default_timezone | optional fields | Меняет переданные поля; защита последнего администратора сохраняется. Для паролей есть отдельная операция users.security. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"display_name": "Store manager"
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"display_name": "Store manager"
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"display_name": "Store manager"
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"display_name": "Store manager"
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"id": "22222222-2222-4222-8222-222222222222",
"merchant_id": "11111111-1111-4111-8111-111111111111",
"email": "admin@example.test",
"display_name": "Shop administrator",
"access_level": "admin",
"project_ids": [],
"enabled": true,
"password_setup_required": false,
"custody_acceptance_required": true,
"password_change_required": true
}POSTУстановить пароль пользователя/v1/operator/merchants/{merchant_id}/users/{user_id}/passwordЧтение и запись
Устанавливает пароль размещённого аккаунта и отзывает сеансы. Существующий TOTP сохраняется.
- Нужно users.security; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- До отправки сохрани уникальный Idempotency-Key и точное тело. Повторы не выполняют уже зафиксированное действие. Секреты при повторе не возвращаются; если ответ с секретом потерян, проверь созданный ресурс и явно смени или перевыпусти секрет. 409 operator_request_in_progress может означать прерванный запрос с неизвестным результатом: проверь ресурс и аудит, не повторяй вслепую с новым ключом.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | обязательно | 16–128 букв, цифр, -, _ или точек; сохраняется для этой операции |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| user_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| password | string · required | Меняет пароль и отзывает сеансы, сохраняя TOTP. Нужно users.security. |
| require_password_change | boolean · default true | Пользователь должен задать собственный пароль при следующем успешном входе. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID/password" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"password": "REPLACE_WITH_A_NEW_UNIQUE_PASSWORD",
"require_password_change": true
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"password": "REPLACE_WITH_A_NEW_UNIQUE_PASSWORD",
"require_password_change": true
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID/password", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"password": "REPLACE_WITH_A_NEW_UNIQUE_PASSWORD",
"require_password_change": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID/password");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"password": "REPLACE_WITH_A_NEW_UNIQUE_PASSWORD",
"require_password_change": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID/password",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"user_id": "11111111-1111-4111-8111-111111111111",
"sessions_revoked": true,
"totp_preserved": true
}POSTОтозвать сеансы пользователя/v1/operator/merchants/{merchant_id}/users/{user_id}/revoke-sessionsЧтение и запись
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно users.security; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- До отправки сохрани уникальный Idempotency-Key и точное тело. Повторы не выполняют уже зафиксированное действие. Секреты при повторе не возвращаются; если ответ с секретом потерян, проверь созданный ресурс и явно смени или перевыпусти секрет. 409 operator_request_in_progress может означать прерванный запрос с неизвестным результатом: проверь ресурс и аудит, не повторяй вслепую с новым ключом.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | обязательно | 16–128 букв, цифр, -, _ или точек; сохраняется для этой операции |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| user_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID/revoke-sessions" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID/revoke-sessions", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID/revoke-sessions");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID/revoke-sessions",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"user_id": "11111111-1111-4111-8111-111111111111",
"sessions_revoked": true,
"totp_preserved": true
}GETСписок приглашений/v1/operator/merchants/{merchant_id}/invitationsТолько чтение
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно invitations.read; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| page, search | query · optional | Страницы с 1, по 25 записей. Поиск поддерживается для продавцов, пользователей, проектов, магазинов, кошельков, ключей и вебхуков; списки событий используют свои фильтры. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/invitations" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/invitations", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/invitations");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/invitations",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POSTСоздать приглашение/v1/operator/merchants/{merchant_id}/invitationsЧтение и запись
Создаёт или заменяет одноразовую ссылку приглашения либо сброса пароля.
- Нужно invitations.write; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- До отправки сохрани уникальный Idempotency-Key и точное тело. Повторы не выполняют уже зафиксированное действие. Секреты при повторе не возвращаются; если ответ с секретом потерян, проверь созданный ресурс и явно смени или перевыпусти секрет. 409 operator_request_in_progress может означать прерванный запрос с неизвестным результатом: проверь ресурс и аудит, не повторяй вслепую с новым ключом.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | обязательно | 16–128 букв, цифр, -, _ или точек; сохраняется для этой операции |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| user_id, send_email | UUID, boolean | Выдаёт или заменяет одноразовую ссылку существующему аккаунту. Активированные пользователи получают ссылку сброса на час; нужно users.security. |
| new user fields | alternative to user_id | Для приглашённого пользователя используй email, display_name, access_level и project_ids; нужно users.write. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/invitations" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"user_id": "11111111-1111-4111-8111-111111111111",
"send_email": false
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"user_id": "11111111-1111-4111-8111-111111111111",
"send_email": false
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/invitations", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"user_id": "11111111-1111-4111-8111-111111111111",
"send_email": false
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/invitations");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"user_id": "11111111-1111-4111-8111-111111111111",
"send_email": false
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/invitations",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 201 или 200 application/json
{
"user_id": "22222222-2222-4222-8222-222222222222",
"email": "admin@example.test",
"kind": "invitation",
"expires_at": "2026-10-03T12:00:00Z",
"url": "https://merchant.example.com/account-access.html#token=YOUR_PRIVATE_INVITATION_TOKEN"
}GETПолучить приглашение/v1/operator/invitations/{invitation_id}Только чтение
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно invitations.read; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| invitation_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"id": "44444444-4444-4444-8444-444444444444",
"user_id": "22222222-2222-4222-8222-222222222222",
"merchant_id": "11111111-1111-4111-8111-111111111111",
"kind": "invitation",
"status": "pending",
"created_at": "2026-10-01T12:00:00Z",
"expires_at": "2026-10-03T12:00:00Z"
}POSTПовторить приглашение/v1/operator/invitations/{invitation_id}/resendЧтение и запись
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно invitations.write; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- До отправки сохрани уникальный Idempotency-Key и точное тело. Повторы не выполняют уже зафиксированное действие. Секреты при повторе не возвращаются; если ответ с секретом потерян, проверь созданный ресурс и явно смени или перевыпусти секрет. 409 operator_request_in_progress может означать прерванный запрос с неизвестным результатом: проверь ресурс и аудит, не повторяй вслепую с новым ключом.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | обязательно | 16–128 букв, цифр, -, _ или точек; сохраняется для этой операции |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| invitation_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| send_email | boolean · default false | Заменяет старый токен, не начисляет баланс. Возвращает новую ссылку один раз. Для уже активированного аккаунта нужно users.security. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID/resend" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"send_email": false
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"send_email": false
}`;
const response = await fetch("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID/resend", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"send_email": false
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID/resend");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"send_email": false
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID/resend",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"user_id": "22222222-2222-4222-8222-222222222222",
"email": "admin@example.test",
"kind": "invitation",
"expires_at": "2026-10-03T12:00:00Z",
"url": "https://merchant.example.com/account-access.html#token=YOUR_PRIVATE_INVITATION_TOKEN"
}POSTОтозвать приглашение/v1/operator/invitations/{invitation_id}/revokeЧтение и запись
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно invitations.write; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- До отправки сохрани уникальный Idempotency-Key и точное тело. Повторы не выполняют уже зафиксированное действие. Секреты при повторе не возвращаются; если ответ с секретом потерян, проверь созданный ресурс и явно смени или перевыпусти секрет. 409 operator_request_in_progress может означать прерванный запрос с неизвестным результатом: проверь ресурс и аудит, не повторяй вслепую с новым ключом.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | обязательно | 16–128 букв, цифр, -, _ или точек; сохраняется для этой операции |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| invitation_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID/revoke" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{}`;
const response = await fetch("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID/revoke", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID/revoke");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID/revoke",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"revoked": true,
"invitation_id": "44444444-4444-4444-8444-444444444444"
}GETПолучить баланс оплаты/v1/operator/merchants/{merchant_id}/creditsТолько чтение
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно credits.read; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"id": "11111111-1111-4111-8111-111111111111",
"name": "Example shop",
"currency": "EUR",
"balance": "10",
"fee_bps": 300,
"enabled": true,
"payments_paused": false,
"external_id": "customer-1042"
}GETСписок операций баланса/v1/operator/merchants/{merchant_id}/credits/ledgerТолько чтение
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно credits.read; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| page, q | query · optional | Страницы с 1, по 25 записей. Поиск поддерживается для продавцов, пользователей, проектов, магазинов, кошельков, ключей и вебхуков; списки событий используют свои фильтры. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits/ledger" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits/ledger", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits/ledger");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits/ledger",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POSTСкорректировать баланс/v1/operator/merchants/{merchant_id}/credits/adjustmentsЧтение и запись
Добавляет обоснованное начисление или корректировку в журнал предоплаченного баланса продавца.
- Нужно credits.write; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- До отправки сохрани уникальный Idempotency-Key и точное тело. Повторы не выполняют уже зафиксированное действие. Секреты при повторе не возвращаются; если ответ с секретом потерян, проверь созданный ресурс и явно смени или перевыпусти секрет. 409 operator_request_in_progress может означать прерванный запрос с неизвестным результатом: проверь ресурс и аудит, не повторяй вслепую с новым ключом.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | обязательно | 16–128 букв, цифр, -, _ или точек; сохраняется для этой операции |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| amount | signed decimal string · required | Положительное начисление или отрицательная корректировка, до шести знаков после запятой в валюте баланса продавца. Не ончейн-перевод. |
| note | string · required | Причина сохраняется в журнале только для добавления. |
| request_id | UUID · required | Сохраняй вместе с суммой и причиной в дополнение к HTTP Idempotency-Key. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits/adjustments" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"amount": "10.00",
"note": "Promotional credit",
"request_id": "11111111-1111-4111-8111-111111111111"
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"amount": "10.00",
"note": "Promotional credit",
"request_id": "11111111-1111-4111-8111-111111111111"
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits/adjustments", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"amount": "10.00",
"note": "Promotional credit",
"request_id": "11111111-1111-4111-8111-111111111111"
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits/adjustments");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"amount": "10.00",
"note": "Promotional credit",
"request_id": "11111111-1111-4111-8111-111111111111"
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits/adjustments",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"balance": "25"
}GETСписок пополнений/v1/operator/merchants/{merchant_id}/topupsТолько чтение
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно topups.read; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| page, search | query · optional | Страницы с 1, по 25 записей. Поиск поддерживается для продавцов, пользователей, проектов, магазинов, кошельков, ключей и вебхуков; списки событий используют свои фильтры. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POSTСоздать пополнение/v1/operator/merchants/{merchant_id}/topupsЧтение и запись
Создаёт оплату для пополнения баланса; не отмечай её оплаченной вручную.
- Нужно topups.write; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- До отправки сохрани уникальный Idempotency-Key и точное тело. Повторы не выполняют уже зафиксированное действие. Секреты при повторе не возвращаются; если ответ с секретом потерян, проверь созданный ресурс и явно смени или перевыпусти секрет. 409 operator_request_in_progress может означать прерванный запрос с неизвестным результатом: проверь ресурс и аудит, не повторяй вслепую с новым ключом.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | обязательно | 16–128 букв, цифр, -, _ или точек; сохраняется для этой операции |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| amount | decimal string · required | Не менее одной единицы валюты баланса продавца. Нужен готовый магазин приёма оператора. |
| request_id | UUID · required | Сохраняй между повторами. Если счёт уже создан, возвращается он же. Баланс начисляется только после обнаруженного окончательного зачисления. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"amount": "25.00",
"request_id": "11111111-1111-4111-8111-111111111111"
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"amount": "25.00",
"request_id": "11111111-1111-4111-8111-111111111111"
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"amount": "25.00",
"request_id": "11111111-1111-4111-8111-111111111111"
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"amount": "25.00",
"request_id": "11111111-1111-4111-8111-111111111111"
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 201 или 200 application/json
{
"id": "44444444-4444-4444-8444-444444444444",
"invoice_id": "33333333-3333-4333-8333-333333333333",
"checkout_url": "https://pay.example.com/invoice/33333333-3333-4333-8333-333333333333"
}GETПолучить пополнение/v1/operator/merchants/{merchant_id}/topups/{topup_id}Только чтение
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно topups.read; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| topup_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups/YOUR_TOPUP_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups/YOUR_TOPUP_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups/YOUR_TOPUP_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups/YOUR_TOPUP_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"id": "44444444-4444-4444-8444-444444444444",
"merchant_id": "11111111-1111-4111-8111-111111111111",
"invoice_id": "33333333-3333-4333-8333-333333333333",
"amount": "25",
"currency": "EUR",
"status": "pending",
"invoice_status": "new",
"checkout_url": "https://pay.example.com/invoice/33333333-3333-4333-8333-333333333333"
}GETОтчёты/v1/operator/reportsТолько чтение
Финансовый обзор оператора. Нужен доступ ко всем размещённым продавцам.
- Нужно reports.read; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| period, start, end, currency, timezone, merchant_id | query · optional | Финансовые фильтры. period по умолчанию last30; для custom укажи start/end в YYYY-MM-DD. Только ключи с доступом ко всем продавцам. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/reports" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/reports", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/reports");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/reports",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"summary": {
"fees": "10",
"costs": "3",
"margin": "7",
"credits": "25",
"pending": 0,
"missing_rates": 0
},
"merchants": [],
"filters": {
"period": "last30",
"currency": "EUR",
"timezone": "UTC"
},
"basis": "first_settlement_latest_net_fees"
}GETЖурнал аудита/v1/operator/auditТолько чтение
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно audit.read; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id, event_type / search | query · optional | Фильтр разрешённого продавца, точного типа события для events или текста действия для audit. События хранятся 30 дней. |
| page, search | query · optional | Страницы с 1, по 25 записей. Поиск поддерживается для продавцов, пользователей, проектов, магазинов, кошельков, ключей и вебхуков; списки событий используют свои фильтры. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/audit" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/audit", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/audit");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/audit",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}GETСписок событий/v1/operator/eventsТолько чтение
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно events.read; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id, event_type / search | query · optional | Фильтр разрешённого продавца, точного типа события для events или текста действия для audit. События хранятся 30 дней. |
| page, search | query · optional | Страницы с 1, по 25 записей. Поиск поддерживается для продавцов, пользователей, проектов, магазинов, кошельков, ключей и вебхуков; списки событий используют свои фильтры. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/events" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/events", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/events");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/events",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}GETСписок вебхуков/v1/operator/webhooksТолько чтение
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно events.read; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| page, search | query · optional | Страницы с 1, по 25 записей. Поиск поддерживается для продавцов, пользователей, проектов, магазинов, кошельков, ключей и вебхуков; списки событий используют свои фильтры. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/webhooks" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/webhooks", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/webhooks");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/webhooks",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POSTСоздать вебхук/v1/operator/webhooksЧтение и запись
Подписка на будущие события жизненного цикла оператора. Не платёжный вебхук магазина.
- Нужны webhooks.write + events.read; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- До отправки сохрани уникальный Idempotency-Key и точное тело. Повторы не выполняют уже зафиксированное действие. Секреты при повторе не возвращаются; если ответ с секретом потерян, проверь созданный ресурс и явно смени или перевыпусти секрет. 409 operator_request_in_progress может означать прерванный запрос с неизвестным результатом: проверь ресурс и аудит, не повторяй вслепую с новым ключом.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | обязательно | 16–128 букв, цифр, -, _ или точек; сохраняется для этой операции |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| url | public HTTPS URL · required | Без данных доступа, частных IP и перенаправлений. DNS/IP проверяются заново при доставке. |
| events | string[] · required | Выбирай события жизненного цикла из руководства оператора, не уведомления счетов. |
| merchant_ids | UUID[] · optional | Пустой список означает всех продавцов, разрешённых ключу. Текущие ограничения области проверяются повторно. |
| enabled | boolean · default true | Приостановленные эндпоинты сохраняют очередь; повторное включение возобновляет действующие сохранённые доставки. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/webhooks" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://shop.example.test/operator-events",
"events": [
"merchant.created",
"topup.settled"
],
"merchant_ids": [],
"enabled": true
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"url": "https://shop.example.test/operator-events",
"events": [
"merchant.created",
"topup.settled"
],
"merchant_ids": [],
"enabled": true
}`;
const response = await fetch("https://api.example.com/v1/operator/webhooks", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"url": "https://shop.example.test/operator-events",
"events": [
"merchant.created",
"topup.settled"
],
"merchant_ids": [],
"enabled": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/webhooks");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"url": "https://shop.example.test/operator-events",
"events": [
"merchant.created",
"topup.settled"
],
"merchant_ids": [],
"enabled": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/webhooks",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 201 или 200 application/json
{
"id": "11111111-1111-4111-8111-111111111111",
"signing_secret": "wco_whsec_EXAMPLE_ONLY_SAVE_ONCE"
}POSTОбновить вебхук/v1/operator/webhooks/{webhook_id}Чтение и запись
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужны webhooks.write + events.read; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- До отправки сохрани уникальный Idempotency-Key и точное тело. Повторы не выполняют уже зафиксированное действие. Секреты при повторе не возвращаются; если ответ с секретом потерян, проверь созданный ресурс и явно смени или перевыпусти секрет. 409 operator_request_in_progress может означать прерванный запрос с неизвестным результатом: проверь ресурс и аудит, не повторяй вслепую с новым ключом.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | обязательно | 16–128 букв, цифр, -, _ или точек; сохраняется для этой операции |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| webhook_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| url | public HTTPS URL · required | Без данных доступа, частных IP и перенаправлений. DNS/IP проверяются заново при доставке. |
| events | string[] · required | Выбирай события жизненного цикла из руководства оператора, не уведомления счетов. |
| merchant_ids | UUID[] · optional | Пустой список означает всех продавцов, разрешённых ключу. Текущие ограничения области проверяются повторно. |
| enabled | boolean · default true | Приостановленные эндпоинты сохраняют очередь; повторное включение возобновляет действующие сохранённые доставки. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://shop.example.test/operator-events",
"events": [
"topup.settled"
],
"merchant_ids": [],
"enabled": false
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"url": "https://shop.example.test/operator-events",
"events": [
"topup.settled"
],
"merchant_ids": [],
"enabled": false
}`;
const response = await fetch("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"url": "https://shop.example.test/operator-events",
"events": [
"topup.settled"
],
"merchant_ids": [],
"enabled": false
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"url": "https://shop.example.test/operator-events",
"events": [
"topup.settled"
],
"merchant_ids": [],
"enabled": false
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"id": "44444444-4444-4444-8444-444444444444",
"signing_secret": null
}POSTСменить секрет вебхука/v1/operator/webhooks/{webhook_id}/rotateЧтение и запись
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужны webhooks.write + events.read; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- До отправки сохрани уникальный Idempotency-Key и точное тело. Повторы не выполняют уже зафиксированное действие. Секреты при повторе не возвращаются; если ответ с секретом потерян, проверь созданный ресурс и явно смени или перевыпусти секрет. 409 operator_request_in_progress может означать прерванный запрос с неизвестным результатом: проверь ресурс и аудит, не повторяй вслепую с новым ключом.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | обязательно | 16–128 букв, цифр, -, _ или точек; сохраняется для этой операции |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| webhook_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID/rotate" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{}`;
const response = await fetch("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID/rotate", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID/rotate");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID/rotate",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"id": "11111111-1111-4111-8111-111111111111",
"signing_secret": "wco_whsec_EXAMPLE_ONLY_SAVE_ONCE"
}GETСписок доставок вебхуков/v1/operator/webhooks/{webhook_id}/deliveriesТолько чтение
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно events.read; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| webhook_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| page | query · optional | Страницы с 1, по 25 записей. Поиск поддерживается для продавцов, пользователей, проектов, магазинов, кошельков, ключей и вебхуков; списки событий используют свои фильтры. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID/deliveries" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID/deliveries", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID/deliveries");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID/deliveries",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}GETСписок проектов/v1/operator/merchants/{merchant_id}/projectsТолько чтение
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно projects.read; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| page, search | query · optional | Страницы с 1, по 25 записей. Поиск поддерживается для продавцов, пользователей, проектов, магазинов, кошельков, ключей и вебхуков; списки событий используют свои фильтры. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POSTСоздать проект/v1/operator/merchants/{merchant_id}/projectsЧтение и запись
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно projects.write; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- До отправки сохрани уникальный Idempotency-Key и точное тело. Повторы не выполняют уже зафиксированное действие. Секреты при повторе не возвращаются; если ответ с секретом потерян, проверь созданный ресурс и явно смени или перевыпусти секрет. 409 operator_request_in_progress может означать прерванный запрос с неизвестным результатом: проверь ресурс и аудит, не повторяй вслепую с новым ключом.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | обязательно | 16–128 букв, цифр, -, _ или точек; сохраняется для этой операции |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| name, slug | strings · required | Название и уникальный постоянный идентификатор проекта. Создаёт локальные кошельки штатной инициализацией проекта, не переводит средства. |
| enabled, reporting_timezone, checkout_title, checkout_description, checkout_theme, checkout_accent_color | optional | enabled по умолчанию true; рекомендуется создать приостановленным и сначала настроить магазин. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Online shop",
"slug": "online-shop",
"enabled": false
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"name": "Online shop",
"slug": "online-shop",
"enabled": false
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"name": "Online shop",
"slug": "online-shop",
"enabled": false
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"name": "Online shop",
"slug": "online-shop",
"enabled": false
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 201 или 200 application/json
{
"id": "33333333-3333-4333-8333-333333333333",
"slug": "example-shop",
"name": "Example shop",
"enabled": false,
"reporting_currency": "EUR",
"reporting_timezone": "Europe/Berlin",
"stores": []
}GETПолучить проект/v1/operator/merchants/{merchant_id}/projects/{project_id}Только чтение
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно projects.read; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| project_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"id": "33333333-3333-4333-8333-333333333333",
"slug": "example-shop",
"name": "Example shop",
"enabled": false,
"reporting_currency": "EUR",
"reporting_timezone": "Europe/Berlin",
"stores": []
}POSTОбновить проект/v1/operator/merchants/{merchant_id}/projects/{project_id}Чтение и запись
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно projects.write; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- До отправки сохрани уникальный Idempotency-Key и точное тело. Повторы не выполняют уже зафиксированное действие. Секреты при повторе не возвращаются; если ответ с секретом потерян, проверь созданный ресурс и явно смени или перевыпусти секрет. 409 operator_request_in_progress может означать прерванный запрос с неизвестным результатом: проверь ресурс и аудит, не повторяй вслепую с новым ключом.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | обязательно | 16–128 букв, цифр, -, _ или точек; сохраняется для этой операции |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| project_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| name, enabled, reporting_currency, reporting_timezone, checkout_title, checkout_description, checkout_theme, checkout_accent_color | optional | Частичное обновление. Идентификатор и принадлежность продавцу не меняются. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"enabled": true
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"enabled": true
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"enabled": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"enabled": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"id": "33333333-3333-4333-8333-333333333333",
"slug": "example-shop",
"name": "Example shop",
"enabled": false,
"reporting_currency": "EUR",
"reporting_timezone": "Europe/Berlin",
"stores": []
}GETСписок магазинов/v1/operator/merchants/{merchant_id}/projects/{project_id}/storesТолько чтение
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно projects.read; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| project_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| page, search | query · optional | Страницы с 1, по 25 записей. Поиск поддерживается для продавцов, пользователей, проектов, магазинов, кошельков, ключей и вебхуков; списки событий используют свои фильтры. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POSTСоздать магазин/v1/operator/merchants/{merchant_id}/projects/{project_id}/storesЧтение и запись
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно projects.write; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- До отправки сохрани уникальный Idempotency-Key и точное тело. Повторы не выполняют уже зафиксированное действие. Секреты при повторе не возвращаются; если ответ с секретом потерян, проверь созданный ресурс и явно смени или перевыпусти секрет. 409 operator_request_in_progress может означать прерванный запрос с неизвестным результатом: проверь ресурс и аудит, не повторяй вслепую с новым ключом.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | обязательно | 16–128 букв, цифр, -, _ или точек; сохраняется для этой операции |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| project_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| name, slug | strings · required | Название магазина и постоянный идентификатор. |
| default_currency, invoice_expiry_minutes, exchange_rate_spread_percent, underpayment_tolerance_percent | optional | Проценты передавай десятичными строками. Новые магазины наследуют оформление магазина проекта по умолчанию. |
| enabled, is_default, allow_zero_amount_invoices, allow_underpayments, allow_overpayments, allowed_chain_slugs | optional | Настраивай разрешённые активы через payment-assets; счета с нулевой суммой по умолчанию выключены. |
| ipn_enabled, default_ipn_url, default_redirect_url, default_cancel_url, redirect_automatically | optional | IPN и URL возврата проходят действующую проверку URL. Произвольные HTML/JavaScript запрещены. |
| checkout_language, embed_enabled, allowed_embed_origins, domains | optional | Используй поддерживаемый язык и активные домены нужных ролей; origin для встраивания задавай явно. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Web checkout",
"slug": "web-checkout",
"default_currency": "EUR",
"enabled": false
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"name": "Web checkout",
"slug": "web-checkout",
"default_currency": "EUR",
"enabled": false
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"name": "Web checkout",
"slug": "web-checkout",
"default_currency": "EUR",
"enabled": false
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"name": "Web checkout",
"slug": "web-checkout",
"default_currency": "EUR",
"enabled": false
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 201 или 200 application/json
{
"id": "44444444-4444-4444-8444-444444444444",
"project_id": "33333333-3333-4333-8333-333333333333",
"slug": "online",
"name": "Online shop",
"enabled": false,
"default_currency": "EUR",
"invoice_expiry_minutes": 60
}GETПолучить магазин/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}Только чтение
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно projects.read; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| project_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| store_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"id": "44444444-4444-4444-8444-444444444444",
"project_id": "33333333-3333-4333-8333-333333333333",
"slug": "online",
"name": "Online shop",
"enabled": false,
"default_currency": "EUR",
"invoice_expiry_minutes": 60
}POSTОбновить магазин/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}Чтение и запись
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно projects.write; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- До отправки сохрани уникальный Idempotency-Key и точное тело. Повторы не выполняют уже зафиксированное действие. Секреты при повторе не возвращаются; если ответ с секретом потерян, проверь созданный ресурс и явно смени или перевыпусти секрет. 409 operator_request_in_progress может означать прерванный запрос с неизвестным результатом: проверь ресурс и аудит, не повторяй вслепую с новым ключом.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | обязательно | 16–128 букв, цифр, -, _ или точек; сохраняется для этой операции |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| project_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| store_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| store fields | optional | Те же изменяемые настройки, что при создании, кроме slug. Меняются только переданные поля. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"enabled": true
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"enabled": true
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"enabled": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"enabled": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"id": "44444444-4444-4444-8444-444444444444",
"project_id": "33333333-3333-4333-8333-333333333333",
"slug": "online",
"name": "Online shop",
"enabled": false,
"default_currency": "EUR",
"invoice_expiry_minutes": 60
}GETПолучить оформление магазина/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/checkout-appearanceТолько чтение
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно projects.read; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| project_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| store_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/checkout-appearance" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/checkout-appearance", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/checkout-appearance");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/checkout-appearance",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"revision": 1,
"settings": {
"inherit_default_store": true
},
"effective": {
"title": "Pay securely"
}
}POSTОбновить оформление магазина/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/checkout-appearanceЧтение и запись
Сохраняет проверенное оформление магазина с защитой по ревизии.
- Нужно projects.write; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- До отправки сохрани уникальный Idempotency-Key и точное тело. Повторы не выполняют уже зафиксированное действие. Секреты при повторе не возвращаются; если ответ с секретом потерян, проверь созданный ресурс и явно смени или перевыпусти секрет. 409 operator_request_in_progress может означать прерванный запрос с неизвестным результатом: проверь ресурс и аудит, не повторяй вслепую с новым ключом.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | обязательно | 16–128 букв, цифр, -, _ или точек; сохраняется для этой операции |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| project_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| store_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| revision | integer · required | Сначала прочитай текущую ревизию через GET. Устаревшая ревизия отклоняется без перезаписи изменений другого редактора. |
| settings | appearance object · required | Проверенное оформление, включая inherit_default_store, бренд, вступление и завершение, размеры шрифта и видимость. Произвольные HTML/JavaScript запрещены. Байты изображений загружаются только в консоли. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/checkout-appearance" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"revision": 1,
"settings": {
"inherit_default_store": false,
"title": "Pay securely",
"theme": "light"
}
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"revision": 1,
"settings": {
"inherit_default_store": false,
"title": "Pay securely",
"theme": "light"
}
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/checkout-appearance", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"revision": 1,
"settings": {
"inherit_default_store": false,
"title": "Pay securely",
"theme": "light"
}
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/checkout-appearance");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"revision": 1,
"settings": {
"inherit_default_store": false,
"title": "Pay securely",
"theme": "light"
}
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/checkout-appearance",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"settings": {
"inherit_default_store": false,
"title": "Pay securely",
"theme": "light"
},
"revision": 2
}GETСписок платёжных активов магазина/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/payment-assetsТолько чтение
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно projects.read; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| project_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| store_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"data": []
}POSTОбновить платёжные активы магазина/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/payment-assetsЧтение и запись
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно projects.write; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- До отправки сохрани уникальный Idempotency-Key и точное тело. Повторы не выполняют уже зафиксированное действие. Секреты при повторе не возвращаются; если ответ с секретом потерян, проверь созданный ресурс и явно смени или перевыпусти секрет. 409 operator_request_in_progress может означать прерванный запрос с неизвестным результатом: проверь ресурс и аудит, не повторяй вслепую с новым ключом.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | обязательно | 16–128 букв, цифр, -, _ или точек; сохраняется для этой операции |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| project_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| store_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| assets | array · required | Полная замена ончейн-способов: asset_id UUID и display_order. [] очищает разрешённые ончейн-активы. Только проверенные активы проекта; Lightning не настраивает. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"assets": [
{
"asset_id": "11111111-1111-4111-8111-111111111111",
"display_order": 0
}
]
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"assets": [
{
"asset_id": "11111111-1111-4111-8111-111111111111",
"display_order": 0
}
]
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"assets": [
{
"asset_id": "11111111-1111-4111-8111-111111111111",
"display_order": 0
}
]
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"assets": [
{
"asset_id": "11111111-1111-4111-8111-111111111111",
"display_order": 0
}
]
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"data": []
}GETСписок вебхуков магазина/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/webhooksТолько чтение
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно projects.read; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| project_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| store_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| page, search | query · optional | Страницы с 1, по 25 записей. Поиск поддерживается для продавцов, пользователей, проектов, магазинов, кошельков, ключей и вебхуков; списки событий используют свои фильтры. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POSTСоздать вебхук магазина/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/webhooksЧтение и запись
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно projects.write; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- До отправки сохрани уникальный Idempotency-Key и точное тело. Повторы не выполняют уже зафиксированное действие. Секреты при повторе не возвращаются; если ответ с секретом потерян, проверь созданный ресурс и явно смени или перевыпусти секрет. 409 operator_request_in_progress может означать прерванный запрос с неизвестным результатом: проверь ресурс и аудит, не повторяй вслепую с новым ключом.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | обязательно | 16–128 букв, цифр, -, _ или точек; сохраняется для этой операции |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| project_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| store_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| name, url, event_types | strings / array · required | Публичный HTTPS-приёмник и имена событий счетов из документации IPN и вебхуков. |
| enabled, automatic_redelivery | booleans · default true | Создание возвращает секрет подписи один раз. Это платёжные уведомления магазина, не события жизненного цикла оператора. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Orders",
"url": "https://shop.example.test/payments",
"event_types": [
"invoice.settled"
],
"enabled": true,
"automatic_redelivery": true
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"name": "Orders",
"url": "https://shop.example.test/payments",
"event_types": [
"invoice.settled"
],
"enabled": true,
"automatic_redelivery": true
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"name": "Orders",
"url": "https://shop.example.test/payments",
"event_types": [
"invoice.settled"
],
"enabled": true,
"automatic_redelivery": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"name": "Orders",
"url": "https://shop.example.test/payments",
"event_types": [
"invoice.settled"
],
"enabled": true,
"automatic_redelivery": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 201 или 200 application/json
{
"signing_secret": "EXAMPLE_ONLY_SAVE_ONCE",
"endpoint": {
"id": "44444444-4444-4444-8444-444444444444",
"name": "Orders",
"enabled": true
},
"secret_visible_once": true
}POSTОбновить вебхук магазина/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/webhooks/{webhook_id}Чтение и запись
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно projects.write; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- До отправки сохрани уникальный Idempotency-Key и точное тело. Повторы не выполняют уже зафиксированное действие. Секреты при повторе не возвращаются; если ответ с секретом потерян, проверь созданный ресурс и явно смени или перевыпусти секрет. 409 operator_request_in_progress может означать прерванный запрос с неизвестным результатом: проверь ресурс и аудит, не повторяй вслепую с новым ключом.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | обязательно | 16–128 букв, цифр, -, _ или точек; сохраняется для этой операции |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| project_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| store_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| webhook_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| name, url, event_types | strings / array · required | Публичный HTTPS-приёмник и имена событий счетов из документации IPN и вебхуков. |
| enabled, automatic_redelivery | booleans · default true | Создание возвращает секрет подписи один раз. Это платёжные уведомления магазина, не события жизненного цикла оператора. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks/YOUR_WEBHOOK_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Orders",
"url": "https://shop.example.test/payments",
"event_types": [
"invoice.settled"
],
"enabled": false,
"automatic_redelivery": true
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"name": "Orders",
"url": "https://shop.example.test/payments",
"event_types": [
"invoice.settled"
],
"enabled": false,
"automatic_redelivery": true
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks/YOUR_WEBHOOK_ID", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"name": "Orders",
"url": "https://shop.example.test/payments",
"event_types": [
"invoice.settled"
],
"enabled": false,
"automatic_redelivery": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks/YOUR_WEBHOOK_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"name": "Orders",
"url": "https://shop.example.test/payments",
"event_types": [
"invoice.settled"
],
"enabled": false,
"automatic_redelivery": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks/YOUR_WEBHOOK_ID",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"id": "44444444-4444-4444-8444-444444444444",
"name": "Orders",
"enabled": true
}GETСписок счетов/v1/operator/merchants/{merchant_id}/projects/{project_id}/invoicesТолько чтение
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно reports.read; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| project_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| limit, offset, search, status, store_id | query · optional | Пагинация и фильтры счетов как в списке счетов проекта. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/invoices" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/invoices", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/invoices");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/invoices",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"data": [],
"pagination": {
"limit": 25,
"offset": 0,
"total": 0,
"has_more": false
}
}GETПолучить счёт/v1/operator/merchants/{merchant_id}/projects/{project_id}/invoices/{invoice_id}Только чтение
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно reports.read; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
- invoice_id - публичный ID счёта из создания и уведомлений, не внутренний id.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| project_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| invoice_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/invoices/YOUR_INVOICE_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/invoices/YOUR_INVOICE_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/invoices/YOUR_INVOICE_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/invoices/YOUR_INVOICE_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"invoice_id": "44444444-4444-4444-8444-444444444444",
"project_id": "33333333-3333-4333-8333-333333333333",
"amount": "25",
"currency": "EUR",
"status": "new",
"metadata": {},
"payment_intents": []
}GETСписок кошельков/v1/operator/merchants/{merchant_id}/projects/{project_id}/walletsТолько чтение
Чтение кешированных публичных балансов кошельков, без закрытых ключей и сид-фраз.
- Нужно reports.read; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- Балансы - кешированные наблюдения с полями актуальности, не гарантия доступной к отправке суммы. Отправка и экспорт ключей через этот API недоступны.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| project_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| page, search | query · optional | Страницы с 1, по 25 записей. Поиск поддерживается для продавцов, пользователей, проектов, магазинов, кошельков, ключей и вебхуков; списки событий используют свои фильтры. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/wallets" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/wallets", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/wallets");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/wallets",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}GETСписок адресов кошелька/v1/operator/merchants/{merchant_id}/projects/{project_id}/wallets/{wallet_id}/addressesТолько чтение
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно reports.read; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| project_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| wallet_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| limit, before, search, has_balance, hide_small_balances | query · optional | Лимит 1–50, по умолчанию 25. Для следующей страницы передай next_cursor как before. Для первой не передавай before. has_balance=false и hide_small_balances=false включают пустые и малые остатки. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/wallets/YOUR_WALLET_ID/addresses" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/wallets/YOUR_WALLET_ID/addresses", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/wallets/YOUR_WALLET_ID/addresses");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/wallets/YOUR_WALLET_ID/addresses",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"wallet": {
"id": "22222222-2222-4222-8222-222222222222",
"chain_slug": "ethereum"
},
"items": [],
"total": 0,
"total_pages": 1,
"next_cursor": null,
"reporting_currency": "EUR",
"has_balance": true,
"hide_small_balances": true,
"small_balance_threshold": {
"amount": "0.20",
"currency": "EUR"
}
}GETСписок ключей API продавца/v1/operator/merchants/{merchant_id}/api-credentialsТолько чтение
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно merchant_credentials.read; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| page, search | query · optional | Страницы с 1, по 25 записей. Поиск поддерживается для продавцов, пользователей, проектов, магазинов, кошельков, ключей и вебхуков; списки событий используют свои фильтры. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POSTСоздать ключ API продавца/v1/operator/merchants/{merchant_id}/api-credentialsЧтение и запись
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно merchant_credentials.write; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- До отправки сохрани уникальный Idempotency-Key и точное тело. Повторы не выполняют уже зафиксированное действие. Секреты при повторе не возвращаются; если ответ с секретом потерян, проверь созданный ресурс и явно смени или перевыпусти секрет. 409 operator_request_in_progress может означать прерванный запрос с неизвестным результатом: проверь ресурс и аудит, не повторяй вслепую с новым ключом.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | обязательно | 16–128 букв, цифр, -, _ или точек; сохраняется для этой операции |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| name | string · required | Метка нового обычного ключа продавца, не ключа оператора. |
| access_level | read_only | read_write · default read_only | Чтение и запись включает действующий контракт API продавца. |
| project_ids | UUID[] | Только проекты выбранного продавца; пустой список следует существующей политике всех проектов продавца. |
| enabled, ip_restriction_enabled, allowed_ips, requests_per_minute | optional | Действующие настройки ключа продавца. Секрет возвращается один раз; нужно merchant_credentials.write. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Store integration",
"access_level": "read_only",
"enabled": true,
"ip_restriction_enabled": false,
"allowed_ips": [],
"project_ids": [
"11111111-1111-4111-8111-111111111111"
]
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"name": "Store integration",
"access_level": "read_only",
"enabled": true,
"ip_restriction_enabled": false,
"allowed_ips": [],
"project_ids": [
"11111111-1111-4111-8111-111111111111"
]
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"name": "Store integration",
"access_level": "read_only",
"enabled": true,
"ip_restriction_enabled": false,
"allowed_ips": [],
"project_ids": [
"11111111-1111-4111-8111-111111111111"
]
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"name": "Store integration",
"access_level": "read_only",
"enabled": true,
"ip_restriction_enabled": false,
"allowed_ips": [],
"project_ids": [
"11111111-1111-4111-8111-111111111111"
]
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 201 или 200 application/json
{
"credential": {
"id": "22222222-2222-4222-8222-222222222222",
"name": "Checkout",
"access_level": "read_only",
"project_ids": [
"33333333-3333-4333-8333-333333333333"
],
"enabled": true
},
"token": "wc_live_EXAMPLE_ONLY_SAVE_ONCE"
}POSTОбновить ключ API продавца/v1/operator/merchants/{merchant_id}/api-credentials/{credential_id}Чтение и запись
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно merchant_credentials.write; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- До отправки сохрани уникальный Idempotency-Key и точное тело. Повторы не выполняют уже зафиксированное действие. Секреты при повторе не возвращаются; если ответ с секретом потерян, проверь созданный ресурс и явно смени или перевыпусти секрет. 409 operator_request_in_progress может означать прерванный запрос с неизвестным результатом: проверь ресурс и аудит, не повторяй вслепую с новым ключом.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | обязательно | 16–128 букв, цифр, -, _ или точек; сохраняется для этой операции |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| credential_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| name, access_level, enabled, ip_restriction_enabled, allowed_ips | required fields | Передавай полную текущую конфигурацию ключа с изменениями. project_ids по умолчанию []; requests_per_minute - квота API продавца. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Checkout",
"access_level": "read_only",
"enabled": true,
"ip_restriction_enabled": false,
"allowed_ips": [],
"project_ids": [
"33333333-3333-4333-8333-333333333333"
]
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"name": "Checkout",
"access_level": "read_only",
"enabled": true,
"ip_restriction_enabled": false,
"allowed_ips": [],
"project_ids": [
"33333333-3333-4333-8333-333333333333"
]
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"name": "Checkout",
"access_level": "read_only",
"enabled": true,
"ip_restriction_enabled": false,
"allowed_ips": [],
"project_ids": [
"33333333-3333-4333-8333-333333333333"
]
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"name": "Checkout",
"access_level": "read_only",
"enabled": true,
"ip_restriction_enabled": false,
"allowed_ips": [],
"project_ids": [
"33333333-3333-4333-8333-333333333333"
]
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"id": "22222222-2222-4222-8222-222222222222",
"name": "Checkout",
"access_level": "read_only",
"project_ids": [
"33333333-3333-4333-8333-333333333333"
],
"enabled": true
}POSTСменить ключ API продавца/v1/operator/merchants/{merchant_id}/api-credentials/{credential_id}/rotateЧтение и запись
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно merchant_credentials.write; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- До отправки сохрани уникальный Idempotency-Key и точное тело. Повторы не выполняют уже зафиксированное действие. Секреты при повторе не возвращаются; если ответ с секретом потерян, проверь созданный ресурс и явно смени или перевыпусти секрет. 409 operator_request_in_progress может означать прерванный запрос с неизвестным результатом: проверь ресурс и аудит, не повторяй вслепую с новым ключом.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | обязательно | 16–128 букв, цифр, -, _ или точек; сохраняется для этой операции |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| credential_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID/rotate" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID/rotate", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID/rotate");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID/rotate",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"credential": {
"id": "22222222-2222-4222-8222-222222222222",
"name": "Checkout",
"access_level": "read_only",
"project_ids": [
"33333333-3333-4333-8333-333333333333"
],
"enabled": true
},
"token": "wc_live_EXAMPLE_ONLY_SAVE_ONCE"
}POSTОтозвать ключ API продавца/v1/operator/merchants/{merchant_id}/api-credentials/{credential_id}/revokeЧтение и запись
Управляй указанным ресурсом размещённого продавца или просматривай его отдельным ключом оператора.
- Нужно merchant_credentials.write; только разрешённые размещённые продавцы. Ключи оператора не видят пространство собственного бизнеса владельца.
- До отправки сохрани уникальный Idempotency-Key и точное тело. Повторы не выполняют уже зафиксированное действие. Секреты при повторе не возвращаются; если ответ с секретом потерян, проверь созданный ресурс и явно смени или перевыпусти секрет. 409 operator_request_in_progress может означать прерванный запрос с неизвестным результатом: проверь ресурс и аудит, не повторяй вслепую с новым ключом.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | обязательно | 16–128 букв, цифр, -, _ или точек; сохраняется для этой операции |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| merchant_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
| credential_id | path UUID | Канонический UUID ресурса в нижнем регистре; должен принадлежать области продавцов ключа. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID/revoke" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID/revoke", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID/revoke");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID/revoke",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"revoked": true
}POSTПроверить токен приглашения/v1/onboarding/invitations/checkПубличный
Регистрация только по токену. Не принимает ключ оператора и не выполняет автоматический вход. Для консоли всё ещё нужны Basic Auth сайта и существующий TOTP.
- Приглашение на 48 часов; сброс пароля на час. Одноразовые хешированные токены. Перевыпуск отзывает прежнюю ссылку. Принятие сохраняет TOTP и отзывает старые сеансы.
- Без автоматических повторов. При тайм-ауте принятия проверь статус ссылки и попробуй войти; не считай действие неудачным. Лимит по наблюдаемому IP источника. Получатель должен сам подтвердить условия доступа к ключам.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Параметр | Тип / расположение | Правило |
|---|---|---|
| token | string · required | Секрет из фрагмента URL приглашения. Никогда не записывай его в журналы. |
Запрос
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/onboarding/invitations/check" \
--header 'Content-Type: application/json' \
--data-raw '{
"token": "YOUR_PRIVATE_INVITATION_TOKEN"
}'// Node.js 18+ · run on your server, never in browser code.
const body = `{
"token": "YOUR_PRIVATE_INVITATION_TOKEN"
}`;
const response = await fetch("https://api.example.com/v1/onboarding/invitations/check", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$body = <<<'JSON'
{
"token": "YOUR_PRIVATE_INVITATION_TOKEN"
}
JSON;
$ch = curl_init("https://api.example.com/v1/onboarding/invitations/check");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Content-Type": "application/json"
}
body = """{
"token": "YOUR_PRIVATE_INVITATION_TOKEN"
}""".encode("utf-8")
request = Request("https://api.example.com/v1/onboarding/invitations/check",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"kind": "invitation",
"email": "admin@example.test",
"merchant_name": "Example shop"
}POSTПринять приглашение или сброс пароля/v1/onboarding/invitations/acceptПубличный
Регистрация только по токену. Не принимает ключ оператора и не выполняет автоматический вход. Для консоли всё ещё нужны Basic Auth сайта и существующий TOTP.
- Приглашение на 48 часов; сброс пароля на час. Одноразовые хешированные токены. Перевыпуск отзывает прежнюю ссылку. Принятие сохраняет TOTP и отзывает старые сеансы.
- Без автоматических повторов. При тайм-ауте принятия проверь статус ссылки и попробуй войти; не считай действие неудачным. Лимит по наблюдаемому IP источника. Получатель должен сам подтвердить условия доступа к ключам.
- Примеры ответов показывают выбранные поля. Дополнительные поля считай совместимым расширением.
| Параметр | Тип / расположение | Правило |
|---|---|---|
| token | string · required | Секрет из фрагмента URL приглашения. Никогда не записывай его в журналы. |
| password | string · required | Новый пароль, 12–128 символов, не более 512 байт UTF-8. |
| custody_acknowledged | boolean | Должно быть true при принятии нового приглашения с размещёнными кошельками. |
Запрос
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/onboarding/invitations/accept" \
--header 'Content-Type: application/json' \
--data-raw '{
"token": "YOUR_PRIVATE_INVITATION_TOKEN",
"password": "REPLACE_WITH_YOUR_OWN_UNIQUE_PASSWORD",
"custody_acknowledged": true
}'// Node.js 18+ · run on your server, never in browser code.
const body = `{
"token": "YOUR_PRIVATE_INVITATION_TOKEN",
"password": "REPLACE_WITH_YOUR_OWN_UNIQUE_PASSWORD",
"custody_acknowledged": true
}`;
const response = await fetch("https://api.example.com/v1/onboarding/invitations/accept", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$body = <<<'JSON'
{
"token": "YOUR_PRIVATE_INVITATION_TOKEN",
"password": "REPLACE_WITH_YOUR_OWN_UNIQUE_PASSWORD",
"custody_acknowledged": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/onboarding/invitations/accept");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Content-Type": "application/json"
}
body = """{
"token": "YOUR_PRIVATE_INVITATION_TOKEN",
"password": "REPLACE_WITH_YOUR_OWN_UNIQUE_PASSWORD",
"custody_acknowledged": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/onboarding/invitations/accept",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"password_set": true
}GETСписок исключений платежей/v1/projects/{project_id}/reconciliationТолько чтение
Единая очередь с пагинацией для недоплат, переплат, поздних, реорганизованных и неоднозначных платежей, неудачных доставок и отключённых/истёкших способов. Отмеченные оператором случаи открываются снова при новых доказательствах.
- Только чтение в рамках проекта, с учётом квоты ключа. Финансовые решения и возвраты остаются в консоли.
- Строки содержат id - внутренний UUID, invoice_id - публичный UUID как в уведомлениях, данные магазина, исходные фиатные сумму и валюту, invoice_status, статус случая, причины, ревизию и updated_at. В эндпоинте подробностей продавца используй invoice_id.
- Автоматическое обнаружение следует исходному окну мониторинга счёта; повторное сканирование продлевает наблюдение на час без включения оплаты. После зачисления и отмены способы продолжают отслеживаться в этом окне.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_MERCHANT_API_TOKEN |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| project_id | path UUID | Проект, назначенный этому ключу. |
| status | query string | open по умолчанию, resolved или all. |
| reason | query string | underpaid, overpaid, late, reorged, ambiguous, delivery_failed, disabled_method или expired_method. |
| search | query string | До 100 символов: ID счёта, заказ, покупатель или магазин. |
| store_id | query UUID | Необязательный фильтр магазина. |
| page | query integer | 1–40001. По 25 случаев на странице. |
Ответ очереди исключений
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| data | ExceptionRow[] | всегда | Сначала недавно обновлённые случаи. В URL подробностей продавца используй invoice_id, не внутренний id. |
| pagination | object | всегда | page (1–40001), per_page (25), total - число совпавших строк, has_more. |
| counts | object | всегда | Итоги open и resolved для всего проекта, независимо от текущих фильтров. |
ExceptionRow
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| id / invoice_id | UUID | всегда | Внутренний ID записи / публичный UUID счёта. invoice_id совпадает с данными уведомлений. |
| store_id / store_name | UUID / string | всегда | Магазин-владелец. |
| order_id / email | string | null | всегда | Приватный номер заказа продавца и email покупателя. |
| amount / currency | decimal string / string | всегда | Исходные фиатные сумма и валюта счёта. |
| invoice_status | invoice status | всегда | Текущий статус жизненного цикла платежа. |
| status / reasons | open|resolved / string[] | всегда | Состояние случая и типы исключений из фильтра причин. |
| revision / updated_at | integer / timestamp | всегда | Текущая ревизия проверки и время обновления. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation?status=open&page=1" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation?status=open&page=1", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation?status=open&page=1");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation?status=open&page=1",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{"data":[],"pagination":{"page":1,"per_page":25,"total":0,"has_more":false},"counts":{"open":0,"resolved":0}}GETПолучить данные для сверки/v1/projects/{project_id}/reconciliation/{invoice_id}Только чтение
Возвращает счёт, случай, точные итоги способов и доступные суммы возврата, обнаруженные транзакции, доставки, решения продавца и связанные возвратные переводы. Не раскрывает ключи подписи или секреты уведомлений.
- case равен null, если счёт не создавал исключение. Возвращаются последние 100 наблюдений и 50 доставок; история решений постраничная.
- refundable_atomic требует минимум одно подтверждение сети, исключает уже зарезервированные возвраты и не гарантирует доступность средств кошелька. Актуальная котировка дополнительно проверяет готовность кошелька, остатки источников и комиссии.
- Broadcast возврата означает отправку эндпоинту сети, не независимо подтверждённое получение покупателем. Комиссии оплачиваются отдельно; возврат не зачисляет автоматически обратно фиатную комиссию обработки.
- В консоли Проект → Требует внимания доступны отмена, принятие, отклонение, повторное открытие, проверка, заметки, пересканирование, повтор доставки и возвраты поддерживаемых сетей. Решения требуют CSRF-защищённого сеанса, уникального request_id, текущей ревизии случая, обязательной заметки и явного подтверждения; bearer-токены не могут выполнять эти изменения.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_MERCHANT_API_TOKEN |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| project_id | path UUID | Назначенный проект. |
| invoice_id | path UUID | Публичный UUID счёта, не внутренний id. |
| page | query integer | Страница истории решений с 1; по 25 решений. |
Ответ сверки
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| invoice | InvoiceDetail | всегда | Полный счёт продавца: сводные поля, приватные метаданные и payment_intents. Без обёртки data. |
| case | object | null | всегда | Текущий случай со статусом, причинами, ревизией и временем; null без исключения. Внутренние доказательства исключены. |
| methods | object[] | всегда | id, wallet_id, asset_id, symbol, chain, decimals, expected_atomic, received_atomic, confirmed_atomic, refundable_atomic, address, tag, monitor_error, last_checked_at, monitoring_expires_at и spending_supported. Суммы в минимальных единицах - строки. |
| history | object[] | всегда | Последние 25 решений страницы: id, action, note, actor, result, created_at. |
| history_pagination | object | всегда | page, per_page (25), total. Только история решений разбивается параметром page. |
| refunds | object[] | всегда | Последние 100 возвратов: id, payment_intent_id, amount_atomic, destination, status, request, treasury_intent_id, transfer_status, created_at и transactions (id/status). Запуск возврата только в консоли. |
| observations | object[] | всегда | Последние 100: payment_intent_id, transaction_id, event_index, amount, status, confirmations, observed_at, symbol, chain и disabled_at_detection. При поддержке включаются explorer_name/explorer_url. |
| deliveries | object[] | всегда | Последние 50: id, kind, status, attempts, response_status, error, next_attempt_at, event_type и created_at. Без секретов уведомлений. |
Сводка счёта
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| id | UUID | всегда | Внутренний UUID счёта. Не используй в путях подробностей продавца или оплаты. |
| invoice_id | UUID | всегда | Публичный UUID счёта для путей подробностей продавца и оплаты. |
| project_id | UUID | всегда | Проект-владелец. |
| store_id | UUID | всегда | Магазин-владелец. |
| source | manual | api | всегда | Как создан счёт. |
| order_id | string | null | всегда | Номер заказа продавца. |
| string | null | всегда | Email покупателя только для продавца. Не возвращается публичной оплатой. | |
| customer_name | string | null | всегда | Отображаемое имя из приватных метаданных firstname, lastname и company. |
| customer_address | string | null | всегда | Однострочный адрес для продавца из приватных метаданных company, street, street2, zip, city, country, countryiso2 и vatid. |
| description | string | null | всегда | Описание для покупателя. |
| amount | decimal string | всегда | Каноническая сумма счёта. |
| currency | string | всегда | Нормализованный код валюты или актива счёта. |
| exchange_rate_spread_percent | decimal string | всегда | Зафиксированная наценка: переопределение при создании или настройка магазина, если не передано. Применяется до округления вверх; для этого счёта не меняется. |
| underpayment_tolerance_percent | decimal string | всегда | Неизменяемый процент допустимой недоплаты, сохранённый при создании счёта. |
| status | invoice status | всегда | new, processing, settled, expired, invalid или cancelled. |
| amount_status | amount status | всегда | none, partial, paid или overpaid. Явно разрешённый счёт на нулевую сумму завершается с none и без способов оплаты. |
| timing_status | timing status | всегда | on_time или late. |
| resolution | resolution | всегда | automatic, manually_settled или manually_invalidated. |
| sequence | integer | всегда | Монотонная последовательность состояния счёта, начиная с 1. |
| winning_payment_intent_id | UUID | null | всегда | Способ оплаты, завершивший счёт, если выбран. |
| expires_at | RFC 3339 timestamp | всегда | Срок котировки и оплаты. |
| monitoring_expires_at | RFC 3339 timestamp | всегда | Самый поздний настроенный срок отслеживания поздних платежей среди способов. |
| settled_at | timestamp | null | всегда | Время окончательного зачисления при settled. |
| cancelled_at | timestamp | null | всегда | Время отмены при cancelled. |
| archived_at | timestamp | null | всегда | Время архивирования, если выполнено. |
| created_at | RFC 3339 timestamp | всегда | Время создания. |
| updated_at | RFC 3339 timestamp | всегда | Время последнего обновления состояния. |
Дополнения подробностей счёта
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| ipn_url | string | null | всегда | Действующий IPN-адрес этого счёта. Только в ответе продавцу; не в публичной оплате. |
| redirect_url | string | null | всегда | Действующий URL успеха после зачисления. |
| cancel_url | string | null | всегда | Действующий URL возврата при завершении оплаты без успеха. |
| redirect_automatically | boolean | всегда | Перенаправлять ли автоматически после успешной оплаты. |
| checkout_language | string | всегда | Действующий языковой тег оплаты. |
| metadata | object | всегда | Метаданные продавца. Не возвращаются публичной оплатой. |
| payment_intents | PaymentIntent[] | всегда | Котированные способы оплаты и состояние мониторинга. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation/YOUR_PUBLIC_INVOICE_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation/YOUR_PUBLIC_INVOICE_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation/YOUR_PUBLIC_INVOICE_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation/YOUR_PUBLIC_INVOICE_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{"invoice":{"invoice_id":"YOUR_PUBLIC_INVOICE_ID","status":"processing"},"case":{"status":"open","reasons":["underpaid"],"revision":1},"methods":[],"history":[],"history_pagination":{"page":1,"per_page":25,"total":0},"refunds":[],"observations":[],"deliveries":[]}GETОбнаружение сервиса API/Публичный
Ответ управляемого API-хоста, подтверждающий роль публичного API v1. Формируется управляемым прокси, не маршрутизатором Axum продавца.
- Bearer-токен не нужен.
- Только управляемый API-хост гарантирует именно этот ответ корневого пути.
Запрос
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/"// Node.js 18+ · run on your server, never in browser code.
const response = await fetch("https://api.example.com/", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://api.example.com/");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://api.example.com/",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"service": "Wholly Crypto API",
"status": "ready",
"version": "v1"
}GETСостояние сервиса/healthzПубличный
Проверяет доступность приложения и соединение с базой с тайм-аутом две секунды. Для мониторинга, не вместо статуса счёта.
- Bearer-токен не нужен.
- version - версия работающего пакета, не версия пути API.
Запрос
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/healthz"// Node.js 18+ · run on your server, never in browser code.
const response = await fetch("https://api.example.com/healthz", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://api.example.com/healthz");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://api.example.com/healthz",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 исправен; 503 база недоступна
{
"status": "ok",
"database": "ok",
"version": "0.1.0"
}GETСписок платёжных активов проекта/v1/projects/{project_id}/payment-assetsТолько чтение
Список нативных активов и проверенных токенов с политикой проекта, готовностью кошелька и установленными возможностями сканера и балансов. scanner_ready - наличие адаптера в сборке, не текущий кворум эндпоинтов. С 6.0.6 создание сохраняет настроенные способы при простое сканера. Для проверки поступления всё ещё нужно настроенное число исправных провайдеров точной роли: по умолчанию 2, опционально 1.
- Токен может быть в общем списке, но недоступен для выбора при scanner_ready или payment_supported равном false.
- Матрица возможностей оператора также требует точной роли эндпоинта сканера; исправный эндпоинт с несовместимым API не учитывается.
- Токены используют кошелёк нативной сети проекта; новая сид-фраза не создаётся.
- Встроенные сводки кошельков показывают только готовность, балансы пусты; расширенные балансы получай через GET /v1/projects/{project_id}/wallets.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_MERCHANT_API_TOKEN |
| Accept | рекомендуется | application/json |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| project_id | path UUID | Включённый проект, назначенный ключу. |
PaymentAsset
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| id | UUID | всегда | Постоянный идентификатор платёжного актива для маршрутов политики проекта и магазина. |
| asset_key | string | всегда | Канонический CAIP-подобный идентификатор нативного актива или контракта. |
| chain_slug / network | string | всегда | Идентификатор сети Wholly Crypto и настроенная сеть. |
| caip_network_id / caip_asset_id | string / string|null | всегда | Канонические идентификаторы сети и актива. |
| asset_kind | native | token | всегда | Используется ли валюта сети или проверенный контракт/mint. |
| payment_rail | string | всегда | Способ выполнения: utxo, evm-native, solana-native, account-native, privacy-native или token-transfer. |
| symbol / name / decimals | string / string / integer | всегда | Отображаемое имя и точная разрядность минимальных единиц. |
| contract_address | string | null | всегда | Канонический контракт ERC-20 или mint SPL для токенов; null для нативных активов. |
| coingecko_id | string | null | всегда | Идентификатор поиска и цены. Null для пользовательских контрактов; не выводи рыночную цену из тикера. Метаданные CoinGecko сами по себе не делают токен доступным. |
| custom_token | boolean | всегда | Пользовательский ончейн-проверенный контракт с ценой проекта: фиксированной USD или выбранного DEX-пула. |
| icon_path | path | null | всегда | Локально кешированная иконка токена, если доступна. |
| token_standard | erc20 | spl-token | null | всегда | Проверенный поддерживаемый стандарт токена; null для нативных активов. |
| metadata_verified_at | timestamp | null | всегда | Время ончейн-проверки метаданных зарегистрированных токенов. |
| payment_supported / scanner_ready / balance_ready | boolean | всегда | Проверки реестра сборки. scanner_ready означает установленный сканер; подтверждение требует настроенное число исправных провайдеров точной роли: 2 по умолчанию, опционально 1. Временная недоступность сканера не блокирует создание с 6.0.6. balance_ready равен true только для реализованных адаптеров баланса. |
| default_finality_mode | confirmations | finalized | всегда | Модель финальности по умолчанию для новой политики проекта. |
| default_required_confirmations / default_monitoring_minutes | integer | всегда | Политика подтверждений и мониторинга по умолчанию. |
ProjectPaymentAsset
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| asset | PaymentAsset | всегда | Постоянный нативный или проверенный токен-актив. |
| policy | ProjectAssetPolicy | null | всегда | Политика активации и финальности проекта либо null, если не настроена. Включает custom_price_mode (fixed/dex), custom_price_usd (фиксированная десятичная строка или null), custom_dex_pair (выбранный пул или null) и custom_dex (dex_id, quote_symbol, текущий price_usd или null, liquidity_usd, fetched_at, last_error). Пользовательские цены общие для магазинов проекта. |
| wallet | WalletSummary | null | всегда | Некастодиальный кошелёк сети проекта. Токены используют её нативный кошелёк. |
| wallet_readiness | readiness enum | всегда | unsupported, project_disabled, project_asset_disabled, store_disabled, store_asset_disabled, wallet_missing, wallet_pending, wallet_disabled, wallet_error, backup_required, account_activation_required, external_wallet_rpc_required или ready. |
| receive_readiness | ReceiveReadiness | null | 5.5.0+ | Общая оценка настройки приёма проекта. Включает кошелёк и независимых провайдеров сканера, отдельно от актуальности баланса и газа отправки. Null без политики проекта. Валюта и курсы счёта проверяются при создании. |
WalletSummary
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| id / project_id / native_asset_id | UUID | всегда | Идентификаторы кошелька, проекта-владельца и нативного актива сети. |
| chain_slug / network | string | всегда | Блокчейн и сеть кошелька. |
| asset_symbol / asset_name | string | всегда | Отображаемое имя нативного актива. |
| status | pending | active | disabled | error | всегда | Рабочее состояние кошелька. |
| label | string | всегда | Метка оператора. |
| public_key / primary_address | string | null | всегда | Публичные данные кошелька; сид-фраза и закрытый ключ не раскрываются. |
| derivation_scheme / address_format | string | null | всегда | Политика и формат адресов. |
| backup_confirmed_at | timestamp | null | всегда | Не null после подтверждения оператором резервной копии для восстановления. |
| activation_required / activation_verified_at | boolean / timestamp|null | всегда | Общие аккаунты XRP и Stellar недоступны, пока оператор не пополнит показанный адрес, а настроенные провайдеры не проверят именно этот аккаунт. Сохранённое доказательство не истекает; текущая работоспособность сканеров проверяется отдельно для платежей, не создания счетов. |
| receive_readiness | ReceiveReadiness | null | 5.5.0+ | В списках кошельков: настройка приёма проекта и требования сканера сети. Отдельно от балансов, газа токенов и готовности отправки. Другие ответы кошелька могут оставлять null. |
| monero_wallet_rpc | MoneroWalletRpcBinding | null | всегда | Очищенное состояние привязки внешнего view-only wallet-RPC Monero. Включает эндпоинт, режим авторизации, основной адрес account-0, флаги и высоты технических доказательств и время подтверждений оператора; данные доступа, ключи и файлы кошелька не сериализуются. |
| last_secret_revealed_at / secret_reveal_count | timestamp|null / integer | всегда | Метаданные аудита раскрытия секретов в консоли. |
| next_receive_index | integer | всегда | Следующий зарезервированный индекс дочернего адреса. |
| last_scanned_height / last_scanned_at / last_error | integer|null / timestamp|null / string|null | всегда | Состояние сканера кошелька. |
| balances | WalletAssetBalance[] | всегда | Кешированные балансы всех 30 нативных сетей и проверенных ERC-20 и SPL. Для Monero нужен настроенный внешний view-only wallet-RPC. |
| total_value_usd | decimal string | null | всегда | Справочная сумма балансов с текущей ценой USD. |
| balance_status | pending | refreshing | fresh | stale | error | unknown | всегда | Общая актуальность кеша; unknown - защитное значение. Ни одно из этих состояний не доказывает оплату счёта. |
| balance_checked_at | timestamp | null | всегда | Самая старая релевантная успешная проверка баланса в агрегате. |
| recent_payments | WalletRecentPayment[] | всегда | До трёх последних действительных наблюдений detected, confirming или final, относящихся именно к этому кошельку. |
| created_at / updated_at | RFC 3339 timestamp | всегда | Время создания и последнего обновления кошелька. |
ReceiveReadiness
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| ready | boolean | всегда | Проверки настройки приёма пройдены. Не описывает готовность отправки, газ, обновление баланса и не гарантирует будущую котировку. |
| invoice_creatable | boolean | 6.0.6+ | Конфигурация разрешает способ счёта, несмотря на временные предупреждения сканера. Цена валюты проверяется при создании. Это не проверка платежа: ready может быть false, когда invoice_creatable - true. Отсутствие кошелька, отключённая политика и неподдерживаемые адаптеры по-прежнему безопасно блокируют действие. |
| checked_at | timestamp | всегда | Время оценки. Получение списка не делает сетевых запросов и не выделяет адреса. |
| issues | PaymentMethodIssue[] | всегда | Пусто при готовности; иначе предупреждение приёма или блокирующая настройка. Проверяй invoice_creatable, чтобы отличить временную проблему сканера от ошибки настройки счёта. |
PaymentMethodIssue
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| chain_slug / asset_id / asset_ticker | string / UUID / string | если известно | Указывает затронутые сеть и актив. Для Lightning asset_id может отсутствовать. |
| reason_code | string | всегда | scanner_provider_quorum, scanner_not_checked, scanner_unavailable, wallet_missing, wallet_disabled, wallet_backup_required, wallet_key_unavailable, wallet_activation_required, monero_binding_unavailable, rate_unavailable, custom_rate_unavailable, lightning_unavailable, project_disabled, store_disabled, chain_disabled, asset_disabled или asset_not_accepted. |
| message / action | string | если доступно | Пояснение для продавца и идентификатор действия: chain_connections, wallets, rates, payment_methods, project_settings или store_settings. Без секретов и приватных URL провайдеров. |
| required_endpoint_role | string | null | ончейн | Предпочтительная роль API сканера - устаревшее поле. Полный список совместимости в accepted_endpoint_roles. Базовая работоспособность узла не доказывает доступность истории платежей. |
| accepted_endpoint_roles | string[] | null | ончейн | Совместимые варианты API, не доказательство истории или ресурсов эндпоинта. Прямой node-rpc поддерживает BTC/BCH/LTC/DOGE/DASH и прозрачный ZEC - полные декодированные блоки, 1–48 подтверждений, solidified нативный TRX, нативный ALGO через algod, XTZ через Octez, финализированный DOT Asset Hub через метаданные SCALE и нативный XLM через Stellar RPC с memo ID счёта. Обрезанная или неполная история не подходит. Эти прямые адаптеры не добавляют токены. Индексированные API остаются альтернативами; смотри таблицу ниже. Смешанные прямые и индексированные источники независимо проверяют ограниченные окна; по умолчанию нужны два независимых провайдера, не псевдонимы одного оператора. Высота узла, сведения сети ORDnet и EVM-relay для не-EVM приёма не доказывают платёж. Monero всё ещё требует привязанный к проекту view-only wallet-RPC. |
| healthy_endpoints | integer | ончейн | Исправные подходящие эндпоинты, не число независимых провайдеров. |
| usable_independent_providers / required_independent_providers | integer | ончейн | Доступные проверочные слоты, максимум два. required_independent_providers - настройка сети: 2 по умолчанию или 1 после явного выбора администратора. При двух провайдерах должны различаться и ключи провайдеров, и хосты. Отключённые, устаревшие более чем на десять минут или остывающие источники слот не занимают. У Lightning свои правила подключения. |
| last_checked_at | timestamp | null | ончейн | Последняя проверка подходящего эндпоинта, отдельно от времени оценки. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Accept: application/json'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
"Accept": "application/json"
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Accept: application/json"],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Accept": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"data": [
{
"asset": {
"id": "10000000-0000-4000-8000-000000000003",
"asset_key": "eip155:1/slip44:60",
"chain_slug": "ethereum",
"network": "mainnet",
"caip_network_id": "eip155:1",
"caip_asset_id": "eip155:1/slip44:60",
"asset_kind": "native",
"payment_rail": "evm-native",
"symbol": "ETH",
"name": "Ethereum",
"decimals": 18,
"contract_address": null,
"coingecko_id": "ethereum",
"icon_path": "/assets/coingecko/ethereum.png",
"token_standard": null,
"metadata_verified_at": null,
"payment_supported": true,
"scanner_ready": true,
"balance_ready": true,
"default_finality_mode": "confirmations",
"default_required_confirmations": 12,
"default_monitoring_minutes": 60
},
"policy": {
"enabled": true,
"finality_mode": "confirmations",
"required_confirmations": 12,
"monitoring_minutes": 60,
"late_monitoring_days": 30
},
"wallet": null,
"wallet_readiness": "wallet_missing",
"receive_readiness": { "ready": false, "invoice_creatable": false, "checked_at": "2026-09-16T09:00:00Z", "issues": [{ "chain_slug": "ethereum", "asset_id": "10000000-0000-4000-8000-000000000003", "asset_ticker": "ETH", "reason_code": "wallet_missing", "message": "ethereum / ETH: Create a project wallet for this chain.", "action": "wallets" }] }
}
]
}PUTОбновить политику активов проекта/v1/projects/{project_id}/payment-assets/{asset_id}Чтение и запись
Создаёт или заменяет политику проекта для одного постоянного актива и возвращает обновлённый список. Отключение нативной сети убирает монету и токены из новых счетов, но сохраняет политики токенов, кошельки и выбор магазинов для возобновления.
- Тело полностью заменяет политику и отклоняет неизвестные поля.
- Включение в проекте само по себе не выбирает актив ни в одном магазине.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_MERCHANT_API_TOKEN |
| Content-Type | обязательно | application/json |
| Accept | рекомендуется | application/json |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| project_id | path UUID | Включённый проект, назначенный ключу. |
| asset_id | path UUID | ID актива из списка проекта или регистрации токена. |
Обновление политики актива проекта
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| enabled | boolean | обязательно | Включает или отключает актив для проекта. Перед токенами нужно включить нативную сеть. |
| finality_mode | confirmations | finalized | обязательно | Политика финальности, поддерживаемая способом актива. finalized требует required_confirmations=1. |
| required_confirmations | integer | обязательно | Bitcoin и EVM допускают ноль; другие способы с подтверждениями требуют минимум одно, только-finalized - ровно одно. EVM ограничен 0–48, чтобы каждый перевод оставался внутри окна повторной проверки транзакций. |
| monitoring_minutes | integer | обязательно | Окно опроса 1–10 080 минут, пока счёт активен. |
| late_monitoring_days | integer | обязательно | 0–3 650 дней отслеживания после истечения счёта. |
PaymentAsset
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| id | UUID | всегда | Постоянный идентификатор платёжного актива для маршрутов политики проекта и магазина. |
| asset_key | string | всегда | Канонический CAIP-подобный идентификатор нативного актива или контракта. |
| chain_slug / network | string | всегда | Идентификатор сети Wholly Crypto и настроенная сеть. |
| caip_network_id / caip_asset_id | string / string|null | всегда | Канонические идентификаторы сети и актива. |
| asset_kind | native | token | всегда | Используется ли валюта сети или проверенный контракт/mint. |
| payment_rail | string | всегда | Способ выполнения: utxo, evm-native, solana-native, account-native, privacy-native или token-transfer. |
| symbol / name / decimals | string / string / integer | всегда | Отображаемое имя и точная разрядность минимальных единиц. |
| contract_address | string | null | всегда | Канонический контракт ERC-20 или mint SPL для токенов; null для нативных активов. |
| coingecko_id | string | null | всегда | Идентификатор поиска и цены. Null для пользовательских контрактов; не выводи рыночную цену из тикера. Метаданные CoinGecko сами по себе не делают токен доступным. |
| custom_token | boolean | всегда | Пользовательский ончейн-проверенный контракт с ценой проекта: фиксированной USD или выбранного DEX-пула. |
| icon_path | path | null | всегда | Локально кешированная иконка токена, если доступна. |
| token_standard | erc20 | spl-token | null | всегда | Проверенный поддерживаемый стандарт токена; null для нативных активов. |
| metadata_verified_at | timestamp | null | всегда | Время ончейн-проверки метаданных зарегистрированных токенов. |
| payment_supported / scanner_ready / balance_ready | boolean | всегда | Проверки реестра сборки. scanner_ready означает установленный сканер; подтверждение требует настроенное число исправных провайдеров точной роли: 2 по умолчанию, опционально 1. Временная недоступность сканера не блокирует создание с 6.0.6. balance_ready равен true только для реализованных адаптеров баланса. |
| default_finality_mode | confirmations | finalized | всегда | Модель финальности по умолчанию для новой политики проекта. |
| default_required_confirmations / default_monitoring_minutes | integer | всегда | Политика подтверждений и мониторинга по умолчанию. |
ProjectPaymentAsset
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| asset | PaymentAsset | всегда | Постоянный нативный или проверенный токен-актив. |
| policy | ProjectAssetPolicy | null | всегда | Политика активации и финальности проекта либо null, если не настроена. Включает custom_price_mode (fixed/dex), custom_price_usd (фиксированная десятичная строка или null), custom_dex_pair (выбранный пул или null) и custom_dex (dex_id, quote_symbol, текущий price_usd или null, liquidity_usd, fetched_at, last_error). Пользовательские цены общие для магазинов проекта. |
| wallet | WalletSummary | null | всегда | Некастодиальный кошелёк сети проекта. Токены используют её нативный кошелёк. |
| wallet_readiness | readiness enum | всегда | unsupported, project_disabled, project_asset_disabled, store_disabled, store_asset_disabled, wallet_missing, wallet_pending, wallet_disabled, wallet_error, backup_required, account_activation_required, external_wallet_rpc_required или ready. |
| receive_readiness | ReceiveReadiness | null | 5.5.0+ | Общая оценка настройки приёма проекта. Включает кошелёк и независимых провайдеров сканера, отдельно от актуальности баланса и газа отправки. Null без политики проекта. Валюта и курсы счёта проверяются при создании. |
ReceiveReadiness
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| ready | boolean | всегда | Проверки настройки приёма пройдены. Не описывает готовность отправки, газ, обновление баланса и не гарантирует будущую котировку. |
| invoice_creatable | boolean | 6.0.6+ | Конфигурация разрешает способ счёта, несмотря на временные предупреждения сканера. Цена валюты проверяется при создании. Это не проверка платежа: ready может быть false, когда invoice_creatable - true. Отсутствие кошелька, отключённая политика и неподдерживаемые адаптеры по-прежнему безопасно блокируют действие. |
| checked_at | timestamp | всегда | Время оценки. Получение списка не делает сетевых запросов и не выделяет адреса. |
| issues | PaymentMethodIssue[] | всегда | Пусто при готовности; иначе предупреждение приёма или блокирующая настройка. Проверяй invoice_creatable, чтобы отличить временную проблему сканера от ошибки настройки счёта. |
PaymentMethodIssue
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| chain_slug / asset_id / asset_ticker | string / UUID / string | если известно | Указывает затронутые сеть и актив. Для Lightning asset_id может отсутствовать. |
| reason_code | string | всегда | scanner_provider_quorum, scanner_not_checked, scanner_unavailable, wallet_missing, wallet_disabled, wallet_backup_required, wallet_key_unavailable, wallet_activation_required, monero_binding_unavailable, rate_unavailable, custom_rate_unavailable, lightning_unavailable, project_disabled, store_disabled, chain_disabled, asset_disabled или asset_not_accepted. |
| message / action | string | если доступно | Пояснение для продавца и идентификатор действия: chain_connections, wallets, rates, payment_methods, project_settings или store_settings. Без секретов и приватных URL провайдеров. |
| required_endpoint_role | string | null | ончейн | Предпочтительная роль API сканера - устаревшее поле. Полный список совместимости в accepted_endpoint_roles. Базовая работоспособность узла не доказывает доступность истории платежей. |
| accepted_endpoint_roles | string[] | null | ончейн | Совместимые варианты API, не доказательство истории или ресурсов эндпоинта. Прямой node-rpc поддерживает BTC/BCH/LTC/DOGE/DASH и прозрачный ZEC - полные декодированные блоки, 1–48 подтверждений, solidified нативный TRX, нативный ALGO через algod, XTZ через Octez, финализированный DOT Asset Hub через метаданные SCALE и нативный XLM через Stellar RPC с memo ID счёта. Обрезанная или неполная история не подходит. Эти прямые адаптеры не добавляют токены. Индексированные API остаются альтернативами; смотри таблицу ниже. Смешанные прямые и индексированные источники независимо проверяют ограниченные окна; по умолчанию нужны два независимых провайдера, не псевдонимы одного оператора. Высота узла, сведения сети ORDnet и EVM-relay для не-EVM приёма не доказывают платёж. Monero всё ещё требует привязанный к проекту view-only wallet-RPC. |
| healthy_endpoints | integer | ончейн | Исправные подходящие эндпоинты, не число независимых провайдеров. |
| usable_independent_providers / required_independent_providers | integer | ончейн | Доступные проверочные слоты, максимум два. required_independent_providers - настройка сети: 2 по умолчанию или 1 после явного выбора администратора. При двух провайдерах должны различаться и ключи провайдеров, и хосты. Отключённые, устаревшие более чем на десять минут или остывающие источники слот не занимают. У Lightning свои правила подключения. |
| last_checked_at | timestamp | null | ончейн | Последняя проверка подходящего эндпоинта, отдельно от времени оценки. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request PUT \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets/YOUR_ASSET_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Content-Type: application/json' \
--data-raw '{
"enabled": true,
"finality_mode": "confirmations",
"required_confirmations": 2,
"monitoring_minutes": 60,
"late_monitoring_days": 30
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const body = `{
"enabled": true,
"finality_mode": "confirmations",
"required_confirmations": 2,
"monitoring_minutes": 60,
"late_monitoring_days": 30
}`;
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets/YOUR_ASSET_ID", {
method: "PUT",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$body = <<<'JSON'
{
"enabled": true,
"finality_mode": "confirmations",
"required_confirmations": 2,
"monitoring_minutes": 60,
"late_monitoring_days": 30
}
JSON;
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets/YOUR_ASSET_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"enabled": true,
"finality_mode": "confirmations",
"required_confirmations": 2,
"monitoring_minutes": 60,
"late_monitoring_days": 30
}""".encode("utf-8")
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets/YOUR_ASSET_ID",
method="PUT", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"data": [
{ "asset": { "id": "ASSET_UUID", "symbol": "USDC", "asset_kind": "token", "scanner_ready": true }, "policy": { "enabled": true, "finality_mode": "confirmations", "required_confirmations": 2, "monitoring_minutes": 60, "late_monitoring_days": 30 }, "wallet_readiness": "ready" }
]
}GETПосмотреть кандидатов в платёжные токены/v1/projects/{project_id}/payment-token-candidatesТолько чтение
Ищет локально кешированные привязки контрактов CoinGecko только в сетях с реализованными токен-сканером счетов и адаптером баланса. Это кандидаты, а не доверенные платёжные активы.
- Поддерживаемые адаптеры токенов: ERC-20 в Ethereum, Base, BNB Chain, HyperEVM, Avalanche, Polygon, Arbitrum и Optimism; SPL в Solana.
- Неподдерживаемые сети каталога отклоняются, а не показываются доступными для выбора.
- Ранг, иконка и цена CoinGecko - справочные данные поиска.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_MERCHANT_API_TOKEN |
| Accept | рекомендуется | application/json |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| project_id | path UUID | Включённый проект, назначенный ключу. |
| chain_slug | query string | Обязателен поддерживаемый slug EVM-сети или solana. |
| q | query string | Необязательная часть имени, символа, CoinGecko ID, контракта или mint; до 80 символов. |
| limit | query integer | Необязательно 1–100; по умолчанию 50. |
TokenCandidate
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| coingecko_id | string | всегда | CoinGecko ID кандидата для запроса регистрации. |
| chain_slug | string | всегда | Сопоставленная сеть Wholly Crypto. |
| symbol / name | string | всегда | Отображаемое имя каталога. |
| contract_address | string | всегда | Сопоставленный контракт или mint; перед регистрацией проверяется в сети. |
| market_cap_rank | integer | null | всегда | Ранг поиска, не признак доверия или готовности приёма. |
| icon_path | path | всегда | Локальный путь кешированной иконки CoinGecko. |
| current_price_usd | decimal string | null | всегда | Справочная кешированная цена USD. |
| token_standard | erc20 | spl-token | всегда | Стандарт токена, поддерживаемый адаптером выбранной сети. |
| scanner_ready | boolean | всегда | True только для кандидатов, чей токен-способ реализован в этой сборке. |
| registered_asset_id | UUID | null | всегда | Существующий постоянный актив, если уже зарегистрирован. |
| project_enabled | boolean | всегда | Включён ли зарегистрированный актив для проекта. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-candidates?chain_slug=ethereum&q=USDC&limit=50" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-candidates?chain_slug=ethereum&q=USDC&limit=50", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-candidates?chain_slug=ethereum&q=USDC&limit=50");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-candidates?chain_slug=ethereum&q=USDC&limit=50",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"data": [
{
"coingecko_id": "usd-coin",
"chain_slug": "ethereum",
"symbol": "USDC",
"name": "USDC",
"contract_address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"market_cap_rank": 7,
"icon_path": "/assets/coingecko/usd-coin.png",
"current_price_usd": "1.0001",
"token_standard": "erc20",
"scanner_ready": true,
"registered_asset_id": null,
"project_enabled": false
}
]
}POSTПроверить и зарегистрировать токен/v1/projects/{project_id}/payment-token-assetsЧтение и запись
Переносит текущего кандидата в постоянный платёжный реестр только после проверки узлами сети, контракта/mint, десятичной точности и рабочего запроса баланса. Одних метаданных CoinGecko недостаточно; в каждом проекте максимум 20 зарегистрированных токен-активов.
- Включи нативный актив сети проекта перед регистрацией токенов.
- Проект может зарегистрировать до 20 токен-активов; новый кандидат сверх лимита возвращает token_chain_not_ready (409). Повторное использование зарегистрированного актива не занимает место.
- Проверка узлов может занять больше времени, чем чтение каталога; задай явный тайм-аут клиента.
- После регистрации выбери актив в каждом магазине, где он нужен.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_MERCHANT_API_TOKEN |
| Content-Type | обязательно | application/json |
| Accept | рекомендуется | application/json |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| project_id | path UUID | Включённый проект, назначенный ключу. |
Тело регистрации токена
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| chain_slug | string | обязательно | ethereum, base, bnb-chain, hyperliquid, avalanche, polygon, arbitrum, optimism или solana. |
| coingecko_id | string | обязательно | Точный ID кандидата из поиска токенов. Сохраняй начальные подчёркивания и дефисы, например _ или -6. Не вычисляй ID из названия или тикера. |
| enabled | boolean | необязательно | Состояние политики проекта после проверки; по умолчанию true. |
RegisteredTokenAsset
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| asset_id | UUID | всегда | Постоянный идентификатор платёжного актива. |
| chain_slug / coingecko_id | string | всегда | Проверенная сеть и сохранённый ID поиска и цены. |
| contract_address | string | всегда | Канонический проверенный контракт или mint. |
| token_standard | erc20 | spl-token | всегда | Проверенный поддерживаемый стандарт токена. |
| symbol / name / decimals | string / string / integer | всегда | Зарегистрированные имя и точная разрядность. |
| enabled | boolean | всегда | Начальное состояние политики проекта. |
| metadata_verified_at | RFC 3339 timestamp | всегда | Время ончейн-проверки. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Content-Type: application/json' \
--data-raw '{
"chain_slug": "ethereum",
"coingecko_id": "usd-coin",
"enabled": true
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const body = `{
"chain_slug": "ethereum",
"coingecko_id": "usd-coin",
"enabled": true
}`;
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$body = <<<'JSON'
{
"chain_slug": "ethereum",
"coingecko_id": "usd-coin",
"enabled": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"chain_slug": "ethereum",
"coingecko_id": "usd-coin",
"enabled": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 201 application/json
{
"data": {
"asset_id": "44444444-4444-4444-8444-444444444444",
"chain_slug": "ethereum",
"coingecko_id": "usd-coin",
"contract_address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"token_standard": "erc20",
"symbol": "USDC",
"name": "USDC",
"decimals": 6,
"enabled": true,
"metadata_verified_at": "2026-08-31T18:00:00Z"
}
}GETНайти DEX-пулы пользовательского токена/v1/projects/{project_id}/payment-token-dex-poolsТолько чтение
Находит до 12 подходящих пулов по точной сети и контракту базового токена через DEX Screener, по убыванию ликвидности. Не регистрирует и не включает токен.
- Пустой массив data означает отсутствие подходящего пула. Возвращаются только пулы, где точный запрошенный контракт - базовый токен; цена USD стороны котировки не предполагается.
- Присутствие на DEX не является аудитом безопасности. Минимальная ликвидность и недавняя активность уменьшают число непригодных котировок, но не защищают от манипуляций рынком.
- Uniswap, PancakeSwap и другие индексируемые DEX поддерживаются там, где текущий сканер сети поддерживает токены. API ограничен проектом и квотой. Вызовы провайдера также выполняются последовательно и с ограничением частоты.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_MERCHANT_API_TOKEN |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| project_id | path UUID | Назначенный проект. |
| chain_slug | query string | Поддерживаемая токен-сеть EVM или solana. |
| contract_address | query string | Точный контракт ERC-20 или классический mint SPL. |
CustomDexPool
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| pair_address / dex_id / quote_symbol | string | всегда | Точный ID пула, ID биржи, например uniswap/pancakeswap, и тикер пары только для отображения. |
| price_usd / liquidity_usd | decimal string | всегда | Цена USD запрошенного базового токена и общая ликвидность пула. Нужны минимум $10 000 ликвидности и сделка за последний час. |
| fetched_at | RFC 3339 timestamp | всегда | Время получения сервером данных провайдера, не время ончейн-сделки. |
| url | HTTPS URL | всегда | Проверенная ссылка DEX Screener на этот пул. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-dex-pools?chain_slug=ethereum&contract_address=YOUR_TOKEN_CONTRACT" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-dex-pools?chain_slug=ethereum&contract_address=YOUR_TOKEN_CONTRACT", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-dex-pools?chain_slug=ethereum&contract_address=YOUR_TOKEN_CONTRACT");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-dex-pools?chain_slug=ethereum&contract_address=YOUR_TOKEN_CONTRACT",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{"data":[{"pair_address":"0x2222222222222222222222222222222222222222","dex_id":"uniswap","quote_symbol":"WETH","price_usd":"0.25","liquidity_usd":"250000.00","fetched_at":"2026-09-09T12:00:00Z","url":"https://dexscreener.com/ethereum/0x2222222222222222222222222222222222222222"}]}POSTДобавить пользовательский токен или изменить цену/v1/projects/{project_id}/payment-token-assets/customЧтение и запись
Проверяет пользовательский контракт через настроенные узлы и регистрирует без CoinGecko. Фиксированная цена USD или выбранный DEX-пул принадлежат проекту, не тикеру или другим проектам. Повтор того же актива обновляет цену проекта без изменения существующего включения или отключения.
- После регистрации выбери asset_id в эндпоинте payment-assets магазина; одна регистрация не включает способ магазина.
- Пользовательские и каталожные токены делят лимит 20 токенов проекта. Один контракт в разных сетях - разные платёжные активы.
- Контракты из каталога возвращают 409: используй регистрацию каталога для автоматических рыночных курсов. Пользовательский тикер не заимствует цену одноимённого токена.
- Фиксированные цены - оценка оператора. Автоматические DEX-цены - спотовые наблюдения выбранного пула через DEX Screener, не устойчивый к манипуляциям оракул. Наценка магазина и округление вверх с актуальными фиатными курсами сохраняются. Уже выданные котировки не меняются.
- Для DEX сначала найди пул, затем передай price_mode: dex и dex_pair_address без price_usd. Общая фоновая задача обновляет выбранные пулы каждую минуту. Неудачная проверка или цена старше пяти минут убирает токен из новых котировок; скрытого перехода на фиксированную цену или другой тикер нет.
- Принимаются только стандартные ERC-20 и классические SPL. Token-2022, расширения и сети только с нативной монетой отклоняются. Техническая проверка - не аудит эмитента или контракта; токены с комиссией перевода, rebasing или чёрными списками могут работать несовместимо.
- Используй тайм-аут клиента не меньше 60 секунд. Проверка ограничена по времени и может пробовать резервные узлы. Некорректные данные дают 400, ошибка проверки сети или контракта - 422, конфликт идентичности или лимит - 409.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_MERCHANT_API_TOKEN |
| Content-Type | обязательно | application/json |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| project_id | path UUID | Проект, назначенный этому ключу с правом записи. |
Регистрация пользовательского токена
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| chain_slug | string | обязательно | ethereum, base, bnb-chain, hyperliquid, avalanche, polygon, arbitrum, optimism или solana. Для этого контракта фиксируется. |
| contract_address | string | обязательно | Контракт ERC-20: 0x и 40 шестнадцатеричных символов, либо классический mint SPL. Узлы проверяют сеть и точную разрядность; переданные клиентом decimals и URL RPC отклоняются. |
| name / symbol | string / string | обязательно | Имя 1–80 символов и тикер 1–16 букв, цифр, точек, подчёркиваний или дефисов; первый символ - буква или цифра. Эндпоинт не переименовывает существующие активы. |
| price_mode | fixed | dex | необязательно | По умолчанию fixed для обратной совместимости. DEX использует конкретный пул, найденный для точной сети и контракта. |
| price_usd | decimal string | режим fixed | Фиксированная стоимость ОДНОГО токена в USD: положительная, до 30 знаков после запятой, максимум 1000000000000000000000000. Без экспоненты и float. Не передавай в режиме dex. |
| dex_pair_address | string | режим dex | Адрес пула из payment-token-dex-pools. Обязателен для dex, не передаётся для fixed. При каждом сохранении сервер перепроверяет пул, цену, ликвидность и активность. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets/custom" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Content-Type: application/json' \
--data-raw '{
"chain_slug": "ethereum",
"contract_address": "YOUR_VERIFIED_TOKEN_CONTRACT",
"name": "Example token",
"symbol": "EXAMPLE",
"price_usd": "0.25"
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const body = `{
"chain_slug": "ethereum",
"contract_address": "YOUR_VERIFIED_TOKEN_CONTRACT",
"name": "Example token",
"symbol": "EXAMPLE",
"price_usd": "0.25"
}`;
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets/custom", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$body = <<<'JSON'
{
"chain_slug": "ethereum",
"contract_address": "YOUR_VERIFIED_TOKEN_CONTRACT",
"name": "Example token",
"symbol": "EXAMPLE",
"price_usd": "0.25"
}
JSON;
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets/custom");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"chain_slug": "ethereum",
"contract_address": "YOUR_VERIFIED_TOKEN_CONTRACT",
"name": "Example token",
"symbol": "EXAMPLE",
"price_usd": "0.25"
}""".encode("utf-8")
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets/custom",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{"data":{"asset_id":"44444444-4444-4444-8444-444444444444"}}GETСписок способов оплаты магазина/v1/projects/{project_id}/stores/{store_id}/payment-assetsТолько чтение
Ончейн-активы возвращаются в data, отдельная готовность Lightning - в lightning. Ончейн-способам нужны готовые кошельки сети. Lightning использует выбранное магазином проверенное внешнее подключение приёма, независимо от ончейн-кошелька Bitcoin.
- selected - настройка ончейн-способа; wallet_readiness - текущая проверка его пригодности.
- Поле lightning содержит payment_rail: lightning, symbol: BTC, asset_decimals: 11, enabled и ready. Никогда не содержит секретов узла. Настраивай способ в консоли магазина; изменение массива assets не меняет Lightning.
- confirmation_policy применяется только к ончейн-способам. Lightning завершается без блоковых подтверждений и требует полную сумму BOLT11 без допуска частичной оплаты.
- Нативные и токен-способы одной сети используют один адрес назначения счёта для её кошелька.
- Встроенные сводки кошельков показывают только готовность, балансы пусты; для текущих значений используй отдельный маршрут кошельков проекта.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_MERCHANT_API_TOKEN |
| Accept | рекомендуется | application/json |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| project_id | path UUID | Проект, назначенный ключу; может быть приостановлен. |
| store_id | path UUID | Магазин внутри project_id; может быть приостановлен. |
PaymentAsset
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| id | UUID | всегда | Постоянный идентификатор платёжного актива для маршрутов политики проекта и магазина. |
| asset_key | string | всегда | Канонический CAIP-подобный идентификатор нативного актива или контракта. |
| chain_slug / network | string | всегда | Идентификатор сети Wholly Crypto и настроенная сеть. |
| caip_network_id / caip_asset_id | string / string|null | всегда | Канонические идентификаторы сети и актива. |
| asset_kind | native | token | всегда | Используется ли валюта сети или проверенный контракт/mint. |
| payment_rail | string | всегда | Способ выполнения: utxo, evm-native, solana-native, account-native, privacy-native или token-transfer. |
| symbol / name / decimals | string / string / integer | всегда | Отображаемое имя и точная разрядность минимальных единиц. |
| contract_address | string | null | всегда | Канонический контракт ERC-20 или mint SPL для токенов; null для нативных активов. |
| coingecko_id | string | null | всегда | Идентификатор поиска и цены. Null для пользовательских контрактов; не выводи рыночную цену из тикера. Метаданные CoinGecko сами по себе не делают токен доступным. |
| custom_token | boolean | всегда | Пользовательский ончейн-проверенный контракт с ценой проекта: фиксированной USD или выбранного DEX-пула. |
| icon_path | path | null | всегда | Локально кешированная иконка токена, если доступна. |
| token_standard | erc20 | spl-token | null | всегда | Проверенный поддерживаемый стандарт токена; null для нативных активов. |
| metadata_verified_at | timestamp | null | всегда | Время ончейн-проверки метаданных зарегистрированных токенов. |
| payment_supported / scanner_ready / balance_ready | boolean | всегда | Проверки реестра сборки. scanner_ready означает установленный сканер; подтверждение требует настроенное число исправных провайдеров точной роли: 2 по умолчанию, опционально 1. Временная недоступность сканера не блокирует создание с 6.0.6. balance_ready равен true только для реализованных адаптеров баланса. |
| default_finality_mode | confirmations | finalized | всегда | Модель финальности по умолчанию для новой политики проекта. |
| default_required_confirmations / default_monitoring_minutes | integer | всегда | Политика подтверждений и мониторинга по умолчанию. |
StorePaymentAsset
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| asset | PaymentAsset | всегда | Видимый проекту нативный или проверенный токен-актив. |
| project_policy | ProjectAssetPolicy | null | всегда | Родительская политика проекта. |
| selected | boolean | всегда | Входит ли способ в сохранённую желаемую конфигурацию магазина. Предлагается при корректной политике проекта, кошельке, установленном адаптере и цене. Временные сбои сканера не убирают его из новых счетов. |
| display_order | integer | null | всегда | Порядок на странице оплаты магазина, если выбран. |
| confirmation_policy | StoreConfirmationPolicy | null | всегда | Действующая политика магазина для настроенного актива проекта. Null без политики проекта. |
| wallet | WalletSummary | null | всегда | Общий кошелёк сети для нативной монеты и токенов. |
| wallet_readiness | readiness enum | всегда | Только состояние кошелька и политики; требования сканера смотри в receive_readiness. |
| receive_readiness | ReceiveReadiness | null | 5.5.0+ | Общая настройка приёма и разрешение магазина. Использует кешированные наблюдения; это не резервирование и не гарантия. Создание повторно проверяет требования и реальный курс счёта. |
StoreConfirmationPolicy
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| finality_mode | confirmations | finalized | всегда | Используется ли настраиваемое число блоков или финальность сети. |
| project_required_confirmations | integer | всегда | Текущее значение проекта для будущих счетов без переопределения магазина. |
| override_required_confirmations | integer | null | всегда | Число магазина или null для наследования настройки проекта. |
| effective_required_confirmations | integer | всегда | Число, которое будет сохранено в новых счетах этого магазина и актива. |
| editable | boolean | всегда | False для finalized-сетей, где политику финальности нельзя переопределить. |
| minimum_required_confirmations | integer | всегда | Включительная нижняя граница сети; 0 показывается только для способов с принятием при обнаружении. |
| maximum_required_confirmations | integer | всегда | Включительная верхняя граница сети. |
WalletSummary
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| id / project_id / native_asset_id | UUID | всегда | Идентификаторы кошелька, проекта-владельца и нативного актива сети. |
| chain_slug / network | string | всегда | Блокчейн и сеть кошелька. |
| asset_symbol / asset_name | string | всегда | Отображаемое имя нативного актива. |
| status | pending | active | disabled | error | всегда | Рабочее состояние кошелька. |
| label | string | всегда | Метка оператора. |
| public_key / primary_address | string | null | всегда | Публичные данные кошелька; сид-фраза и закрытый ключ не раскрываются. |
| derivation_scheme / address_format | string | null | всегда | Политика и формат адресов. |
| backup_confirmed_at | timestamp | null | всегда | Не null после подтверждения оператором резервной копии для восстановления. |
| activation_required / activation_verified_at | boolean / timestamp|null | всегда | Общие аккаунты XRP и Stellar недоступны, пока оператор не пополнит показанный адрес, а настроенные провайдеры не проверят именно этот аккаунт. Сохранённое доказательство не истекает; текущая работоспособность сканеров проверяется отдельно для платежей, не создания счетов. |
| receive_readiness | ReceiveReadiness | null | 5.5.0+ | В списках кошельков: настройка приёма проекта и требования сканера сети. Отдельно от балансов, газа токенов и готовности отправки. Другие ответы кошелька могут оставлять null. |
| monero_wallet_rpc | MoneroWalletRpcBinding | null | всегда | Очищенное состояние привязки внешнего view-only wallet-RPC Monero. Включает эндпоинт, режим авторизации, основной адрес account-0, флаги и высоты технических доказательств и время подтверждений оператора; данные доступа, ключи и файлы кошелька не сериализуются. |
| last_secret_revealed_at / secret_reveal_count | timestamp|null / integer | всегда | Метаданные аудита раскрытия секретов в консоли. |
| next_receive_index | integer | всегда | Следующий зарезервированный индекс дочернего адреса. |
| last_scanned_height / last_scanned_at / last_error | integer|null / timestamp|null / string|null | всегда | Состояние сканера кошелька. |
| balances | WalletAssetBalance[] | всегда | Кешированные балансы всех 30 нативных сетей и проверенных ERC-20 и SPL. Для Monero нужен настроенный внешний view-only wallet-RPC. |
| total_value_usd | decimal string | null | всегда | Справочная сумма балансов с текущей ценой USD. |
| balance_status | pending | refreshing | fresh | stale | error | unknown | всегда | Общая актуальность кеша; unknown - защитное значение. Ни одно из этих состояний не доказывает оплату счёта. |
| balance_checked_at | timestamp | null | всегда | Самая старая релевантная успешная проверка баланса в агрегате. |
| recent_payments | WalletRecentPayment[] | всегда | До трёх последних действительных наблюдений detected, confirming или final, относящихся именно к этому кошельку. |
| created_at / updated_at | RFC 3339 timestamp | всегда | Время создания и последнего обновления кошелька. |
ReceiveReadiness
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| ready | boolean | всегда | Проверки настройки приёма пройдены. Не описывает готовность отправки, газ, обновление баланса и не гарантирует будущую котировку. |
| invoice_creatable | boolean | 6.0.6+ | Конфигурация разрешает способ счёта, несмотря на временные предупреждения сканера. Цена валюты проверяется при создании. Это не проверка платежа: ready может быть false, когда invoice_creatable - true. Отсутствие кошелька, отключённая политика и неподдерживаемые адаптеры по-прежнему безопасно блокируют действие. |
| checked_at | timestamp | всегда | Время оценки. Получение списка не делает сетевых запросов и не выделяет адреса. |
| issues | PaymentMethodIssue[] | всегда | Пусто при готовности; иначе предупреждение приёма или блокирующая настройка. Проверяй invoice_creatable, чтобы отличить временную проблему сканера от ошибки настройки счёта. |
PaymentMethodIssue
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| chain_slug / asset_id / asset_ticker | string / UUID / string | если известно | Указывает затронутые сеть и актив. Для Lightning asset_id может отсутствовать. |
| reason_code | string | всегда | scanner_provider_quorum, scanner_not_checked, scanner_unavailable, wallet_missing, wallet_disabled, wallet_backup_required, wallet_key_unavailable, wallet_activation_required, monero_binding_unavailable, rate_unavailable, custom_rate_unavailable, lightning_unavailable, project_disabled, store_disabled, chain_disabled, asset_disabled или asset_not_accepted. |
| message / action | string | если доступно | Пояснение для продавца и идентификатор действия: chain_connections, wallets, rates, payment_methods, project_settings или store_settings. Без секретов и приватных URL провайдеров. |
| required_endpoint_role | string | null | ончейн | Предпочтительная роль API сканера - устаревшее поле. Полный список совместимости в accepted_endpoint_roles. Базовая работоспособность узла не доказывает доступность истории платежей. |
| accepted_endpoint_roles | string[] | null | ончейн | Совместимые варианты API, не доказательство истории или ресурсов эндпоинта. Прямой node-rpc поддерживает BTC/BCH/LTC/DOGE/DASH и прозрачный ZEC - полные декодированные блоки, 1–48 подтверждений, solidified нативный TRX, нативный ALGO через algod, XTZ через Octez, финализированный DOT Asset Hub через метаданные SCALE и нативный XLM через Stellar RPC с memo ID счёта. Обрезанная или неполная история не подходит. Эти прямые адаптеры не добавляют токены. Индексированные API остаются альтернативами; смотри таблицу ниже. Смешанные прямые и индексированные источники независимо проверяют ограниченные окна; по умолчанию нужны два независимых провайдера, не псевдонимы одного оператора. Высота узла, сведения сети ORDnet и EVM-relay для не-EVM приёма не доказывают платёж. Monero всё ещё требует привязанный к проекту view-only wallet-RPC. |
| healthy_endpoints | integer | ончейн | Исправные подходящие эндпоинты, не число независимых провайдеров. |
| usable_independent_providers / required_independent_providers | integer | ончейн | Доступные проверочные слоты, максимум два. required_independent_providers - настройка сети: 2 по умолчанию или 1 после явного выбора администратора. При двух провайдерах должны различаться и ключи провайдеров, и хосты. Отключённые, устаревшие более чем на десять минут или остывающие источники слот не занимают. У Lightning свои правила подключения. |
| last_checked_at | timestamp | null | ончейн | Последняя проверка подходящего эндпоинта, отдельно от времени оценки. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"data": [
{ "asset": { "id": "ASSET_UUID", "chain_slug": "ethereum", "symbol": "USDC", "asset_kind": "token", "token_standard": "erc20", "scanner_ready": true }, "project_policy": { "enabled": true, "required_confirmations": 12 }, "selected": true, "display_order": 0, "confirmation_policy": { "finality_mode": "confirmations", "project_required_confirmations": 12, "override_required_confirmations": 3, "effective_required_confirmations": 3, "editable": true, "minimum_required_confirmations": 0, "maximum_required_confirmations": 48 }, "wallet": { "id": "WALLET_UUID", "status": "active" }, "wallet_readiness": "ready" }
],
"lightning": { "payment_rail": "lightning", "symbol": "BTC", "asset_decimals": 11, "enabled": true, "ready": true }
}PUTЗаменить способы оплаты магазина/v1/projects/{project_id}/stores/{store_id}/payment-assetsЧтение и запись
Атомарно заменяет весь упорядоченный набор активов магазина и возвращает обновлённый список. Непереданные активы снимаются с выбора.
- Массив допускает до 64 уникальных активов и значений порядка.
- Выбор - сохранённая желаемая конфигурация; его можно подготовить до резервного копирования кошелька или при паузе сети. Создание счёта предлагает только способы с готовыми политиками проекта и нативного родителя, кошельком и проверками исполнения.
- Отправь пустой массив assets, чтобы не оставить способов оплаты.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_MERCHANT_API_TOKEN |
| Content-Type | обязательно | application/json |
| Accept | рекомендуется | application/json |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| project_id | path UUID | Проект, назначенный ключу; может быть приостановлен. |
| store_id | path UUID | Магазин внутри project_id; может быть приостановлен. |
Тело выбора платёжных активов магазина
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| assets | StoreAssetSelection[] | обязательно | Полный список замены, до 64 записей. Каждая содержит уникальный asset_id и уникальный display_order от 0 до 10 000. |
PaymentAsset
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| id | UUID | всегда | Постоянный идентификатор платёжного актива для маршрутов политики проекта и магазина. |
| asset_key | string | всегда | Канонический CAIP-подобный идентификатор нативного актива или контракта. |
| chain_slug / network | string | всегда | Идентификатор сети Wholly Crypto и настроенная сеть. |
| caip_network_id / caip_asset_id | string / string|null | всегда | Канонические идентификаторы сети и актива. |
| asset_kind | native | token | всегда | Используется ли валюта сети или проверенный контракт/mint. |
| payment_rail | string | всегда | Способ выполнения: utxo, evm-native, solana-native, account-native, privacy-native или token-transfer. |
| symbol / name / decimals | string / string / integer | всегда | Отображаемое имя и точная разрядность минимальных единиц. |
| contract_address | string | null | всегда | Канонический контракт ERC-20 или mint SPL для токенов; null для нативных активов. |
| coingecko_id | string | null | всегда | Идентификатор поиска и цены. Null для пользовательских контрактов; не выводи рыночную цену из тикера. Метаданные CoinGecko сами по себе не делают токен доступным. |
| custom_token | boolean | всегда | Пользовательский ончейн-проверенный контракт с ценой проекта: фиксированной USD или выбранного DEX-пула. |
| icon_path | path | null | всегда | Локально кешированная иконка токена, если доступна. |
| token_standard | erc20 | spl-token | null | всегда | Проверенный поддерживаемый стандарт токена; null для нативных активов. |
| metadata_verified_at | timestamp | null | всегда | Время ончейн-проверки метаданных зарегистрированных токенов. |
| payment_supported / scanner_ready / balance_ready | boolean | всегда | Проверки реестра сборки. scanner_ready означает установленный сканер; подтверждение требует настроенное число исправных провайдеров точной роли: 2 по умолчанию, опционально 1. Временная недоступность сканера не блокирует создание с 6.0.6. balance_ready равен true только для реализованных адаптеров баланса. |
| default_finality_mode | confirmations | finalized | всегда | Модель финальности по умолчанию для новой политики проекта. |
| default_required_confirmations / default_monitoring_minutes | integer | всегда | Политика подтверждений и мониторинга по умолчанию. |
StorePaymentAsset
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| asset | PaymentAsset | всегда | Видимый проекту нативный или проверенный токен-актив. |
| project_policy | ProjectAssetPolicy | null | всегда | Родительская политика проекта. |
| selected | boolean | всегда | Входит ли способ в сохранённую желаемую конфигурацию магазина. Предлагается при корректной политике проекта, кошельке, установленном адаптере и цене. Временные сбои сканера не убирают его из новых счетов. |
| display_order | integer | null | всегда | Порядок на странице оплаты магазина, если выбран. |
| confirmation_policy | StoreConfirmationPolicy | null | всегда | Действующая политика магазина для настроенного актива проекта. Null без политики проекта. |
| wallet | WalletSummary | null | всегда | Общий кошелёк сети для нативной монеты и токенов. |
| wallet_readiness | readiness enum | всегда | Только состояние кошелька и политики; требования сканера смотри в receive_readiness. |
| receive_readiness | ReceiveReadiness | null | 5.5.0+ | Общая настройка приёма и разрешение магазина. Использует кешированные наблюдения; это не резервирование и не гарантия. Создание повторно проверяет требования и реальный курс счёта. |
StoreConfirmationPolicy
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| finality_mode | confirmations | finalized | всегда | Используется ли настраиваемое число блоков или финальность сети. |
| project_required_confirmations | integer | всегда | Текущее значение проекта для будущих счетов без переопределения магазина. |
| override_required_confirmations | integer | null | всегда | Число магазина или null для наследования настройки проекта. |
| effective_required_confirmations | integer | всегда | Число, которое будет сохранено в новых счетах этого магазина и актива. |
| editable | boolean | всегда | False для finalized-сетей, где политику финальности нельзя переопределить. |
| minimum_required_confirmations | integer | всегда | Включительная нижняя граница сети; 0 показывается только для способов с принятием при обнаружении. |
| maximum_required_confirmations | integer | всегда | Включительная верхняя граница сети. |
ReceiveReadiness
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| ready | boolean | всегда | Проверки настройки приёма пройдены. Не описывает готовность отправки, газ, обновление баланса и не гарантирует будущую котировку. |
| invoice_creatable | boolean | 6.0.6+ | Конфигурация разрешает способ счёта, несмотря на временные предупреждения сканера. Цена валюты проверяется при создании. Это не проверка платежа: ready может быть false, когда invoice_creatable - true. Отсутствие кошелька, отключённая политика и неподдерживаемые адаптеры по-прежнему безопасно блокируют действие. |
| checked_at | timestamp | всегда | Время оценки. Получение списка не делает сетевых запросов и не выделяет адреса. |
| issues | PaymentMethodIssue[] | всегда | Пусто при готовности; иначе предупреждение приёма или блокирующая настройка. Проверяй invoice_creatable, чтобы отличить временную проблему сканера от ошибки настройки счёта. |
PaymentMethodIssue
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| chain_slug / asset_id / asset_ticker | string / UUID / string | если известно | Указывает затронутые сеть и актив. Для Lightning asset_id может отсутствовать. |
| reason_code | string | всегда | scanner_provider_quorum, scanner_not_checked, scanner_unavailable, wallet_missing, wallet_disabled, wallet_backup_required, wallet_key_unavailable, wallet_activation_required, monero_binding_unavailable, rate_unavailable, custom_rate_unavailable, lightning_unavailable, project_disabled, store_disabled, chain_disabled, asset_disabled или asset_not_accepted. |
| message / action | string | если доступно | Пояснение для продавца и идентификатор действия: chain_connections, wallets, rates, payment_methods, project_settings или store_settings. Без секретов и приватных URL провайдеров. |
| required_endpoint_role | string | null | ончейн | Предпочтительная роль API сканера - устаревшее поле. Полный список совместимости в accepted_endpoint_roles. Базовая работоспособность узла не доказывает доступность истории платежей. |
| accepted_endpoint_roles | string[] | null | ончейн | Совместимые варианты API, не доказательство истории или ресурсов эндпоинта. Прямой node-rpc поддерживает BTC/BCH/LTC/DOGE/DASH и прозрачный ZEC - полные декодированные блоки, 1–48 подтверждений, solidified нативный TRX, нативный ALGO через algod, XTZ через Octez, финализированный DOT Asset Hub через метаданные SCALE и нативный XLM через Stellar RPC с memo ID счёта. Обрезанная или неполная история не подходит. Эти прямые адаптеры не добавляют токены. Индексированные API остаются альтернативами; смотри таблицу ниже. Смешанные прямые и индексированные источники независимо проверяют ограниченные окна; по умолчанию нужны два независимых провайдера, не псевдонимы одного оператора. Высота узла, сведения сети ORDnet и EVM-relay для не-EVM приёма не доказывают платёж. Monero всё ещё требует привязанный к проекту view-only wallet-RPC. |
| healthy_endpoints | integer | ончейн | Исправные подходящие эндпоинты, не число независимых провайдеров. |
| usable_independent_providers / required_independent_providers | integer | ончейн | Доступные проверочные слоты, максимум два. required_independent_providers - настройка сети: 2 по умолчанию или 1 после явного выбора администратора. При двух провайдерах должны различаться и ключи провайдеров, и хосты. Отключённые, устаревшие более чем на десять минут или остывающие источники слот не занимают. У Lightning свои правила подключения. |
| last_checked_at | timestamp | null | ончейн | Последняя проверка подходящего эндпоинта, отдельно от времени оценки. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request PUT \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Content-Type: application/json' \
--data-raw '{
"assets": [
{
"asset_id": "YOUR_ASSET_ID",
"display_order": 0
}
]
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const body = `{
"assets": [
{
"asset_id": "YOUR_ASSET_ID",
"display_order": 0
}
]
}`;
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets", {
method: "PUT",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$body = <<<'JSON'
{
"assets": [
{
"asset_id": "YOUR_ASSET_ID",
"display_order": 0
}
]
}
JSON;
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"assets": [
{
"asset_id": "YOUR_ASSET_ID",
"display_order": 0
}
]
}""".encode("utf-8")
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets",
method="PUT", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"data": [
{ "asset": { "id": "44444444-4444-4444-8444-444444444444", "symbol": "USDC" }, "selected": true, "display_order": 0, "confirmation_policy": { "finality_mode": "confirmations", "project_required_confirmations": 12, "override_required_confirmations": null, "effective_required_confirmations": 12, "editable": true, "minimum_required_confirmations": 0, "maximum_required_confirmations": 48 }, "wallet_readiness": "ready" }
]
}PUTНастроить подтверждения магазина/v1/projects/{project_id}/stores/{store_id}/payment-assets/{asset_id}/confirmation-policyЧтение и запись
Задаёт или снимает переопределение подтверждений магазина и возвращает обновлённые способы оплаты. Актив уже должен быть выбран магазином. Настройка доступна и при паузе проекта, магазина, сети или кошелька.
- Используй {"strategy":"inherit"}, чтобы убрать переопределение магазина и следовать текущей настройке проекта для будущих счетов.
- Finalized-сети возвращают editable false и используют финальность сети; свой счётчик блоков для них не задаётся.
- 0 означает принятие при обнаружении без подтверждений сети и защиты от реорганизации. Допускается только при minimum_required_confirmations равном 0.
- Изменения политики действуют только на новые счета. Существующие сохраняют снимок политики проекта и магазина на момент создания.
- Обновление выполняется по одному активу; изменения одного актива магазина отправляй последовательно и считай обновлённый ответ текущим состоянием.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_MERCHANT_API_TOKEN |
| Content-Type | обязательно | application/json |
| Accept | рекомендуется | application/json |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| project_id | path UUID | Проект, назначенный ключу; может быть приостановлен. |
| store_id | path UUID | Магазин внутри project_id; может быть приостановлен. |
| asset_id | path UUID | Выбранный сейчас платёжный актив магазина для изменения. |
Тело политики подтверждений магазина
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| strategy | inherit | custom | обязательно | Стратегия с типом. inherit удаляет переопределение магазина; custom требует required_confirmations. |
| required_confirmations | integer | только custom | Целое число в пределах минимума и максимума, возвращённых для актива. Неизвестные или лишние поля отклоняются. |
PaymentAsset
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| id | UUID | всегда | Постоянный идентификатор платёжного актива для маршрутов политики проекта и магазина. |
| asset_key | string | всегда | Канонический CAIP-подобный идентификатор нативного актива или контракта. |
| chain_slug / network | string | всегда | Идентификатор сети Wholly Crypto и настроенная сеть. |
| caip_network_id / caip_asset_id | string / string|null | всегда | Канонические идентификаторы сети и актива. |
| asset_kind | native | token | всегда | Используется ли валюта сети или проверенный контракт/mint. |
| payment_rail | string | всегда | Способ выполнения: utxo, evm-native, solana-native, account-native, privacy-native или token-transfer. |
| symbol / name / decimals | string / string / integer | всегда | Отображаемое имя и точная разрядность минимальных единиц. |
| contract_address | string | null | всегда | Канонический контракт ERC-20 или mint SPL для токенов; null для нативных активов. |
| coingecko_id | string | null | всегда | Идентификатор поиска и цены. Null для пользовательских контрактов; не выводи рыночную цену из тикера. Метаданные CoinGecko сами по себе не делают токен доступным. |
| custom_token | boolean | всегда | Пользовательский ончейн-проверенный контракт с ценой проекта: фиксированной USD или выбранного DEX-пула. |
| icon_path | path | null | всегда | Локально кешированная иконка токена, если доступна. |
| token_standard | erc20 | spl-token | null | всегда | Проверенный поддерживаемый стандарт токена; null для нативных активов. |
| metadata_verified_at | timestamp | null | всегда | Время ончейн-проверки метаданных зарегистрированных токенов. |
| payment_supported / scanner_ready / balance_ready | boolean | всегда | Проверки реестра сборки. scanner_ready означает установленный сканер; подтверждение требует настроенное число исправных провайдеров точной роли: 2 по умолчанию, опционально 1. Временная недоступность сканера не блокирует создание с 6.0.6. balance_ready равен true только для реализованных адаптеров баланса. |
| default_finality_mode | confirmations | finalized | всегда | Модель финальности по умолчанию для новой политики проекта. |
| default_required_confirmations / default_monitoring_minutes | integer | всегда | Политика подтверждений и мониторинга по умолчанию. |
StorePaymentAsset
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| asset | PaymentAsset | всегда | Видимый проекту нативный или проверенный токен-актив. |
| project_policy | ProjectAssetPolicy | null | всегда | Родительская политика проекта. |
| selected | boolean | всегда | Входит ли способ в сохранённую желаемую конфигурацию магазина. Предлагается при корректной политике проекта, кошельке, установленном адаптере и цене. Временные сбои сканера не убирают его из новых счетов. |
| display_order | integer | null | всегда | Порядок на странице оплаты магазина, если выбран. |
| confirmation_policy | StoreConfirmationPolicy | null | всегда | Действующая политика магазина для настроенного актива проекта. Null без политики проекта. |
| wallet | WalletSummary | null | всегда | Общий кошелёк сети для нативной монеты и токенов. |
| wallet_readiness | readiness enum | всегда | Только состояние кошелька и политики; требования сканера смотри в receive_readiness. |
| receive_readiness | ReceiveReadiness | null | 5.5.0+ | Общая настройка приёма и разрешение магазина. Использует кешированные наблюдения; это не резервирование и не гарантия. Создание повторно проверяет требования и реальный курс счёта. |
StoreConfirmationPolicy
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| finality_mode | confirmations | finalized | всегда | Используется ли настраиваемое число блоков или финальность сети. |
| project_required_confirmations | integer | всегда | Текущее значение проекта для будущих счетов без переопределения магазина. |
| override_required_confirmations | integer | null | всегда | Число магазина или null для наследования настройки проекта. |
| effective_required_confirmations | integer | всегда | Число, которое будет сохранено в новых счетах этого магазина и актива. |
| editable | boolean | всегда | False для finalized-сетей, где политику финальности нельзя переопределить. |
| minimum_required_confirmations | integer | всегда | Включительная нижняя граница сети; 0 показывается только для способов с принятием при обнаружении. |
| maximum_required_confirmations | integer | всегда | Включительная верхняя граница сети. |
ReceiveReadiness
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| ready | boolean | всегда | Проверки настройки приёма пройдены. Не описывает готовность отправки, газ, обновление баланса и не гарантирует будущую котировку. |
| invoice_creatable | boolean | 6.0.6+ | Конфигурация разрешает способ счёта, несмотря на временные предупреждения сканера. Цена валюты проверяется при создании. Это не проверка платежа: ready может быть false, когда invoice_creatable - true. Отсутствие кошелька, отключённая политика и неподдерживаемые адаптеры по-прежнему безопасно блокируют действие. |
| checked_at | timestamp | всегда | Время оценки. Получение списка не делает сетевых запросов и не выделяет адреса. |
| issues | PaymentMethodIssue[] | всегда | Пусто при готовности; иначе предупреждение приёма или блокирующая настройка. Проверяй invoice_creatable, чтобы отличить временную проблему сканера от ошибки настройки счёта. |
PaymentMethodIssue
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| chain_slug / asset_id / asset_ticker | string / UUID / string | если известно | Указывает затронутые сеть и актив. Для Lightning asset_id может отсутствовать. |
| reason_code | string | всегда | scanner_provider_quorum, scanner_not_checked, scanner_unavailable, wallet_missing, wallet_disabled, wallet_backup_required, wallet_key_unavailable, wallet_activation_required, monero_binding_unavailable, rate_unavailable, custom_rate_unavailable, lightning_unavailable, project_disabled, store_disabled, chain_disabled, asset_disabled или asset_not_accepted. |
| message / action | string | если доступно | Пояснение для продавца и идентификатор действия: chain_connections, wallets, rates, payment_methods, project_settings или store_settings. Без секретов и приватных URL провайдеров. |
| required_endpoint_role | string | null | ончейн | Предпочтительная роль API сканера - устаревшее поле. Полный список совместимости в accepted_endpoint_roles. Базовая работоспособность узла не доказывает доступность истории платежей. |
| accepted_endpoint_roles | string[] | null | ончейн | Совместимые варианты API, не доказательство истории или ресурсов эндпоинта. Прямой node-rpc поддерживает BTC/BCH/LTC/DOGE/DASH и прозрачный ZEC - полные декодированные блоки, 1–48 подтверждений, solidified нативный TRX, нативный ALGO через algod, XTZ через Octez, финализированный DOT Asset Hub через метаданные SCALE и нативный XLM через Stellar RPC с memo ID счёта. Обрезанная или неполная история не подходит. Эти прямые адаптеры не добавляют токены. Индексированные API остаются альтернативами; смотри таблицу ниже. Смешанные прямые и индексированные источники независимо проверяют ограниченные окна; по умолчанию нужны два независимых провайдера, не псевдонимы одного оператора. Высота узла, сведения сети ORDnet и EVM-relay для не-EVM приёма не доказывают платёж. Monero всё ещё требует привязанный к проекту view-only wallet-RPC. |
| healthy_endpoints | integer | ончейн | Исправные подходящие эндпоинты, не число независимых провайдеров. |
| usable_independent_providers / required_independent_providers | integer | ончейн | Доступные проверочные слоты, максимум два. required_independent_providers - настройка сети: 2 по умолчанию или 1 после явного выбора администратора. При двух провайдерах должны различаться и ключи провайдеров, и хосты. Отключённые, устаревшие более чем на десять минут или остывающие источники слот не занимают. У Lightning свои правила подключения. |
| last_checked_at | timestamp | null | ончейн | Последняя проверка подходящего эндпоинта, отдельно от времени оценки. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request PUT \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets/YOUR_ASSET_ID/confirmation-policy" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Content-Type: application/json' \
--data-raw '{
"strategy": "custom",
"required_confirmations": 0
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const body = `{
"strategy": "custom",
"required_confirmations": 0
}`;
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets/YOUR_ASSET_ID/confirmation-policy", {
method: "PUT",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$body = <<<'JSON'
{
"strategy": "custom",
"required_confirmations": 0
}
JSON;
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets/YOUR_ASSET_ID/confirmation-policy");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"strategy": "custom",
"required_confirmations": 0
}""".encode("utf-8")
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets/YOUR_ASSET_ID/confirmation-policy",
method="PUT", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"data": [
{
"asset": { "id": "YOUR_ASSET_ID", "chain_slug": "bitcoin", "symbol": "BTC" },
"selected": true,
"display_order": 0,
"confirmation_policy": {
"finality_mode": "confirmations",
"project_required_confirmations": 2,
"override_required_confirmations": 0,
"effective_required_confirmations": 0,
"editable": true,
"minimum_required_confirmations": 0,
"maximum_required_confirmations": 10000
},
"wallet_readiness": "ready"
}
]
}GETСписок кошельков и балансов проекта/v1/projects/{project_id}/walletsТолько чтение
Возвращает публичные метаданные кошелька и все зарегистрированные активы с поддержкой баланса в его точной сети. Охвачены все 30 нативных сетей, а также проверенные ERC-20 и SPL. Активы появляются сразу, даже до первого сканирования или если не принимаются к оплате. project_enabled показывает разрешение оплаты; tracking_active отдельно - пригодность обновления только для чтения. Monero требует привязанный к проекту внешний view-only wallet-RPC. «Сканировать балансы» в консоли приоритетно выполняет ограниченные чтения с прогрессом и ошибками по активам; свежие итоги обновляются только по полным циклам. Зачисление счёта определяется мониторингом транзакций и политикой подтверждений, не этими кешированными балансами.
- Этот bearer-маршрут не возвращает сид-фразы, закрытые ключи, зашифрованные секреты или методы расходования средств.
- Новый актив той же сети до первого полного сканирования возвращается с null-балансами и pending, никогда с выдуманным нулём.
- Отключение проекта, кошелька для приёма, нативного способа или отдельного актива не прекращает чтение балансов. Активные и отключённые кошельки с основным адресом продолжают обновлять все зарегистрированные поддерживаемые активы своей сети. Кошельки pending и error не сканируются.
- project_enabled отражает только разрешение актива проектом и может быть false при tracking_active равном true.
- balance и balance_atomic - точные строки; price_usd, value_usd и total_value_usd - справочные, могут быть null. Свежий баланс не гарантирует свежую рыночную цену.
- Оценка предпочитает цены CoinGecko не старше двух часов. Нативные монеты и проверенные канонические USDC/USDT могут использовать включённые котировки USD Kraken/Binance не старше пяти минут, начиная с основного провайдера. Привязка к доллару не предполагается, цена пользовательского токена не берётся только по тикеру; фиксированные и DEX-цены проекта отдельны. Котировки счетов не меняются.
- Pending означает отсутствие полного снимка. Refreshing сохраняет последнюю полную сумму и checked_at; это не ожидание блокчейн-перевода. Stale/error тоже могут сохранять старые суммы. Не считай недоступный кеш нулём или пропущенным платежом. Обычные обновления EVM/Solana повторно используют недавно проверенные пустые адреса до 30 минут между аудитами, а пополненные, новые и изменённые проверяют снова. Явное «Сканировать балансы» запрашивает полное сканирование.
- recent_payments ограничен тремя наблюдениями на кошелёк и исключает недействительную историю.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_MERCHANT_API_TOKEN |
| Accept | рекомендуется | application/json |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| project_id | path UUID | Включённый проект, назначенный ключу. |
WalletSummary
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| id / project_id / native_asset_id | UUID | всегда | Идентификаторы кошелька, проекта-владельца и нативного актива сети. |
| chain_slug / network | string | всегда | Блокчейн и сеть кошелька. |
| asset_symbol / asset_name | string | всегда | Отображаемое имя нативного актива. |
| status | pending | active | disabled | error | всегда | Рабочее состояние кошелька. |
| label | string | всегда | Метка оператора. |
| public_key / primary_address | string | null | всегда | Публичные данные кошелька; сид-фраза и закрытый ключ не раскрываются. |
| derivation_scheme / address_format | string | null | всегда | Политика и формат адресов. |
| backup_confirmed_at | timestamp | null | всегда | Не null после подтверждения оператором резервной копии для восстановления. |
| activation_required / activation_verified_at | boolean / timestamp|null | всегда | Общие аккаунты XRP и Stellar недоступны, пока оператор не пополнит показанный адрес, а настроенные провайдеры не проверят именно этот аккаунт. Сохранённое доказательство не истекает; текущая работоспособность сканеров проверяется отдельно для платежей, не создания счетов. |
| receive_readiness | ReceiveReadiness | null | 5.5.0+ | В списках кошельков: настройка приёма проекта и требования сканера сети. Отдельно от балансов, газа токенов и готовности отправки. Другие ответы кошелька могут оставлять null. |
| monero_wallet_rpc | MoneroWalletRpcBinding | null | всегда | Очищенное состояние привязки внешнего view-only wallet-RPC Monero. Включает эндпоинт, режим авторизации, основной адрес account-0, флаги и высоты технических доказательств и время подтверждений оператора; данные доступа, ключи и файлы кошелька не сериализуются. |
| last_secret_revealed_at / secret_reveal_count | timestamp|null / integer | всегда | Метаданные аудита раскрытия секретов в консоли. |
| next_receive_index | integer | всегда | Следующий зарезервированный индекс дочернего адреса. |
| last_scanned_height / last_scanned_at / last_error | integer|null / timestamp|null / string|null | всегда | Состояние сканера кошелька. |
| balances | WalletAssetBalance[] | всегда | Кешированные балансы всех 30 нативных сетей и проверенных ERC-20 и SPL. Для Monero нужен настроенный внешний view-only wallet-RPC. |
| total_value_usd | decimal string | null | всегда | Справочная сумма балансов с текущей ценой USD. |
| balance_status | pending | refreshing | fresh | stale | error | unknown | всегда | Общая актуальность кеша; unknown - защитное значение. Ни одно из этих состояний не доказывает оплату счёта. |
| balance_checked_at | timestamp | null | всегда | Самая старая релевантная успешная проверка баланса в агрегате. |
| recent_payments | WalletRecentPayment[] | всегда | До трёх последних действительных наблюдений detected, confirming или final, относящихся именно к этому кошельку. |
| created_at / updated_at | RFC 3339 timestamp | всегда | Время создания и последнего обновления кошелька. |
WalletAssetBalance
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| wallet_id / asset_id | UUID | всегда | Идентификаторы кошелька и постоянного актива. |
| project_enabled | boolean | всегда | Включён ли актив текущей политикой проекта. |
| active_store_count | integer | всегда | Число включённых магазинов, выбравших актив. Это отражение приёма; чтение балансов независимо. |
| active_store_ids | UUID[] | всегда | Включённые магазины проекта, сейчас принимающие актив. Позволяет точно фильтровать магазины локально без ещё одного API-запроса. |
| tracking_active | boolean | всегда | Подходят ли читаемый кошелёк и зарегистрированный актив той же сети для фонового обновления баланса. Переключатели проекта и способов оплаты не приостанавливают чтение. |
| asset_kind | native | token | всегда | Нативная валюта или проверенный контракт/mint. |
| contract_address | string | null | всегда | Контракт токена или mint; null для нативной валюты. |
| symbol / name / decimals | string / string / integer | всегда | Отображаемое имя и точность минимальных единиц. |
| coingecko_id | string | null | всегда | Идентификатор цены, если сопоставлен. |
| balance / balance_atomic | decimal string|null / integer string|null | всегда | Точный отображаемый баланс и баланс минимальных единиц по основному адресу и выданным адресам счетов. Null, пока полное значение недоступно. |
| price_usd | decimal string | null | всегда | Справочная кешированная цена единицы в USD для оценки. |
| value_usd | decimal string | null | всегда | Справочная фиатная оценка при наличии текущего курса. |
| status | pending | refreshing | fresh | stale | error | всегда | Состояние кешированного сканирования. refreshing может сохранять завершённый баланс: возраст смотри в checked_at. Pending означает отсутствие полного снимка. Эти состояния не доказывают ни ожидающий перевод, ни оплату счёта. |
| checked_at | timestamp | null | всегда | Время, которому соответствует завершённое сканирование баланса. |
| last_error | string | null | всегда | Безопасная диагностика для оператора. |
WalletRecentPayment
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| invoice_public_id | UUID | всегда | Публичный идентификатор счёта, связанного с наблюдением. |
| chain_slug / symbol | string | всегда | Сеть и отображаемый символ нативной монеты или проверенного токена. |
| transaction_id / event_index | string / integer | всегда | Канонические идентификаторы транзакции и события перевода. |
| amount | decimal string | всегда | Точная обнаруженная сумма актива без преобразования в float. |
| status | detected | confirming | final | всегда | Текущее действительное состояние наблюдения. Реорганизованные, заменённые и недействительные исключаются. |
| confirmations | integer | всегда | Последнее наблюдаемое число подтверждений. |
| observed_at | RFC 3339 timestamp | всегда | Время первого обнаружения платежа Wholly Crypto. |
ReceiveReadiness
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| ready | boolean | всегда | Проверки настройки приёма пройдены. Не описывает готовность отправки, газ, обновление баланса и не гарантирует будущую котировку. |
| invoice_creatable | boolean | 6.0.6+ | Конфигурация разрешает способ счёта, несмотря на временные предупреждения сканера. Цена валюты проверяется при создании. Это не проверка платежа: ready может быть false, когда invoice_creatable - true. Отсутствие кошелька, отключённая политика и неподдерживаемые адаптеры по-прежнему безопасно блокируют действие. |
| checked_at | timestamp | всегда | Время оценки. Получение списка не делает сетевых запросов и не выделяет адреса. |
| issues | PaymentMethodIssue[] | всегда | Пусто при готовности; иначе предупреждение приёма или блокирующая настройка. Проверяй invoice_creatable, чтобы отличить временную проблему сканера от ошибки настройки счёта. |
PaymentMethodIssue
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| chain_slug / asset_id / asset_ticker | string / UUID / string | если известно | Указывает затронутые сеть и актив. Для Lightning asset_id может отсутствовать. |
| reason_code | string | всегда | scanner_provider_quorum, scanner_not_checked, scanner_unavailable, wallet_missing, wallet_disabled, wallet_backup_required, wallet_key_unavailable, wallet_activation_required, monero_binding_unavailable, rate_unavailable, custom_rate_unavailable, lightning_unavailable, project_disabled, store_disabled, chain_disabled, asset_disabled или asset_not_accepted. |
| message / action | string | если доступно | Пояснение для продавца и идентификатор действия: chain_connections, wallets, rates, payment_methods, project_settings или store_settings. Без секретов и приватных URL провайдеров. |
| required_endpoint_role | string | null | ончейн | Предпочтительная роль API сканера - устаревшее поле. Полный список совместимости в accepted_endpoint_roles. Базовая работоспособность узла не доказывает доступность истории платежей. |
| accepted_endpoint_roles | string[] | null | ончейн | Совместимые варианты API, не доказательство истории или ресурсов эндпоинта. Прямой node-rpc поддерживает BTC/BCH/LTC/DOGE/DASH и прозрачный ZEC - полные декодированные блоки, 1–48 подтверждений, solidified нативный TRX, нативный ALGO через algod, XTZ через Octez, финализированный DOT Asset Hub через метаданные SCALE и нативный XLM через Stellar RPC с memo ID счёта. Обрезанная или неполная история не подходит. Эти прямые адаптеры не добавляют токены. Индексированные API остаются альтернативами; смотри таблицу ниже. Смешанные прямые и индексированные источники независимо проверяют ограниченные окна; по умолчанию нужны два независимых провайдера, не псевдонимы одного оператора. Высота узла, сведения сети ORDnet и EVM-relay для не-EVM приёма не доказывают платёж. Monero всё ещё требует привязанный к проекту view-only wallet-RPC. |
| healthy_endpoints | integer | ончейн | Исправные подходящие эндпоинты, не число независимых провайдеров. |
| usable_independent_providers / required_independent_providers | integer | ончейн | Доступные проверочные слоты, максимум два. required_independent_providers - настройка сети: 2 по умолчанию или 1 после явного выбора администратора. При двух провайдерах должны различаться и ключи провайдеров, и хосты. Отключённые, устаревшие более чем на десять минут или остывающие источники слот не занимают. У Lightning свои правила подключения. |
| last_checked_at | timestamp | null | ончейн | Последняя проверка подходящего эндпоинта, отдельно от времени оценки. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/wallets" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/wallets", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/wallets");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/wallets",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"data": [
{
"id": "55555555-5555-4555-8555-555555555555",
"project_id": "11111111-1111-4111-8111-111111111111",
"native_asset_id": "10000000-0000-4000-8000-000000000003",
"chain_slug": "ethereum",
"network": "mainnet",
"asset_symbol": "ETH",
"asset_name": "Ethereum",
"status": "active",
"label": "Primary Ethereum wallet",
"public_key": "0x…",
"primary_address": "0x…",
"derivation_scheme": "bip44",
"address_format": "eip55",
"backup_confirmed_at": "2026-08-31T17:00:00Z",
"last_secret_revealed_at": null,
"secret_reveal_count": 0,
"next_receive_index": 43,
"last_scanned_height": 23123456,
"last_scanned_at": "2026-08-31T18:05:00Z",
"last_error": null,
"balances": [
{ "wallet_id": "55555555-5555-4555-8555-555555555555", "asset_id": "10000000-0000-4000-8000-000000000003", "project_enabled": true, "tracking_active": true, "asset_kind": "native", "contract_address": null, "symbol": "ETH", "name": "Ethereum", "decimals": 18, "coingecko_id": "ethereum", "balance": "0.125", "balance_atomic": "125000000000000000", "price_usd": "4500", "value_usd": "562.50", "status": "fresh", "checked_at": "2026-08-31T18:05:00Z", "last_error": null },
{ "wallet_id": "55555555-5555-4555-8555-555555555555", "asset_id": "10000000-0000-4000-8000-000000000099", "project_enabled": false, "tracking_active": true, "asset_kind": "token", "contract_address": "0xA0b86991c6218b36c1d19d4a2e9eb0cE3606eB48", "symbol": "USDC", "name": "USDC", "decimals": 6, "coingecko_id": "usd-coin", "balance": null, "balance_atomic": null, "price_usd": "1", "value_usd": null, "status": "pending", "checked_at": null, "last_error": null }
],
"total_value_usd": "562.50",
"balance_status": "fresh",
"balance_checked_at": "2026-08-31T18:05:00Z",
"recent_payments": [
{ "invoice_public_id": "0a6a98db-d93d-48ee-8c3c-fd45f90c4a50", "chain_slug": "ethereum", "symbol": "USDC", "transaction_id": "0x…", "event_index": 0, "amount": "25", "status": "final", "confirmations": 12, "observed_at": "2026-08-31T18:04:00Z" }
],
"created_at": "2026-08-31T16:00:00Z",
"updated_at": "2026-08-31T18:05:00Z"
}
]
}POSTСоздать счёт/v1/projects/{project_id}/stores/{store_id}/invoicesЧтение и запись
Атомарно создаёт счёт с адресами кошельков, свежими точными котировками, аудитом и исходящей очередью уведомлений. Повтор идентичных байтов исходного тела с теми же ключом доступа и Idempotency-Key возвращает исходный счёт.
- payment_methods фильтрует включённые способы магазина только для этого счёта. Отсутствие или null сохраняет все способы; [] недопустим. Подсказку chain_slug и тикеры смотри в Проект → Магазины → Способы оплаты. Список payment-assets API даёт chain_slug, asset.symbol и asset.id. Для разрешённых токенов Ethereum используй {chain_slug: ethereum, asset_tickers: [USDC, USDT]}; BTC и PEPE работают так же в выбранных сетях. Тикеры не зависят от регистра, ограничены сетью и разрешаются только внутри магазина. Два разрешённых контракта с одним тикером дают 400, даже если один не готов; тогда используй asset_ids. Правила одинаковы для нативных активов, каталожных и пользовательских токенов. Каждая пара сети и способа может встречаться один раз; итог - до 64 способов. Версия 5.4.0+: неизвестные, отключённые, чужие для сети или неразрешённые варианты игнорируются. Если весь выбор не содержит активных разрешённых совпадений, берутся настройки магазина; иначе - только совпадения. Запись только с сетью включает все её активные разрешённые ончейн-активы. Выбранным активным способам нужны корректные кошельки, установленные адаптеры и надёжные цены. С 6.0.6 недоступные сканеры, паузы и ожидающие или устаревшие проверки здоровья не блокируют создание и не убирают настроенные ончейн-способы. Обнаружение повторяется автоматически; зачисление всё равно требует кворума и подтверждений. Следи за receive_readiness и доступностью провайдеров: счёт может оставаться непроверенным до восстановления сканеров. Выделение субадресов Monero и создание BOLT11 Lightning всё ещё требуют внешнего сервиса кошелька или узла. Ошибки возвращают error.message и error.details.payment_methods с chain_slug, asset_ticker, reason_code, а для сканеров - required_endpoint_role, healthy_endpoints и required_independent_providers. TRON принимает индексированную историю или поддерживаемые прямые API solidified-блоков нативных переводов; базовое здоровье не доказывает совместимость сканера. Ошибки цены указывают актив и валюту. Ничто не включает неразрешённый актив и не меняет политику магазина. До 5.4.0 явные неизвестные или неактивные варианты приводят к ошибке. Способы существующего счёта не расширяются при смене магазина. Lightning выбирается отдельно. Повторы сохраняют исходные способы; изменение выбора с тем же Idempotency-Key даёт 409.
- checkout_appearance поддерживает все перечисленные настройки оформления. Непереданные поля наследуются, массивы заменяются, вложенные поля сообщений объединяются; пустой объект сообщения очищает свою область. Итоговое оформление и изображения сохраняются для счёта без изменения магазина. Результат смотри в appearance публичного JSON оплаты. Весь запрос ограничен 32 КиБ, итоговые настройки - 20 КиБ.
- Изменение checkout_appearance с тем же Idempotency-Key даёт 409; повторяй идентичные байты. Оформление не меняет суммы, курсы, активы, подтверждения, реальный статус или права встраивания. Без HTML, CSS, скриптов и загрузки внешних изображений.
- exchange_rate_spread_percent переопределяет наценку магазина для счёта: не передавай или передай null для наследования, либо "0" для отключения. Существующие котировки не меняются.
- Наценка применяется до округления вверх. Комиссии считаются от исходной фиатной суммы счёта без наценки.
- Всегда отправляй возвращённый expected_amount или expected_amount_atomic. Округление вверх ограничено точностью актива, 0.1% суммы и одной минимальной единицей фиата.
- Повторы сохраняют тот же ключ доступа, Idempotency-Key и точные байты тела. Смена наценки с тем же ключом даёт 409 idempotency_conflict.
- Точный повтор проверяется до новой котировки, DNS уведомления или подготовки адреса. Область ключа и доступ к проекту и магазину проверяются при каждом запросе.
- Действующий ipn_url требует IPN-секрет магазина. Неизвестные поля тела отклоняются.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_MERCHANT_API_TOKEN |
| Idempotency-Key | обязательно | Уникальные 1–128 видимых ASCII-символов без пробелов. |
| Content-Type | рекомендуется | application/json. Текущий обработчик исходного тела разбирает JSON без обязательной проверки media type. |
| Accept | рекомендуется | application/json |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| project_id | path UUID | Скопируй API ID проекта из Проект → Настройки → API ID. Он должен быть назначен ключу; читаемый идентификатор проекта не принимается. |
| store_id | path UUID | Скопируй API ID магазина из Проект → Магазины → выбери магазин → Основное → API ID. Нужен даже для магазина по умолчанию; магазин должен быть включён и принадлежать project_id. |
Тело создания счёта
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| amount | string | обязательно | Обычная неотрицательная десятичная строка без знака и экспоненты, до 48 целых и 30 дробных цифр. По умолчанию сумма положительна. В Магазины → Счета можно разрешить нулевые счета; они завершаются без получения средств, выделения адресов и комиссии обработки. |
| currency | string | null | необязательно | Поддерживаемая трёхбуквенная фиатная валюта, нормализуется в верхний регистр. Отсутствие или null наследует валюту счёта магазина. Нужен и независимо доступный курс расчёта оплаты сервиса. |
| payment_methods | InvoicePaymentSelection[] | null | необязательно | Выбор включённых способов магазина для этого счёта. С 5.4.0 неизвестные, неактивные и неразрешённые игнорируются; без совпадений берутся настройки магазина. Отсутствие/null тоже использует их; [] недопустим. Не включает способы и не меняет магазин. Схема выбора ниже. |
| order_id | string | null | необязательно | Номер заказа продавца, 1–128 символов после обрезки пробелов; управляющие символы запрещены. |
| string | null | необязательно | Email покупателя только для продавца, нормализованный практический ASCII-адрес до 254 символов. Отсутствие или null не сохраняет email. | |
| description | string | null | необязательно | Описание для покупателя, 1–500 символов; переносы строк и табуляция разрешены. |
| expires_in_seconds | integer | null | необязательно | Срок котировки счёта от 300 до 86 400 секунд; отсутствие или null наследует политику магазина. |
| exchange_rate_spread_percent | decimal string | null | необязательно | Наценка котировки от 0 до 100, до двух знаков после запятой. Отсутствие/null наследует магазин; "0" отключает для счёта. Применяется до округления вверх и фиксируется. Не меняет фиатную сумму и базу комиссии обработки. |
| underpayment_tolerance_percent | decimal string | null | необязательно | Допустимая недоплата от 0 до 99.99, до двух знаков после запятой. Отсутствие/null наследует магазин. |
| ipn_url | string | null | необязательно | Публичный HTTPS-адрес уведомлений до 2 048 байт без данных доступа и фрагмента. Переопределяет магазин; null или отсутствие наследует его. |
| redirect_url | string | null | необязательно | HTTPS URL успеха после зачисления, до 2 048 байт без встроенных данных доступа. Отсутствие/null наследует магазин, не очищает значение. |
| cancel_url | string | null | необязательно | HTTPS URL возврата при завершении без успешной оплаты. Отсутствие/null наследует магазин, не очищает значение. |
| redirect_automatically | boolean | null | необязательно | Отсутствие/null наследует магазин. true требует действующий redirect_url. |
| language | string | null | необязательно | Английский или немецкий тег BCP 47, например en, de или de-DE; отсутствие/null наследует магазин. |
| checkout_appearance | CheckoutAppearanceOverride | null | необязательно | Частичные настройки оформления счёта. Отсутствие/null следует текущему оформлению магазина. Объект, включая {}, фиксирует итоговый дизайн и изображения при создании. Схема ниже; без финансовых настроек, HTML, CSS, JavaScript и URL внешних изображений. |
| metadata | object | null | необязательно | JSON-объект только для продавца; отсутствие/null становится {}, максимум 4 096 байт и пять уровней вложенности. firstname, lastname, street, street2, zip, city, country, countryiso2, company и vatid проверяются, нормализуются и выводятся в сводные поля покупателя. |
InvoicePaymentSelection · выбор сетей и активов магазина
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| chain_slug | string | обязательно | Скопируй chain_slug в Проект → Магазины → Способы оплаты или получи через GET /v1/projects/{project_id}/stores/{store_id}/payment-assets, например ethereum, base или bitcoin. Пара сети и способа может встречаться только один раз. |
| asset_ids | UUID[] | null | необязательно | Ончейн UUID asset.id, не адреса контрактов и не ID способов счёта. Используй это ИЛИ asset_tickers. Не передавай оба селектора, чтобы выбрать все активные разрешённые активы сети. [] и дублирующиеся/нулевые ID недопустимы. С 5.4.0 неактивные или неразрешённые здесь ID игнорируются; полностью несовпавший выбор использует настройки магазина. |
| asset_tickers | string[] | null | необязательно | Версия продавца 5.3.0+. Символы вроде BTC, USDC или PEPE в рамках chain_slug и магазина. 1–64 уникальных тикера; пробелы по краям удаляются, регистр не важен, 1–40 ASCII-букв, цифр, точек, подчёркиваний или дефисов. Используй это ИЛИ asset_ids. С 5.4.0 неизвестные, неактивные и неразрешённые тикеры игнорируются. Неоднозначные разрешённые символы дают ошибку: используй asset_ids. Выбранным активным способам нужны кошельки и цены; временный простой ончейн-сканера с 6.0.6 создание не блокирует. Lightning может принимать только BTC. |
| payment_rail | onchain | lightning | необязательно | По умолчанию onchain. Для Bitcoin Lightning используй {chain_slug: bitcoin, payment_rail: lightning} без asset_ids; asset_tickers можно задать как [BTC]. Ончейн-Bitcoin не включает Lightning. Подключение Lightning магазина уже должно быть включено и готово. |
CheckoutAppearanceOverride · все поля необязательны
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| inherit_default_store | boolean | необязательно | true берёт за основу дизайн магазина проекта по умолчанию; иначе - действующий дизайн целевого магазина. Затем применяются и отдельно сохраняются изменения; итоговый флаг счёта false. |
| title | string | необязательно | Заголовок оплаты до 120 символов. Пустой использует стандартный заголовок. |
| intro / outro | string | необязательно | Обычный текст до 2 000 символов каждый. Intro вверху, Outro внизу при любом состоянии. Переносы сохраняются, безопасные текстовые URL становятся ссылками. Пустая строка очищает. Старый customer_message принимается как псевдоним intro; не передавай оба. |
| intro_font_size / outro_font_size | integer | необязательно | Пиксели: 12, 14, 16, 18, 20 или 24. По умолчанию 16, если не унаследовано другое. |
| theme | system | light | dim | dark | необязательно | Следовать устройству покупателя или использовать фиксированную тему. |
| accent_color / background_color / card_color / button_color | string | необязательно | #RRGGBB. Фон, карточка и кнопка могут быть пустыми для автоматических цветов. Контраст текста автоматический. |
| logo_size / logo_alignment | string | необязательно | small, medium или large; left или center. |
| images | object | необязательно | Ключи logo_light, logo_dark, favicon. Пропущенный сохраняет базовое изображение; null удаляет. Объект {store_id: UUID, kind?: logo_light|logo_dark|favicon} использует действующее загруженное изображение магазина В ТОМ ЖЕ проекте. kind по умолчанию соответствует целевому ключу. Сначала загрузи в Магазин → Оформление; API ID магазина скопируй из Основное → API ID. Нет изображения или чужой проект - 400. Внешние URL и байты изображений не принимаются. |
| show_order_id / show_description / details_expanded | boolean | необязательно | Показывать подробности ID заказа и обычное описание под заголовком. details_expanded изначально раскрывает ID заказа. Только отображение, не удаление данных. |
| show_project_name / show_store_name | boolean | необязательно | Версия 5.6.0+: показать или скрыть каждое имя в заголовке оплаты. Оба по умолчанию true. Также доступно в Магазин → Оформление; наследуется и фиксируется для счёта как прочие настройки. Только отображение, не удаление данных. |
| featured_chains | string[] | необязательно | Упорядоченные slug сетей, до 60 уникальных значений: строчные буквы, цифры, дефисы, до 64 символов. [] очищает. Меняется порядок только доступных способов счёта. |
| featured_asset_ids / default_asset_id | UUID[] / UUID|null | необязательно | До 100 уникальных упорядоченных ID активов; [] очищает. Актив по умолчанию может быть null. ID берутся из payment-assets, не ID платёжных намерений. Способы не включаются; полученные платежи и действительные предпочтения покупателя приоритетнее. |
| messages | object | необязательно | Объекты en/de с обычными строками waiting, confirming, paid, underpaid, expired по 500 символов. Меняются только переданные языки/состояния; {} очищает всё, {en:{}} - английский, пустая строка - отдельное состояние. Запасной язык английский. Реальный статус не заменяет. |
| support_email | string | необязательно | ASCII email до 254 символов. Пустой очищает. |
| support_url / terms_url / privacy_url | string | необязательно | HTTPS URL до 2 048 символов без данных доступа. Пустой очищает. Ссылки открываются в новом окне. |
| return_button_text | string | необязательно | Подпись до 60 символов. Для поведения счёта используй верхнеуровневые redirect_url/cancel_url/redirect_automatically/language. |
Сводка счёта
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| id | UUID | всегда | Внутренний UUID счёта. Не используй в путях подробностей продавца или оплаты. |
| invoice_id | UUID | всегда | Публичный UUID счёта для путей подробностей продавца и оплаты. |
| project_id | UUID | всегда | Проект-владелец. |
| store_id | UUID | всегда | Магазин-владелец. |
| source | manual | api | всегда | Как создан счёт. |
| order_id | string | null | всегда | Номер заказа продавца. |
| string | null | всегда | Email покупателя только для продавца. Не возвращается публичной оплатой. | |
| customer_name | string | null | всегда | Отображаемое имя из приватных метаданных firstname, lastname и company. |
| customer_address | string | null | всегда | Однострочный адрес для продавца из приватных метаданных company, street, street2, zip, city, country, countryiso2 и vatid. |
| description | string | null | всегда | Описание для покупателя. |
| amount | decimal string | всегда | Каноническая сумма счёта. |
| currency | string | всегда | Нормализованный код валюты или актива счёта. |
| exchange_rate_spread_percent | decimal string | всегда | Зафиксированная наценка: переопределение при создании или настройка магазина, если не передано. Применяется до округления вверх; для этого счёта не меняется. |
| underpayment_tolerance_percent | decimal string | всегда | Неизменяемый процент допустимой недоплаты, сохранённый при создании счёта. |
| status | invoice status | всегда | new, processing, settled, expired, invalid или cancelled. |
| amount_status | amount status | всегда | none, partial, paid или overpaid. Явно разрешённый счёт на нулевую сумму завершается с none и без способов оплаты. |
| timing_status | timing status | всегда | on_time или late. |
| resolution | resolution | всегда | automatic, manually_settled или manually_invalidated. |
| sequence | integer | всегда | Монотонная последовательность состояния счёта, начиная с 1. |
| winning_payment_intent_id | UUID | null | всегда | Способ оплаты, завершивший счёт, если выбран. |
| expires_at | RFC 3339 timestamp | всегда | Срок котировки и оплаты. |
| monitoring_expires_at | RFC 3339 timestamp | всегда | Самый поздний настроенный срок отслеживания поздних платежей среди способов. |
| settled_at | timestamp | null | всегда | Время окончательного зачисления при settled. |
| cancelled_at | timestamp | null | всегда | Время отмены при cancelled. |
| archived_at | timestamp | null | всегда | Время архивирования, если выполнено. |
| created_at | RFC 3339 timestamp | всегда | Время создания. |
| updated_at | RFC 3339 timestamp | всегда | Время последнего обновления состояния. |
Дополнения подробностей счёта
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| ipn_url | string | null | всегда | Действующий IPN-адрес этого счёта. Только в ответе продавцу; не в публичной оплате. |
| redirect_url | string | null | всегда | Действующий URL успеха после зачисления. |
| cancel_url | string | null | всегда | Действующий URL возврата при завершении оплаты без успеха. |
| redirect_automatically | boolean | всегда | Перенаправлять ли автоматически после успешной оплаты. |
| checkout_language | string | всегда | Действующий языковой тег оплаты. |
| metadata | object | всегда | Метаданные продавца. Не возвращаются публичной оплатой. |
| payment_intents | PaymentIntent[] | всегда | Котированные способы оплаты и состояние мониторинга. |
PaymentIntent
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| id | UUID | всегда | ID платёжного намерения; также intent_id QR-кода оплаты. |
| payment_rail | onchain | lightning | всегда | Канал оплаты счёта. Ончейн-Bitcoin и Lightning могут иметь один asset_id; используй ID намерения и это поле, не только символ. Отличается от scanner payment_rail каталога активов. |
| bolt11 | string | null | всегда | Запрос оплаты Lightning, иначе null. Плати через Lightning-кошелёк, не отправляй ончейн-средства на хеш платежа. |
| asset_id | UUID | всегда | Идентификатор настроенного платёжного актива. |
| asset_key | string | всегда | Канонический CAIP-подобный ключ актива. |
| chain_slug | string | всегда | Идентификатор сети Wholly Crypto. |
| network | string | всегда | Настроенная сеть; сейчас mainnet для поддерживаемых платёжных активов. |
| caip_network_id | string | всегда | Канонический идентификатор сети CAIP-2. |
| caip_asset_id | string | null | всегда | Канонический CAIP-19, если зарегистрирован. |
| symbol | string | всегда | Символ актива. |
| asset_decimals | integer | всегда | Точность минимальных единиц. BTC Lightning использует 11 - миллисатоши, не 8 как ончейн-Bitcoin. Котировки в целых сатоши, поступления сохраняют точность миллисатоши. |
| status | intent status | всегда | pending, partial, paid, overpaid, expired или invalid. |
| finality_mode | confirmations | finalized | всегда | Политика финальности. |
| required_confirmations | integer | всегда | Нужное число подтверждений, если применимо. |
| quote_rate | decimal string | всегда | Единицы актива на одну единицу валюты счёта с зафиксированной наценкой. Например 1.02 USDC на USD. Не обратный курс. |
| quote_details | object | null | всегда | Источники зафиксированной котировки: reference_rate до наценки, unrounded_payment_amount, rounding_adjustment, pricing_provider, asset_provider, pricing_fetched_at и asset_fetched_at. Null у старых счетов; исторические значения не выдумываются. |
| expected_amount | decimal string | всегда | Точная зафиксированная сумма актива после наценки и округления вверх. С 4.1.1 распознанные проверенные фиатные стейблкоины - USDC, USDT, DAI, USDS, EURC - округляются вверх максимум до двух знаков: 1.321 становится 1.33, не 1.32. Это ожидаемая сумма даже с нулевым допуском. Другие активы сохраняют адаптивную точность. Существующие счета не пересчитываются. |
| expected_amount_atomic | integer string | всегда | Точная сумма в минимальной единице актива. |
| minimum_payment_amount | decimal string | всегда | Наименьшая сумма, принимаемая как оплата после допуска счёта. |
| minimum_payment_amount_atomic | integer string | всегда | Точный допустимый порог в минимальной единице актива. |
| received_amount | decimal string | всегда | Обнаруженная сумма. |
| received_amount_atomic | integer string | всегда | Обнаруженная сумма в минимальных единицах. |
| confirmed_amount | decimal string | всегда | Подтверждённая/финальная сумма. |
| confirmed_amount_atomic | integer string | всегда | Подтверждённая/финальная сумма в минимальных единицах. |
| destination_address | string | всегда | Ончейн-адрес приёма или 64-символьный хеш платежа Lightning. Для Lightning используй bolt11; его хеш - не Bitcoin-адрес. |
| destination_tag | string | null | всегда | Обязательная публичная ссылка платежа для соответствующих сетей: destination tag XRP, memo ID Stellar или комментарий счёта TON. Null для способов с уникальным адресом. |
| derivation_index | integer | всегда | Зарезервированный индекс дочернего адреса; только в подробностях продавца. |
| quote_expires_at | RFC 3339 timestamp | всегда | Истечение котировки. |
| monitoring_expires_at | RFC 3339 timestamp | всегда | Конец отслеживания поздних платежей этого способа. |
| next_check_at | timestamp | null | всегда | Следующая плановая проверка сети. |
| last_checked_at | timestamp | null | всегда | Последняя проверка сети. |
| last_chain_height | integer | null | всегда | Последняя достоверная высота, увиденная монитором. |
| last_anchor_hash | string | null | всегда | Последний опорный хеш или хеш блока монитора. |
| last_monitor_error | string | null | всегда | Безопасная диагностика мониторинга для операторов. |
| first_payment_at | timestamp | null | всегда | Время первого обнаружения платежа. |
| fully_paid_at | timestamp | null | всегда | Время первого достижения допустимого минимума. |
| finalized_at | timestamp | null | всегда | Время выполнения политики финальности платежом. |
PaymentMethodIssue
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| chain_slug / asset_id / asset_ticker | string / UUID / string | если известно | Указывает затронутые сеть и актив. Для Lightning asset_id может отсутствовать. |
| reason_code | string | всегда | scanner_provider_quorum, scanner_not_checked, scanner_unavailable, wallet_missing, wallet_disabled, wallet_backup_required, wallet_key_unavailable, wallet_activation_required, monero_binding_unavailable, rate_unavailable, custom_rate_unavailable, lightning_unavailable, project_disabled, store_disabled, chain_disabled, asset_disabled или asset_not_accepted. |
| message / action | string | если доступно | Пояснение для продавца и идентификатор действия: chain_connections, wallets, rates, payment_methods, project_settings или store_settings. Без секретов и приватных URL провайдеров. |
| required_endpoint_role | string | null | ончейн | Предпочтительная роль API сканера - устаревшее поле. Полный список совместимости в accepted_endpoint_roles. Базовая работоспособность узла не доказывает доступность истории платежей. |
| accepted_endpoint_roles | string[] | null | ончейн | Совместимые варианты API, не доказательство истории или ресурсов эндпоинта. Прямой node-rpc поддерживает BTC/BCH/LTC/DOGE/DASH и прозрачный ZEC - полные декодированные блоки, 1–48 подтверждений, solidified нативный TRX, нативный ALGO через algod, XTZ через Octez, финализированный DOT Asset Hub через метаданные SCALE и нативный XLM через Stellar RPC с memo ID счёта. Обрезанная или неполная история не подходит. Эти прямые адаптеры не добавляют токены. Индексированные API остаются альтернативами; смотри таблицу ниже. Смешанные прямые и индексированные источники независимо проверяют ограниченные окна; по умолчанию нужны два независимых провайдера, не псевдонимы одного оператора. Высота узла, сведения сети ORDnet и EVM-relay для не-EVM приёма не доказывают платёж. Monero всё ещё требует привязанный к проекту view-only wallet-RPC. |
| healthy_endpoints | integer | ончейн | Исправные подходящие эндпоинты, не число независимых провайдеров. |
| usable_independent_providers / required_independent_providers | integer | ончейн | Доступные проверочные слоты, максимум два. required_independent_providers - настройка сети: 2 по умолчанию или 1 после явного выбора администратора. При двух провайдерах должны различаться и ключи провайдеров, и хосты. Отключённые, устаревшие более чем на десять минут или остывающие источники слот не занимают. У Lightning свои правила подключения. |
| last_checked_at | timestamp | null | ончейн | Последняя проверка подходящего эндпоинта, отдельно от времени оценки. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new invoice.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: order-1042-attempt-1' \
--header 'Content-Type: application/json' \
--data-raw '{
"payment_methods": [
{
"chain_slug": "bitcoin",
"asset_tickers": [
"BTC"
]
}
],
"amount": "49.90",
"currency": "USD",
"order_id": "order-1042",
"email": "ada@example.com",
"description": "Annual plan",
"underpayment_tolerance_percent": "1",
"ipn_url": "https://merchant.example/wholly/ipn",
"redirect_url": "https://merchant.example/orders/1042",
"cancel_url": "https://merchant.example/cart",
"redirect_automatically": true,
"language": "en",
"metadata": {
"cart_id": "cart-681",
"firstname": "Ada",
"lastname": "Lovelace",
"street": "12 Example Street",
"street2": "Suite 2",
"zip": "10115",
"city": "Berlin",
"country": "Germany",
"countryiso2": "DE",
"company": "Example GmbH",
"vatid": "DE123456789"
},
"exchange_rate_spread_percent": "0.5",
"checkout_appearance": {
"title": "Complete your order",
"intro": "Thanks for choosing our annual plan.",
"outro": "Questions? https://merchant.example/help",
"intro_font_size": 18,
"outro_font_size": 14,
"theme": "light",
"accent_color": "#1768CE",
"messages": {
"en": {
"paid": "Your order is ready."
}
}
}
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new invoice.
const body = `{
"payment_methods": [
{
"chain_slug": "bitcoin",
"asset_tickers": [
"BTC"
]
}
],
"amount": "49.90",
"currency": "USD",
"order_id": "order-1042",
"email": "ada@example.com",
"description": "Annual plan",
"underpayment_tolerance_percent": "1",
"ipn_url": "https://merchant.example/wholly/ipn",
"redirect_url": "https://merchant.example/orders/1042",
"cancel_url": "https://merchant.example/cart",
"redirect_automatically": true,
"language": "en",
"metadata": {
"cart_id": "cart-681",
"firstname": "Ada",
"lastname": "Lovelace",
"street": "12 Example Street",
"street2": "Suite 2",
"zip": "10115",
"city": "Berlin",
"country": "Germany",
"countryiso2": "DE",
"company": "Example GmbH",
"vatid": "DE123456789"
},
"exchange_rate_spread_percent": "0.5",
"checkout_appearance": {
"title": "Complete your order",
"intro": "Thanks for choosing our annual plan.",
"outro": "Questions? https://merchant.example/help",
"intro_font_size": 18,
"outro_font_size": 14,
"theme": "light",
"accent_color": "#1768CE",
"messages": {
"en": {
"paid": "Your order is ready."
}
}
}
}`;
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "order-1042-attempt-1",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new invoice.
$body = <<<'JSON'
{
"payment_methods": [
{
"chain_slug": "bitcoin",
"asset_tickers": [
"BTC"
]
}
],
"amount": "49.90",
"currency": "USD",
"order_id": "order-1042",
"email": "ada@example.com",
"description": "Annual plan",
"underpayment_tolerance_percent": "1",
"ipn_url": "https://merchant.example/wholly/ipn",
"redirect_url": "https://merchant.example/orders/1042",
"cancel_url": "https://merchant.example/cart",
"redirect_automatically": true,
"language": "en",
"metadata": {
"cart_id": "cart-681",
"firstname": "Ada",
"lastname": "Lovelace",
"street": "12 Example Street",
"street2": "Suite 2",
"zip": "10115",
"city": "Berlin",
"country": "Germany",
"countryiso2": "DE",
"company": "Example GmbH",
"vatid": "DE123456789"
},
"exchange_rate_spread_percent": "0.5",
"checkout_appearance": {
"title": "Complete your order",
"intro": "Thanks for choosing our annual plan.",
"outro": "Questions? https://merchant.example/help",
"intro_font_size": 18,
"outro_font_size": 14,
"theme": "light",
"accent_color": "#1768CE",
"messages": {
"en": {
"paid": "Your order is ready."
}
}
}
}
JSON;
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: order-1042-attempt-1", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new invoice.
headers = {
"Idempotency-Key": "order-1042-attempt-1",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"payment_methods": [
{
"chain_slug": "bitcoin",
"asset_tickers": [
"BTC"
]
}
],
"amount": "49.90",
"currency": "USD",
"order_id": "order-1042",
"email": "ada@example.com",
"description": "Annual plan",
"underpayment_tolerance_percent": "1",
"ipn_url": "https://merchant.example/wholly/ipn",
"redirect_url": "https://merchant.example/orders/1042",
"cancel_url": "https://merchant.example/cart",
"redirect_automatically": true,
"language": "en",
"metadata": {
"cart_id": "cart-681",
"firstname": "Ada",
"lastname": "Lovelace",
"street": "12 Example Street",
"street2": "Suite 2",
"zip": "10115",
"city": "Berlin",
"country": "Germany",
"countryiso2": "DE",
"company": "Example GmbH",
"vatid": "DE123456789"
},
"exchange_rate_spread_percent": "0.5",
"checkout_appearance": {
"title": "Complete your order",
"intro": "Thanks for choosing our annual plan.",
"outro": "Questions? https://merchant.example/help",
"intro_font_size": 18,
"outro_font_size": 14,
"theme": "light",
"accent_color": "#1768CE",
"messages": {
"en": {
"paid": "Your order is ready."
}
}
}
}""".encode("utf-8")
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 201 новый счёт; 200 точный идемпотентный повтор
{
"data": {
"id": "2f798f9f-01f2-42f0-9d10-5581d6116b4c",
"invoice_id": "0a6a98db-d93d-48ee-8c3c-fd45f90c4a50",
"project_id": "11111111-1111-4111-8111-111111111111",
"store_id": "22222222-2222-4222-8222-222222222222",
"source": "api",
"order_id": "order-1042",
"email": "ada@example.com",
"customer_name": "Ada Lovelace · Example GmbH",
"customer_address": "Example GmbH · 12 Example Street · Suite 2 · 10115 Berlin · Germany (DE) · VAT DE123456789",
"description": "Annual plan",
"amount": "49.9",
"currency": "USD",
"exchange_rate_spread_percent": "0.5",
"underpayment_tolerance_percent": "1",
"status": "new",
"amount_status": "none",
"timing_status": "on_time",
"resolution": "automatic",
"sequence": 1,
"winning_payment_intent_id": null,
"expires_at": "2026-08-31T18:15:00Z",
"monitoring_expires_at": "2026-09-07T18:15:00Z",
"settled_at": null,
"cancelled_at": null,
"archived_at": null,
"created_at": "2026-08-31T18:00:00Z",
"updated_at": "2026-08-31T18:00:00Z",
"ipn_url": "https://merchant.example/wholly/ipn",
"redirect_url": "https://merchant.example/orders/1042",
"cancel_url": "https://merchant.example/cart",
"redirect_automatically": true,
"checkout_language": "en",
"metadata": { "cart_id": "cart-681", "firstname": "Ada", "lastname": "Lovelace", "street": "12 Example Street", "street2": "Suite 2", "zip": "10115", "city": "Berlin", "country": "Germany", "countryiso2": "DE", "company": "Example GmbH", "vatid": "DE123456789" },
"payment_intents": [
{
"id": "33333333-3333-4333-8333-333333333333",
"asset_id": "10000000-0000-4000-8000-000000000001",
"asset_key": "bip122:000000000019d6689c085ae165831e93/slip44:0",
"chain_slug": "bitcoin",
"network": "mainnet",
"caip_network_id": "bip122:000000000019d6689c085ae165831e93",
"caip_asset_id": "bip122:000000000019d6689c085ae165831e93/slip44:0",
"symbol": "BTC",
"asset_decimals": 8,
"status": "pending",
"finality_mode": "confirmations",
"required_confirmations": 1,
"quote_rate": "0.000009218",
"quote_details": null,
"expected_amount": "0.00046",
"expected_amount_atomic": "46000",
"minimum_payment_amount": "0.0004554",
"minimum_payment_amount_atomic": "45540",
"received_amount": "0",
"received_amount_atomic": "0",
"confirmed_amount": "0",
"confirmed_amount_atomic": "0",
"destination_address": "bc1q…example",
"destination_tag": null,
"derivation_index": 42,
"quote_expires_at": "2026-08-31T18:15:00Z",
"monitoring_expires_at": "2026-09-07T18:15:00Z",
"next_check_at": "2026-08-31T18:00:00Z",
"last_checked_at": null,
"last_chain_height": null,
"last_anchor_hash": null,
"last_monitor_error": null,
"first_payment_at": null,
"fully_paid_at": null,
"finalized_at": null
}
]
},
"links": {
"checkout": "https://pay.example.com/invoice/0a6a98db-d93d-48ee-8c3c-fd45f90c4a50"
}
}GETСписок счетов/v1/projects/{project_id}/invoicesТолько чтение
Возвращает компактную страницу сводок от новых к старым в рамках доступа, включая приватный email и поля покупателя из распознанных метаданных. Поиск, статус и магазин фильтруются на сервере; ответ включает total и has_more для предсказуемой пагинации.
- Сортировка по created_at по убыванию, затем внутреннему id по убыванию.
- Записи - объекты InvoiceSummary; email, customer_name и customer_address только для продавца. Для исходных метаданных и платёжных намерений запроси подробности.
- Для следующей страницы задай offset = pagination.offset + pagination.limit только при has_more равном true.
- Количество и страница читаются из одного repeatable-read снимка базы; параллельные изменения появятся в следующем запросе.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_MERCHANT_API_TOKEN |
| Accept | рекомендуется | application/json |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| project_id | path UUID | Включённый проект, назначенный ключу. |
| store_id | query UUID | Необязательный точный фильтр магазина. |
| status | query enum | Необязательно new, processing, settled, expired, invalid или cancelled. |
| search | query string | Необязательный нечувствительный к регистру префикс ID счёта, номера заказа или email; точный UUID счёта; либо подстрока описания и распознанных полей покупателя. Все ключи метаданных, текстовые, числовые и булевы значения, включая вложенные объекты/массивы, поддерживают индексированный поиск по началам слов: каждое слово должно совпасть, пунктуация разделяет слова. По краям пробелы удаляются, до 100 символов, без управляющих. Совпадение метаданных не добавляет их в список; читай их в подробностях. |
| limit | query integer | Необязательно 1–100; по умолчанию 50. |
| offset | query integer | Необязательно 0–1 000 000; по умолчанию 0. |
Сводка счёта
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| id | UUID | всегда | Внутренний UUID счёта. Не используй в путях подробностей продавца или оплаты. |
| invoice_id | UUID | всегда | Публичный UUID счёта для путей подробностей продавца и оплаты. |
| project_id | UUID | всегда | Проект-владелец. |
| store_id | UUID | всегда | Магазин-владелец. |
| source | manual | api | всегда | Как создан счёт. |
| order_id | string | null | всегда | Номер заказа продавца. |
| string | null | всегда | Email покупателя только для продавца. Не возвращается публичной оплатой. | |
| customer_name | string | null | всегда | Отображаемое имя из приватных метаданных firstname, lastname и company. |
| customer_address | string | null | всегда | Однострочный адрес для продавца из приватных метаданных company, street, street2, zip, city, country, countryiso2 и vatid. |
| description | string | null | всегда | Описание для покупателя. |
| amount | decimal string | всегда | Каноническая сумма счёта. |
| currency | string | всегда | Нормализованный код валюты или актива счёта. |
| exchange_rate_spread_percent | decimal string | всегда | Зафиксированная наценка: переопределение при создании или настройка магазина, если не передано. Применяется до округления вверх; для этого счёта не меняется. |
| underpayment_tolerance_percent | decimal string | всегда | Неизменяемый процент допустимой недоплаты, сохранённый при создании счёта. |
| status | invoice status | всегда | new, processing, settled, expired, invalid или cancelled. |
| amount_status | amount status | всегда | none, partial, paid или overpaid. Явно разрешённый счёт на нулевую сумму завершается с none и без способов оплаты. |
| timing_status | timing status | всегда | on_time или late. |
| resolution | resolution | всегда | automatic, manually_settled или manually_invalidated. |
| sequence | integer | всегда | Монотонная последовательность состояния счёта, начиная с 1. |
| winning_payment_intent_id | UUID | null | всегда | Способ оплаты, завершивший счёт, если выбран. |
| expires_at | RFC 3339 timestamp | всегда | Срок котировки и оплаты. |
| monitoring_expires_at | RFC 3339 timestamp | всегда | Самый поздний настроенный срок отслеживания поздних платежей среди способов. |
| settled_at | timestamp | null | всегда | Время окончательного зачисления при settled. |
| cancelled_at | timestamp | null | всегда | Время отмены при cancelled. |
| archived_at | timestamp | null | всегда | Время архивирования, если выполнено. |
| created_at | RFC 3339 timestamp | всегда | Время создания. |
| updated_at | RFC 3339 timestamp | всегда | Время последнего обновления состояния. |
Пагинация счетов
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| limit | integer | всегда | Фактический размер страницы, 1–100. |
| offset | integer | всегда | Фактическое смещение строк с нуля, 0–1 000 000. |
| total | integer | всегда | Всего строк по фильтрам проекта, магазина, статуса и поиска в снимке страницы. |
| has_more | boolean | всегда | True, если offset плюс число возвращённых строк меньше total. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices?search=order-1042&status=processing&limit=50&offset=0" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Accept: application/json'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices?search=order-1042&status=processing&limit=50&offset=0", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
"Accept": "application/json"
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices?search=order-1042&status=processing&limit=50&offset=0");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Accept: application/json"],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Accept": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices?search=order-1042&status=processing&limit=50&offset=0",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"data": [
{
"id": "2f798f9f-01f2-42f0-9d10-5581d6116b4c",
"invoice_id": "0a6a98db-d93d-48ee-8c3c-fd45f90c4a50",
"project_id": "11111111-1111-4111-8111-111111111111",
"store_id": "22222222-2222-4222-8222-222222222222",
"source": "api",
"order_id": "order-1042",
"email": "ada@example.com",
"customer_name": "Ada Lovelace · Example GmbH",
"customer_address": "Example GmbH · 12 Example Street · Suite 2 · 10115 Berlin · Germany (DE) · VAT DE123456789",
"description": "Annual plan",
"amount": "49.9",
"currency": "USD",
"exchange_rate_spread_percent": "0.5",
"underpayment_tolerance_percent": "1",
"status": "processing",
"amount_status": "paid",
"timing_status": "on_time",
"resolution": "automatic",
"sequence": 3,
"winning_payment_intent_id": "33333333-3333-4333-8333-333333333333",
"expires_at": "2026-08-31T18:15:00Z",
"monitoring_expires_at": "2026-09-07T18:15:00Z",
"settled_at": null,
"cancelled_at": null,
"archived_at": null,
"created_at": "2026-08-31T18:00:00Z",
"updated_at": "2026-08-31T18:04:10Z"
}
],
"pagination": {
"limit": 50,
"offset": 0,
"total": 143,
"has_more": true
}
}GETПолучить счёт/v1/projects/{project_id}/invoices/{invoice_id}Только чтение
Полные подробности счёта продавца и текущий активный URL оплаты. Используй для опроса и сверки.
- Поиск в области доступа намеренно возвращает invoice_not_found, если публичный ID не принадлежит разрешённому проекту.
- links.checkout использует Магазин → Основное → Домены магазина: активный pay-хост этого магазина, затем выбор магазина по умолчанию, затем основной системный. Удалённые, черновые или хосты другой роли игнорируются. То же для создания и MCP; ссылки разрешаются в момент ответа, включая идемпотентные повторы. Подписанные ссылки уведомлений фиксируются при событии и не меняются при повторах. Настройки только формируют ссылки, не перенаправляют трафик и не меняют IP-ограничения.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_MERCHANT_API_TOKEN |
| Accept | рекомендуется | application/json |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| project_id | path UUID | Включённый проект, назначенный ключу. |
| invoice_id | path UUID | invoice_id из создания или списка, не внутренний id. |
Сводка счёта
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| id | UUID | всегда | Внутренний UUID счёта. Не используй в путях подробностей продавца или оплаты. |
| invoice_id | UUID | всегда | Публичный UUID счёта для путей подробностей продавца и оплаты. |
| project_id | UUID | всегда | Проект-владелец. |
| store_id | UUID | всегда | Магазин-владелец. |
| source | manual | api | всегда | Как создан счёт. |
| order_id | string | null | всегда | Номер заказа продавца. |
| string | null | всегда | Email покупателя только для продавца. Не возвращается публичной оплатой. | |
| customer_name | string | null | всегда | Отображаемое имя из приватных метаданных firstname, lastname и company. |
| customer_address | string | null | всегда | Однострочный адрес для продавца из приватных метаданных company, street, street2, zip, city, country, countryiso2 и vatid. |
| description | string | null | всегда | Описание для покупателя. |
| amount | decimal string | всегда | Каноническая сумма счёта. |
| currency | string | всегда | Нормализованный код валюты или актива счёта. |
| exchange_rate_spread_percent | decimal string | всегда | Зафиксированная наценка: переопределение при создании или настройка магазина, если не передано. Применяется до округления вверх; для этого счёта не меняется. |
| underpayment_tolerance_percent | decimal string | всегда | Неизменяемый процент допустимой недоплаты, сохранённый при создании счёта. |
| status | invoice status | всегда | new, processing, settled, expired, invalid или cancelled. |
| amount_status | amount status | всегда | none, partial, paid или overpaid. Явно разрешённый счёт на нулевую сумму завершается с none и без способов оплаты. |
| timing_status | timing status | всегда | on_time или late. |
| resolution | resolution | всегда | automatic, manually_settled или manually_invalidated. |
| sequence | integer | всегда | Монотонная последовательность состояния счёта, начиная с 1. |
| winning_payment_intent_id | UUID | null | всегда | Способ оплаты, завершивший счёт, если выбран. |
| expires_at | RFC 3339 timestamp | всегда | Срок котировки и оплаты. |
| monitoring_expires_at | RFC 3339 timestamp | всегда | Самый поздний настроенный срок отслеживания поздних платежей среди способов. |
| settled_at | timestamp | null | всегда | Время окончательного зачисления при settled. |
| cancelled_at | timestamp | null | всегда | Время отмены при cancelled. |
| archived_at | timestamp | null | всегда | Время архивирования, если выполнено. |
| created_at | RFC 3339 timestamp | всегда | Время создания. |
| updated_at | RFC 3339 timestamp | всегда | Время последнего обновления состояния. |
Дополнения подробностей счёта
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| ipn_url | string | null | всегда | Действующий IPN-адрес этого счёта. Только в ответе продавцу; не в публичной оплате. |
| redirect_url | string | null | всегда | Действующий URL успеха после зачисления. |
| cancel_url | string | null | всегда | Действующий URL возврата при завершении оплаты без успеха. |
| redirect_automatically | boolean | всегда | Перенаправлять ли автоматически после успешной оплаты. |
| checkout_language | string | всегда | Действующий языковой тег оплаты. |
| metadata | object | всегда | Метаданные продавца. Не возвращаются публичной оплатой. |
| payment_intents | PaymentIntent[] | всегда | Котированные способы оплаты и состояние мониторинга. |
PaymentIntent
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| id | UUID | всегда | ID платёжного намерения; также intent_id QR-кода оплаты. |
| payment_rail | onchain | lightning | всегда | Канал оплаты счёта. Ончейн-Bitcoin и Lightning могут иметь один asset_id; используй ID намерения и это поле, не только символ. Отличается от scanner payment_rail каталога активов. |
| bolt11 | string | null | всегда | Запрос оплаты Lightning, иначе null. Плати через Lightning-кошелёк, не отправляй ончейн-средства на хеш платежа. |
| asset_id | UUID | всегда | Идентификатор настроенного платёжного актива. |
| asset_key | string | всегда | Канонический CAIP-подобный ключ актива. |
| chain_slug | string | всегда | Идентификатор сети Wholly Crypto. |
| network | string | всегда | Настроенная сеть; сейчас mainnet для поддерживаемых платёжных активов. |
| caip_network_id | string | всегда | Канонический идентификатор сети CAIP-2. |
| caip_asset_id | string | null | всегда | Канонический CAIP-19, если зарегистрирован. |
| symbol | string | всегда | Символ актива. |
| asset_decimals | integer | всегда | Точность минимальных единиц. BTC Lightning использует 11 - миллисатоши, не 8 как ончейн-Bitcoin. Котировки в целых сатоши, поступления сохраняют точность миллисатоши. |
| status | intent status | всегда | pending, partial, paid, overpaid, expired или invalid. |
| finality_mode | confirmations | finalized | всегда | Политика финальности. |
| required_confirmations | integer | всегда | Нужное число подтверждений, если применимо. |
| quote_rate | decimal string | всегда | Единицы актива на одну единицу валюты счёта с зафиксированной наценкой. Например 1.02 USDC на USD. Не обратный курс. |
| quote_details | object | null | всегда | Источники зафиксированной котировки: reference_rate до наценки, unrounded_payment_amount, rounding_adjustment, pricing_provider, asset_provider, pricing_fetched_at и asset_fetched_at. Null у старых счетов; исторические значения не выдумываются. |
| expected_amount | decimal string | всегда | Точная зафиксированная сумма актива после наценки и округления вверх. С 4.1.1 распознанные проверенные фиатные стейблкоины - USDC, USDT, DAI, USDS, EURC - округляются вверх максимум до двух знаков: 1.321 становится 1.33, не 1.32. Это ожидаемая сумма даже с нулевым допуском. Другие активы сохраняют адаптивную точность. Существующие счета не пересчитываются. |
| expected_amount_atomic | integer string | всегда | Точная сумма в минимальной единице актива. |
| minimum_payment_amount | decimal string | всегда | Наименьшая сумма, принимаемая как оплата после допуска счёта. |
| minimum_payment_amount_atomic | integer string | всегда | Точный допустимый порог в минимальной единице актива. |
| received_amount | decimal string | всегда | Обнаруженная сумма. |
| received_amount_atomic | integer string | всегда | Обнаруженная сумма в минимальных единицах. |
| confirmed_amount | decimal string | всегда | Подтверждённая/финальная сумма. |
| confirmed_amount_atomic | integer string | всегда | Подтверждённая/финальная сумма в минимальных единицах. |
| destination_address | string | всегда | Ончейн-адрес приёма или 64-символьный хеш платежа Lightning. Для Lightning используй bolt11; его хеш - не Bitcoin-адрес. |
| destination_tag | string | null | всегда | Обязательная публичная ссылка платежа для соответствующих сетей: destination tag XRP, memo ID Stellar или комментарий счёта TON. Null для способов с уникальным адресом. |
| derivation_index | integer | всегда | Зарезервированный индекс дочернего адреса; только в подробностях продавца. |
| quote_expires_at | RFC 3339 timestamp | всегда | Истечение котировки. |
| monitoring_expires_at | RFC 3339 timestamp | всегда | Конец отслеживания поздних платежей этого способа. |
| next_check_at | timestamp | null | всегда | Следующая плановая проверка сети. |
| last_checked_at | timestamp | null | всегда | Последняя проверка сети. |
| last_chain_height | integer | null | всегда | Последняя достоверная высота, увиденная монитором. |
| last_anchor_hash | string | null | всегда | Последний опорный хеш или хеш блока монитора. |
| last_monitor_error | string | null | всегда | Безопасная диагностика мониторинга для операторов. |
| first_payment_at | timestamp | null | всегда | Время первого обнаружения платежа. |
| fully_paid_at | timestamp | null | всегда | Время первого достижения допустимого минимума. |
| finalized_at | timestamp | null | всегда | Время выполнения политики финальности платежом. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Accept: application/json'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
"Accept": "application/json"
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Accept: application/json"],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Accept": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"data": {
"id": "2f798f9f-01f2-42f0-9d10-5581d6116b4c",
"invoice_id": "0a6a98db-d93d-48ee-8c3c-fd45f90c4a50",
"project_id": "11111111-1111-4111-8111-111111111111",
"store_id": "22222222-2222-4222-8222-222222222222",
"source": "api",
"order_id": "order-1042",
"email": "ada@example.com",
"customer_name": "Ada Lovelace · Example GmbH",
"customer_address": "Example GmbH · 12 Example Street · Suite 2 · 10115 Berlin · Germany (DE) · VAT DE123456789",
"description": "Annual plan",
"amount": "49.9",
"currency": "USD",
"exchange_rate_spread_percent": "0.5",
"underpayment_tolerance_percent": "1",
"status": "settled",
"amount_status": "paid",
"timing_status": "on_time",
"resolution": "automatic",
"sequence": 4,
"winning_payment_intent_id": "33333333-3333-4333-8333-333333333333",
"expires_at": "2026-08-31T18:15:00Z",
"monitoring_expires_at": "2026-09-07T18:15:00Z",
"settled_at": "2026-08-31T18:05:00Z",
"cancelled_at": null,
"archived_at": null,
"created_at": "2026-08-31T18:00:00Z",
"updated_at": "2026-08-31T18:05:00Z",
"ipn_url": "https://merchant.example/wholly/ipn",
"redirect_url": "https://merchant.example/orders/1042",
"cancel_url": "https://merchant.example/cart",
"redirect_automatically": true,
"checkout_language": "en",
"metadata": { "cart_id": "cart-681", "firstname": "Ada", "lastname": "Lovelace", "street": "12 Example Street", "street2": "Suite 2", "zip": "10115", "city": "Berlin", "country": "Germany", "countryiso2": "DE", "company": "Example GmbH", "vatid": "DE123456789" },
"payment_intents": [
{
"id": "33333333-3333-4333-8333-333333333333",
"asset_id": "10000000-0000-4000-8000-000000000001",
"asset_key": "bip122:000000000019d6689c085ae165831e93/slip44:0",
"chain_slug": "bitcoin",
"network": "mainnet",
"caip_network_id": "bip122:000000000019d6689c085ae165831e93",
"caip_asset_id": "bip122:000000000019d6689c085ae165831e93/slip44:0",
"symbol": "BTC",
"asset_decimals": 8,
"status": "paid",
"finality_mode": "confirmations",
"required_confirmations": 1,
"quote_rate": "0.000009218",
"quote_details": null,
"expected_amount": "0.00046",
"expected_amount_atomic": "46000",
"minimum_payment_amount": "0.0004554",
"minimum_payment_amount_atomic": "45540",
"received_amount": "0.0004554",
"received_amount_atomic": "45540",
"confirmed_amount": "0.0004554",
"confirmed_amount_atomic": "45540",
"destination_address": "bc1q…example",
"destination_tag": null,
"derivation_index": 42,
"quote_expires_at": "2026-08-31T18:15:00Z",
"monitoring_expires_at": "2026-09-07T18:15:00Z",
"next_check_at": null,
"last_checked_at": "2026-08-31T18:05:00Z",
"last_chain_height": 912345,
"last_anchor_hash": "000000000000000000example",
"last_monitor_error": null,
"first_payment_at": "2026-08-31T18:03:00Z",
"fully_paid_at": "2026-08-31T18:03:00Z",
"finalized_at": "2026-08-31T18:05:00Z"
}
]
},
"links": {
"checkout": "https://pay.example.com/invoice/0a6a98db-d93d-48ee-8c3c-fd45f90c4a50"
}
}GETСписок платежей счёта/v1/projects/{project_id}/invoices/{invoice_id}/paymentsТолько чтение
Полная текущая история переводов, включая недействительные наблюдения. Используй при payments_truncated в уведомлении. Это текущее состояние, не восстановление старого события.
- Наблюдение - лог токена, UTXO-выход или перевод другого способа, не обязательно уникальный хеш. Убирай дубли по payment_id; transaction_id вместе с event_index определяют перевод сети.
- status: detected, confirming, final, reorged, replaced или invalid. Только наблюдения counts_towards_received входят в полученные суммы. Не складывай разные активы.
- Lightning-записи используют payment_hash, а transaction_id, confirmations и ссылки обозревателя равны null; точность BTC - 11, миллисатоши. Preimage, BOLT11 и секреты кошелька не раскрываются.
- Сортировка по observed_at по убыванию, затем payment_id. Число и страница из одного repeatable-read снимка; следующие страницы могут меняться при поступлениях. При просмотре живого счёта убирай дубли по payment_id.
- Действуют существующая область чтения проекта, IP-ограничения и квота ключа. Не переходи по ссылке уведомления с токеном, если её origin не совпадает с настроенным API-хостом.
| Заголовок | Наличие | Правило |
|---|---|---|
| Authorization | обязательно | Bearer YOUR_MERCHANT_API_TOKEN |
| Accept | рекомендуется | application/json |
| Параметр | Тип / расположение | Правило |
|---|---|---|
| project_id | path UUID | Проект, назначенный этому ключу. |
| invoice_id | path UUID | Публичный invoice_id, возвращённый при создании. |
| payment_method_id | optional query UUID | Ограничить одним способом оплаты счёта. |
| limit | query integer | 1–100; по умолчанию 25. |
| offset | query integer | 0–1 000 000; по умолчанию 0. |
Запрос
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID/payments?limit=25&offset=0" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Accept: application/json'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID/payments?limit=25&offset=0", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
"Accept": "application/json"
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID/payments?limit=25&offset=0");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Accept: application/json"],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Accept": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID/payments?limit=25&offset=0",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"invoice_id": "11111111-2222-4333-8444-555555555555",
"data": [{
"payment_id": "44444444-4444-4444-8444-444444444444",
"payment_method_id": "33333333-3333-4333-8333-333333333333",
"transaction_id": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"payment_hash": null,
"event_index": 12,
"payment_rail": "onchain",
"chain_slug": "ethereum",
"network": "mainnet",
"asset_id": "55555555-5555-4555-8555-555555555555",
"asset_key": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"caip_asset_id": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"symbol": "USDC",
"asset_decimals": 6,
"amount": "58.17342",
"amount_atomic": "58173420",
"status": "final",
"counts_towards_received": true,
"confirmations": 2,
"block_height": 25975377,
"observed_at": "2026-09-14T12:03:00Z",
"chain_time": "2026-09-14T12:02:48Z",
"finalized_at": "2026-09-14T12:04:00Z",
"explorer_name": "Etherscan",
"explorer_url": "https://etherscan.io/tx/0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}],
"pagination": {"limit": 25, "offset": 0, "total": 1, "has_more": false}
}GETОболочка платёжной страницы/Публичный
Корень управляемого pay-хоста отдаёт приложение оплаты без выбора счёта. Интеграциям обычно следует использовать links.checkout.
- Bearer-токен не нужен.
- Управляемый сервер оплаты разрешает GET/HEAD и отклоняет другие методы.
Запрос
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/" \
--output 'checkout.html'// Node.js 18+ · run on your server, never in browser code.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://pay.example.com/", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
await writeFile("checkout.html", Buffer.from(await response.arrayBuffer()));<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
file_put_contents("checkout.html", $response);# Python 3 · standard library; run on your server.
import json
from pathlib import Path
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://pay.example.com/",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
Path("checkout.html").write_bytes(response.read())Пример ответа · 200 text/html
<!doctype html>
<!-- Hosted Wholly Crypto checkout shell -->GETРазмещённая платёжная страница/invoice/{invoice_id}Публичный
HTML-оплата для покупателя. Страница получает безопасный JSON с того же pay-хоста. Встраивание запрещено, пока магазин не включит его и явно не разрешит HTTPS-origin родителя.
- Bearer-токен не принимается и не нужен.
- HTML-оболочка возвращает 200 даже без счёта; её JSON-запрос затем получает invoice_not_found.
- Ответ имеет no-store, noindex и индивидуальный frame-ancestors CSP счёта.
- Отключённый проект/магазин или неизвестный счёт не раскрывает данные оплаты.
| Параметр | Тип / расположение | Правило |
|---|---|---|
| invoice_id | path UUID | Публичный UUID счёта из API продавца. |
Запрос
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/invoice/YOUR_PUBLIC_INVOICE_ID" \
--output 'checkout.html'// Node.js 18+ · run on your server, never in browser code.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://pay.example.com/invoice/YOUR_PUBLIC_INVOICE_ID", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
await writeFile("checkout.html", Buffer.from(await response.arrayBuffer()));<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/invoice/YOUR_PUBLIC_INVOICE_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
file_put_contents("checkout.html", $response);# Python 3 · standard library; run on your server.
import json
from pathlib import Path
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://pay.example.com/invoice/YOUR_PUBLIC_INVOICE_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
Path("checkout.html").write_bytes(response.read())Пример ответа · 200 text/html
<!doctype html>
<!-- Hosted Wholly Crypto checkout application -->GETБезопасные данные счёта для оплаты/checkout-api/invoices/{invoice_id}Публичный
Возвращает только нужное для оплаты. Намеренно исключает внутренние ID, email и производный адрес покупателя, IPN URL, метаданные продавца, ID кошельков, пути деривации и диагностику монитора.
- Bearer-токен не нужен.
- Cache-Control - no-store, поисковая индексация отключена.
- Считай invoice_id данными, дающими доступ покупателю; не публикуй без необходимости.
- asset_icon_url - локальный ресурс того же origin; странице оплаты не нужно обращаться к CoinGecko за иконкой.
- Если destination_tag не null, показывай и копируй его рядом с адресом: обязательный destination tag XRP, memo ID Stellar или комментарий TON нужно передать точно.
- Для проверенных токенов asset_kind равен token, contract_address задаёт точный ERC-20 контракт или SPL mint, token_standard - стандарт, payment_uri содержит идентичность токена.
| Параметр | Тип / расположение | Правило |
|---|---|---|
| invoice_id | path UUID | Публичный UUID счёта. |
Публичный счёт оплаты
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| invoice_id | UUID | всегда | Публичный UUID счёта. |
| order_id | string | null | всегда | Номер заказа продавца. |
| description | string | null | всегда | Описание для покупателя. |
| amount | decimal string | всегда | Сумма счёта. |
| currency | string | всегда | Валюта счёта. |
| exchange_rate_spread_percent | decimal string | всегда | Действующая наценка, зафиксированная при создании, включая индивидуальное переопределение. |
| underpayment_tolerance_percent | decimal string | всегда | Процент допустимой недоплаты счёта. |
| status | invoice status | всегда | Текущий статус счёта. |
| amount_status | amount status | всегда | none, partial, paid или overpaid. Явно разрешённый счёт на нулевую сумму завершается с none и без способов оплаты. |
| timing_status | timing status | всегда | on_time или late. |
| sequence | integer | всегда | Текущая последовательность состояния. |
| active_payment_method_id | UUID | null | всегда | Способ из списка, на который поступили средства. Оплата остаётся на нём, чтобы недоплата не продолжалась несовместимым активом. |
| payment_method_locked | boolean | всегда | True после выбора active_payment_method_id действительным платежом. |
| server_time | RFC 3339 timestamp | всегда | Время сервера для ответа; используй с expires_at против расхождения часов устройства покупателя. |
| expires_at | RFC 3339 timestamp | всегда | Срок счёта. |
| expires_in_seconds | integer | всегда | Целые оставшиеся секунды на server_time, округлены вверх и ограничены снизу нулём. |
| payment_open | boolean | всегда | True только для new или processing до срока, если есть хотя бы один доступный к оплате способ с остатком. |
| redirect_url | string | null | всегда | Адрес возврата покупателя после успешного зачисления. |
| cancel_url | string | null | всегда | Адрес возврата покупателя при уходе без успешного зачисления. |
| redirect_automatically | boolean | всегда | Политика автоматического перенаправления. |
| checkout_language | string | всегда | Язык оплаты. |
| project | object | всегда | name, checkout_title, checkout_description, theme, accent_color и logo_url. |
| store | object | всегда | Публичное название магазина. |
| appearance | CheckoutAppearance | всегда | Действующее оформление: зафиксированное переопределение счёта, если передано, иначе текущий дизайн магазина. Не меняет финансовые поля и предупреждения безопасности. |
| payment_methods | CheckoutPaymentMethod[] | всегда | Безопасные для публичной оплаты способы. |
CheckoutAppearance
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| inherit_default_store | boolean | всегда | True, если оформление берётся из магазина проекта по умолчанию. False для независимых магазинов и зафиксированных настроек счёта. |
| invoice_override | boolean | всегда | True, если checkout_appearance передан при создании. При отсутствии/null остаётся false. |
| title / intro / outro | string | всегда | Обычные заголовок продавца, верхнее и нижнее сообщения. intro заменяет customer_message; старый текст сохраняется. Никогда не интерпретируй как разметку. |
| intro_font_size / outro_font_size | integer | всегда | Размеры шрифта в пикселях: 12, 14, 16, 18, 20 или 24. |
| customer_message | string | всегда | Устаревший псевдоним совместимости intro. В новых интеграциях используй intro. |
| theme | system | light | dim | dark | всегда | Предпочтение устройства покупателя или фиксированная тема. |
| accent_color / background_color / card_color / button_color | string | всегда | Строгие цвета #RRGGBB. Необязательные пусты для автоматического выбора; контраст текста рассчитывается. |
| logo_size / logo_alignment | string | всегда | small, medium или large; left или center. Изображения вписываются, не обрезаются. |
| images | object | всегда | Необязательные URL logo_light, logo_dark и favicon: ограниченные областью, нормализованные PNG того же origin. |
| show_order_id / show_description / details_expanded | boolean | всегда | Видимость ID заказа, описание под заголовком и начальное раскрытие ID. Сумма видна всегда; это управление отображением, не удаление данных. |
| show_project_name / show_store_name | boolean | всегда | Версия 5.6.0+: видимость имён в заголовке. Оба по умолчанию true. Идентичность проекта и магазина остаётся в JSON. |
| featured_chains / featured_asset_ids | array | всегда | Упорядоченные предпочтения только для способов уже в счёте. Отсутствующие или отключённые игнорируются. |
| default_asset_id | UUID | null | всегда | Предлагаемый начальный способ. Действительное сохранённое предпочтение покупателя или уже получающий средства способ приоритетнее. |
| messages | object | всегда | Обычный текст en/de по ключам waiting, confirming, paid, underpaid и expired. Запасной английский. Дополняет, но не заменяет реальный статус. |
| support_email / support_url / terms_url / privacy_url | string | всегда | Необязательные контакты и HTTPS-ссылки без данных доступа в URL. Внешние ссылки открываются в новом окне. |
| return_button_text | string | всегда | Только необязательная подпись. Адреса успеха и отмены и правила перенаправления по-прежнему принадлежат счёту. |
CheckoutPaymentMethod
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| payment_rail | onchain | lightning | всегда | Lightning остаётся способом Bitcoin, отдельным от ончейн-BTC. Определяй вариант по ID намерения и способу, не только asset_id. |
| bolt11 | string | null | всегда | Подписанный запрос Lightning; null для ончейн. Не плати после payable = false. |
| payment_hash | string | null | всегда | Хеш платежа Lightning для сверки, не адрес приёма. Null для ончейн-способов. |
| id | UUID | всегда | Идентификатор платёжного намерения. |
| asset_id | UUID | всегда | UUID актива для настроек оформления; отличается от ID платёжного намерения счёта. |
| asset_key | string | всегда | Канонический ключ актива. |
| chain_slug / chain_name | string | всегда | Машинное и отображаемое названия сети. |
| network | string | всегда | Платёжная сеть. |
| caip_network_id | string | всегда | Канонический идентификатор, однозначно определяющий выбранную сеть. |
| caip_asset_id | string | null | всегда | Точный канонический идентификатор актива, включая проверенный контракт токена или mint, если применимо. |
| asset_name / symbol | string | всегда | Отображаемые значения платёжного актива. |
| asset_icon_url | string | null | всегда | Локально кешированная иконка актива того же origin или null без проверенной привязки CoinGecko. |
| asset_kind | native | token | всегда | Отличает нативную валюту от оплаты контрактом/mint. |
| contract_address | string | null | всегда | Канонический ERC-20 контракт или SPL mint токена; null для нативной валюты. |
| token_standard | erc20 | spl-token | null | всегда | Проверенный способ выполнения токена или null для нативной валюты. |
| asset_decimals | integer | всегда | Точность минимальных единиц: 11 для миллисатоши BTC Lightning, 8 для сатоши ончейн-BTC. |
| status | intent status | всегда | Текущий статус способа оплаты. |
| payable | boolean | всегда | True только когда именно этот способ сейчас принимает оплату; false для неактивных способов после поступления другого актива. |
| finality_mode / required_confirmations | string / integer | всегда | Политика финальности. |
| expected_amount / expected_amount_atomic | decimal / integer string | всегда | Полная зафиксированная котировка в отображаемых и реальных ончейн-единицах. Распознанные фиатные стейблкоины используют до двух дробных знаков, всегда округляясь вверх после наценки; другие активы - адаптивную точность. Реальная разрядность токена, поступления и остатки частичной оплаты точны. Используй суммы ответа без изменений. |
| minimum_payment_amount / minimum_payment_amount_atomic | decimal / integer string | всегда | Допустимый порог зачисления с учётом недоплаты. |
| received_amount / received_amount_atomic | decimal / integer string | всегда | Обнаруженная сумма. |
| remaining_amount | decimal string | всегда | Точная отображаемая недостающая сумма до допустимого порога, минимум ноль. |
| remaining_amount_atomic | integer string | всегда | Недостача до допустимого порога в минимальных единицах. Это не запрошенная сумма оплаты: допуск влияет только на принятие. |
| confirmed_amount / confirmed_amount_atomic | decimal / integer string | всегда | Подтверждённая/финальная сумма. |
| destination_address / destination_tag | string / string|null | всегда | Ончейн-адрес и необязательная ссылка платежа. Для Lightning - хеш без тега; плати по bolt11/payment_uri. |
| quote_expires_at | RFC 3339 timestamp | всегда | Истечение котировки. |
| payment_uri | string | null | всегда | Запрос с учётом сети: ERC-681, Solana Pay, нативный URI или lightning:<bolt11>. Запросы с суммой используют полную ожидаемую сумму минус полученное, не порог допуска. Null при payable = false, в том числе после принятой допустимой недоплаты. QR Lightning содержит полный запрос, не хеш платежа. |
| qr_url | path | null | всегда | Путь SVG QR того же origin с версией последовательности и точного остатка либо null при payable = false. SVG имеет no-store. |
| address_explorer_name / address_explorer_url | string|null | всегда | Проверенная запасная ссылка mainnet-обозревателя, если поддерживается. |
| transaction_count | integer | всегда | Число разных публичных действительных транзакций этого способа. |
| transactions_truncated | boolean | всегда | True, если transaction_count больше возвращённого списка последних транзакций. |
| transactions | CheckoutTransaction[] | всегда | До 10 последних публичных действительных транзакций. Точные полученные итоги не зависят от ограничения отображения. |
CheckoutTransaction
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| transaction_id | string | всегда | Идентификатор обнаруженной транзакции. |
| status | detected | confirming | final | всегда | Публичное состояние наблюдения. |
| confirmations | integer | всегда | Наблюдаемое число подтверждений. |
| block_height | integer | null | всегда | Наблюдаемая высота блока или реестра. |
| explorer_name | string | если возвращено | Проверенное фиксированное имя обозревателя. |
| explorer_url | string | если возвращено | Проверенный фиксированный URL mainnet-обозревателя. |
Запрос
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID" \
--header 'Accept: application/json'// Node.js 18+ · run on your server, never in browser code.
const response = await fetch("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID", {
method: "GET",
headers: {
"Accept": "application/json"
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ["Accept: application/json"],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Accept": "application/json"
}
request = Request("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"data": {
"invoice_id": "0a6a98db-d93d-48ee-8c3c-fd45f90c4a50",
"order_id": "order-1042",
"description": "Annual plan",
"amount": "49.9",
"currency": "USD",
"exchange_rate_spread_percent": "0.5",
"underpayment_tolerance_percent": "1",
"status": "processing",
"amount_status": "partial",
"timing_status": "on_time",
"sequence": 3,
"active_payment_method_id": "33333333-3333-4333-8333-333333333333",
"payment_method_locked": true,
"server_time": "2026-08-31T18:10:00Z",
"expires_at": "2026-08-31T18:15:00Z",
"expires_in_seconds": 300,
"payment_open": true,
"redirect_url": "https://merchant.example/orders/1042",
"cancel_url": "https://merchant.example/cart",
"redirect_automatically": true,
"checkout_language": "en",
"project": {
"name": "Example project",
"checkout_title": "Complete your payment",
"checkout_description": "Send the exact amount shown.",
"theme": "system",
"accent_color": "#42e39b",
"logo_url": "/checkout-api/invoices/…/logo/…/image.png"
},
"store": { "name": "Online shop" },
"payment_methods": [
{
"id": "33333333-3333-4333-8333-333333333333",
"asset_key": "bip122:000000000019d6689c085ae165831e93/slip44:0",
"chain_slug": "bitcoin",
"chain_name": "Bitcoin",
"network": "mainnet",
"caip_network_id": "bip122:000000000019d6689c085ae165831e93",
"caip_asset_id": "bip122:000000000019d6689c085ae165831e93/slip44:0",
"asset_name": "Bitcoin",
"symbol": "BTC",
"asset_icon_url": "/assets/coingecko/bitcoin.png",
"asset_kind": "native",
"contract_address": null,
"token_standard": null,
"asset_decimals": 8,
"status": "partial",
"payable": true,
"finality_mode": "confirmations",
"required_confirmations": 1,
"expected_amount": "0.00046",
"expected_amount_atomic": "46000",
"minimum_payment_amount": "0.0004554",
"minimum_payment_amount_atomic": "45540",
"received_amount": "0.0002",
"received_amount_atomic": "20000",
"remaining_amount": "0.0002554",
"remaining_amount_atomic": "25540",
"confirmed_amount": "0",
"confirmed_amount_atomic": "0",
"destination_address": "bc1q…example",
"destination_tag": null,
"quote_expires_at": "2026-08-31T18:15:00Z",
"payment_uri": "bitcoin:bc1q…example?amount=0.00026",
"qr_url": "/checkout-api/invoices/…/payment-methods/…/qr.svg?sequence=3&amount_atomic=26000",
"address_explorer_name": "mempool.space",
"address_explorer_url": "https://mempool.space/address/…",
"transaction_count": 0,
"transactions_truncated": false,
"transactions": []
}
]
}
}GETПредпросмотр оплаты магазина/invoice/preview/{project_id}Публичный
Показывает сохранённое оформление магазина с примерной суммой и реальными метаданными разрешённых активов. Переключай примеры ожидания, подтверждения, оплаты, недоплаты и истечения без создания платежей.
- Предпросмотр показывает только оформление; не отправляй его покупателю как запрос оплаты.
- Нет адреса приёма, платёжного QR, действий кошелька, перенаправлений и опроса оплаты. Примеры не меняют реальные статусы счетов.
- Ответ имеет no-store, noindex и запрещает встраивание.
| Параметр | Тип / расположение | Правило |
|---|---|---|
| project_id | path UUID | UUID проекта, скопированный в ссылку предпросмотра авторизованной консолью. |
| store_id | query UUID, optional | Магазин этого проекта. Не указывай для первого магазина или магазина по умолчанию. |
| state | query string, optional | waiting, confirming, paid, underpaid или expired. Только иллюстрация в браузере. |
Запрос
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/invoice/preview/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID&state=confirming" \
--output 'checkout-preview.html'// Node.js 18+ · run on your server, never in browser code.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://pay.example.com/invoice/preview/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID&state=confirming", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
await writeFile("checkout-preview.html", Buffer.from(await response.arrayBuffer()));<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/invoice/preview/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID&state=confirming");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
file_put_contents("checkout-preview.html", $response);# Python 3 · standard library; run on your server.
import json
from pathlib import Path
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://pay.example.com/invoice/preview/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID&state=confirming",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
Path("checkout-preview.html").write_bytes(response.read())Пример ответа · 200 text/html
<!doctype html>
<!-- Hosted branding preview; no invoice is created -->GETДанные предпросмотра оплаты/checkout-api/previews/{project_id}Публичный
Возвращает действующее оформление магазина и безопасные метаданные разрешённых активов. payment_methods остаётся пустым; preview_methods не содержит платёжных адресов, котировок или приватных данных кошельков.
- Bearer-токен не принимается и не нужен.
- Не возвращает счёт, адрес назначения, кошелёк, транзакцию, IPN, вебхук или метаданные продавца.
- Получи правильную ссылку предпросмотра на pay-домене в авторизованной консоли.
| Параметр | Тип / расположение | Правило |
|---|---|---|
| project_id | path UUID | UUID проекта из ссылки предпросмотра консоли. |
| store_id | query UUID, optional | Должен принадлежать проекту; несовпадающие ID дают 404. Неизвестные параметры запроса отклоняются. |
CheckoutAppearance
| Поле | Тип | Наличие | Описание |
|---|---|---|---|
| inherit_default_store | boolean | всегда | True, если оформление берётся из магазина проекта по умолчанию. False для независимых магазинов и зафиксированных настроек счёта. |
| invoice_override | boolean | всегда | True, если checkout_appearance передан при создании. При отсутствии/null остаётся false. |
| title / intro / outro | string | всегда | Обычные заголовок продавца, верхнее и нижнее сообщения. intro заменяет customer_message; старый текст сохраняется. Никогда не интерпретируй как разметку. |
| intro_font_size / outro_font_size | integer | всегда | Размеры шрифта в пикселях: 12, 14, 16, 18, 20 или 24. |
| customer_message | string | всегда | Устаревший псевдоним совместимости intro. В новых интеграциях используй intro. |
| theme | system | light | dim | dark | всегда | Предпочтение устройства покупателя или фиксированная тема. |
| accent_color / background_color / card_color / button_color | string | всегда | Строгие цвета #RRGGBB. Необязательные пусты для автоматического выбора; контраст текста рассчитывается. |
| logo_size / logo_alignment | string | всегда | small, medium или large; left или center. Изображения вписываются, не обрезаются. |
| images | object | всегда | Необязательные URL logo_light, logo_dark и favicon: ограниченные областью, нормализованные PNG того же origin. |
| show_order_id / show_description / details_expanded | boolean | всегда | Видимость ID заказа, описание под заголовком и начальное раскрытие ID. Сумма видна всегда; это управление отображением, не удаление данных. |
| show_project_name / show_store_name | boolean | всегда | Версия 5.6.0+: видимость имён в заголовке. Оба по умолчанию true. Идентичность проекта и магазина остаётся в JSON. |
| featured_chains / featured_asset_ids | array | всегда | Упорядоченные предпочтения только для способов уже в счёте. Отсутствующие или отключённые игнорируются. |
| default_asset_id | UUID | null | всегда | Предлагаемый начальный способ. Действительное сохранённое предпочтение покупателя или уже получающий средства способ приоритетнее. |
| messages | object | всегда | Обычный текст en/de по ключам waiting, confirming, paid, underpaid и expired. Запасной английский. Дополняет, но не заменяет реальный статус. |
| support_email / support_url / terms_url / privacy_url | string | всегда | Необязательные контакты и HTTPS-ссылки без данных доступа в URL. Внешние ссылки открываются в новом окне. |
| return_button_text | string | всегда | Только необязательная подпись. Адреса успеха и отмены и правила перенаправления по-прежнему принадлежат счёту. |
Запрос
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID" \
--header 'Accept: application/json'// Node.js 18+ · run on your server, never in browser code.
const response = await fetch("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID", {
method: "GET",
headers: {
"Accept": "application/json"
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ["Accept: application/json"],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Accept": "application/json"
}
request = Request("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Пример ответа · 200 application/json
{
"data": {
"preview": true,
"invoice_id": "YOUR_PROJECT_ID",
"amount": "100.00",
"currency": "USD",
"project": {
"name": "Example project",
"checkout_title": "Complete your payment",
"checkout_description": "Choose a network and send the exact amount shown.",
"theme": "system",
"accent_color": "#42e39b",
"logo_url": "/checkout-api/previews/…/logo/…/image.png"
},
"appearance": {"inherit_default_store": true, "theme": "system", "accent_color": "#42E39B", "images": {}},
"preview_methods": [],
"payment_methods": []
}
}GETИзображение платёжной страницы магазина/checkout-api/invoices/{invoice_id}/appearance-images/{kind}/{revision}/image.pngПубличный
Возвращает нормализованный логотип или favicon магазина, принадлежащий счёту. Используй URL appearance.images из данных оплаты.
- Используй appearance.images из JSON оплаты. Зафиксированные изображения счёта работают после замены или удаления загрузки магазином-источником. Явно удалённые, относящиеся к другому счёту, неверного вида и неизвестной версии дают 404; снимок не подменяется текущим изображением магазина.
- Без переопределения счёта используется текущее действующее изображение магазина, а заменённые/удалённые версии дают 404. Только PNG, nosniff и private-кеширование.
- Загрузка изображений магазина в авторизованной консоли принимает ограниченные PNG, JPEG или WebP; не SVG, HTML или внешние URL.
| Параметр | Тип / расположение | Правило |
|---|---|---|
| invoice_id | path UUID | Публичный UUID счёта. |
| kind | path enum | logo_light, logo_dark или favicon. |
| revision | path UUID | Текущая версия изображения. |
Запрос
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png" \
--output 'store-logo.png'// Node.js 18+ · run on your server, never in browser code.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
await writeFile("store-logo.png", Buffer.from(await response.arrayBuffer()));<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
file_put_contents("store-logo.png", $response);# Python 3 · standard library; run on your server.
import json
from pathlib import Path
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
Path("store-logo.png").write_bytes(response.read())Пример ответа · 200 image/png
(binary PNG response)GETИзображение предпросмотра магазина/checkout-api/previews/{project_id}/stores/{store_id}/appearance-images/{kind}/{revision}/image.pngПубличный
Нормализованное изображение предпросмотра возвращается только при совпадении проекта, магазина, вида и текущей версии.
- Используй appearance.images из предпросмотра. Неизвестные или несовпадающие ID дают 404. Данные кошельков и платежей не раскрываются.
| Параметр | Тип / расположение | Правило |
|---|---|---|
| project_id | path UUID | UUID проекта. |
| store_id | path UUID | Магазин, принадлежащий проекту. |
| kind | path enum | logo_light, logo_dark или favicon. |
| revision | path UUID | Текущая версия изображения. |
Запрос
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png" \
--output 'store-preview-logo.png'// Node.js 18+ · run on your server, never in browser code.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
await writeFile("store-preview-logo.png", Buffer.from(await response.arrayBuffer()));<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
file_put_contents("store-preview-logo.png", $response);# Python 3 · standard library; run on your server.
import json
from pathlib import Path
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
Path("store-preview-logo.png").write_bytes(response.read())Пример ответа · 200 image/png
(binary PNG response)GETЛоготип предпросмотра с номером версии/checkout-api/previews/{project_id}/logo/{revision}/image.pngПубличный
Возвращает нормализованный логотип проекта только при совпадении проекта и безопасной для кеша версии. Используй project.logo_url из предпросмотра, не собирай URL сам.
- Неизвестный проект и устаревшая версия логотипа дают invoice_not_found без раскрытия, какой компонент отсутствует.
- Успешно полученное версионное изображение неизменяемо и может кешироваться.
| Параметр | Тип / расположение | Правило |
|---|---|---|
| project_id | path UUID | UUID проекта. |
| revision | path UUID | Текущая версия логотипа оплаты из project.logo_url. |
Запрос
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/logo/YOUR_LOGO_REVISION/image.png" \
--output 'checkout-preview-logo.png'// Node.js 18+ · run on your server, never in browser code.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/logo/YOUR_LOGO_REVISION/image.png", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
await writeFile("checkout-preview-logo.png", Buffer.from(await response.arrayBuffer()));<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/logo/YOUR_LOGO_REVISION/image.png");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
file_put_contents("checkout-preview-logo.png", $response);# Python 3 · standard library; run on your server.
import json
from pathlib import Path
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/logo/YOUR_LOGO_REVISION/image.png",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
Path("checkout-preview-logo.png").write_bytes(response.read())Пример ответа · 200 image/png
(binary PNG response)GETQR-код оплаты/checkout-api/invoices/{invoice_id}/payment-methods/{intent_id}/qr.svgПубличный
Создаёт SVG QR 512×512 для точного платёжного запроса способа счёта с учётом сети.
- Bearer-токен не нужен.
- Используй qr_url с версией последовательности и остатка из JSON оплаты; SVG имеет private и no-store.
- После частичной оплаты запрашивается точный остаток с привязкой к тому же активу.
- Возвращает 409 после истечения, завершения или при активном другом способе; payment_qr_unavailable (422), если запрос слишком велик для кодирования.
| Параметр | Тип / расположение | Правило |
|---|---|---|
| invoice_id | path UUID | Публичный UUID счёта. |
| intent_id | path UUID | ID способа оплаты из JSON оплаты. |
Запрос
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/payment-methods/YOUR_INTENT_ID/qr.svg" \
--output 'payment-qr.svg'// Node.js 18+ · run on your server, never in browser code.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/payment-methods/YOUR_INTENT_ID/qr.svg", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
await writeFile("payment-qr.svg", Buffer.from(await response.arrayBuffer()));<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/payment-methods/YOUR_INTENT_ID/qr.svg");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
file_put_contents("payment-qr.svg", $response);# Python 3 · standard library; run on your server.
import json
from pathlib import Path
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/payment-methods/YOUR_INTENT_ID/qr.svg",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
Path("payment-qr.svg").write_bytes(response.read())Пример ответа · 200 image/svg+xml
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">…</svg>GETЛоготип оплаты с номером версии/checkout-api/invoices/{invoice_id}/logo/{revision}/image.pngПубличный
Возвращает нормализованный логотип оплаты проекта только при совпадении счёта и текущей версии. Предпочитай project.logo_url из JSON оплаты вместо самостоятельного построения пути.
- Bearer-токен не нужен.
- Публичный кеш на год с immutable, поскольку версия отражает содержимое.
- Неизвестные или несовпадающие версии дают invoice_not_found.
| Параметр | Тип / расположение | Правило |
|---|---|---|
| invoice_id | path UUID | Публичный UUID счёта. |
| revision | path UUID | Текущая версия логотипа оплаты, встроенная в project.logo_url. |
Запрос
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/logo/YOUR_LOGO_REVISION/image.png" \
--output 'checkout-logo.png'// Node.js 18+ · run on your server, never in browser code.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/logo/YOUR_LOGO_REVISION/image.png", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
await writeFile("checkout-logo.png", Buffer.from(await response.arrayBuffer()));<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/logo/YOUR_LOGO_REVISION/image.png");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
file_put_contents("checkout-logo.png", $response);# Python 3 · standard library; run on your server.
import json
from pathlib import Path
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/logo/YOUR_LOGO_REVISION/image.png",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
Path("checkout-logo.png").write_bytes(response.read())Пример ответа · 200 image/png
(binary PNG response)Справочник Wholly Crypto 7.5.5. Для установленной версии открой Настройки → Доступ к API → Документация в консоли. Посмотреть релизы.