Vitrin Digital — Guia de Integração

Integre pagamentos na sua aplicação em minutos. Checkout transparente, Pix, Pix Automático (recorrência via SPI/BC), boleto, cartão de crédito, assinaturas e produtos avulsos — tudo via API REST.

Recursos:

  • 📖 Swagger UI público — explore interativamente
  • 📦 Schema OpenAPI — importe direto no Postman ou Insomnia
  • 📜 Este guia — referência completa, passo a passo

⚡ Integre em 15 minutos

Os 5 passos abaixo cobrem o caso mais comum: receber um pagamento Pix e ser notificado quando ele for confirmado.

1. Cadastre-se e copie sua API key (humano, 2 min)

Cadastro, verificação documental e configuração do PIN são feitos no dashboard — não há cURL pra isso. Acesse:

vitrin.digitalCriar conta → preencha CNPJ, endereço, dados do sócio responsável, conta bancária para repasse e PIN → conclua o onboarding documental.

💡 Conta bancária no signup: o cadastro coleta sua conta de repasse já no formulário inicial (banco, agência, conta, dígito, tipo). Isso evita que seu primeiro recebível trave esperando essa info depois da aprovação. Pode editá-la depois em Configurações → Conta bancária (com PIN).

Como você recebe as chaves:

  • vd_test_* (sandbox) — gerada imediatamente no signup e exibida na tela final. Funciona já: pode criar cobranças sandbox, clientes, simular webhooks — tudo sem mover dinheiro real, sem aguardar nada.
  • vd_live_* (produção) — gerada apenas após aprovação do cadastro pela equipe Vitrin (1-2 dias úteis). Quando aprovada, a chave plaintext é enviada uma única vez no email de aprovação. Guarde em local seguro — depois disso, só dá pra regenerar (e a anterior expira).

Use a vd_test_* nos exemplos abaixo pra testar enquanto aguarda aprovação. Troque pra vd_live_* quando for pro ar.

2. Assine os 4 contratos comerciais (humano, 3 min)

A primeira chamada de cobrança retorna HTTP 403 enquanto os contratos não forem assinados. A assinatura é feita pelo dono da org no dashboard com PIN — não há endpoint REST pra isso (é exigido pela natureza da assinatura eletrônica).

Acesse /dashboard/contract, revise os 4 documentos e confirme com seu PIN.

Sua aplicação pode checar programaticamente se a org está liberada via GET /contracts/status/ antes de tentar cobrar — útil pra mostrar mensagem amigável no seu UI.

3. Crie um cliente (1 min)

curl -X POST https://api.vitrin.digital/api/v1/customers/ \
  -H "Authorization: Bearer vd_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Maria da Silva",
    "email": "maria@exemplo.com",
    "cpf_cnpj": "12345678909",
    "phone": "11988776655"
  }'

Retorna { "id": "<customer_id>", ... }.

4. Crie uma cobrança Pix (2 min)

curl -X POST https://api.vitrin.digital/api/v1/checkout/pix/ \
  -H "Authorization: Bearer vd_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "customer_id": "<customer_id>",
    "amount": "99.90",
    "description": "Pedido #1234"
  }'

Retorna o payment_id, o QR Code base64 (qr_code) e o copia-cola (copy_paste). Renderize isso na sua UI pro cliente final.

5. Configure o webhook e valide a assinatura (5 min)

Configure a URL HTTPS de webhook em /dashboard/webhooks (requer PIN). Quando o cliente pagar, a Vitrin envia:

{
  "event": "PAYMENT_RECEIVED",
  "payment": { "id": "pay_xxx", "status": "received", "amount": "99.90" }
}

Valide a assinatura HMAC no header X-Vitrin-Signature (SHA256 do body com seu webhook secret) — código de exemplo na seção 5. É só. Você está integrado.


Próximos passos

Depois desses 5 passos, você provavelmente vai querer:

  • Boleto ou cartão de crédito → seção 3.2 e 3.3
  • Cobrança recorrentePix Automático ou Assinaturas
  • Antecipar recebíveis → endpoints /anticipations/
  • Cronograma de recebíveisGET /balance/scheduled/ (Pix D+1, Boleto D+2, Cartão Nx D+30·n)
  • Defender chargebacksPOST /payments/{id}/evidences/ pra anexar comprovantes

SDKs oficiais

Em vez de chamar a API com fetch/requests cru, instale o pacote da sua linguagem. Os dois SDKs cobrem charges, customers, plans, subscriptions, products, balance + verificação de webhook HMAC + retry automático em 429/5xx + erros tipados (VitrinAuthError, VitrinValidationError com fieldErrors, VitrinRateLimitError, etc).

Node.js

Pacote: @vitrindigital/node — Node 18+, zero deps de runtime.

npm install @vitrindigital/node
# ou: pnpm add @vitrindigital/node    yarn add @vitrindigital/node
import { Vitrin } from '@vitrindigital/node';

const vitrin = new Vitrin({ apiKey: process.env.VITRIN_API_KEY! });

const charge = await vitrin.charges.create({
  customer_id: 'cus_xxx',
  amount: 99.90,
  billing_type: 'PIX',
  idempotencyKey: `pedido-${orderId}`,  // evita duplo-débito em retry
});

Validação de webhook (Express):

import { Webhooks } from '@vitrindigital/node';

app.post('/webhooks/vitrin', express.raw({ type: 'application/json' }), (req, res) => {
  const event = Webhooks.constructEvent({
    payload: req.body,
    signature: req.header('x-vitrin-signature'),
    timestamp: req.header('x-vitrin-timestamp'),
    secret: process.env.VITRIN_WEBHOOK_SECRET!,
  });
  // process event.data ...
  res.sendStatus(200);
});

Detalhes completos em sdk/node/README.md.

Python

Pacote: vitrin — Python 3.10+, única dep requests.

pip install vitrin
import os
from vitrin import Vitrin

vitrin = Vitrin(api_key=os.environ["VITRIN_API_KEY"])

charge = vitrin.charges.create(
    customer_id="cus_xxx",
    amount=99.90,
    billing_type="PIX",
    idempotency_key=f"pedido-{order_id}",
)

Validação de webhook (Flask):

from flask import Flask, request
from vitrin import webhooks, VitrinError
import os

app = Flask(__name__)

@app.post("/webhooks/vitrin")
def handle():
    try:
        event = webhooks.construct_event(
            payload=request.get_data(),
            signature=request.headers.get("X-Vitrin-Signature"),
            timestamp=request.headers.get("X-Vitrin-Timestamp"),
            secret=os.environ["VITRIN_WEBHOOK_SECRET"],
        )
    except VitrinError as e:
        return str(e), 400
    # process event.data ...
    return "", 200

Detalhes completos em sdk/python/README.md.

Outras linguagens

Sem SDK oficial ainda. Use a API REST direto — exemplos em cURL ao longo deste guia.


1. Primeiros Passos

Cadastro e API key (humano, no dashboard)

Cadastro, verificação documental, definição do PIN e geração da primeira API key são feitos no dashboard web — não há endpoint REST pra essas ações. Isso é proposital: o cadastro precisa de upload de documentos físicos, validação CNPJ junto ao processador, e definição do PIN que protege ações sensíveis. Tudo isso é interação humana.

Como obter suas chaves:

  1. Acesse vitrin.digitalCriar conta
  2. Preencha CNPJ, razão social, endereço, dados do sócio responsável e defina seu PIN de 6 dígitos
  3. Anote a vd_test_* que aparece na tela final — você já pode integrar e testar imediatamente
  4. Aguarde aprovação (1-2 dias úteis). Quando aprovado, a vd_live_* chega no seu email registrado — plaintext exibida uma única vez. Guarde em local seguro
  5. Se perder a vd_live_*, regenere pelo dashboard ou via POST /auth/api-key/regenerate/ (com PIN)

Autenticação

Cada organização recebe 2 chaves com momentos de geração diferentes:

ChavePrefixoQuando é geradaQuando funciona
Teste (sandbox)vd_test_*No signup — exibida na telaImediatamente após cadastro
Produçãovd_live_*No momento da aprovação — enviada por emailApós aprovação + contratos assinados

A vd_test_* permite cobranças sandbox, gestão de clientes, e teste de webhooks antes da aprovação. Cobranças sandbox são simulação (sem dinheiro real). Quando estiver pronto pro ar, troque pra vd_live_* em produção.

Todas as requisições usam a API key no header:

Authorization: Bearer vd_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Ou pra sandbox:

Authorization: Bearer vd_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Ambas as chaves autenticam nos mesmos endpoints. A diferença é qual ambiente do provedor conecta (produção vs sandbox) e quais status de org são aceitos (vd_test_* aceita inclusive pending_review).

Rotacionar/recuperar a API key

A vd_live_* é exibida apenas uma vez no email de aprovação. Se perder ou quiser rotacionar (boas práticas, CI/CD, key comprometida), regenere via API com PIN do dono:

POST /api/v1/auth/api-key/regenerate/

{ "pin": "123456", "key_type": "live" }

A resposta inclui a nova api_key plaintext (também só uma vez). Armazene imediatamente — a antiga é invalidada na hora e qualquer chamada subsequente retorna 401.

Pra rotacionar a chave de teste, use "key_type": "test". Default é live.

Por que o PIN está aqui? Rotação invalida a chave anterior. O PIN garante que o dono autorizou a operação, mesmo que alguém com acesso temporário à API key faça a chamada.


1.5. Status do onboarding (read-only)

Após o cadastro a org passa por verificação documental antes de poder cobrar e sacar. O upload dos documentos é feito no dashboard em /dashboard/onboarding — sem cURL, sem API. Sua aplicação pode, no entanto, consultar o status do onboarding pra mostrar mensagens contextuais ou habilitar funcionalidades só quando a org estiver liberada:

GET /api/v1/onboarding/status/

Resposta:

{
  "account_ready": true,
  "org_status": "onboarding",
  "document_status": "PENDING",
  "account_status": {
    "general": "PENDING",
    "documentation": "AWAITING_APPROVAL",
    "commercial_info": "APPROVED",
    "bank_account_info": "APPROVED"
  }
}

Quando account_status.general == "APPROVED", a org passa automaticamente para org_status='active' e libera saques. O dashboard reflete isso em tempo real.


1.6. Contratos eletrônicos

A Vitrin opera sob 4 contratos comerciais (Termos de Uso, Anexo Comercial, Política de Chargebacks e Política de Privacidade) que precisam estar assinados antes que sua org consiga gerar cobranças. Tentativas de cobrar antes da assinatura retornam HTTP 403:

{ "detail": "Voce precisa assinar todos os contratos comerciais antes de operar. Acesse /dashboard/contract para revisar e assinar." }

Como assinar (humano, no dashboard)

A assinatura é feita pelo dono da org em /dashboard/contract. Lá ele revisa cada contrato (com as taxas reais preenchidas no Anexo Comercial), faz scroll até o fim e confirma com PIN. Não há endpoint REST pra assinar — assinatura eletrônica avançada exige interação humana inequívoca.

Consultar status (programático)

Antes de cada chamada de cobrança, sua aplicação pode (opcionalmente) checar se está tudo em ordem:

GET /api/v1/contracts/status/

Resposta:

{
  "all_signed": true,
  "pending": [],
  "active": [
    {
      "id": "uuid",
      "contract_slug": "terms-of-use",
      "contract_title": "Termos de Uso",
      "contract_version": 1,
      "signed_at": "2026-04-10T12:34:56Z",
      "valid_until": null,
      "status": "active"
    }
  ]
}

Se all_signed == false, redirecione o operador para /dashboard/contract.

Re-assinatura quando o contrato muda

Se a Vitrin publicar uma nova versão de qualquer contrato (ex: atualização da Política de Chargebacks), o pending volta a ter itens com reason: "new_version" e a operação fica bloqueada até nova assinatura. Você é avisado por e-mail com 30 dias de antecedência.


