API Cards Realm Gestão de Lojas v1
API REST para integrar sua loja com sistemas externos: estoque, pedidos e busca de cartas.
Autenticação
Gere uma chave de API na página de configurações da sua loja (Configurações → API) e envie em todas as requisições no cabeçalho:
Authorization: Bearer crk_live_xxxxxxxxxxxxxxxxxxxxxxxx
Guarde sua chave com segurança. Ela dá acesso total ao estoque e pedidos da sua loja e só é exibida uma vez, no momento em que é gerada.
Limites de uso
120 requisições/minuto para leitura, 60/minuto para escrita, 10/minuto para atualização de preços em massa, por chave de API.
Loja
GET /api/v1/store
Retorna os dados básicos da sua loja.
Estoque
GET /api/v1/products?page=1&q=ilha
POST /api/v1/products
PATCH /api/v1/products/<selling_id>
DELETE /api/v1/products/<selling_id>
POST /api/v1/products/bulk-price
Estes endpoints operam sobre CARTAS do catálogo. Produtos que não são carta (board game, sleeve, playmat, hora de estúdio) vivem em outra tabela e têm endpoints próprios - ver abaixo.
Produtos que não são carta
GET /api/v1/custom-products
POST /api/v1/custom-products
PATCH /api/v1/custom-products/<id>
DELETE /api/v1/custom-products/<id>
Só name e price_cents são
obrigatórios na criação; o restante (slug, descrição, categoria, código de barras, localização,
atributos livres) é opcional. O PATCH é parcial: o que você não mandar
fica como está.
O limite de produtos do seu plano conta cartas e produtos customizados JUNTOS - contar só um lado transformaria a API na porta dos fundos do limite.
Exemplo: adicionar uma carta ao estoque (use /api/v1/cards/search e
/api/v1/cards/<card_id>/editions para descobrir os IDs):
curl -X POST https://SEU-DOMINIO/api/v1/products \
-H "Authorization: Bearer crk_live_..." \
-H "Content-Type: application/json" \
-d '{
"card_id": 15377,
"print_id": 222,
"set_number": "273",
"quality_id": 1,
"language_id": 1,
"quantity": 4,
"price": "12.50",
"foil": false
}'
Atualização de preços em massa:
curl -X POST https://SEU-DOMINIO/api/v1/products/bulk-price \
-H "Authorization: Bearer crk_live_..." \
-H "Content-Type: application/json" \
-d '{"rule": "percent", "value": 10}'
rule: "percent" (percentual +/-), "fixed_add" (soma/subtrai valor fixo), "set_fixed" (define preço fixo). Filtros opcionais: print_id, price_min, price_max.
Carrinho pronto para enviar ao cliente
POST /api/v1/carts
{"items": [{"selling_id": 123, "quantity": 4},
{"custom_product_id": 9, "quantity": 1},
{"bundle_id": 3, "quantity": 1}]}
Monta um carrinho e devolve um link para você mandar ao cliente por WhatsApp, e-mail ou o que preferir. Quem abrir vê a lista com preço e estoque do momento e leva tudo para o próprio carrinho com um clique. Serve para recuperação de venda, orçamento e reserva.
{"token": "kR3...", "url": "https://sualoja.com.br/lista/kR3...",
"lines": 3, "unavailable": [{"selling_id": 123, "requested": 4, "available": 1}]}
Cada item traz exatamente um entre selling_id,
custom_product_id e bundle_id, mais um
quantity opcional (padrão 1). Este endpoint aceita só JSON - lista de
objetos não cabe em formulário. Até 100 linhas por chamada.
Um item que não é da sua loja recusa a chamada inteira (400), dizendo
qual linha e por quê - gerar um link com parte da lista faria você achar que deu certo e o
cliente receber menos do que foi prometido. Já estoque que acabou não é erro: o link sai
assim mesmo e o que falta vem em unavailable, para a sua automação
decidir se avisa, troca o item ou não envia.
O link não guarda preço, de propósito: preço e disponibilidade são
lidos ao vivo quando o cliente abre, então um link de semanas atrás nunca vende pelo valor de
antes. Se você precisa do valor para escrever a mensagem, leia
GET /api/v1/products no mesmo fluxo.
Pedidos
GET /api/v1/orders
GET /api/v1/orders/<order_id>
O mesmo formato é usado nos webhooks de pedido:
{
"order_id": 123,
"status": "paid",
"buyer_name": "...", "buyer_email": "...",
"currency": "BRL",
"subtotal_cents": 30000,
"discount_cents": 6000,
"coupon_code": "PROMO20",
"commission_cents": 2020,
"commission_rate": 0.08,
"commission_fixed_cents": 100,
"store_payout_cents": 21980,
"shipping_cents": 1500, "shipping_carrier": "jadlog", "shipping_service": "Package",
"insurance_cents": 0,
"fulfillment_method": "shipping",
"pickup_scheduled_at": null,
"created_at": "...", "paid_at": "...",
"items": [
{
"card_name": "...", "edition": "...", "quality": "NM", "language": "English",
"quantity": 2, "unit_price_cents": 10000,
"is_custom_product": false,
"is_preorder": false, "full_price_cents": null,
"bundle_name": null
}
]
}
Atenção ao conferir valores: subtotal_cents é o valor ANTES do desconto. O que o cliente pagou pelos produtos é subtotal_cents - discount_cents, e é sobre esse valor que commission_cents é calculada. A comissão tem duas partes: commission_fixed_cents somado a commission_rate vezes esse valor - as duas ficam congeladas no pedido, então uma mudança de preço da plataforma nunca reescreve uma venda já feita. Em um item de pré-venda, unit_price_cents é apenas o depósito cobrado agora - full_price_cents traz o preço cheio.
Busca de cartas
GET /api/v1/cards/search?q=ilha
GET /api/v1/cards/<card_id>/editions
Marcar um pedido como enviado
POST /api/v1/orders/<order_id>/ship
{"tracking_code": "AA123456789BR", "tracking_carrier": "correios"}
Guarda o rastreio, avisa o comprador por e-mail com o link da transportadora e dispara
o evento order.shipped. Os dois campos são opcionais - dá para despachar
sem código (entrega em mãos, motoboy), e o que muda a vida do comprador é saber que saiu. Só um
pedido pago pode ser despachado.
Agendamento
Para um bot (n8n, WhatsApp) marcar horário e cobrar a caução sozinho. Estes endpoints obedecem exatamente as mesmas regras da página pública de agendamento - janela de atendimento, antecedência mínima, intervalo entre atendimentos e bloqueios -, então o bot nunca oferece um horário que a loja não abre.
GET /api/v1/booking-services
GET /api/v1/booking-services/<id>/slots?from=2026-09-01&days=30
POST /api/v1/bookings
GET /api/v1/bookings (filtro opcional ?status=confirmed)
GET /api/v1/bookings/<id>
POST /api/v1/bookings/<id>/cancel
POST /api/v1/bookings/<id>/payment-link
GET /api/v1/clients?phone=5521999998888
GET /api/v1/clients?email=...&document=...
POST /api/v1/clients
Criar um agendamento (só JSON):
POST /api/v1/bookings
{"service_id": 3, "starts_at": "2026-09-01T14:00",
"customer_name": "Maria Silva", "customer_email": "maria@exemplo.com",
"customer_phone": "21999998888", "notes": "primeira vez"}
O link da caução EXPIRA em 24h - `payment-link` gera outro reusando o mesmo pedido, em vez de criar uma cobrança nova a cada reenvio. É o que permite um bot marcar hoje e reenviar amanhã sem desmarcar.
A busca de cliente responde 200 com found: false
quando o contato não é cliente - não é um erro, é a resposta à pergunta. O telefone é
normalizado, e o código do país que o WhatsApp manda (55) é tratado. A resposta traz o saldo de
horas do kit e se o cliente dispensa caução.
A resposta traz o agendamento e, quando o atendimento tem caução,
payment_url - o link da Stripe para mandar na conversa. Enquanto ele não é
pago o horário fica RESERVADO até hold_expires_at e depois volta a ficar
livre sozinho. Atendimento gratuito nasce confirmed na hora e sem link.
Desmarcar libera o horário e NÃO estorna a caução: quanto volta depende da política da loja, e isso continua sendo feito na tela de vendas.
Webhooks
Configure uma URL em Configurações → Webhooks para receber uma notificação HTTP quando algo acontecer, em vez de precisar consultar
GET /api/v1/orders repetidamente.
Eventos disponíveis: order.created, order.paid,
order.shipped, order.refunded, return.requested,
return.resolved, booking.created, booking.canceled,
booking.reminder, além de ping (evento de teste).
Os eventos de agendamento carregam o mesmo objeto que
GET /api/v1/bookings/<id>. booking.reminder sai uma vez por
agendamento, cerca de 24h antes da hora marcada - é o gancho para mandar a lembrança no WhatsApp
pelo n8n. NÃO existe um evento separado de "sinal pago": quando a caução cai, chega o
order.paid de sempre, e a linha dele traz o booking_id
do agendamento que acabou de ser confirmado.
Os eventos de devolução carregam return_id,
order_id, resolution e refund_cents.
Uma devolução parcial NÃO muda o status do pedido, que continua paid -
o valor devolvido aparece em refunded_cents no pedido.
Cada requisição é assinada em HMAC-SHA256 (chave mostrada na página de Webhooks) e enviada com os cabeçalhos:
X-CardsRealm-Event: order.paid
X-CardsRealm-Signature: <hmac_sha256(secret, corpo_da_requisicao)>
Verifique a assinatura antes de confiar no conteúdo - calcule o HMAC-SHA256 do corpo bruto da requisição com sua chave e compare com o cabeçalho recebido.
Conectando com o n8n
O n8n fala HTTP puro, então não precisa de nada especial dos dois lados: um nó HTTP Request chama esta API, e um nó Webhook recebe os nossos eventos.
Nó pronto: montar o carrinho e pegar o link
Copie o bloco abaixo e cole direto na tela do n8n (Ctrl+V no canvas cria o nó).
Troque a chave e o domínio, e ligue a saída dele no seu nó de WhatsApp ou e-mail - o link
fica em url.
{
"nodes": [
{
"parameters": {
"method": "POST",
"url": "https://SEU-DOMINIO/api/v1/carts",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{ "name": "Authorization", "value": "Bearer crk_live_SUA_CHAVE" }
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ items: [ { selling_id: 123, quantity: 4 } ] }) }}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [0, 0],
"name": "Criar carrinho"
}
],
"connections": {}
}
Para montar a lista a partir do que veio antes no fluxo, troque o
conteúdo de items por uma expressão que devolva a mesma forma - por
exemplo mapeando as linhas de uma planilha para
{ selling_id, quantity }.
Nó pronto: marcar horário e pegar o link da caução
Mesma ideia, para a agenda. A resposta traz payment_url quando o
atendimento tem caução - é esse link que vai na conversa. Um atendimento gratuito devolve
null ali e já nasce confirmado, então ramifique nesse campo em vez de assumir
que sempre há cobrança.
{
"nodes": [
{
"parameters": {
"method": "POST",
"url": "https://SEU-DOMINIO/api/v1/bookings",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{ "name": "Authorization", "value": "Bearer crk_live_SUA_CHAVE" }
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ service_id: 3, starts_at: '2026-09-01T14:00', customer_name: $json.nome, customer_phone: $json.telefone, customer_email: $json.email }) }}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [0, 0],
"name": "Marcar horario"
}
],
"connections": {}
}
Antes deste nó, chame
GET /api/v1/booking-services/<id>/slots para oferecer à pessoa só os
horários que existem de verdade - a API recusa qualquer outro com 409, e é melhor não oferecer do
que oferecer e voltar atrás.
Recebendo nossos eventos no n8n
Crie um nó Webhook (método POST), copie a URL de produção dele e cole em
Configurações → Webhooks. Confira a assinatura antes de
confiar no conteúdo, num nó Code logo depois:
const crypto = require('crypto');
const segredo = 'SEU_SEGREDO_DE_WEBHOOK';
const corpo = JSON.stringify($input.first().json.body);
const esperado = crypto.createHmac('sha256', segredo).update(corpo).digest('hex');
const recebido = $input.first().json.headers['x-cardsrealm-signature'];
if (esperado !== recebido) { throw new Error('assinatura invalida'); }
return $input.all();
Ative "Raw body" no nó Webhook se a sua versão do n8n reordenar as chaves do JSON: a assinatura é calculada sobre os bytes exatos que enviamos, então qualquer reserialização pelo caminho muda o resultado.
Erros
Respostas de erro sempre têm o formato {"error": "mensagem"} com o código HTTP apropriado (400, 401, 404, 429).