Desenvolvedores/Integração

Catálogo e pedidos

Este guia mostra o fluxo de compra do começo ao fim: consultar o saldo, encontrar um produto, ver o preço, fazer o pedido e receber os códigos.

Veja também: Autenticação · Convenções · Webhooks

#Conta e saldo

#Conta

GET/api/v1/account mostra os dados da sua empresa e se a API está liberada.

JSON
{
  "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 é a moeda da sua carteira. Todos os preços que você paga estão nessa moeda.
  • api_access_enabled fica true depois que a CardV aprova a sua empresa.

#Saldo

GET/api/v1/balance mostra quanto você tem para gastar.

JSON
{
  "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 é o que você pode gastar agora: balance menos reserved_amount.
  • Um pedido maior que o available_balance é recusado, e nada é cobrado.
  • low_balance_threshold é o valor que dispara o e-mail de saldo baixo. Você define esse valor no Portal.
  • Para adicionar saldo, use o Portal.

#Taxa de câmbio

GET/api/v1/fx-rates?quote=EUR retorna a taxa de câmbio de USD para outra moeda.

Use-a se sua loja vende em uma moeda diferente de USD. A CardV sempre debita sua carteira em USD. A taxa serve apenas para você definir seus próprios preços.

JSON
{
  "base": "USD",
  "quote": "EUR",
  "rate": "0.9234",
  "as_of": "2026-10-11T07:05:00Z"
}
  • quote é um código de moeda ISO 4217, como EUR, GBP ou JPY.
  • base é opcional. Somente USD é aceito.
  • rate é quantas unidades de quote você recebe por 1 USD. É uma string decimal. Não a leia como float.
  • as_of é quando a CardV atualizou esta taxa pela última vez. As taxas são atualizadas a cada hora.
  • quote=USD retorna "rate": "1".
  • Guarde a taxa do seu lado. Consultar algumas vezes por dia é suficiente.
StatusSignificadoO que fazer
400quote está ausente ou não é um código de 3 letras, ou base não é USDCorrija a requisição
404No exchange rate for this currency.Defina você mesmo os preços nesta moeda
503Exchange rate temporarily unavailable.Continue usando a última taxa salva e tente novamente mais tarde

Assine esta requisição como qualquer outra. O caminho assinado inclui a query string, por exemplo /api/v1/fx-rates?quote=EUR.

#Produtos (SKUs)

Um SKU é um produto que você pode comprar, por exemplo "Steam Wallet 10 USD". O ID tem este formato: S000456. É pelo SKU que você consulta o preço e faz o pedido.

#Modelos de produto

Todo SKU segue um destes modelos. Use denomination_type e product_type para diferenciá-los.

ModeloComo reconhecerPreçoPedidoO que você recebe
Valor fixodenomination_type: "fixed"merchant_price é o preço de uma unidade.Envie quantity.Códigos em deliveries.
Valor variáveldenomination_type: "range"De merchant_price_min a merchant_price_max. O preço real depende do valor: peça uma cotação.Envie quantity e amount.Códigos em deliveries.
Recarga diretaproduct_type: "direct_charge"Igual ao valor fixo.Envie também inputs (veja recargas diretas).Normalmente sem códigos. succeeded significa que a conta foi recarregada.
Recarga de celularFora deste catálogoCotação própriaAPI própriaVeja Recarga de celular.
  • vertical é a linha de produto, não o modelo. Recargas diretas também podem ter vertical: "gift_card".
  • Qualquer SKU de valor fixo ou variável pode ser entregue como um link de resgate da CardV em vez de um código. Nesse caso, o SKU tem delivery_mode: "voucher". Você recebe um link (delivery_type: "link", o link fica em redeem_url). O seu cliente o abre para obter o código. Envie-o ao seu cliente como faria com um código.

GET/api/v1/skus lista os SKUs que você pode comprar. Exemplo:

HTTP
GET /api/v1/skus?search=steam&region=US&limit=50&offset=0

Filtros (todos opcionais):

FiltroExemploO que busca
searchsteamID do SKU, nome ou marca
brandSteamNome da marca (maiúsculas ou minúsculas)
regionUSCódigo ou nome do país. GLC = global, EU = Europa
categoryTravelCategoria comercial, por exemplo Travel, Digital Wallets & Payment, Game Credits
verticalgift_cardLinha de produto
product_typepin_codeForma de entrega

Paginação: envie limit (padrão 100, máximo 500) e offset. Continue pedindo com um offset maior até offset chegar a count.

JSON
{
  "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} retorna um único SKU, com os mesmos campos.

Os campos mais úteis:

CampoSignificado
sku_idO ID que você usa para ver o preço e fazer o pedido.
merchant_priceO seu preço por unidade, em settlement_currency. Num SKU range é apenas o preço de min_face_value, um preço inicial. Não o use para outros valores: peça uma cotação com amount.
availabilityavailable ou unavailable. Só compre SKUs available.
denomination_typefixed ou range. Veja valor fixo ou variável.
denomination_valueValor de face de um SKU fixed. "0" num SKU range.
min_face_value, max_face_valueSKUs range: o valor mínimo e máximo que o seu cliente pode escolher, em face_currency.
merchant_price_min, merchant_price_maxPreço do valor mínimo e do máximo. Em SKUs fixed, são iguais a merchant_price. Use-os para mostrar uma faixa de preço; antes de fazer o pedido, peça sempre uma cotação do valor exato. merchant_price_max é "" quando não pode ser calculado no momento.
price_labelmerchant_price como texto de exibição. Em SKUs range começa com From, por exemplo From 2.28 USD.
face_value_labelValor de face como texto de exibição, por exemplo 10 USD ou 50 - 1000 GBP.
discountDesconto sobre o valor de face, por exemplo 7.50% off face value. Só em SKUs fixed cuja moeda do cartão é a moeda da sua carteira; caso contrário, "".
amount_stepSó SKUs de valor variável. O valor precisa ser múltiplo deste número; por exemplo, "1" significa apenas números inteiros. null significa sem incremento.
face_currencyMoeda impressa no cartão. Pode ser diferente da moeda da sua carteira.
min_quantity, max_quantityQuantas unidades cabem em um item do pedido.
product_typepin_code (você recebe um código) ou direct_charge (a CardV recarrega uma conta).
delivery_typeO que o fornecedor entrega: code, code_pin, code_serial, url ou direct_charge. Quando delivery_mode é voucher, você recebe um link no lugar.
delivery_modecode (você recebe o próprio código) ou voucher (você recebe um link de resgate da CardV).
voucher_instructionsEm SKUs voucher: texto que explica ao seu cliente como usar o link. Caso contrário, "".
vertical, categoryLinha de produto e categoria de negócio, para filtros e exibição.
region, region_name, region_labelCódigo do país ou região, o nome dele e um rótulo de exibição.
required_input_schemaDados que você precisa enviar nas recargas diretas.
brand_logo_url, image_urlImagens hospedadas pela CardV, ou "".
description, redemption_instructions, terms, disclaimerTextos que você pode mostrar aos seus clientes.

Dicas:

  • Você só vê SKUs ativos e liberados para a sua conta. Os outros retornam 404.
  • Atualize a lista de SKUs a cada 5–15 minutos. Consulte sempre o preço logo antes de fazer o pedido.
  • Mostre aos seus clientes os preços da sua lista sincronizada. Não chame o endpoint de cotação a cada visualização de página ou visitante.
  • Os preços de SKUs de valor variável acompanham o custo do fornecedor e podem mudar algumas vezes por dia. Atualize merchant_price_min e merchant_price_max junto com a sincronização de SKUs.
  • filter_options mostra as marcas, regiões e linhas de produto que você pode usar nos filtros.

#Sincronizar por produto

Se a sua loja mostra uma página por produto com seletor de valor, sincronize produtos em vez de SKUs. GET/api/v1/products lista produtos. Cada produto traz todos os SKUs que você pode comprar nele.

HTTP
GET /api/v1/products?brand=Steam&region=GB&limit=50&offset=0

Aceita os mesmos filtros e a mesma paginação de GET/api/v1/skus. count conta produtos e sku_count conta os SKUs deles.

JSON
{
  "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} retorna um produto com seus SKUs, além de description, redemption_instructions, terms e disclaimer.

  • Cada SKU em skus tem os mesmos campos e valores de GET/api/v1/skus. Cote e compre com o sku_id como sempre.
  • Use label como texto da opção, por exemplo 5 GBP ou Tinder Plus 1 Month. Alguns produtos são planos, não valores.
  • Um produto tem uma única moeda de valor de face e um único tipo de valor. Um SKU de valor variável é sempre um produto separado.
  • A lista não traz os textos longos. Leia-os uma vez por produto em GET/api/v1/products/{product_id}.

#Valor fixo ou variável

A maioria dos SKUs tem valor de face fixo (fixed), como 10 USD. Alguns têm valor variável (range): o seu cliente escolhe o valor, por exemplo de 5 a 500 USD.

TipoAo consultar o preçoAo fazer o pedido
fixedEnvie quantityNão envie amount
rangeEnvie quantity e amountEnvie amount

Em um SKU de valor variável, amount precisa estar entre min_face_value e max_face_value. O valor é em face_currency.

Preço de um SKU de valor variável. Nas listas de SKUs e de produtos, merchant_price e price_label são apenas o preço de min_face_value. merchant_price_min e merchant_price_max dão o preço do valor mínimo e do máximo. O preço de um valor intermediário não é um múltiplo simples de nenhum deles. Antes de definir o seu preço de venda ou de encomendar, peça uma cotação com o amount exato e envie o merchant_price dessa cotação como expected_unit_price.

Alguns SKUs de valor variável também têm um amount_step. Quando não é null, amount precisa ser múltiplo dele. Por exemplo, com "amount_step": "1" você pode enviar 25, mas não 25.50. Uma cotação ou pedido com um valor que não respeita o incremento retorna HTTP 400 e não cobra nada.

#Recargas diretas

Alguns produtos recarregam direto a conta do seu cliente, por exemplo uma conta de jogo. Nesses casos, a CardV precisa dos dados da conta. O SKU informa quais são:

JSON
"required_input_schema": [
  {"key": "player_id", "label": "Player ID", "type": "text", "required": true},
  {"key": "server", "label": "Server", "type": "text", "required": false}
]

Envie os valores no campo inputs do item do pedido, usando cada key:

JSON
"inputs": {"player_id": "123456789", "server": "EU"}
  • Todo campo é obrigatório, a menos que venha com "required": false. type é uma dica para o campo do seu formulário, como text.
  • Se faltar um valor obrigatório, o pedido é recusado com um erro em items.
  • Esses valores são dados pessoais do seu cliente. Proteja-os (veja Segurança).

#Consulta de preço

A consulta de preço (quote) informa o preço atual para uma quantidade.

HTTP
GET /api/v1/skus/S000456/quote?quantity=2
GET /api/v1/skus/S000789/quote?quantity=1&amount=25.00
JSON
{
  "sku_id": "S000456",
  "settlement_currency": "USD",
  "merchant_price": "9.25",
  "quantity": 2,
  "total_price": "18.50",
  "min_quantity": 1,
  "max_quantity": 100,
  "availability": "available"
}
  • A consulta não garante o preço. Os preços podem mudar a qualquer momento.
  • Consulte o preço uma única vez, quando o cliente finalizar a compra, com um tempo limite curto, por exemplo 5 segundos. Se a consulta exceder o tempo limite, continue mostrando o preço sincronizado e tente de novo; nunca mostre uma página de erro porque uma consulta demorou.
  • Para se proteger, envie o merchant_price como expected_unit_price ao fazer o pedido. Envie o preço de uma consulta, não um preço que você mesmo calculou: ele precisa ser exatamente igual, até o centavo. Se o preço tiver mudado, a CardV recusa o pedido e não cobra nada.
  • Quantidade ou valor fora do intervalo, ou um valor que não é múltiplo de amount_step, retorna HTTP 400 com erro em quantity ou amount.

#Fazer um pedido

POST/api/v1/orders compra um ou mais SKUs e paga com a sua carteira. Como toda chamada, ela precisa ser assinada.

JSON
{
  "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"}
    }
  ]
}
CampoObrigatórioSignificado
external_order_idSimO seu número de pedido, de 1 a 120 caracteres. Não pode se repetir.
itemsSimUm ou mais itens do pedido.
items[].sku_idSimO SKU que você quer comprar.
items[].quantityNãoQuantas unidades. Padrão: 1.
items[].amountSKUs de valor variávelO valor de face que você quer comprar. Precisa respeitar min_face_value, max_face_value e amount_step.
items[].expected_unit_priceRecomendadoO merchant_price da consulta. Envie sempre.
items[].inputsRecargas diretasDados da conta para recargas diretas.

Quando a CardV aceita o pedido, ela cobra o valor total da sua carteira na hora. A entrega começa em seguida, em segundo plano.

A resposta é HTTP 201:

JSON
{
  "idempotent_replay": false,
  "order": {
    "order_id": "O-00001234",
    "external_order_id": "SHOP-20260929-10001",
    "status": "accepted",
    "total_amount": "46.20",
    "...": "more fields"
  }
}

Guarde o order.order_id. Esta resposta nunca traz os códigos. Você os busca depois (veja Consultar um pedido).

#Pedidos recusados

Um pedido recusado retorna HTTP 400 e não cobra nada. A chave do erro diz o motivo:

ChaveMotivoO que fazer
itemsPreço mudou, SKU indisponível, valor inválido ou dado faltandoConsulte o preço de novo, corrija e reenvie
balanceSaldo insuficiente na carteiraAdicione saldo no Portal
riskAcima do seu limite por pedido ou do limite diárioFale com a CardV
external_order_idO seu número de pedido já foi usado em outro pedidoVeja Reenviar com segurança

Exemplo de mudança de preço:

JSON
{
  "items": "SKU S000456 price changed from 9.25 to 9.41 USD; refresh quote and confirm again."
}

A sua conta tem limites por pedido, de gasto diário e de quantidade de pedidos por dia. GET/api/v1/account mostra esses limites em purchase_limits, com o quanto você já usou hoje. 0 significa sem limite. Os limites diários zeram às 00:00 UTC.

#Reenviar com segurança

O seu número de pedido (external_order_id) evita que você compre duas vezes. Se você reenviar o mesmo pedido com o mesmo número, a CardV não cobra de novo. Ela devolve o pedido que já existe.

Você enviaVocê recebe
Um número de pedido novoHTTP 201. Um pedido novo. A carteira é cobrada.
O mesmo número e o mesmo pedidoHTTP 200 e "idempotent_replay": true. O pedido que já existe. Nenhuma cobrança.
O mesmo número, mas um pedido diferenteHTTP 400 em external_order_id. Nada acontece.

"O mesmo pedido" quer dizer os mesmos itens, na mesma ordem, com o mesmo SKU, quantidade, valor e dados de recarga. Se você enviar expected_unit_price, ele precisa ser igual ao preço do primeiro pedido.

A CardV reconhece o pedido repetido antes de verificar saldo e preço. Por isso a resposta é sempre o primeiro pedido, mesmo que o preço tenha mudado depois.

#Fluxo de reenvio seguro

Se você não tiver uma resposta clara, basta reenviar o mesmo pedido.

Text
POST /orders com o número de pedido R
 ├─ 201 ou 200 → guarde o order_id. Pronto.
 ├─ 400 items / balance / risk → nenhum pedido foi criado.
 │      Corrija a causa e reenvie. Você pode usar R de novo.
 ├─ 400 external_order_id → R pertence a outro pedido. Pare e verifique.
 ├─ 403 erro de assinatura → assine de novo e envie o mesmo corpo.
 ├─ 429 → espere o Retry-After, assine de novo e envie o mesmo corpo.
 └─ timeout, 5xx ou conexão perdida
        → reenvie o mesmo corpo com o mesmo R.
          Você recebe 201 (a primeira tentativa não chegou) ou 200 (chegou).

Regras:

  • Nunca crie um número de pedido novo só porque uma resposta se perdeu. Se a primeira requisição tiver chegado, um número novo compraria tudo duas vezes.
  • Cada reenvio precisa de timestamp, nonce e assinatura novos. O corpo continua igual.

#Consultar um pedido

GET/api/v1/orders/{order_id} retorna o pedido, o status e os códigos.

JSON
{
  "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 é o valor cobrado quando o pedido foi aceito.
  • items[].unit_price é o preço travado para este pedido.
  • items[].deliveries traz os códigos completos. Trate a resposta como confidencial.
  • invoice_url e delivery_file_url são caminhos para a fatura e para os códigos em CSV. Os dois só funcionam no Portal. Com chave de API, retornam HTTP 403.
  • A resposta também traz id (um número antigo, não use), events (histórico só para exibição) e o andamento da entrega de cada item. Você pode ignorar esses campos.
  • Um número de pedido desconhecido retorna HTTP 404.

#Status do pedido e códigos

#Status

Text
accepted ──► processing ──► succeeded
                  │
                  ├──► partially_succeeded   (alguns itens entregues, outros não)
                  │
                  └──► failed ──► refunded   (dinheiro devolvido à sua carteira)
StatusTerminou?O que fazer
acceptedNãoAguarde. A carteira já foi cobrada e a entrega ainda não começou.
processingNãoAguarde. Não faça o pedido de novo.
succeededSimBusque os códigos e entregue ao seu cliente.
partially_succeededSimEntregue o que chegou. O restante é reembolsado depois.
failedAinda nãoAguarde o refunded. Falha ainda não é reembolso.
refundedSimO dinheiro voltou para a sua carteira.

Se você não usa webhooks, consulte o pedido assim: depois de 5 segundos, depois 10 s, 30 s, 60 s e então a cada 5 minutos. Respeite o limite de requisições. A maioria dos pedidos termina em segundos. Alguns precisam de verificação manual e podem levar horas.

#Códigos

Cada unidade entregue é um objeto em items[].deliveries:

JSON
{
  "status": "stored",
  "delivery_type": "card_pin",
  "card_number": "X1234",
  "pin_code": "9876",
  "redeem_url": "",
  "expiry_date": "2027-09-29",
  "instructions": "Redeem at ..."
}
CampoO que é
card_numberO código principal: número do cartão, código do voucher ou PIN.
pin_codeO segundo código, como um PIN ou código de segurança. Vazio quando não existe.
redeem_urlLink de resgate. Nas entregas do tipo link, o próprio link é o código.
expiry_dateÚltimo dia em que o código pode ser usado, no formato YYYY-MM-DD. "" significa que o fornecedor não informou data de validade. Não significa que o código expirou.
  • Entregue ao seu cliente todos os campos que não estiverem vazios: card_number, pin_code, redeem_url, expiry_date e instructions.
  • delivery_type diz o que você recebeu: code, card_pin, link, code_link ou qr.
  • Mantenha redeem_url em sigilo, como um código.
  • Nunca exclua, oculte ou anule um código porque expiry_date está vazio. Mostre uma data de validade ao seu cliente apenas quando ela não estiver vazia.
  • Nunca entregue ao cliente uma unidade com status igual a voided.
  • Produtos de recarga direta normalmente não têm entregas. succeeded significa que a conta foi recarregada.
  • Os webhooks nunca trazem códigos. Consulte o pedido depois que o webhook chegar.

Dúvidas sobre a integração? Envie um e-mail para [email protected] com seu Merchant ID e o ID do pedido ou da requisição.