2. Clientes

Antes de cobrar, cadastre o pagador.

Criar

POST /api/v1/customers/

{
  "name": "Maria Silva",
  "email": "maria@email.com",
  "cpf_cnpj": "12345678909",
  "phone": "11999887766"
}

Resposta: retorna id do cliente (UUID) para usar nas cobranças.

Campos opcionais — pessoais:

{
  "birth_date": "1990-03-15",        // data de nascimento (PF)
  "company_name": "Empresa LTDA",     // razão social (PJ)
  "notes": "Cliente VIP — desconto",  // observação interna, não visível ao cliente
  "custom_data": {                    // respostas dos campos customizados do checkout
    "como_conheceu": "Instagram",
    "tamanho_camiseta": "M"
  }
}

Campos opcionais — endereço (recomendado para produtos físicos e defesa de chargeback):

{
  "postal_code": "01001000",
  "address": "Rua das Flores",
  "address_number": "42",
  "complement": "Apto 12",
  "province": "Centro",
  "city": "São Paulo",
  "state": "SP"
}

O dashboard auto-preenche endereço via ViaCEP quando o pagador digita o CEP. Em integrações server-side você pode replicar essa lógica chamando https://viacep.com.br/ws/{cep}/json/.

Listar

GET /api/v1/customers/?page=1

Resposta paginada:

{
  "count": 142,
  "next": "https://api.vitrin.digital/api/v1/customers/?page=2",
  "previous": null,
  "results": [ { "id": "uuid", "name": "Maria Silva", ... } ]
}

Filtros aceitos via query string: email, cpf_cnpj, name__icontains.

Detalhes

GET /api/v1/customers/{id}/

Atualizar

PUT  /api/v1/customers/{id}/    # substitui todos os campos
PATCH /api/v1/customers/{id}/   # atualiza só os enviados

Excluir

DELETE /api/v1/customers/{id}/

Excluir um cliente que tem transações associadas é bloqueado — você recebe HTTP 409. Use para limpar cadastros que nunca pagaram.


3. Cobranças

3.1 Pix

POST /api/v1/checkout/pix/

{
  "customer_id": "uuid-do-cliente",
  "amount": 99.90,
  "description": "Plano Mensal"
}

Resposta:

{
  "payment_id": "uuid-da-transacao",
  "qr_code": "base64-do-qr-code",
  "copy_paste": "00020101021226880014br.gov.bcb...",
  "amount": 99.90,
  "status": "pending"
}

O frontend da sua app exibe o QR code (qr_code em base64) ou o código copia-e-cola (copy_paste).

3.2 Boleto

POST /api/v1/checkout/boleto/

{
  "customer_id": "uuid-do-cliente",
  "amount": 99.90,
  "description": "Plano Mensal"
}

Resposta:

{
  "payment_id": "uuid-da-transacao",
  "barcode": "23793.38128 60000.000003 00000.000406 1 84340000009990",
  "url": "https://vitrin.digital/b/pdf/xxxx",
  "amount": 99.90,
  "status": "pending"
}

3.3 Cartão de Crédito

Passo 1: Tokenizar o cartão

POST /api/v1/checkout/tokenize/

{
  "customer_id": "uuid-do-cliente",
  "credit_card": {
    "holderName": "MARIA SILVA",
    "number": "5162306219378829",
    "expiryMonth": "01",
    "expiryYear": "2028",
    "ccv": "318"
  },
  "credit_card_holder_info": {
    "name": "Maria Silva",
    "email": "maria@email.com",
    "cpfCnpj": "12345678909",
    "postalCode": "01001000",
    "addressNumber": "42",
    "phone": "11999887766"
  }
}

Resposta:

{
  "credit_card_token": "tok_xxxxxxxxxxxxx",
  "credit_card_number": "5162 **** **** 8829",
  "credit_card_brand": "MASTERCARD"
}

Passo 2: Cobrar com o token

POST /api/v1/checkout/pay/

{
  "customer_id": "uuid-do-cliente",
  "amount": 299.90,
  "billing_type": "CREDIT_CARD",
  "installments": 3,
  "credit_card_token": "tok_xxxxxxxxxxxxx",
  "description": "Pacote Premium"
}

Dados do cartão nunca passam pelo seu servidor. A tokenização é feita via API e o token é usado para cobranças futuras.

Passo 3: 3D Secure (obrigatório por padrão — pré-requisito para antecipação 100%)

Padrão Vitrin: 3DS é obrigatório em toda cobrança de cartão. Cobranças sem authentication retornam HTTP 422 com error: "Cobrança recusada — 3DS obrigatório" e return_code: "VITRIN_3DS_MISSING". A tentativa é persistida como Transaction com status='failed' — você consegue ver em GET /api/v1/transactions/?status=failed e no painel. O customer_message é genérico ("Não foi possível processar este cartão. Use outro cartão ou tente novamente.") — não vazamos a política pro pagador. Isso garante liability shift e mantém todas as orgs elegíveis para antecipação 100%. Cenários excepcionais (cartões corporativos sem 3DS, bandeiras não-suportadas com volume relevante) podem ser baixados pra optional caso a caso pela equipe Vitrin via Anexo Comercial.

Cobranças de cartão autenticadas via 3DS ficam elegíveis para antecipação 100% em D+1, em vez do D+31 padrão.

Importante: a antecipação é um benefício concedido caso a caso pela Vitrin após análise (perfil de risco, TPV, histórico, vertical). Mesmo com 3DS implementado, a liberação da antecipação 100% e a taxa aplicada dependem de aprovação Vitrin e/ou negociação comercial específica para sua organização. Confirme as condições no Anexo Comercial assinado.

3DS dispensa antifraude. Quando a cobrança chega com authentication, a análise antifraude (R$ 0,50/tx, +60s de latência) é automaticamente desligada — o liability shift do 3DS já protege contra chargeback de "transação não reconhecida". Só faz sentido manter o antifraude ligado para fluxos onde 3DS não é viável (cartões não-enrolados, bandeiras sem suporte, integração server-only). Veja Antifraude abaixo.

A Vitrin oferece o SDK vitrin.js, que abstrai todo o fluxo browser-side em três chamadas (tokenizar → autenticar → cobrar). Este é o caminho recomendado.

Modo recomendado — via vitrin.js (SDK oficial):

<script src="https://vitrin.digital/vitrin.js"></script>
<script>
Vitrin.init({ apiKey: 'vd_test_xxxxx', sandbox: true });

async function pagar() {
  // 1. Tokeniza o cartão (PCI compliant)
  const { token } = await Vitrin.tokenize({
    customerId: 'cus_xxx',
    holderName: 'MARIA SILVA',
    number: '5162306219378829',
    expiryMonth: '01',
    expiryYear: '2028',
    cvv: '318',
  });

  // 2. Autentica via 3DS (carrega script lazy + roda challenge)
  const auth = await Vitrin.authenticate3DS({
    cardToken: token,
    amount: 299.90,
  });

  // 3. Cobra. `authentication` é opcional — se cartão não suporta 3DS,
  //    `auth` será null e a cobrança segue (sem antecipação 100%).
  const result = await Vitrin.charge({
    customerId: 'cus_xxx',
    amount: 299.90,
    installments: 3,
    cardToken: token,
    description: 'Pacote Premium',
    authentication: auth,   // pode ser null
  });
}
</script>

O SDK trata automaticamente:

  • Carregamento lazy do MPI 3DS (script só baixa quando precisa)
  • Detecção de bandeira não-enrolada (onUnenrolled → resolve com null)
  • Detecção de bandeira não-suportada (JCB/Diners → resolve com null)
  • Mapeamento dos campos retornados (Eci/Cavv/Xid/dsTransID/Version)
  • Tratamento de erros via Vitrin.Error

Modo manual (server-to-server):

Se preferir não usar o SDK (ex: app mobile), monte o payload authentication direto:

POST /api/v1/checkout/pay/

{
  "customer_id": "uuid-do-cliente",
  "amount": 299.90,
  "billing_type": "CREDIT_CARD",
  "installments": 3,
  "credit_card_token": "tok_xxxxxxxxxxxxx",
  "description": "Pacote Premium",

  "authentication": {
    "eci": "05",
    "cavv": "AAABBBCCCDDDEEEFFFGGG=",
    "ds_trans_id": "ds-trans-uuid-2x",
    "protocol_version": "2.2.0",
    "reference_id": "ref_mpi_001",
    "status": "AUTHENTICATED"
  }
}

Bandeiras com 3DS: Visa e Mastercard (3DS 2.2), Elo e Amex (3DS 2.1). JCB, Aura, Diners, Discover não suportam 3DS — siga sem authentication (cobrança permanece elegível, mas sem o liability shift / antecipação 100%).

Tabela ECI (resumida):

MasterVisa/Elo/AmexResultadoLiability shiftAntecipação 100%
0205Autenticada pelo emissor✅ Sim✅ Elegível
0106Autenticada pela bandeira✅ Sim✅ Elegível
0407Data Only❌ Não❌ Não

"Elegível" significa que a transação atende ao requisito técnico para antecipação 100%. A liberação efetiva depende de aprovação Vitrin e das condições negociadas no Anexo Comercial da sua organização (taxa de antecipação, prazos, modalidade automática vs sob demanda).

Se o cartão estiver não-enrolado ou for de bandeira sem 3DS, você ainda consegue cobrar — só envie a request sem authentication (cobrança aceita normalmente; perde-se o benefício da antecipação acelerada).

Recusas: como tratar

Quando o emissor (banco do cliente) recusa a cobrança de cartão, a Vitrin devolve HTTP 422 com um payload pronto pra você decidir o próximo passo:

{
  "error": "Pagamento não autorizado.",
  "description": "Transação não permitida",
  "return_code": "GD",
  "return_code_description": "Transação não permitida",
  "customer_message": "Pagamento não autorizado pelo emissor. Use outro cartão para finalizar a compra.",
  "retryable": false
}

Os campos chave são:

  • customer_message: texto pronto para exibir ao cliente final no checkout. Já vem em PT-BR e adaptado ao motivo da recusa (saldo insuficiente vs cartão bloqueado vs erro transitório).
  • retryable:
    • true → emissor admite nova tentativa com o mesmo cartão (ex.: timeout, sistema indisponível, parcelamento inválido). Você pode oferecer "Tentar novamente" ao cliente.
    • false → recusa definitiva para esse cartão. Não tente de novo com o mesmo cartão — a próxima tentativa é recusada de imediato pelo nosso processador (sem chegar ao emissor) e fica registrada no histórico do cliente. Ofereça troca de cartão.
  • return_code / return_code_description: código ABECS oficial e descrição interna. Útil pra logs e suporte; não recomendamos exibir ao cliente final (use customer_message).

Exemplo de UX recomendado:

const resp = await vitrin.charges.create({ /* ... */ });

if (resp.status === 422) {
  const body = await resp.json();
  // Mostre a customer_message ao usuário
  showError(body.customer_message);

  // Se retryable, ofereça botão "Tentar de novo"
  if (body.retryable) {
    showRetryButton();
  } else {
    // Recusa definitiva: ofereça outro cartão
    showCardSwitcher();
  }
}

Importante: mesmo com 3DS autenticado (Is3DSAuthenticated: true), o emissor pode recusar — o 3DS dá liability shift, mas não garante autorização. Recusas após 3DS geralmente vêm de checagens de risco do banco emissor (fraude, saldo, divergência de dados do titular).

Antifraude (fallback quando 3DS não vem)

A Vitrin oferece análise antifraude opcional para cobranças de cartão. Custo: R$ 0,50/tx. Você ativa pela tela /dashboard/settings (toggle "Antifraude").

Decisão automática por cobrança (você não controla por chamada — depende do que chega no POST /checkout/pay/):

3DS na cobrançaAntifraude na contaResultado
✅ presente (authentication)qualquerAntifraude OFF — 3DS dispensa (liability shift + economia de R$ 0,50/tx + ~60s)
❌ ausenteONAntifraude ON — fallback de proteção
❌ ausenteOFFSem proteção — você assume o risco de chargeback

