Разработчикам/Интеграция
Каталог и заказы
Здесь по шагам описан весь процесс покупки: проверить баланс, найти товар, узнать цену, оформить заказ и получить коды.
См. также: Аутентификация · Общие правила · Вебхуки
#Аккаунт и баланс
#Аккаунт
GET/api/v1/account показывает данные вашей компании и то, включён ли доступ к API.
{
"merchant_id": "M00000001",
"name": "Acme Shop",
"legal_name": "Acme Shop Ltd",
"billing_email": "[email protected]",
"status": "active",
"kyb_status": "approved",
"api_access_enabled": true,
"default_currency": "USD",
"purchase_limits": {
"currency": "USD",
"verified": true,
"max_order_amount": "1000.00",
"daily_order_amount_limit": "10000.00",
"daily_order_count_limit": 1000,
"used_today_amount": "120.50",
"used_today_count": 3,
"day_resets_at": "2026-10-01T00:00:00+00:00"
}
}default_currency— валюта вашего кошелька. Все цены, которые вы платите, указаны в ней.api_access_enabledстановитсяtrue, когда CardV одобрит вашу компанию.
#Баланс
GET/api/v1/balance показывает, сколько денег вы можете потратить.
{
"currency": "USD",
"balance": "1520.40",
"reserved_amount": "0.00",
"available_balance": "1520.40",
"low_balance_threshold": "200.00",
"low_balance_notified_at": null,
"is_active": true
}available_balance— сумма, которую можно потратить прямо сейчас. Этоbalanceминусreserved_amount.- Заказ на сумму больше
available_balanceотклоняется, деньги не списываются. low_balance_threshold— порог, при котором приходит письмо о низком балансе. Он задаётся в кабинете.- Пополнить кошелёк можно в кабинете.
#Курс валют
GET/api/v1/fx-rates?quote=EUR возвращает курс USD к другой валюте.
Используйте его, если ваш магазин продаёт не в USD. CardV всегда списывает средства с кошелька в USD. Курс нужен только для того, чтобы вы сами задавали цены.
{
"base": "USD",
"quote": "EUR",
"rate": "0.9234",
"as_of": "2026-10-11T07:05:00Z"
}quote— код валюты ISO 4217, напримерEUR,GBPилиJPY.baseнеобязателен. Поддерживается толькоUSD.rate— сколько единицquoteвы получаете за 1 USD. Это десятичная строка. Не читайте её как float.as_of— когда CardV последний раз обновил этот курс. Курсы обновляются каждый час.quote=USDвозвращает"rate": "1".- Сохраняйте курс у себя. Достаточно запрашивать его несколько раз в день.
| Статус | Значение | Что делать |
|---|---|---|
| 400 | Нет quote, это не трёхбуквенный код, или base не USD | Исправьте запрос |
| 404 | No exchange rate for this currency. | Задайте цены в этой валюте сами |
| 503 | Exchange rate temporarily unavailable. | Продолжайте использовать последний сохранённый курс и повторите позже |
Подписывайте этот запрос, как и любой другой. Подписываемый путь включает строку запроса, например /api/v1/fx-rates?quote=EUR.
#Товары (SKU)
SKU — это конкретный товар, который можно купить, например «Steam Wallet 10 USD».
Его ID выглядит так: S000456. Цену запрашивают и заказ оформляют именно по SKU.
#Модели товаров
Каждый SKU относится к одной из этих моделей. Различайте их по denomination_type и product_type.
| Модель | Как распознать | Цена | Заказ | Что вы получаете |
|---|---|---|---|---|
| Фиксированный номинал | denomination_type: "fixed" | merchant_price — цена одной единицы. | Передайте quantity. | Коды в deliveries. |
| Диапазон | denomination_type: "range" | От merchant_price_min до merchant_price_max. Реальная цена зависит от суммы: запросите котировку. | Передайте quantity и amount. | Коды в deliveries. |
| Прямое пополнение | product_type: "direct_charge" | Как у фиксированного номинала. | Передайте также inputs (см. прямое пополнение). | Обычно без кодов. succeeded означает, что аккаунт пополнен. |
| Пополнение мобильной связи | Нет в этом каталоге | Своя котировка | Свой API | См. Пополнение мобильной связи. |
vertical— это линейка товаров, а не модель. У прямого пополнения тоже может бытьvertical: "gift_card".- Любой SKU с фиксированным номиналом или диапазоном может выдаваться как ссылка для активации CardV вместо кода. Тогда у SKU
delivery_mode: "voucher". Вы получаете ссылку (delivery_type: "link", ссылка вredeem_url). Клиент открывает её и получает код. Передайте её клиенту так же, как код.
GET/api/v1/skus возвращает список товаров, которые вам доступны. Пример:
GET /api/v1/skus?search=steam®ion=US&limit=50&offset=0Фильтры (все необязательные):
| Фильтр | Пример | Что ищет |
|---|---|---|
search | steam | ID товара, название или бренд |
brand | Steam | Название бренда (регистр не важен) |
region | US | Код или название страны. GLC — весь мир, EU — Европа |
category | Travel | Бизнес-категория, например Travel, Digital Wallets & Payment, Game Credits |
vertical | gift_card | Товарное направление |
product_type | pin_code | Способ выдачи |
Постраничная выдача: передайте limit (по умолчанию 100, максимум 500) и offset.
Увеличивайте offset и запрашивайте снова, пока offset не дойдёт до count.
{
"count": 7,
"limit": 2,
"results": [
{
"sku_id": "S000456",
"product_id": "P000123",
"name": "Steam Wallet 10 USD",
"product_name": "Steam Wallet US",
"brand": "Steam",
"region": "US",
"vertical": "gift_card",
"product_type": "pin_code",
"denomination_type": "fixed",
"denomination_value": "10",
"face_currency": "USD",
"merchant_price": "9.25",
"merchant_price_min": "9.25",
"merchant_price_max": "9.25",
"settlement_currency": "USD",
"availability": "available",
"min_quantity": 1,
"max_quantity": 100,
"required_input_schema": [],
"...": "more fields"
},
{
"sku_id": "S006126",
"product_id": "P002210",
"name": "Apple ES",
"product_name": "Apple ES",
"brand": "Apple",
"region": "ES",
"vertical": "gift_card",
"product_type": "pin_code",
"denomination_type": "range",
"denomination_value": "0",
"min_face_value": "2",
"max_face_value": "250",
"amount_step": null,
"face_currency": "EUR",
"merchant_price": "2.28",
"merchant_price_min": "2.28",
"merchant_price_max": "284.15",
"price_label": "From 2.28 USD",
"settlement_currency": "USD",
"availability": "available",
"...": "more fields"
}
],
"filter_options": {"brands": [], "regions": [], "categories": [], "verticals": []}
}GET/api/v1/skus/{sku_id} возвращает один товар с теми же полями.
Самые полезные поля:
| Поле | Что значит |
|---|---|
sku_id | ID, по которому запрашивают цену и оформляют заказ. |
merchant_price | Ваша цена за единицу в settlement_currency. Для SKU range это только цена суммы min_face_value, начальная цена. Не используйте её для других сумм: запросите котировку с amount. |
availability | available или unavailable. Заказывайте только available. |
denomination_type | fixed или range. См. фиксированный номинал и диапазон. |
denomination_value | Номинал SKU fixed. Для SKU range — "0". |
min_face_value, max_face_value | SKU range: минимальная и максимальная сумма, которую может выбрать клиент, в face_currency. |
merchant_price_min, merchant_price_max | Цена минимальной и максимальной суммы. Для SKU fixed равны merchant_price. Используйте их, чтобы показать диапазон цен; перед заказом всегда запрашивайте котировку точной суммы. merchant_price_max равен "", если сейчас его нельзя рассчитать. |
price_label | merchant_price в виде текста для показа. У SKU range начинается с From, например From 2.28 USD. |
face_value_label | Номинал в виде текста для показа, например 10 USD или 50 - 1000 GBP. |
discount | Скидка от номинала, например 7.50% off face value. Только для SKU fixed, у которых валюта карты совпадает с валютой кошелька; иначе "". |
amount_step | Только для товаров с диапазоном. Сумма должна быть кратна этому значению, например "1" означает только целые числа. null означает, что шага нет. |
face_currency | Валюта, указанная на карте. Может отличаться от валюты кошелька. |
min_quantity, max_quantity | Сколько единиц может быть в одной строке заказа. |
product_type | pin_code (вы получаете код) или direct_charge (мы пополняем аккаунт). |
delivery_type | Что выдаёт поставщик: code, code_pin, code_serial, url или direct_charge. Если delivery_mode равен voucher, вместо этого вы получаете ссылку. |
delivery_mode | code (вы получаете сам код) или voucher (вы получаете ссылку для активации CardV). |
voucher_instructions | Для SKU voucher: текст, который объясняет клиенту, как пользоваться ссылкой. Иначе "". |
vertical, category | Линейка товаров и бизнес-категория, для фильтров и показа. |
region, region_name, region_label | Код страны или региона, его название и подпись для показа. |
required_input_schema | Данные, которые нужно передать для прямого пополнения. |
brand_logo_url, image_url | Картинки на серверах CardV или "". |
description, redemption_instructions, terms, disclaimer | Тексты, которые можно показать покупателям. |
Советы:
- Вы видите только активные товары, открытые для вашего аккаунта. Для остальных придёт 404.
- Обновляйте список товаров каждые 5–15 минут. Прямо перед заказом всегда запрашивайте цену.
- Показывайте клиентам цены из синхронизированного списка. Не вызывайте запрос цены при каждом просмотре страницы или для каждого посетителя.
- Цены SKU с диапазоном следуют за стоимостью у поставщика и могут меняться несколько раз в день. Обновляйте
merchant_price_minиmerchant_price_maxвместе с синхронизацией списка товаров. filter_optionsперечисляет бренды, регионы и направления, по которым можно фильтровать.
#Синхронизация по товарам
Если в вашем магазине одна страница на товар с выбором номинала, синхронизируйте товары, а не SKU.
GET/api/v1/products возвращает список товаров. В каждом товаре есть все SKU, которые вы можете в нём купить.
GET /api/v1/products?brand=Steam®ion=GB&limit=50&offset=0Фильтры и постраничный вывод те же, что у GET/api/v1/skus. count — число товаров, sku_count — число их SKU.
{
"count": 1,
"sku_count": 4,
"limit": 50,
"results": [
{
"product_id": "P000123",
"product_name": "Steam Wallet UK",
"brand": "Steam",
"category": "Gaming",
"region": "GB",
"product_type": "pin_code",
"image_url": "https://b2b.cardv.net/api/v1/catalog-assets/steam.png",
"required_input_schema": [],
"skus": [
{
"sku_id": "S000456",
"name": "Steam Wallet 5 GBP",
"label": "5 GBP",
"denomination_type": "fixed",
"denomination_value": "5",
"face_currency": "GBP",
"merchant_price": "7.61",
"settlement_currency": "USD",
"availability": "available",
"min_quantity": 1,
"max_quantity": 100,
"...": "more fields"
}
]
}
],
"filter_options": {"brands": [], "regions": [], "categories": [], "verticals": []}
}GET/api/v1/products/{product_id} возвращает один товар с его SKU, а также description, redemption_instructions, terms и disclaimer.
- У каждого SKU в
skusте же поля и значения, что и вGET/api/v1/skus. Расчёт цены и заказ — как обычно, поsku_id. - Для текста варианта используйте
label, например5 GBPилиTinder Plus 1 Month. Некоторые товары — это тарифы, а не номиналы. - У товара одна валюта номинала и один тип суммы. SKU с произвольной суммой всегда выделен в отдельный товар.
- В списке нет длинных текстов. Получите их один раз для каждого товара через
GET/api/v1/products/{product_id}.
#Фиксированный номинал и диапазон
У большинства товаров фиксированный номинал, например 10 USD. У некоторых номинал задаётся диапазоном: покупатель сам выбирает сумму, например от 5 до 500 USD.
| Тип | При запросе цены | При заказе |
|---|---|---|
fixed | Передайте quantity | Не передавайте amount |
range | Передайте quantity и amount | Передайте amount |
Для товара с диапазоном amount должен быть между min_face_value и max_face_value.
Сумма указывается в face_currency.
Цена SKU с диапазоном. В списках SKU и товаров merchant_price и price_label — это только цена суммы min_face_value. merchant_price_min и merchant_price_max дают цену минимальной и максимальной суммы. Цена промежуточной суммы не является простым кратным ни одной из них. Прежде чем назначать свою цену продажи или заказывать, запросите котировку с точной amount и передайте её merchant_price как expected_unit_price.
У некоторых товаров с диапазоном есть ещё amount_step. Если он не null, amount должен быть ему кратен.
Например, при "amount_step": "1" можно отправить 25, но нельзя 25.50.
Запрос цены или заказ с суммой, не подходящей под шаг, вернёт HTTP 400, деньги не списываются.
#Прямое пополнение
Некоторые товары пополняют аккаунт вашего покупателя напрямую, например игровой аккаунт. Для этого CardV нужны данные аккаунта. Какие именно — указано в товаре:
"required_input_schema": [
{"key": "player_id", "label": "Player ID", "type": "text", "required": true},
{"key": "server", "label": "Server", "type": "text", "required": false}
]Передайте значения в поле inputs строки заказа, используя каждый key:
"inputs": {"player_id": "123456789", "server": "EU"}- Поле обязательно, если у него не указано
"required": false.type— подсказка для поля вашей формы, напримерtext. - Если обязательного значения нет, заказ отклоняется с ошибкой
items. - Это персональные данные вашего покупателя. Защищайте их (см. Безопасность).
#Запрос цены
Запрос цены показывает текущую цену за нужное количество.
GET /api/v1/skus/S000456/quote?quantity=2
GET /api/v1/skus/S000789/quote?quantity=1&amount=25.00{
"sku_id": "S000456",
"settlement_currency": "USD",
"merchant_price": "9.25",
"quantity": 2,
"total_price": "18.50",
"min_quantity": 1,
"max_quantity": 100,
"availability": "available"
}- Запрос цены не закрепляет цену. Цены могут измениться в любой момент.
- Запрашивайте цену один раз, когда клиент оформляет покупку, с коротким тайм-аутом, например 5 секунд. Если запрос не уложился в тайм-аут, продолжайте показывать синхронизированную цену и повторите попытку; никогда не показывайте страницу ошибки из-за того, что запрос цены был медленным.
- Чтобы подстраховаться, при заказе передайте
merchant_priceв полеexpected_unit_price. Передавайте цену из запроса цены, а не рассчитанную самостоятельно: она должна совпадать точно, до цента. Если цена изменилась, CardV отклонит заказ и ничего не спишет. - Количество или сумма вне допустимого диапазона, а также сумма, не кратная
amount_step, вернёт HTTP 400 с ошибкойquantityилиamount.
#Оформление заказа
POST/api/v1/orders покупает один или несколько товаров и оплачивает их из кошелька.
Как и любой запрос, его нужно подписать.
{
"external_order_id": "SHOP-20260929-10001",
"items": [
{"sku_id": "S000456", "quantity": 2, "expected_unit_price": "9.25"},
{"sku_id": "S000789", "amount": "25.00", "expected_unit_price": "23.75"},
{
"sku_id": "S000900",
"expected_unit_price": "4.90",
"inputs": {"player_id": "123456789"}
}
]
}| Поле | Обязательно | Что значит |
|---|---|---|
external_order_id | Да | Ваш номер заказа, от 1 до 120 символов. Должен быть уникальным. |
items | Да | Одна или несколько строк заказа. |
items[].sku_id | Да | Товар, который вы покупаете. |
items[].quantity | Нет | Сколько штук. По умолчанию 1. |
items[].amount | Для товаров с диапазоном | Номинал, который вы покупаете. Должен соответствовать min_face_value, max_face_value и amount_step. |
items[].expected_unit_price | Желательно | merchant_price из запроса цены. Передавайте всегда. |
items[].inputs | Для прямого пополнения | Данные аккаунта для прямого пополнения. |
Приняв заказ, CardV сразу списывает с кошелька всю сумму. Затем CardV в фоновом режиме начинает выдачу кодов.
Ответ приходит с HTTP 201:
{
"idempotent_replay": false,
"order": {
"order_id": "O-00001234",
"external_order_id": "SHOP-20260929-10001",
"status": "accepted",
"total_amount": "46.20",
"...": "more fields"
}
}Сохраните order.order_id. Кодов в этом ответе нет никогда — их нужно получить позже (см. Получение заказа).
#Отклонённые заказы
Отклонённый заказ возвращает HTTP 400, деньги не списываются. Причину подскажет ключ ошибки:
| Ключ | Причина | Что делать |
|---|---|---|
items | Изменилась цена, товар недоступен, неверная сумма или не хватает данных | Запросите цену заново, исправьте и отправьте снова |
balance | В кошельке не хватает денег | Пополните кошелёк в кабинете |
risk | Превышен лимит на размер заказа или дневной лимит | Свяжитесь с CardV |
external_order_id | Ваш номер заказа уже занят другим заказом | См. Безопасный повтор |
Пример ошибки из-за изменения цены:
{
"items": "SKU S000456 price changed from 9.25 to 9.41 USD; refresh quote and confirm again."
}У вашего аккаунта есть лимиты на один заказ, на сумму покупок за день и на число заказов за день.
GET/api/v1/account показывает их в purchase_limits вместе с тем, сколько уже использовано сегодня.
0 означает «без лимита». Дневные лимиты сбрасываются в 00:00 UTC.
#Безопасный повтор
Ваш номер заказа (external_order_id) защищает от двойной покупки.
Если вы отправите тот же заказ с тем же номером ещё раз, CardV не спишет деньги повторно,
а вернёт уже существующий заказ.
| Что вы отправляете | Что получаете |
|---|---|
| Новый номер заказа | HTTP 201. Новый заказ. Деньги списываются с кошелька. |
| Тот же номер и тот же заказ | HTTP 200 и "idempotent_replay": true. Существующий заказ. Без списания. |
| Тот же номер, но другой заказ | HTTP 400 с ошибкой external_order_id. Ничего не происходит. |
«Тот же заказ» — это те же строки в том же порядке, с теми же товарами, количеством, суммой и данными inputs.
Если вы передаёте expected_unit_price, он должен совпадать с ценой из первого заказа.
Повторный заказ распознаётся ещё до проверки баланса и цены. Поэтому всегда возвращается первый заказ, даже если с тех пор цена изменилась.
#Как повторять без риска
Если вы не получили понятного ответа, просто отправьте тот же заказ ещё раз.
POST /orders с номером заказа R
├─ 201 или 200 → сохраните order_id. Готово.
├─ 400 items / balance / risk → заказ не создан.
│ Устраните причину и отправьте снова. R можно оставить.
├─ 400 external_order_id → R уже занят другим заказом. Остановитесь и разберитесь.
├─ 403 ошибка подписи → подпишите заново и отправьте то же тело.
├─ 429 → дождитесь Retry-After, подпишите заново, отправьте то же тело.
└─ тайм-аут, 5xx или обрыв соединения
→ отправьте то же тело с тем же R.
Придёт 201 (первая попытка не дошла) или 200 (дошла).Правила:
- Никогда не придумывайте новый номер заказа из-за того, что ответ потерялся. Если первый запрос всё-таки дошёл, с новым номером вы купите всё дважды.
- Для каждой повторной отправки нужны новые время, nonce и подпись. Тело остаётся прежним.
#Получение заказа
GET/api/v1/orders/{order_id} возвращает заказ, его статус и коды.
{
"order_id": "O-00001234",
"external_order_id": "SHOP-20260929-10001",
"status": "succeeded",
"currency": "USD",
"total_amount": "18.50",
"created_at": "2026-09-29T08:15:30.123456Z",
"updated_at": "2026-09-29T08:15:41.004211Z",
"items": [
{
"sku_id": "S000456",
"product_name": "Steam Wallet US",
"quantity": 2,
"unit_price": "9.25",
"total_price": "18.50",
"delivery_count": 2,
"deliveries": [{"...": "see Codes below"}]
}
],
"...": "more fields"
}total_amount— сумма, списанная при приёме заказа.items[].unit_price— цена, закреплённая за этим заказом.- В
items[].deliveriesлежат коды целиком. Обращайтесь с ответом как с секретом. invoice_urlиdelivery_file_url— пути к счёту и к кодам в формате CSV. Оба работают только в кабинете. С API-ключом они вернут HTTP 403.- Ещё в ответе есть
id(старый номер, не используйте его),events(история только для показа) и ход выдачи по каждой строке. Всё это можно не учитывать. - Для неизвестного номера заказа придёт HTTP 404.
#Статус заказа и коды
#Статус
accepted ──► processing ──► succeeded
│
├──► partially_succeeded (часть строк выдана, часть нет)
│
└──► failed ──► refunded (деньги вернулись в кошелёк)| Статус | Завершён? | Что делать |
|---|---|---|
accepted | Нет | Ждать. Деньги списаны, выдача ещё не началась. |
processing | Нет | Ждать. Не оформляйте заказ повторно. |
succeeded | Да | Получите коды и передайте их покупателю. |
partially_succeeded | Да | Выдайте то, что пришло. За остальное деньги вернутся позже. |
failed | Ещё нет | Дождитесь refunded. Статус failed — это ещё не возврат денег. |
refunded | Да | Деньги вернулись в кошелёк. |
Если вы не используете вебхуки, опрашивайте так: через 5 секунд, затем через 10 с, 30 с, 60 с, а дальше каждые 5 минут. Не выходите за лимит запросов. Большинство заказов завершается за секунды. Некоторым нужна ручная проверка, и она может занять несколько часов.
#Коды
Каждая выданная единица — отдельный объект в items[].deliveries:
{
"status": "stored",
"delivery_type": "card_pin",
"card_number": "X1234",
"pin_code": "9876",
"redeem_url": "",
"expiry_date": "2027-09-29",
"instructions": "Redeem at ..."
}| Поле | Что это |
|---|---|
card_number | Основной код: номер карты, код ваучера или PIN. |
pin_code | Второй код, например PIN или защитный код. Пустое, если второго кода нет. |
redeem_url | Ссылка для активации. Для выдачи типа link сама ссылка и есть код. |
expiry_date | Последний день, когда код можно использовать, в формате YYYY-MM-DD. "" означает, что поставщик не указал срок действия. Это не значит, что срок действия кода истёк. |
- Передайте покупателю все непустые поля:
card_number,pin_code,redeem_url,expiry_dateиinstructions. delivery_typeпоказывает, что именно вы получили:code,card_pin,link,code_linkилиqr.- Держите
redeem_urlв секрете, как и код. - Никогда не удаляйте, не скрывайте и не аннулируйте код из-за того, что
expiry_dateпуст. Показывайте клиенту срок действия, только если поле не пустое. - Никогда не передавайте покупателю единицу со
statusравнымvoided. - У товаров с прямым пополнением выдачи обычно нет.
succeededзначит, что аккаунт пополнен. - Кодов в вебхуках никогда нет. Получив вебхук, запросите заказ.
Вопросы по интеграции? Напишите на [email protected], указав ваш Merchant ID и ID заказа или запроса.