Quando ligar o antifraude da conta: se você integra server-only (sem checkout web), aceita bandeiras sem suporte 3DS (JCB, Aura, Diners, Discover) ou tem cartões não-enrolados chegando com frequência. Para checkout web com vitrin.js, o caminho recomendado é deixar antifraude OFF e enviar 3DS em todas as cobranças (mais barato, mais rápido, melhor proteção).

Os parâmetros do antifraude (regras, score mínimo) são definidos pela Vitrin globalmente — você não controla via API. O toggle só liga ou desliga.

3.4 Consultar Parcelamento

Antes de cobrar, consulte as opções de parcelamento que o cliente final vê:

GET /api/v1/checkout/installments/?amount=299.90

Resposta:

{
  "amount": 299.90,
  "installments": [
    { "installment_count": 1, "installment_value": 299.90, "total_value": 299.90 },
    { "installment_count": 2, "installment_value": 154.46, "total_value": 308.92 },
    { "installment_count": 3, "installment_value": 103.99, "total_value": 311.97 }
  ]
}

A resposta é desenhada pra alimentar diretamente o frontend de checkout do cliente final: o que ele paga por parcela (installment_value) e o total (total_value). Os juros embutidos no parcelamento ficam transparentes pro pagador (consolidados no total_value) e não são expostos como campo separado.

Para conhecer o custo aplicado a cada cobrança da sua conta, consulte os termos comerciais ativos em GET /api/v1/commercial-terms/active/ ou a aba "Comissões" do dashboard. Os valores líquidos por transação aparecem em Transaction.net_amount quando a cobrança é criada.

3.5 Guest Checkout (sem cadastro prévio)

Cria o cliente e cobra em um único passo:

POST /api/v1/checkout/guest/

{
  "customer_name": "João Santos",
  "customer_email": "joao@email.com",
  "customer_cpf_cnpj": "98765432100",
  "customer_phone": "11988776655",
  "amount": 49.90,
  "billing_type": "PIX",
  "description": "Ingresso Evento"
}

3.6 Pix Automático (recorrência via SPI/BC)

Para cobranças recorrentes sem cartão. O pagador escaneia um QR Code uma única vez, autoriza a primeira cobrança + futuras recorrências via app bancário, e a partir daí você gera as cobranças periódicas.

Requisitos: o método precisa estar habilitado pelo admin (PIX_AUTOMATIC em allowed_payment_methods) e pela própria org em enabled_payment_methods. A conta de pagamentos no provedor precisa estar aprovada (CNPJ ativo há 6+ meses).

Passo 1: Criar a autorização (gera QR Code de aceite)

POST /api/v1/pix-automatic/authorizations/

{
  "customer_id": "uuid-do-cliente",
  "contract_id": "ASSINATURA-001",
  "frequency": "MONTHLY",
  "start_date": "2026-05-01",
  "value": 99.90,
  "description": "Mensalidade Plus",
  "first_charge_value": 99.90,
  "first_charge_description": "Primeira mensalidade"
}

Campos:

  • frequency: WEEKLY, MONTHLY, QUARTERLY, SEMIANNUALLY, ANNUALLY
  • value: valor fixo cobrado a cada ciclo. Obrigatório — sem value (ou min_limit_value) o BACEN força o pagador a definir um teto no app do banco, comportamento que normalmente não é o desejado para assinatura de valor fixo. value e min_limit_value são mutuamente exclusivos.
  • min_limit_value (alternativa a value): piso de valor variável. O pagador define um teto no app que precisa ser ≥ esse piso, e a org pode cobrar qualquer valor ≤ teto a cada ciclo.
  • finish_date (opcional): se omitido, prazo indeterminado.
  • first_charge_value: valor da primeira cobrança (vinculada ao QR Code de aceite — obrigatório). Renderiza no QR Code que o cliente escaneia ao aceitar a autorização.

Endereço e telefone do Customer: o BACEN exige que o pagador veja todos os dados no app do banco antes de aceitar. Antes de criar a autorização, garanta que o Customer tem phone, postal_code, address, address_number, province, city e state preenchidos — se faltar algum, a API retorna 400 com missing_fields.

Resposta:

{
  "id": "uuid-da-autorizacao",
  "provider_authorization_id": "pix_auto_xxx",
  "status": "CREATED",
  "immediate_qr_code": "<base64 PNG do QR Code>",
  "immediate_payload": "00020101021226880014br.gov.bcb...",
  "frequency": "MONTHLY",
  "value": "99.90"
}

Renderize o QR Code para o cliente:

<img src="data:image/png;base64,<immediate_qr_code>" alt="QR Code Pix Automatico" />

Ou ofereça o copia-e-cola via immediate_payload.

Passo 2: Aguardar o aceite + 1ª cobrança (via webhook)

Quando o cliente escaneia, aceita no app bancário e paga a primeira cobrança, dois sinais chegam:

  1. A autorização vai de CREATED para ACTIVE. Você recebe o evento:
    PIX_AUTOMATIC_RECURRING_AUTHORIZATION_ACTIVATED
    
  2. A Transaction da primeira cobrança (criada automaticamente no momento da autorização, com status='pending') vai para received quando o cliente paga, via webhook PIX padrão. Ela aparece em GET /api/v1/transactions/ com billing_type='PIX_AUTOMATIC' e provider_payment_id igual ao identificador da transação no processador.

Passo 3: Gerar cobranças recorrentes

Com a autorização ACTIVE, gere cada cobrança periódica:

POST /api/v1/pix-automatic/authorizations/{id}/charges/

{
  "value": 99.90,
  "due_date": "2026-06-01",
  "description": "Mensalidade junho"
}

Janela do BC: o due_date deve estar entre 2 e 10 dias úteis no futuro. Tentativas fora dessa janela retornam HTTP 400.

Se a autorização tem value fixo, você precisa enviar exatamente esse valor.

Resposta: retorna a Transaction criada (mesmo formato do /checkout/pix/).

Outras operações

GET    /api/v1/pix-automatic/authorizations/                      # Listar autorizações
GET    /api/v1/pix-automatic/authorizations/{id}/                 # Detalhes
PATCH  /api/v1/pix-automatic/authorizations/{id}/                 # Alterar Customer/start_date (limitado pelo processador)
GET    /api/v1/pix-automatic/authorizations/{id}/instructions/    # Listar cobranças geradas
POST   /api/v1/pix-automatic/authorizations/{id}/sync/            # Forçar sync com o provedor
DELETE /api/v1/pix-automatic/authorizations/{id}/                 # Cancelar (PIN obrigatório)
GET    /api/v1/pix-automatic/instructions/                        # Todas as instruções da org
GET    /api/v1/pix-automatic/instructions/{id}/                   # Detalhe da instrução
POST   /api/v1/pix-automatic/instructions/{id}/sync/              # Forçar sync da instrução
DELETE /api/v1/pix-automatic/instructions/{id}/                   # Cancelar agendamento (PIN obrigatório)

PATCH é limitado pelo processador a alterar dados do Customer e start_date. Mudanças de valor (value/min_limit_value), finish_date, frequency, customer ou contract_id exigem criar nova autorização (cancelar a atual e emitir um novo QR de aceite). Status terminal (CANCELLED/REFUSED/EXPIRED) bloqueia qualquer alteração.

DELETE /instructions/{id}/ cancela um agendamento ainda não pago. Status terminal (DONE/CANCELLED/REFUSED) bloqueia o cancelamento. A Transaction pendente associada é marcada como canceled automaticamente.

Status possíveis

AutorizaçãoSignificado
CREATEDAguardando aceite do pagador
ACTIVEAceita — cobranças podem ser geradas
CANCELLEDCancelada (pela org ou pelo pagador)
REFUSEDRecusada pelo banco do pagador
EXPIREDExpirada sem aceite
Instrução (cobrança)Significado
AWAITING_REQUESTCriada localmente
SCHEDULEDAgendada no SPI
DONEPagamento confirmado
CANCELLEDCancelada antes do débito
REFUSEDRecusada (saldo insuficiente, divergência etc.)

3.7 Cartões Salvos e One-Click Buy

Permite cobrar um cliente recorrente sem pedir os dados do cartão de novo. O fluxo é:

  1. Cliente preenche o cartão uma vez com consentimento explícito
  2. Você guarda o saved_card_token (a Vitrin persiste a referência; o número do cartão fica no cofre PCI do processador)
  3. Cobranças futuras chamam /charges/charge-saved/ informando só o customer_id e o valor

Um cartão preferido por cliente. Salvar um novo cartão substitui o anterior. Cliente final pode remover via link assinado que você gera pelo painel ou pela API.

Salvar cartão (consentimento obrigatório — LGPD)

POST /api/v1/customers/{customer_id}/saved-card/

{
  "credit_card": {
    "holder_name": "JOAO DA SILVA",
    "number": "4000000000001000",
    "expiry_month": "12",
    "expiry_year": "2030",
    "cvv": "123"
  },
  "consent": {
    "accepted_at": "2026-05-16T12:00:00Z",
    "ip": "189.10.20.30"
  }
}

Resposta 201 Created:

{
  "brand": "visa",
  "last4": "1000",
  "exp_month": 12,
  "exp_year": 2030,
  "holder_name": "JOAO DA SILVA",
  "consent_at": "2026-05-16T12:00:00Z"
}

O bloco consent é obrigatório — sem accepted_at e ip o cartão não é persistido (400). Use o timestamp em que o cliente final aceitou o checkbox "Salvar cartão para próximas compras" e o IP capturado no mesmo request.

Cobrar com o cartão salvo (one-click)

POST /api/v1/charges/charge-saved/

{
  "customer_id": "5e3a0a1b-9b1f-4f87-9b6b-1a2b3c4d5e6f",
  "amount": 99.90,
  "description": "Recompra plano Pro",
  "installments": 1,
  "external_reference": "reorder-2026-05-16"
}

Retorna o mesmo Transaction que /checkout/pay/ retorna. O cliente paga sem digitar nada — você cobra com 1 request.

ErroQuando
404 customer_not_foundcustomer_id não existe ou pertence a outra org
422 no_saved_cardCliente sem cartão salvo
422 card_expiredMês/ano de expiração já passou — peça novo cartão
422 <ABECS code>Recusa do emissor (vide tabela em 3.3)

Consultar / remover (lado da org)

GET    /api/v1/customers/{customer_id}/saved-card/   # 404 se não houver
DELETE /api/v1/customers/{customer_id}/saved-card/   # 204

A resposta do GET nunca inclui o token — só brand, last4, expiração, nome e timestamp do consentimento.

Você pode gerar uma URL que o cliente final abre para ver/remover o próprio cartão, sem precisar do seu painel (ex: enviar por email após assinatura de plano, página de minha-conta no site da loja):

POST /api/v1/customers/{customer_id}/saved-card/management-link/

Resposta:

{
  "url": "https://vitrin.digital/customer/cards/eyJhbGciOi...",
  "token": "eyJhbGciOi...",
  "expires_at": "2026-05-17T12:00:00Z"
}

O token é um JWT de 24h. A página servida em url permite ao cliente final:

  • GET /api/v1/public/saved-card/?token=<jwt> — ver bandeira + últimos 4 + nome da loja
  • DELETE /api/v1/public/saved-card/?token=<jwt> — remover o próprio cartão

LGPD: o link assinado é o mecanismo recomendado para atender o direito de revogação do consentimento. Inclua-o em emails transacionais relevantes e na sua página de minha-conta.

Enquanto charge-saved é servidor pra servidor (você cobra direto via API), o link de recompra entrega o controle pro cliente final: você gera uma URL assinada, manda por email/WhatsApp, e o cliente confirma a compra com 1 clique numa página hospedada pela Vitrin.

POST /api/v1/customers/{customer_id}/one-click-link/

{
  "product_id": "5e3a0a1b-9b1f-4f87-9b6b-1a2b3c4d5e6f",
  "amount": "99.90",         // opcional — só pra produto customer_defined ou plano com valor flexível
  "installments": 3,         // opcional, default 1
  "coupon_code": "OFF10",    // opcional — só pra produto; valida targeting na emissão
  "bump_ids": ["..."],       // opcional — só pra produto; bumps pré-aceitos vinculados ao parent
  "expires_in": 900          // opcional (segundos), default 900 = 15min, máx 3600
}

Use plan_id no lugar de product_id pra gerar link de assinatura. Exatamente um dos dois. Cupom e bumps só funcionam pra produto — planos rejeitam (400).

Resposta:

{
  "url": "https://vitrin.digital/c/loja/produto-x/oneclick?t=eyJhbGciOi...",
  "token": "eyJhbGciOi...",
  "expires_at": "2026-05-16T15:15:00Z"
}

O cliente abre a url, vê o card do cartão salvo (bandeira + ••••1234), valor e parcelas, e clica "Pagar — 1 clique". A Vitrin valida o JWT, cobra usando o token salvo (mesma engine do checkout normal — cria Order/Subscription, calcula fees, executa split de comissão, dispara webhook) e redireciona pro thank_you_url configurado no item.

Segurança:

  • JWT HS256 com TTL curto (15min default, 1h máx) — limita janela de fraude
  • Uso único: depois do primeiro clique, o token é consumido (nonce Redis) — segundo POST retorna 401 token_consumed
  • Nonce só é consumido após validações reversíveis (cartão removido / expirado / item inativo / subscription duplicada) — falhas reversíveis preservam o link; cliente pode retentar
  • Cartão precisa estar salvo e válido no momento do clique. Se foi removido entre emissão e uso → 422 card_removed
  • Item precisa estar is_active=True na sua org — mudanças refletem em tempo real
  • Rate limit: 10 req/min por IP no endpoint público (/public/oneclick/), abaixo do checkout normal por cobrar sem CVV
ErroQuando
400 coupon_not_found / coupon_invalid / coupon_not_applicableCupom inexistente, inválido (expirado/inativo) ou não aplicável ao produto (na emissão)
400 bumps_not_foundAlgum bump_id não pertence ao parent product (na emissão)
422 no_saved_cardCustomer sem cartão salvo (na emissão)
422 card_expiredCartão com mês/ano expirado (na emissão ou no clique)
422 card_removedCartão foi deletado entre emissão e clique
422 provider_refusedCartão recusado pelo processador (customer_message pronto pra exibir)
502 provider_errorProcessador instável; retryable: true
409 subscription_already_activeCliente já tem subscription vigente desse plano (preserva nonce)
404 item_not_foundProduto/plano não existe ou ficou inativo
401 token_expiredTTL estourou
401 token_consumedCliente já usou o link
401 token_invalidAssinatura inválida / scope errado / tampering
GET /api/v1/customers/{customer_id}/one-click-history/?limit=20

Retorna items[] com status (paid / pending / expired / consumed_failed), jti, issued_at, paid_at + paid_resource_id quando aplicável. Útil pra reconciliar campanhas de email e ver quem clicou.

Conformidade

  • O número do cartão nunca toca o servidor da Vitrin — só o token retornado pelo cofre PCI do processador
  • Cada token é exclusivo da sua organização e não pode ser usado por outra org
  • O texto de consentimento sugerido está em docs/MANUAL-ORG.md — cole na sua política de privacidade
  • Cobranças com cartão salvo não pedem CVV. Isso é parte do one-click. Para fluxos sensíveis (valor alto, antifraude), prefira /checkout/pay/ com cartão fresco + 3DS

SDKs

// Node
await vitrin.customers.savedCard.create(customer.id, {
  credit_card: { holder_name, number, expiry_month, expiry_year, cvv },
  consent: { accepted_at, ip },
})

await vitrin.charges.createWithSavedCard({
  customer_id: customer.id,
  amount: 99.90,
  description: 'Recompra plano Pro',
})

await vitrin.customers.savedCard.createManagementLink(customer.id)
# Python
vitrin.customers.save_card(
    customer.id,
    credit_card={"holder_name": ..., "number": ..., "expiry_month": ..., "expiry_year": ..., "cvv": ...},
    consent={"accepted_at": ..., "ip": ...},
)

vitrin.charges.create_with_saved_card(
    customer_id=customer.id, amount="99.90", description="Recompra plano Pro",
)

vitrin.customers.saved_card_management_link(customer.id)

# Link de recompra one-click (cliente clica e paga)
vitrin.customers.one_click_link(
    customer.id, product_id=product.id, amount="99.90", installments=3,
)
// Node — link de recompra
await vitrin.customers.oneClickLink(customer.id, {
  product_id: product.id,
  amount: "99.90",
  installments: 3,
})

3.8 Cobrança das taxas

As taxas da plataforma são descontadas automaticamente do valor líquido recebido. O valor que você passa em amount é o valor que o cliente paga; você recebe amount − taxa.

Exemplo: numa cobrança de R$ 100 com taxa de R$ 5,49, o cliente paga R$ 100 e você recebe R$ 94,51 líquido na sua conta. A taxa sai do seu repasse, não é somada à fatura do pagador.

Esse comportamento é uniforme para Pix, Boleto e Cartão. Confira a taxa exata aplicada à sua org no simulador ou em GET /org/settings/.


4. Polling de Status (Pix)

Para Pix, faça polling até o pagamento ser confirmado:

GET /api/v1/payments/{payment_id}/status/

Resposta:

{
  "payment_id": "uuid",
  "status": "pending",
  "confirmed": false,
  "pix_copy_paste": "00020101..."
}

Faça polling a cada 3-5 segundos. Quando confirmed: true, o pagamento foi recebido.


5. Webhooks

Configurar

Configure a URL onde sua aplicação recebe os eventos. Pode ser via dashboard ou API:

Via dashboard: acesse /dashboard/webhooks (requer PIN), informe a URL HTTPS, salve. O secret HMAC é exibido na tela após salvar — guarde-o.

Via API:

PUT /api/v1/webhooks/config/

{
  "webhook_url": "https://suaapp.com/webhooks/vitrin",
  "pin": "123456"
}

O secret HMAC é gerado automaticamente e retornado no response. Você pode regenerá-lo a qualquer momento (e qualquer requisição feita à URL com signature antiga ficará inválida).

Sem URL configurada, a Vitrin processa o evento internamente (atualizando suas Transactions) mas não repassa ao seu backend. Configure a URL antes de ir pro ar.

Receber eventos

A Vitrin envia um POST para a sua URL quando eventos ocorrem:

POST https://suaapp.com/webhooks/vitrin

Headers:
  X-Vitrin-Signature: sha256=abc123...
  X-Vitrin-Event: PAYMENT_RECEIVED
  X-Vitrin-Event-Id: PAYMENT_RECEIVED:pay_xxx:2026-04-09
  Content-Type: application/json

Body:
{
  "event": "PAYMENT_RECEIVED",
  "payment": {
    "id": "<uuid-da-transacao>",
    "status": "received",
    "billing_type": "PIX",
    "amount": 99.90,
    "net_amount": 96.41,
    "due_date": "2026-05-07",
    "paid_at": "2026-05-07T12:34:56Z",
    "customer_id": "<uuid-do-cliente>",
    "subscription_id": null,
    "description": "Mensalidade",
    "installments": 1
  }
}

Cobranças de cartão trazem também o snapshot da bandeira/últimos 4 (em qualquer status) e, no caso de recusa, um bloco refusal com o motivo:

{
  "event": "PAYMENT_REFUSED",
  "payment": {
    "id": "<uuid-da-transacao>",
    "status": "failed",
    "billing_type": "CREDIT_CARD",
    "amount": 133.32,
    "installments": 12,
    "customer_id": "<uuid-do-cliente>",
    "card": { "brand": "Mastercard", "last4": "0523" },
    "refusal": {
      "code": "51",
      "description": "Saldo/limite insuficiente",
      "message": "Autorizacao negada",
      "customer_message": "Cartão sem saldo/limite. Use outro cartão ou tente menos parcelas.",
      "retryable": false
    }
  }
}
  • card aparece em qualquer cobrança CC (autorizada ou recusada).
  • refusal só vem com status='failed' e contém:
    • code: ABECS oficial (ex: "51", "GD")
    • description: descrição interna em português
    • message: mensagem original do emissor
    • customer_message: mensagem pronta pra mostrar ao cliente final no checkout
    • retryable: false significa que tentar de novo no mesmo cartão vai falhar — peça outro cartão ou outra forma de pagamento.

Pagamentos via checkout público (ou via API de Orders) trazem também o bloco order enriquecido — use para provisionar acesso/entrega sem chamada GET extra:

{
  "event": "PAYMENT_RECEIVED",
  "payment": {
    "id": "<uuid-tx>",
    "status": "received",
    "amount": 75.00,
    "order": {
      "id": "<uuid-order>",
      "status": "pending",
      "unit_price": 75.00,
      "total_amount": 75.00,
      "quantity": 1,
      "product": {
        "id": "<uuid-product>",
        "slug": "doacao-vitrin",
        "name": "Apoie o canal",
        "type": "contribution",
        "thank_you_url": "https://..."
      },
      "coupon": { "id": "<uuid>", "code": "PROMO10", "discount_amount": 10.00 },
      "bump": { "id": "<uuid>", "product_id": "<uuid>", "product_name": "E-book bônus", "amount": 19.00 },
      "customer_chosen_amount": 75.00
    }
  }
}
  • order só aparece quando há um Order vinculado (compra via checkout público ou POST /orders/create/).
  • coupon / bump / customer_chosen_amount só aparecem quando aplicáveis.

Cobranças com atribuição de afiliado trazem o bloco affiliate com o slug, valor da comissão e status de liquidação. Use para provisionar comissão no seu sistema interno (ex: gamificação, ranking) ou para notificar o afiliado fora da Vitrin:

{
  "event": "PAYMENT_RECEIVED",
  "payment": {
    "id": "<uuid-tx>",
    "status": "received",
    "amount": 100.00,
    "affiliate": {
      "slug": "joao-silva",
      "commission_amount": 30.00,
      "status": "settled",
      "is_recurring": false,
      "settled_at": "2026-05-19T22:34:56Z"
    }
  }
}
  • affiliate só aparece quando a venda foi atribuída a um afiliado (cliente entrou no checkout pelo link dele dentro da janela de atribuição configurada na sua loja).
  • status reflete o estado da comissão na carteira do afiliado:
    • pending: comissão registrada mas ainda não liquidada (cobrança ainda não confirmou).
    • settled: comissão liquidada na carteira do afiliado — vai pra ele no próximo repasse.
    • reversed: comissão estornada (chargeback ou refund).
  • is_recurring: true em renovações de assinatura/Pix Automático; false na primeira venda ou produto avulso.
  • settled_at / reversed_at aparecem conforme o estado.

Quando há coprodutores vinculados ao produto, vem também o bloco coproducers:

{
  "payment": {
    "coproducers": [
      { "name": "Editora Parceira", "amount": 20.00, "mode": "pct" }
    ]
  }
}
  • mode é pct ou fixed; o amount em reais é o valor que foi destacado pra cada coprodutor.

Importante: a fatia da Vitrin não aparece no webhook canônico — amount já é o que sua loja vende; net_amount já está líquido de taxas. Comissão de afiliado e splits de coprodutor são debitados desse líquido (não da Vitrin).

Eventos sintéticos da Vitrin (dunning de assinaturas):

EventoQuando
subscription.dunning_warningAssinatura overdue há ≥ 7 dias
subscription.canceled_for_dunningAssinatura overdue há ≥ 30 dias — Vitrin cancelou no provedor

Ambos trazem subscription_id, plan_id, customer_id, overdue_since no payload.

Nota: o payment.id é o UUID da Transaction na Vitrin — use ele para reconciliar contra GET /payments/{id}/. Em raros casos onde o webhook chega antes da Transaction ser persistida internamente (condição de corrida), o body vem com payment.provider_payment_id_legacy no lugar de payment.id — você pode reconciliar via lookup posterior.

Validar assinatura

import hmac, hashlib

def verify_webhook(body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(), body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature)

Eventos suportados

EventoQuando
PAYMENT_RECEIVEDPagamento confirmado
PAYMENT_CONFIRMEDPagamento confirmado (cartão)
PAYMENT_OVERDUEPagamento vencido
PAYMENT_REFUSEDPagamento recusado (cartão) — body inclui refusal
PAYMENT_REFUNDEDReembolso processado
PAYMENT_CHARGEBACK_REQUESTEDChargeback aberto
PAYMENT_DELETEDPagamento cancelado
SUBSCRIPTION_CREATEDAssinatura criada
SUBSCRIPTION_DELETEDAssinatura cancelada
PIX_AUTOMATIC_RECURRING_AUTHORIZATION_CREATEDAutorização Pix Automático criada
PIX_AUTOMATIC_RECURRING_AUTHORIZATION_ACTIVATEDPagador aceitou — autorização ativa
PIX_AUTOMATIC_RECURRING_AUTHORIZATION_REFUSEDAutorização recusada pelo banco
PIX_AUTOMATIC_RECURRING_AUTHORIZATION_CANCELLEDAutorização cancelada
PIX_AUTOMATIC_RECURRING_AUTHORIZATION_EXPIREDAutorização expirou sem aceite
PIX_AUTOMATIC_RECURRING_PAYMENT_INSTRUCTION_CREATEDInstrução de cobrança criada
PIX_AUTOMATIC_RECURRING_PAYMENT_INSTRUCTION_SCHEDULEDInstrução agendada no SPI
PIX_AUTOMATIC_RECURRING_PAYMENT_INSTRUCTION_REFUSEDInstrução recusada (ver refusalReason)
PIX_AUTOMATIC_RECURRING_PAYMENT_INSTRUCTION_CANCELLEDInstrução cancelada

6. Assinaturas

Recorrência via cartão de crédito ou boleto. Cada plano define a periodicidade e o valor; cada assinatura vincula um cliente a um plano.

Planos

Criar:

POST /api/v1/plans/

{
  "name": "Plano Pro",
  "price": 179.90,
  "billing_cycle": "monthly",
  "description": "Acesso completo",
  "trial_days": 7,
  "trial_price": "0.00",
  "max_cycles": 0,
  "setup_fee": "0.00",
  "cover_image_url": "https://..."
}

Ciclos: weekly, biweekly, monthly, bimonthly, quarterly, semiannually, yearly.

Campos opcionais (catálogo expandido):

  • trial_days (int, default 0) — dias de teste antes da primeira cobrança real. 0 desativa.
  • trial_price (decimal, default 0) — valor cobrado no trial. Hoje só 0 é suportado (trial gratuito); trial pago é roadmap.
  • max_cycles (int, default 0) — encerra a assinatura após N cobranças. 0 = indeterminado.
  • setup_fee (decimal, default 0) — taxa de adesão única na primeira mensalidade.
  • cover_image_url — capa exibida no checkout público.

Listar / Detalhes / Atualizar / Excluir:

GET    /api/v1/plans/
GET    /api/v1/plans/{id}/
PUT    /api/v1/plans/{id}/        # substitui
PATCH  /api/v1/plans/{id}/        # parcial
DELETE /api/v1/plans/{id}/        # bloqueado se houver assinaturas ativas

Assinaturas

Criar:

POST /api/v1/subscriptions/

{
  "customer_id": "uuid-do-cliente",
  "plan_id": "uuid-do-plano",
  "billing_type": "CREDIT_CARD",
  "credit_card_token": "tok_xxx"
}

Para boleto, omita o token e use "billing_type": "BOLETO".

Listar:

GET /api/v1/subscriptions/?page=1

Resposta paginada. Filtros aceitos: status, customer_id, plan_id.

Detalhes (com dados ao vivo do processador):

GET /api/v1/subscriptions/{id}/detail/

Retorna o histórico de cobranças geradas, próxima fatura, e status atual sincronizado com o provedor.

Cancelar:

POST /api/v1/subscriptions/{id}/cancel/

7. Produtos e Pedidos

Para vendas avulsas (eventos, pacotes, créditos, ingressos, infoprodutos).

Produtos

Criar:

POST /api/v1/products/

{
  "name": "Evento Premium",
  "product_type": "one_time",
  "price": 347.00,
  "description": "Acesso a 1 evento com recursos Pro",
  "long_description": "Texto rico em markdown para a página pública",
  "cover_image_url": "https://...",
  "video_url": "https://www.youtube.com/embed/...",
  "thank_you_url": "https://...",
  "allow_installments": true,
  "max_installments": 12
}

Tipos (product_type):

  • one_time — pagamento único (ingresso, infoproduto, serviço)
  • pack — pacote com N unidades (curso com aulas, conjunto de itens)
  • credits — créditos (saldo pré-pago para uso futuro)
  • contribution — doação, gorjeta, apoio voluntário; pagador define o valor
  • bundle — combo agrupando outros produtos (ver seção "Bundle")

Modo de preço (price_mode):

ValorComportamento
fixed (default)Cliente paga exatamente price
customer_definedCliente digita o valor no checkout. price vira sugestão pré-preenchida.

Para customer_defined, configure piso/teto:

  • min_price (decimal, opcional) — valor mínimo aceito
  • max_price (decimal, opcional) — teto opcional

O tipo contribution automaticamente força price_mode=customer_defined. Para um produto regular ("Pague quanto quiser pelo curso"), use one_time com price_mode=customer_defined.

Listar / Detalhes / Atualizar / Excluir:

GET    /api/v1/products/
GET    /api/v1/products/{id}/
PUT    /api/v1/products/{id}/
PATCH  /api/v1/products/{id}/
DELETE /api/v1/products/{id}/

Filtros: active, product_type, name__icontains.

Bundle (combo de produtos)

Agrupa N produtos em uma única venda com preço promocional.

GET    /api/v1/bundle-items/?bundle=<uuid>
POST   /api/v1/bundle-items/
PATCH  /api/v1/bundle-items/{id}/
DELETE /api/v1/bundle-items/{id}/

POST /api/v1/bundle-items/

{
  "bundle": "uuid-do-produto-tipo-bundle",
  "item": "uuid-do-produto-incluido",
  "quantity": 1,
  "position": 0
}

Restrições:

  • O produto pai precisa ter product_type='bundle'.
  • Bundle aninhado (bundle dentro de bundle) é rejeitado.
  • Item precisa pertencer à mesma organização.

Comportamento no checkout: o pagador paga bundle.price e o backend cria 1 Order parent (com a receita) + N child Orders com unit_price=0 (marcadas em metadata.bundle_parent_id). Cada child Order tem o produto incluído + quantidade — use isso pra provisionar a entrega item por item via webhook.

O webhook canônico inclui payment.order.bundle_items[] com {order_id, product_id, product_name, quantity} para todos os itens incluídos.

Cupons

Códigos de desconto aplicáveis no checkout público.

GET    /api/v1/coupons/
POST   /api/v1/coupons/
GET    /api/v1/coupons/{id}/
PATCH  /api/v1/coupons/{id}/
DELETE /api/v1/coupons/{id}/

POST /api/v1/coupons/

{
  "code": "BLACKFRIDAY",
  "discount_type": "percent",     // "percent" | "fixed"
  "discount_value": "20.00",       // 20% ou R$ 20,00 conforme o tipo
  "valid_until": "2026-12-31T23:59:59Z",
  "usage_limit": 100,              // 0 = ilimitado
  "min_amount": "50.00",
  "is_active": true,
  "plans": [],                     // lista de UUIDs (vazio = todos os planos)
  "products": []                   // lista de UUIDs (vazio = todos os produtos)
}

Order Bumps

Ofertas adicionais exibidas no checkout do produto principal.

GET    /api/v1/order-bumps/?parent_product=<uuid>
POST   /api/v1/order-bumps/
PATCH  /api/v1/order-bumps/{id}/
DELETE /api/v1/order-bumps/{id}/

POST /api/v1/order-bumps/

{
  "parent_product": "uuid-produto-principal",
  "bump_product": "uuid-produto-adicional",
  "title": "Adicione o e-book complementar",
  "description": "Material extra para aprofundar",
  "discount_percent": "30.00"      // 30% off sobre o preço do bump_product
}

Quando o cliente aceita um ou mais bumps no checkout, é criado 1 Order parent (com o total cobrado) + N child Orders (metadata.via='order_bump', metadata.parent_order_id=<parent.id>). O webhook PAYMENT_RECEIVED inclui:

  • payment.order.bump — primeiro bump (compat).
  • payment.order.bumps[]todos os bumps aceitos com {order_id, product_id, product_name, amount}.

Checkout público (sem auth)

Endpoints sem API key — alimentam a página https://vitrin.digital/c/<org-slug>/<item-slug>.

GET  /api/v1/public/checkout/<org_slug>/<item_slug>/
POST /api/v1/public/checkout/<org_slug>/<item_slug>/pay/
POST /api/v1/public/checkout/<org_slug>/coupon/validate/
GET  /api/v1/public/checkout/payments/<tx_id>/status/

GET /<item_slug>/ — retorna produto/plano + branding da org + bumps disponíveis (order_bumps[]). Slug pode apontar tanto pra Plan quanto pra Product (resolução 1:1; em conflito Product ganha).

POST .../pay/ — cria customer guest e cobra:

{
  "customer_name": "Maria Silva",
  "customer_cpf_cnpj": "12345678900",
  "customer_email": "maria@exemplo.com",
  "customer_phone": "11999998888",
  "customer_birth_date": "1990-03-15",
  "customer_company_name": "Empresa LTDA",
  "customer_postal_code": "01310100",
  "customer_address": "Av Paulista",
  "customer_address_number": "1000",
  "customer_complement": "Apto 12",
  "customer_province": "Bela Vista",
  "customer_city": "São Paulo",
  "customer_state": "SP",
  "custom_data": { "como_conheceu": "Instagram" },
  "billing_type": "CREDIT_CARD",
  "installments": 3,
  "credit_card_token": "tok_...",
  "custom_amount": "75.00",
  "coupon_code": "PROMO10",
  "bump_ids": ["uuid-bump-1", "uuid-bump-2"]
}
  • Sistema (sempre obrigatórios): customer_name, customer_cpf_cnpj.
  • Toggleáveis: os demais customer_* são exigidos conforme field_config da org (retornado em branding.field_config no GET acima). Se um campo enabled=true, required=true vem vazio, a resposta é 400 {error, missing: [...]}.
  • custom_data aceita as chaves definidas em branding.field_config.custom_fields — campos required vazios também caem na mesma validação.
  • custom_amount obrigatório quando product.price_mode=customer_defined.
  • coupon_code valida e aplica desconto se válido.
  • bump_ids[] cria N child Orders adicionais vinculados via metadata.parent_order_id (até 10 por checkout). bump_id singular continua aceito por compat.

Resposta inclui transaction_id, order_id/subscription_id, pix_qr_code/boleto_url quando aplicável, e thank_you_url para redirecionamento pós-pagamento.

POST .../coupon/validate/ — útil para validação em tempo real no front:

{ "code": "PROMO10", "item_slug": "curso-pro", "amount": "297.00" }

Configurações do Checkout (org)

Branding e campos do formulário usados pelas páginas públicas:

GET  /api/v1/checkout/settings/
PUT  /api/v1/checkout/settings/

{
  "logo_url": "https://...",
  "primary_color": "#00BCB5",
  "accent_color": "#390043",
  "show_org_name": true,
  "support_email": "ajuda@minha-org.com",
  "support_whatsapp": "+5511...",
  "default_thank_you_url": "https://...",
  "terms_text": "Ao comprar, você concorda com...",
  "field_config": {
    "fields": {
      "email":          {"enabled": true,  "required": true},
      "phone":          {"enabled": true,  "required": false},
      "birth_date":     {"enabled": false, "required": false},
      "address":        {"enabled": true,  "required": true},
      "address_number": {"enabled": true,  "required": true},
      "complement":     {"enabled": true,  "required": false},
      "postal_code":    {"enabled": true,  "required": true},
      "city":           {"enabled": true,  "required": true},
      "state":          {"enabled": true,  "required": true}
    },
    "custom_fields": [
      {
        "key": "como_conheceu",
        "label": "Como nos conheceu?",
        "type": "select",
        "required": true,
        "options": ["Instagram", "Indicação", "Google"]
      }
    ]
  }
}

Sobre field_config:

  • Campos sistema (name, cpf_cnpj) sempre aparecem e são obrigatórios — não toggleáveis.
  • Toggleáveis: email, phone, birth_date, address, address_number, complement, postal_code, city, state. Cada um tem enabled e required.
  • custom_fields[]: tipos suportados — text, email, tel, select, textarea. Para select, popule options[].
  • O backend sempre devolve field_config mesclado com defaults — campos não enviados em PUT continuam com o valor anterior; chaves desconhecidas são ignoradas.
  • O endpoint público GET /public/checkout/<org>/<slug>/ devolve branding.field_config já consolidado com os requisitos dos métodos habilitados (a UI recebe o conjunto efetivo, não precisa fazer merge).

Requisitos automáticos por método de pagamento. Independente do field_config configurado, cada método tem campos mínimos exigidos pelo provedor — e a Vitrin força esses campos como enabled=true, required=true quando o método correspondente é o escolhido na cobrança:

Método (billing_type)Campos exigidos automaticamente
PIX / PIX_AUTOMATICemail
CREDIT_CARDemail, phone
BOLETOemail, postal_code, address, address_number, city, state

O endpoint público também devolve branding.method_requirements — um mapa {billing_type: [field_keys]} — para o frontend ajustar dinamicamente o que mostrar como obrigatório quando o usuário troca de método. A validação no POST .../pay/ aplica os requisitos do método selecionado (não a união de todos): pagador pode pagar via Pix sem informar endereço, e via Boleto será obrigado.

Métodos disponíveis (enabled_payment_methods). A organização escolhe quais métodos expor no checkout entre os que o admin Vitrin liberou (allowed_payment_methods). O endpoint GET /public/checkout/<org>/<slug>/ devolve apenas os métodos enabled_payment_methods. Tentar pagar com método não habilitado retorna 400.

Pedidos

Criar:

POST /api/v1/orders/create/

{
  "customer_id": "uuid-do-cliente",
  "product_id": "uuid-do-produto",
  "billing_type": "PIX",
  "quantity": 1
}

O pedido cria a cobrança automaticamente. Quando o webhook PAYMENT_RECEIVED chega, o pedido muda para paid.

Listar:

GET /api/v1/orders/?status=paid&page=1

Status possíveis: pending, paid, fulfilled, cancelled. Filtros: status, product_id, customer_id.

Detalhes:

GET /api/v1/orders/{id}/

Inclui o cliente, produto, transação vinculada e timestamps de cada transição de status.

Marcar como entregue (fulfill):

POST /api/v1/orders/{id}/fulfill/

Use depois que você entregou o produto (mandou ingresso por e-mail, liberou acesso ao curso, despachou o item físico). Marca fulfilled_at e expõe ao cliente final via dashboard.

Pedidos são read-only via REST (não há PUT/PATCH/DELETE direto). Para modificar, cancele e crie um novo.


8. Reembolso e Cancelamento

Reembolso (requer PIN)

POST /api/v1/payments/{id}/refund/

{
  "pin": "123456",
  "amount": 50.00
}

Omita amount para reembolso total.

Cancelar cobrança pendente

POST /api/v1/payments/{id}/cancel/

Só funciona para cobranças com status pending ou confirmed.


9. Saldo e Transferências

Consultar saldo

GET /api/v1/balance/

Resposta:

{
  "available": 1500.00,
  "total": 2300.00,
  "pending": 800.00,
  "withdrawal_fees": { "pix": 3.49, "ted": 9.49 }
}
  • available: pronto para saque
  • pending: aguardando compensação (D+1 Pix, D+2 Boleto, D+30 Crédito)
  • total: available + pending

Cronograma de recebíveis

GET /api/v1/balance/scheduled/?days_ahead=90

Lista quando cada cobrança paga vai cair na conta. Útil pra projeção de fluxo de caixa.

Resposta:

{
  "horizon_days": 90,
  "total_scheduled": 12500.0,
  "count": 18,
  "by_day": [
    { "date": "2026-04-29", "total": 350.0, "count": 2 },
    { "date": "2026-04-30", "total": 199.9, "count": 1 }
  ],
  "items": [
    {
      "release_date": "2026-04-29",
      "amount": 99.9,
      "method": "PIX",
      "installment": null,
      "transaction_id": "tx_xxx",
      "anticipated": false
    },
    {
      "release_date": "2026-05-15",
      "amount": 100.0,
      "method": "CREDIT_CARD",
      "installment": "1/3",
      "transaction_id": "tx_yyy",
      "anticipated": false
    }
  ]
}

Regras de release: PIX = paid_at + 1d; Boleto = paid_at + 2d; Cartão Nx = uma linha por parcela em paid_at + 30·i. Antecipação consolida tudo em paid_at + 1d.

Limitação importante: /balance/scheduled/ é uma projeção heurística baseada em paid_at + janela do método. Ela não considera cut-off de horário do processador, retenção de saldo mínimo, falhas de repasse, fins de semana ou feriados. Para previsão fiel do que vai cair na sua conta bancária hoje (ou nos próximos dias úteis), use /treasury/calendar/ — esse sim consulta o processador em tempo real e devolve o estado oficial (Previsto, Depositado, Rejeitado).

Calendário de saques (fonte de verdade)

GET /api/v1/treasury/calendar/?start=YYYY-MM-DD&end=YYYY-MM-DD

Reflete exatamente o calendário oficial do processador — esse é o endpoint que sua app deve consultar para mostrar "Caindo hoje", "Próximos saques", "Já depositado", "Recusado". Cada item corresponde a um repasse concreto que vai sair (ou já saiu) para a conta bancária cadastrada da sua loja, com a janela de cut-off do processador já aplicada.

Resposta:

{
  "range": { "start": "2026-05-18", "end": "2026-05-22" },
  "totals": {
    "previsto":   3890.29,
    "depositado": 3249.30,
    "disponivel":    0.00,
    "rejeitado":     0.00,
    "indefinido":    0.00
  },
  "count": 5,
  "items": [
    {
      "status": "Depositado",
      "amount": 3249.30,
      "release_date": "2026-05-20T00:00:00",
      "is_today": true,
      "is_transferred": false,
      "message": "Pagamento Efetivado."
    },
    {
      "status": "Previsto",
      "amount": 3890.29,
      "release_date": "2026-05-21T00:00:00",
      "is_today": false,
      "is_transferred": false,
      "message": ""
    }
  ]
}

Status possíveis em items[].status:

StatusSignificado
PrevistoRepasse agendado pra essa data (ainda não saiu da conta do processador).
DepositadoRepasse efetivado — o valor já caiu (ou está caindo) na sua conta bancária.
DisponívelSaldo acumulado disponível pra saque (orgs com auto-repasse desligado).
RejeitadoRepasse falhou — message traz o motivo (dados bancários ausentes, conta inativa, etc.).
IndefinidoPlaceholder do processador antes da consolidação do dia. Ignore no agregado.

Como usar nas suas telas:

  • "Caindo hoje" = items onde release_date == today AND status == 'Depositado' → some amount. Esse é o número que vai bater exato com o extrato bancário da sua loja no dia.
  • "Próximos saques" = items onde status == 'Previsto', agrupado por release_date.
  • "Saldo disponível pra saque" = totals.disponivel (só pra orgs com auto-repasse pausado).
  • "Repasses falhos" = items onde status == 'Rejeitado' — mostre o message ao operador da loja.

Não some Transaction.net_amount localmente para estimar saque do dia. Esse cálculo ignora:

  • Cut-off horário do processador (cobranças pagas após ~18h BRT entram no batch do dia seguinte).
  • Reservas / retenções de saldo mínimo aplicadas pelo processador.
  • Fins de semana e feriados (repasse roda em dia útil).
  • Recusas de TED/Pix (conta destino inativa, divergência de titular, etc.).

A diferença entre soma local e valor real chega a 5–10% facilmente. Use /treasury/calendar/ sempre que precisar do número que vai bater com o extrato bancário.

Descritor do Pix de repasse (limitação de white-label)

Quando o processador transfere o repasse para a conta bancária da sua loja, o descritor da transação no extrato bancário do destinatário (ex.: Cora, Nubank, Itaú) carrega o nome legal da instituição de pagamento que executou o Pix — não o nome da Vitrin nem o da sua loja. Essa é uma exigência regulatória do SPI/BACEN: o ordenador da transferência precisa identificar quem está mandando o dinheiro.

Pra você como operador da loja, isso aparece no seu próprio extrato (R$ X recebido de "<nome da instituição de pagamento>") e não há configuração para mudar — vale tanto pra Vitrin quanto pra qualquer outro intermediador no Brasil.

Esse vazamento de marca é restrito ao extrato bancário do repasse. Em toda outra superfície (checkout, e-mails transacionais, webhooks, dashboard) a Vitrin se mantém white-label e a sua loja aparece como a marca-fim.

Transferir via Pix (requer PIN)

POST /api/v1/transfers/pix/

{
  "value": 500.00,
  "pix_key": "12345678909",
  "pix_key_type": "CPF",
  "pin": "123456"
}

Tipos de chave: CPF, CNPJ, EMAIL, PHONE, EVP (aleatória).

Taxa de saque retida do valor: pediu R$500, R$500 são debitados da sua conta de pagamentos, R$500 − fee vai pro destino. Resposta carrega requested, withdrawal_fee e net_to_destination pra contabilização. Saques ≤ taxa retornam HTTP 400.

Transferir via dados bancários (requer PIN)

POST /api/v1/transfers/bank/

{
  "value": 500.00,
  "bank_code": "237",
  "agency": "0001",
  "account": "12345",
  "account_digit": "6",
  "account_type": "CONTA_CORRENTE",
  "owner_name": "Maria Silva",
  "cpf_cnpj": "12345678909",
  "pin": "123456"
}

Histórico de transferências

GET /api/v1/transfers/

Combina transferências locais (já solicitadas pela sua org) + dados ao vivo do processador (status real, falhas, datas de efetivação).


9.5. Programa de Afiliados

A Vitrin oferece um programa de afiliação no padrão Hotmart/Kiwify: terceiros se cadastram como afiliados na plataforma, sua loja libera produtos e planos para afiliação com uma comissão definida, eles compartilham um link rastreado e cada venda atribuída paga uma comissão automática no afiliado. Toda a infraestrutura de atribuição, ledger, repasse e estorno é da Vitrin — sua API só precisa ligar o programa, configurar comissões e (opcional) receber os blocos affiliate / coproducers no webhook.

9.5.1. Como funciona, em uma frase

Quando o cliente chega no seu checkout via ?aff=<slug-do-afiliado>, a Vitrin grava um cookie de 1ª parte com validade attribution_window_days (padrão 30 dias, configurável de 7 a 90). Se o pagamento acontecer dentro dessa janela, a comissão configurada no produto/plano (ou o default do programa) é debitada do líquido da sua venda e creditada na carteira do afiliado. Sua taxa Vitrin continua a mesma — afiliado não sai da fatia da plataforma.

9.5.2. Ligar o programa

PATCH /api/v1/organization/affiliate-program/

{
  "enabled": true,
  "attribution_window_days": 30,
  "requires_approval": true,
  "default_commission_pct": "20.00",
  "default_commission_recurring_pct": "10.00",
  "public_marketplace": false
}
  • requires_approval=true (recomendado): cada afiliado que pede para promover seus produtos fica em pending até você aprovar. false libera afiliação automática — use só em programas abertos.
  • default_commission_pct é a comissão default aplicada quando o produto/plano não tem comissão própria.
  • default_commission_recurring_pct é a comissão default em renovações (assinatura/Pix Automático). Em branco usa a mesma da primeira venda.
  • public_marketplace=true lista seus produtos no marketplace público da Vitrin (qualquer afiliado descobre). false mantém privado — só quem você convidar e for aprovado consegue ver e promover.

Para ler o estado atual:

GET /api/v1/organization/affiliate-program/

9.5.3. Comissão por produto / plano

Cada produto e plano pode sobrescrever o default do programa.

PUT /api/v1/products/{product_id}/affiliate-config/

{
  "enabled": true,
  "commission_pct": "30.00",
  "auto_approve": false,
  "max_affiliates": null
}
PUT /api/v1/plans/{plan_id}/affiliate-config/

{
  "enabled": true,
  "commission_pct": "30.00",
  "commission_recurring_pct": "15.00",
  "affiliate_recurring_months": null,
  "auto_approve": false,
  "max_affiliates": null
}
  • enabled=false retira o item do marketplace e bloqueia novos pedidos. Comissões já liquidadas não são afetadas.
  • auto_approve=true faz com que pedidos pra este item específico virem aprovados sem moderação (útil pra produtos de baixo risco mesmo com requires_approval=true no programa).
  • max_affiliates: limite opcional de quantos afiliados podem estar simultaneamente aprovados no item.
  • Para planos (recorrência):
    • commission_recurring_pct: comissão paga nas renovações. Em branco usa commission_pct.
    • affiliate_recurring_months: cap em N renovações. Em branco = vitalícia enquanto a assinatura existir.

Importante — A comissão é calculada sobre o preço do produto, não sobre o valor cobrado do cliente. Em cartão parcelado, os juros não entram na base da comissão (juros são receita exclusiva da plataforma).

9.5.4. Moderar pedidos de afiliação

Quando um afiliado solicita seu produto via marketplace, ele aparece em:

GET /api/v1/organization/affiliate-applications/?status=pending

Resposta:

[
  {
    "id": "<uuid-app>",
    "affiliate_slug": "joao-silva",
    "product_slug": "ebook-marketing",
    "plan_slug": null,
    "target_name": "E-book Marketing",
    "status": "pending",
    "created_at": "2026-05-19T22:34:56Z"
  }
]

Para aprovar, rejeitar ou revogar:

POST /api/v1/affiliate-applications/{app_id}/moderate/

{
  "action": "approve",
  "commission_pct_override": "35.00",
  "commission_recurring_pct_override": "18.00"
}
  • action: approve, reject ou revoke.
  • commission_pct_override / commission_recurring_pct_override: opcional — define comissão personalizada só pra este afiliado nesse produto/plano. Use pra parceiros estratégicos. Em branco, herda do produto/plano/programa.
  • rejection_reason: usado em action=reject, mostrado ao afiliado.

9.5.5. Tracking no checkout

Se você usa o checkout público da Vitrin (/c/{org}/{item}), não precisa fazer nada — a captura de ?aff= e o cookie de atribuição já são tratados pelo frontend hospedado.

Se você roda checkout próprio chamando /api/v1/checkout/ diretamente:

  1. Capture ?aff= na URL de chegada do cliente.
  2. Guarde em cookie/localStorage de 1ª parte (TTL ≤ attribution_window_days).
  3. (Opcional, mas recomendado) chame:
    POST /api/v1/public/affiliate-track/
    
    {
      "affiliate_slug": "joao-silva",
      "org_slug": "minha-loja",
      "product_slug": "ebook-marketing"
    }
    
    pra registrar o clique antes da conversão (alimenta o histórico do afiliado).
  4. No POST do checkout, envie no body:
    {
      "affiliate_slug": "joao-silva",
      "affiliate_attribution_ts": 1716234567890
    }
    
    affiliate_attribution_ts é o epoch em milissegundos do clique original — usado pra rechecar a janela no servidor. A Vitrin valida o slug, a janela e a permissão do afiliado no item; em qualquer falha, a venda segue sem atribuição (falha-soft, nunca quebra o checkout).

9.5.6. Relatório de comissões

GET /api/v1/organization/affiliate-report/

Resposta:

{
  "totals": {
    "settled": "1234.56",
    "pending": "78.90",
    "reversed": "12.00"
  },
  "recent": [
    {
      "id": "<uuid-comissao>",
      "org_slug": "minha-loja",
      "product_slug": "ebook-marketing",
      "is_recurring": false,
      "sale_amount": "100.00",
      "commission_pct": "30.00",
      "commission_amount": "30.00",
      "status": "settled",
      "settled_at": "2026-05-19T22:34:56Z",
      "created_at": "2026-05-19T22:30:00Z"
    }
  ]
}

totals agrega todas as comissões da loja por status. recent traz as 200 últimas. Use pra reconciliação e relatórios próprios — o webhook (§5) já entrega o detalhe transação a transação.

9.5.7. Pontos importantes

  • Comissão de afiliado é debitada do líquido da sua loja, não da taxa Vitrin. Modelo idêntico ao Hotmart/Kiwify.
  • Estorno proporcional: quando uma cobrança com comissão é estornada (refund ou chargeback), a comissão é revertida proporcionalmente. Se o afiliado já tinha sacado, o saldo dele fica negativo e a Vitrin compensa no próximo repasse — você não precisa cobrar do afiliado.
  • Renovações de assinatura/Pix Automático propagam o afiliado automaticamente. O cap (affiliate_recurring_months) limita por quantas renovações o afiliado continua ganhando.
  • Coprodutores (split fixo, sem janela de atribuição) são entidade separada de afiliado. A configuração é feita por produto no painel.
  • White-label: o afiliado nunca vê dados da sua loja além do que você publica (nome, descrição, preço, comissão). Pra ele, a Vitrin é a plataforma. Pra você, ele aparece só como slug no webhook.

10. Antecipação de Recebíveis

Antecipação te permite receber agora valores que cairiam no futuro (cartão D+30, parcelado D+(30·N), boleto D+2). A taxa é proporcional ao tempo restante até a liberação original e é aplicada por transação.

Pré-requisito: a org precisa ter allow_anticipation=true (ativado pelo admin no signup). Tentativas com a feature desabilitada retornam HTTP 403.

Listar transações elegíveis

GET /api/v1/anticipations/?status=eligible

Lista as Transactions com status='received' que ainda não foram antecipadas. Use ?status=anticipated para ver as já antecipadas.

Simular antes de solicitar

Antes de gastar, simule pra ver quanto você recebe líquido:

POST /api/v1/anticipations/simulate/

{ "payment_id": "uuid-da-transacao" }

Resposta (vinda do processador):

{
  "value": 100.00,
  "fee": 5.50,
  "netValue": 94.50
}

Em sandbox, o processador pode recusar a simulação (a conta de pagamentos de teste não tem permissão para antecipar). O dashboard cai em uma estimativa local com base nas suas taxas — programaticamente, trate o erro 502 e calcule você mesmo se quiser.

Solicitar antecipação (requer PIN)

POST /api/v1/anticipations/request/

{
  "payment_id": "uuid-da-transacao",
  "pin": "123456"
}

Resposta:

{
  "transaction_id": "uuid",
  "anticipated": true,
  "anticipation_fee": 5.50,
  "result": { ... resposta crua do processador ... }
}

A Transaction passa a ter anticipated=true e o valor cai em available no /balance/ em D+2 útil.


11. Defesa de Chargeback

Disputa é sempre manual. O lojista sustenta a defesa diretamente com a operadora do cartão. Não há endpoint de submissão automática. A Vitrin é o organizador: você anexa evidências aqui, baixa o dossiê pré-montado e encaminha pra operadora pelo canal manual.

Listar disputas em aberto

GET /api/v1/chargebacks/

Retorna transações com status chargeback ou disputed, com evidence_count por transação.

Anexar evidências

POST /api/v1/payments/{id}/evidences/
Content-Type: multipart/form-data

  file: <arquivo>            # PDF, imagem, etc — máx 25 MB
  evidence_type: receipt     # taxonomia abaixo
  description: ""            # opcional

Tipos válidos: delivery_proof, communication, contract, receipt, refund_attempt, ip_log, other.

GET    /api/v1/payments/{id}/evidences/             # listar
DELETE /api/v1/payments/{id}/evidences/{ev_id}/     # remover (bloqueia se já submetida)

Storage é privado (URLs presigned com expiração de 1h). Pode coletar evidências preventivamente em transações ainda não disputadas (status received/confirmed) — útil pra ter o material pronto antes de qualquer disputa surgir.

Dossiê de defesa pré-montado

A Vitrin compila automaticamente o dossiê com tudo que ela tem da transação:

GET /api/v1/payments/{id}/chargeback-defense/

Resposta:

{
  "transaction": {
    "id": "uuid",
    "amount": 99.90,
    "billing_type": "CREDIT_CARD",
    "installments": 1,
    "paid_at": "2026-04-09T15:30:00Z",
    "payer_ip": "189.6.x.y"
  },
  "customer": {
    "name": "Maria Silva",
    "cpf_cnpj": "12345678909",
    "email": "maria@email.com",
    "phone": "11988776655",
    "address": "Rua das Flores, 42 - Centro",
    "city_state": "São Paulo/SP"
  },
  "order": {
    "product_name": "Curso Premium",
    "fulfilled_at": "2026-04-09T16:00:00Z"
  },
  "subscription": null,
  "previous_transactions": [
    { "id": "uuid", "amount": 49.90, "paid_at": "2026-03-15T..." }
  ],
  "webhook_events": [
    { "event_type": "PAYMENT_CONFIRMED", "created_at": "..." },
    { "event_type": "PAYMENT_RECEIVED", "created_at": "..." }
  ],
  "audit_trail": [
    { "action": "transaction.created", "timestamp": "...", "ip_address": "..." }
  ]
}

Use esses dados pra montar a contestação no painel da bandeira (Visa/Master) ou via processador. O ideal é guardar tudo: comprovante de entrega, IP do pagador, transações anteriores do mesmo cliente bem-sucedidas, fulfillment do pedido, eventos de webhook que comprovam a confirmação do pagamento.

Boas práticas para reduzir chargebacks: cobre só após confirmar identidade, marque pedidos como fulfilled assim que entregar, mantenha cadastro de cliente completo (endereço!), responda chargebacks rapidamente.


12. Configurações da Org

Self-service de tudo que você pode customizar pelo dashboard /dashboard/settings/.

Ler configurações atuais

GET /api/v1/settings/

Resposta:

{
  "enabled_payment_methods": ["PIX", "CREDIT_CARD", "BOLETO"],
  "org_max_installments": 12,
  "org_min_installment_value": "10.00",
  "auto_anticipation_enabled": false,
  "checkout_logo": "https://...",
  "checkout_primary_color": "#00BCB5",
  "webhook_url": "https://suaapp.com/webhooks/vitrin",
  "antifraud_enabled": false,
  "payout_frequency": "monthly",

  "allowed_payment_methods": ["PIX", "CREDIT_CARD", "BOLETO"],
  "max_installments": 12,
  "min_installment_value": "10.00",
  "allow_anticipation": true,
  "allow_auto_anticipation": false,

  "fee_percentage_pix": "5.49",
  "fee_fixed_pix": "3.49",
  "fee_percentage_credit": "5.49",
  "anticipation_rate_credit_vista": "2.99",
  "withdrawal_fee_fixed_pix": "3.49"
}

Os campos dividem-se em três blocos:

BlocoEditável pela org?
Preferências da org (enabled_payment_methods, org_max_installments, auto_anticipation_enabled, antifraud_enabled, payout_frequency, checkout_logo, checkout_primary_color)✅ via PATCH
Limites da plataforma (allowed_payment_methods, max_installments, allow_anticipation)❌ definido pelo admin no onboarding
Tabela de taxas (fee_*, anticipation_*, withdrawal_*)❌ congelada no Anexo Comercial assinado

Atualizar (parcial)

PATCH /api/v1/settings/

{
  "enabled_payment_methods": ["PIX", "CREDIT_CARD"],
  "checkout_primary_color": "#FD8000",
  "auto_anticipation_enabled": true
}

A enabled_payment_methods precisa ser um subconjunto de allowed_payment_methods. Tentar habilitar um método não-permitido retorna HTTP 400.

Configurar webhook (já documentado em §5)

PUT /api/v1/webhooks/config/ — requer PIN.

Conta bancária para repasse

GET /api/v1/settings/bank-account/
PUT /api/v1/settings/bank-account/        (requer PIN)

{
  "bank_code": "237",
  "bank_agency": "1234",
  "bank_agency_digit": "0",
  "bank_account": "987654",
  "bank_account_digit": "5",
  "bank_account_type": "CHECKING",
  "pin": "123456"
}

bank_account_type: CHECKING (corrente) ou SAVINGS (poupança). O titular precisa estar no mesmo CPF/CNPJ da org — o processador rejeita repasse pra conta de terceiros.

Checklist pós-aprovação

GET /api/v1/onboarding/checklist/

Retorna uma lista de 4 passos (conta bancária, webhook, primeiro plano/produto, primeira cobrança) com status completed: true|false cada. Usado pelo dashboard pra renderizar o widget "ative sua conta" — útil também pra mostrar progresso ao usuário no seu próprio painel.

{
  "is_approved": true,
  "completed_count": 2,
  "total_count": 4,
  "all_completed": false,
  "steps": [
    { "key": "bank_account", "completed": true, ... },
    { "key": "webhook", "completed": false, "cta_label": "Configurar", "cta_href": "/dashboard/webhooks" },
    { "key": "product_or_plan", "completed": true, ... },
    { "key": "first_charge", "completed": false, "cta_label": "Ver integração", "cta_href": "/docs/integracao" }
  ]
}

Export LGPD dos dados da org

A org pode pedir um snapshot completo dos próprios dados (clientes, transações, assinaturas, planos, produtos) atendendo ao direito de portabilidade da LGPD.

POST /api/v1/export/

{ "format": "json" }     # ou "csv" (zip com 5 tabelas)

Cria um export pending, agenda processamento async, e retorna o registro. O processamento leva alguns segundos. Quando completa, a org recebe um email com link de download (URL presigned, 1h de validade).

GET  /api/v1/export/                       # histórico + status atuais
GET  /api/v1/export/{id}/download/         # gera URL presigned fresca

Limites: 1 export pendente/processando por org (HTTP 429 se já tem um em andamento), retenção de 90 dias após conclusão.


13. Relatórios

Relatórios agregados sobre a operação da sua org. Todos retornam dados restritos à org autenticada.

Overview do dashboard

GET /api/v1/reports/overview/?days=30

Resposta:

{
  "total_revenue": 28900.50,
  "total_transactions": 142,
  "active_subscriptions": 23,
  "active_customers": 89,
  "anticipated_value": 8500.00,
  "by_method": {
    "PIX": 18200.00,
    "CREDIT_CARD": 9100.50,
    "BOLETO": 1600.00
  }
}

Receita por dia

GET /api/v1/reports/revenue/?days=30

Retorna uma série temporal com receita bruta e líquida por dia, pronta para gráficos.

Receita mensal

GET /api/v1/reports/revenue/monthly/?months=12

Transações com filtros

GET /api/v1/reports/transactions/?status=received&billing_type=PIX&from=2026-04-01&to=2026-04-30

Filtros: status, billing_type, customer_id, from, to, min_amount, max_amount.

Churn de assinaturas

GET /api/v1/reports/churn/

Retorna taxa de cancelamento por mês, MRR perdido e principais motivos.

Relatório de antecipações

GET /api/v1/reports/anticipation/?from=2026-04-01&to=2026-04-30

Resumo de antecipações solicitadas no período: total antecipado, taxa total paga, economia média de tempo.


14. Referência Rápida de Endpoints

Checkout

MétodoEndpointDescrição
POST/checkout/pix/Cobrar via Pix
POST/checkout/boleto/Cobrar via Boleto
POST/checkout/tokenize/Tokenizar cartão
POST/checkout/pay/Cobrar com token
POST/checkout/guest/Checkout sem cadastro prévio
GET/checkout/installments/?amount=XConsultar parcelamento

Pix Automático

MétodoEndpointDescrição
GET/pix-automatic/authorizations/Listar autorizações
POST/pix-automatic/authorizations/Criar (gera QR Code de aceite)
GET/pix-automatic/authorizations/{id}/Detalhes
PATCH/pix-automatic/authorizations/{id}/Alterar (value/min_limit_value/finish_date/description)
DELETE/pix-automatic/authorizations/{id}/Cancelar (PIN)
POST/pix-automatic/authorizations/{id}/charges/Criar cobrança recorrente (2-10 dias úteis)
GET/pix-automatic/authorizations/{id}/instructions/Listar instruções da autorização
POST/pix-automatic/authorizations/{id}/sync/Forçar sync com o provedor
GET/pix-automatic/instructions/Todas as instruções da org
GET/pix-automatic/instructions/{id}/Detalhe da instrução
POST/pix-automatic/instructions/{id}/sync/Forçar sync da instrução
DELETE/pix-automatic/instructions/{id}/Cancelar agendamento (PIN)

Transações

MétodoEndpointDescrição
GET/transactions/Listar transações
GET/payments/{id}/status/Status (polling Pix)
POST/payments/{id}/refund/Reembolsar (PIN)
POST/payments/{id}/cancel/Cancelar pendente
GET/payments/{id}/chargeback-defense/Dossiê de defesa

Clientes

MétodoEndpointDescrição
GET/customers/Listar clientes (paginado)
POST/customers/Criar cliente
GET/customers/{id}/Detalhes
PUT/PATCH/customers/{id}/Atualizar
DELETE/customers/{id}/Excluir (bloqueado se houver transações)

Assinaturas

MétodoEndpointDescrição
GET/subscriptions/Listar assinaturas
POST/subscriptions/Criar assinatura
POST/subscriptions/{id}/cancel/Cancelar
GET/subscriptions/{id}/detail/Detalhe com dados do provedor

Planos

MétodoEndpointDescrição
GET/plans/Listar planos
POST/plans/Criar plano
PUT/plans/{id}/Atualizar
DELETE/plans/{id}/Excluir

Produtos e Pedidos

MétodoEndpointDescrição
GET/products/Listar produtos
POST/products/Criar produto
GET/products/{id}/Detalhes
PUT/PATCH/products/{id}/Atualizar
DELETE/products/{id}/Excluir
POST/orders/create/Criar pedido (gera cobrança)
GET/orders/Listar pedidos
GET/orders/{id}/Detalhes
POST/orders/{id}/fulfill/Marcar entregue

Contratos eletrônicos (read-only)

MétodoEndpointDescrição
GET/contracts/status/Visão geral (pending + active + all_signed)
GET/contracts/pending/Apenas contratos a assinar
GET/contracts/active/Apenas vigentes
GET/contracts/{id}/preview/Render do template para leitura
GET/contracts/history/Histórico de aceitações
GET/contracts/acceptances/{id}/Detalhe de uma aceitação

Assinar é ação humana — feita em /dashboard/contract com PIN.

Onboarding (read-only)

MétodoEndpointDescrição
GET/onboarding/status/Status do compliance interno
GET/onboarding/checklist/Checklist pós-aprovação (4 passos)

Upload de documentos é ação humana — feita em /dashboard/onboarding.

Auth (rotação de chave)

MétodoEndpointDescrição
POST/auth/api-key/regenerate/Rotaciona a API key (requer PIN)

Cadastro, login, PIN e password reset são ações humanas — feitas no dashboard.

Financeiro

MétodoEndpointDescrição
GET/balance/Saldo (disponível + pendente)
GET/balance/scheduled/Cronograma de recebíveis (projeção heurística)
GET/treasury/calendar/Calendário oficial de saques (Previsto/Depositado/Rejeitado — use pra "Caindo hoje")
GET/finance/statement/Extrato financeiro
POST/transfers/pix/Transferir via Pix (PIN)
POST/transfers/bank/Transferir via banco (PIN)
GET/transfers/Histórico de transferências

Antecipação

MétodoEndpointDescrição
GET/anticipations/Listar transações elegíveis
POST/anticipations/simulate/Simular antecipação
POST/anticipations/request/Solicitar (PIN)

Configurações

MétodoEndpointDescrição
GET/settings/Configurações da org
PATCH/settings/Atualizar configurações
GET/settings/bank-account/Conta bancária de repasse
PUT/settings/bank-account/Atualizar conta bancária (PIN)

Defesa de chargeback

MétodoEndpointDescrição
GET/chargebacks/Lista de transações em disputa
GET/payments/{id}/chargeback-defense/Dossiê pré-montado
GET/payments/{id}/evidences/Evidências anexadas
POST/payments/{id}/evidences/Upload de evidência (multipart, máx 25 MB)
DELETE/payments/{id}/evidences/{ev_id}/Remover evidência

Export LGPD

MétodoEndpointDescrição
GET/export/Histórico de exports
POST/export/Solicitar export (format: json ou csv)
GET/export/{id}/download/URL presigned de download
PUT/webhooks/config/Configurar webhook (PIN)
GET/onboarding/status/Status do onboarding

Relatórios

MétodoEndpointDescrição
GET/reports/overview/Dashboard overview
GET/reports/revenue/?days=30Receita por período
GET/reports/transactions/Transações com filtros
GET/reports/churn/Churn de assinaturas
GET/reports/anticipation/Relatório de antecipações

Programa de Afiliados

MétodoEndpointDescrição
GET/organization/affiliate-program/Config do programa da loja
PATCH/organization/affiliate-program/Ligar/desligar + janela + comissão default
GET/products/{id}/affiliate-config/Comissão configurada no produto
PUT/products/{id}/affiliate-config/Atualizar comissão do produto
GET/plans/{id}/affiliate-config/Comissão configurada no plano
PUT/plans/{id}/affiliate-config/Atualizar comissão do plano + cap renovações
GET/organization/affiliate-applications/Pedidos de afiliação (filtro ?status=pending)
POST/affiliate-applications/{id}/moderate/Aprovar / rejeitar / revogar
GET/products/{id}/affiliates/Afiliados ativos no produto
GET/organization/affiliate-report/Relatório de comissões pagas
POST/public/affiliate-track/Registrar clique (sem auth; checkout próprio)

15. Códigos de Erro

StatusSignificado
200Sucesso
201Criado com sucesso
400Dados inválidos (verifique o corpo da requisição)
401API key inválida ou expirada
403Sem permissão (org suspensa ou feature desabilitada)
404Recurso não encontrado
429Rate limit excedido (100 req/min padrão)
502Erro no processador de pagamentos (provedor indisponível)

Todos os erros retornam JSON:

{ "error": "Descrição do erro." }

16. Prazos de Recebimento

MétodoPrazo padrãoCom antecipação
PixD+1D+2 útil
BoletoD+2D+2 útil
Crédito à vistaD+30D+2 útil
Crédito parcelado1 parcela a cada 30 diasTodas em D+2 útil

17. Limites e Rate Limiting

RecursoLimite
API key (geral)100 req/min
Checkout30 req/min
Webhooks500 req/min

Header Retry-After é retornado quando o limite é excedido.


Suporte