API do DePix App

Introdução

A API do DePix App permite criar e gerenciar checkouts Pix programaticamente. É a mesma API que alimenta o plugin do BTCPay Server, a área Meu Negócio e agora agentes de IA — pelo MCP e pelo SDK.

URL base

https://api.depixapp.com

Formato

Todos os requests e responses usam JSON (Content-Type: application/json). Valores monetários são sempre em centavos (inteiros). Exemplo: R$ 10,00 = 1000.

Todas as datas e horas que a API retorna — respostas e webhooks — vêm num único formato, RFC 3339 em UTC: "2026-07-01T12:00:00.000Z". As duas exceções são campos que carregam só a data, sem hora: due_date e cycle_due_date das cobranças ("2026-08-05"). O conteúdo de metadata nunca é alterado — volta exatamente como você enviou.

Para começar, crie uma conta, ative sua conta de negócio em Meu Negócio e gere uma API key.

Autenticação

Use uma API key no header Authorization em todos os requests autenticados.

Header
Authorization: Bearer sk_live_<sua-chave>

Nos exemplos curl desta documentação, a chave vem da variável de ambiente DEPIX_API_KEY. Defina-a uma vez no seu shell — export DEPIX_API_KEY=sk_live_... (ou sua chave sk_test_ no sandbox) — em vez de colar a chave inline em cada comando.

Tipos de chave

PrefixoTipoComportamento
sk_live_LiveCheckouts reais. Dinheiro de verdade.
sk_test_TestCheckouts de teste. Nenhum dinheiro real é movimentado.

Para gerenciar suas chaves (criar, listar, revogar), acesse Meu Negócio em depixapp.com/#merchant. Máximo de 5 chaves live e 5 chaves test ativas por conta.

Cada chave nasce com scopes explícitos (merchant_read, merchant_write, wallet_read, wallet_write) e, no caso de chaves com scope wallet_write, com limites de gasto obrigatórios. A chave é imutável após a criação — veja Scopes e limites por chave.

Acesso à API de produção

Chaves sk_test_ são liberadas automaticamente após criar sua conta de negócio — comece integrando contra o sandbox sem esperar nada. Chaves sk_live_ exigem aprovação manual: na área de API Keys, clique em Solicitar acesso, responda 5 perguntas curtas sobre sua integração, e nossa equipe avalia. Aprovações costumam sair em algumas horas em dias úteis.

Guarde sua chave com segurança. Ela só é exibida uma vez no momento da criação. Se perdê-la, revogue e gere uma nova.

Scopes e limites por chave

Cada API key carrega um conjunto explícito de scopes que define o que ela pode fazer. Os scopes são escolhidos na criação da chave e não podem ser alterados depois — a chave é imutável; para mudar scopes ou limites, revogue e crie outra.

Os scopes se dividem em dois eixos: merchant_* é o lado lojista (o gateway — checkouts e produtos) e wallet_* é o lado carteira (o on/off-ramp Pix — depósitos e saques).

ScopeLibera
merchant_readTodos os GETs da superfície de lojista: listar/consultar checkouts, produtos e GET /api/me.
merchant_writeO lado "receber": criar/simular checkouts, o CRUD de produtos e editar os campos leves do perfil da loja (PATCH /api/merchants/me).
wallet_readLer o status do lado carteira: GET /api/deposits/:id e GET /api/withdrawals/:id. Nunca é concedido por padrão.
wallet_writeO lado "pagar" (mover dinheiro): POST /api/deposit, POST /api/withdraw. Nunca é concedido por padrão.
  • Sem hierarquia implícitamerchant_write não inclui merchant_read, e os scopes wallet_* não incluem nenhum deles. Uma chave pode combinar os quatro: ["merchant_read", "merchant_write", "wallet_read", "wallet_write"].
  • Chamada sem o scope exigido → 403 com error.code = "insufficient_scope" e details.required_scope (ver Erros). A resposta nunca ecoa a lista de scopes da chave.

Chaves wallet_write nascem com limites obrigatórios

Toda chave com scope wallet_write tem limites de gasto próprios, aplicados além dos limites da conta (os limites de conta sempre prevalecem). Se você não informar valores na criação, os defaults se aplicam: R$ 100,00 por transação (per_tx_limit_cents = 10000) e R$ 500,00 por dia (daily_limit_cents = 50000, janela rolante de 24h somando depósitos e saques atribuídos à chave). Você pode elevar os valores conscientemente na criação — mas uma chave wallet_write sem limites não existe.

Operação que excede um limite da chave → 400 com error.code = "key_limit_exceeded" e details: { limit: "per_tx" | "daily", limit_cents, used_cents }.

Criação de chave com scopes e limites

A gestão de credenciais é exclusiva do dono logado: POST /api/api-keys aceita apenas o JWT do dashboard (nunca outra API key). Além dos campos existentes (type, label, expires_in_days), a criação aceita:

CampoTipoDescrição
scopesarrayopcionalSubconjunto de ["merchant_read", "merchant_write", "wallet_read", "wallet_write"], sem duplicatas. Default: ["merchant_read", "merchant_write"] (o comportamento de sempre).
per_tx_limit_centsintegeropcionalLimite por transação em centavos (mínimo 100). Com scope wallet_write, default 10000 (R$ 100,00) quando omitido.
daily_limit_centsintegeropcionalLimite diário em centavos, janela rolante de 24h (mínimo 100). Com scope wallet_write, default 50000 (R$ 500,00) quando omitido.
rate_limit_per_minintegeropcionalRate limit adicional por chave (1–600 req/min). Omitido = sem limite próprio; vale só o budget agregado do merchant.
Resposta — 201 Created
{
  "id":                  "a1b2c3d4e5f6...",
  "key":                 "sk_test_...",       // exibida só uma vez
  "prefix":              "sk_test_",
  "label":               "agent-payments",
  "is_live":             false,
  "expires_at":          null,
  "scopes":              ["merchant_read", "merchant_write", "wallet_read", "wallet_write"],
  "per_tx_limit_cents":  10000,
  "daily_limit_cents":   50000,
  "rate_limit_per_min":  30
}
Imutável pós-criação. Não existe endpoint de edição de scopes ou limites. Para mudar qualquer coisa numa chave viva, revogue-a e crie uma nova — permissão mutável em credencial ativa é superfície de ataque.

Idempotência

Os POSTs com efeito monetário aceitam o header opcional Idempotency-Key (1–255 caracteres ASCII visíveis). Fortemente recomendado para agentes e para qualquer integração com retry automático: um retry com a mesma chave devolve a resposta original em vez de criar um segundo QR ou uma segunda cobrança.

Endpoints cobertos

  • POST /api/deposit
  • POST /api/withdraw
  • POST /api/checkouts

Semântica exata

CenárioResultado
Mesma chave + mesmo bodyReplay da resposta original (mesmo status, mesmo body) + header Idempotency-Replayed: true. Nenhum efeito colateral novo.
Mesma chave + body diferente422 idempotency_key_reuse — o handler nunca executa.
Mesma chave em endpoint diferenteIndependentes — o escopo de unicidade inclui o endpoint.
Request concorrente com a mesma chave409 idempotency_in_flight com retry_after: 5 — repita em alguns segundos e receba o replay.
  • Escopo de unicidade: (identidade, endpoint, chave) — chaves de merchants diferentes nunca colidem.
  • Comparação do body: hash do JSON parseado. Whitespace/formatação não afetam; a ordem dos campos afeta — body semanticamente igual com campos reordenados → 422. Retry byte-idêntico sempre casa.
  • Respostas 5xx e 429 nunca são armazenadas — o retry re-executa. 4xx determinísticos (validação, limites) são armazenados e replayados.
  • TTL de 24 horas — depois disso a mesma chave vale como nova.
  • Operações sandbox (sk_test_) participam normalmente — o agente treina o fluxo completo.
Effectively-once, nunca exactly-once. A garantia é de no máximo UMA execução concorrente. Se a invocação dona morrer entre a chamada ao provedor e a persistência da resposta, um takeover após 60s re-executa a operação. Desenhe seu ledger tratando o retry como potencialmente duplicador.

Exemplo

curl
curl -X POST https://api.depixapp.com/api/deposit \
  -H "Authorization: Bearer $DEPIX_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: agent-run-42-deposit-1" \
  -d '{ "amountInCents": 1500, "depixAddress": "lq1qq...", "payer_tax_number": "52998224725" }'

Primeira chamada: 200 com o QR. Retry idêntico: 200 com o mesmo body + Idempotency-Replayed: true. Retry com amountInCents alterado:

Resposta — 422 Unprocessable Entity
{
  "response": { "errorMessage": "Idempotency-Key já utilizada com um corpo diferente." },
  "error": {
    "code": "idempotency_key_reuse",
    "message": "This Idempotency-Key was already used with a different request body.",
    "request_id": "gru1::abcd-1234",
    "docs_url": "https://depixapp.com/docs/en/#errors"
  }
}

Erros

Erros carregam um envelope duplo: response.errorMessage (mensagem legada em português, sempre presente e sempre legível para humanos) e o objeto error (contrato estruturado para máquinas, mensagens em inglês). Programe contra error.code — nunca contra o texto das mensagens.

Resposta de erro
{
  "response": { "errorMessage": "Muitas requisições. Tente novamente em 1 minuto." },
  "error": {
    "code": "rate_limited",
    "message": "Too many requests for this scope.",
    "request_id": "gru1::iad1::v9x4k-1751476800000-abc123",
    "retry_after": 37,
    "docs_url": "https://depixapp.com/docs/en/#errors",
    "details": { "scope": "deposit" }
  }
}
  • request_id — id de correlação, devolvido em toda resposta (sucesso incluso) no header X-Request-Id. Cite-o em pedidos de suporte.
  • retry_after — segundos até poder repetir; presente em todo 429, 503 e no 409 idempotency_in_flight. Espelhado no header HTTP Retry-After.
  • details — extras opcionais e normativos por code: campo ofensor, scope exigido, limites em centavos (ver a tabela abaixo).
  • Irmãos legados preservados: erros de validação do criar checkout mantêm response.errors[] (lista por campo) e o 403 de conta bloqueada mantém blocked: true.
  • Handlers legados fora da superfície de agente ainda podem responder só com response.errorMessage, sem o objeto error.

next_action — o próximo passo, legível por máquina

Todo erro tipado voltado a agentes carrega uma ação seguinte, em error.details.next_action. Ela existe para que um agente que nunca leu esta página saiba o que fazer em vez de adivinhar pelo texto da mensagem. Nas ferramentas do MCP o mesmo objeto chega em data.next_action. É por isso que o docs_url de todo erro aponta para cá.

Erro com next_action
{
  "error": {
    "code": "merchant_required",
    "message": "A merchant profile is required for this operation.",
    "request_id": "gru1::iad1::v9x4k-1751476800000-abc123",
    "docs_url": "https://depixapp.com/docs/en/#errors",
    "details": {
      "next_action": {
        "kind": "call_tool",
        "tool": "get_onboarding_status"
      }
    }
  }
}

kind é um conjunto fechado de cinco. Programe contra ele:

kindO que fazerCampos
call_toolChame a ferramenta indicada e siga o que ela devolver — o caminho é resolvível sem sair da conversa.tool
human_stepSó um humano destrava isto. Repasse o texto de relay e espere; repetir a chamada não muda nada.url, relay
http_callChame o endpoint indicado antes de tentar de novo. (Reservado — nenhum code produz hoje.)url
waitEspere e repita a MESMA chamada.retry_after_seconds
reconnectA credencial da conexão sumiu ou expirou — reconecte o conector e refaça a chamada. Não há chave nova para emitir.url
  • Uma ação por erro, sempre. Oferecer duas devolveria ao agente a escolha que este contrato existe para acabar.
  • relay — o texto pronto que o agente cola para o humano, em pt e en: no máximo 4 passos, sem jargão. Está presente exatamente quando kind é human_step, e nunca nos outros. Ele mora no MCP, não neste servidor: a fronteira anti-injeção do MCP deriva a mensagem só do code e descarta texto livre vindo daqui, então copy enviada por este servidor seria jogada fora.
  • wait traz retry_after_seconds como espelho do error.retry_after (e do header Retry-After), nunca uma terceira fonte: dois números diferentes para o mesmo prazo fariam o agente ou martelar porta fechada ou dormir além da reabertura.
  • Levam next_action os codes voltados ao dono da conta: credenciais, escada de verificação, domínio, bloqueios e limites de retentativa. As rotas do pagador não levam — quem paga é um estranho, e mandá-lo ao painel do lojista é o erro de categoria que o filtro existe para evitar.

Catálogo de codes

Os codes transversais — os que qualquer rota pode devolver, incluindo todos os que carregam next_action. Cada linha tem âncora estável no formato #error-<code> (ex.: #error-rate_limited). Erros específicos de um fluxo (tickets, cobranças, trilho DePix, assinatura de agente) estão na seção daquele fluxo.

CodeHTTPQuando
unauthorized401Rota exclusiva de login (JWT) sem token válido.
invalid_api_key401Token com prefixo sk_ não encontrado, revogado ou expirado.
invalid_token401JWT inválido/expirado ou header Authorization ausente/malformado.
invalid_operator_token401O código de operador (op_…) está ausente, malformado ou desconhecido. O humano pega o dele em https://api.depixapp.com/api/agents/oauth/start — a mesma página mostra sempre o mesmo código.
operator_token_revoked403Este código de operador foi revogado. Relogar não o reativa; só o suporte resolve.
insufficient_scope403A API key não tem o scope exigido pela operação — details.required_scope.
invalid_password401A senha da conta informada está incorreta.
password_required400A senha da conta é obrigatória para esta operação sensível.
agent_account_no_password403Contas de agente não têm senha — autentique com o par de chaves do agente.
oauth_account_not_linked403A identidade Google/GitHub não está vinculada a uma conta DePix. Vincule no app, em Agentes de IA, e tente de novo.
step_up_required403Conta que entra com Google/GitHub não tem senha, e esta operação sensível pedia senha. Refaça o login com o provedor em /api/auth/step-up/start e repita a chamada com o stepup_ref recém-emitido.
account_already_linked409A conta já está vinculada a outra identidade OAuth. Desvincule primeiro.
workos_identity_in_use409Esta identidade OAuth já está vinculada a outra conta DePix.
account_exists409Já existe conta para esta pessoa. Entre nela em vez de criar uma segunda.
email_in_use409Já existe conta para este e-mail. Entre nela e conecte esta identidade a ela.
registration_blocked403O cadastro não pode prosseguir.
operator_oauth_failed502Não foi possível verificar a identidade do operador com o provedor. Tente novamente.
account_blocked403Conta bloqueada (irmão legado blocked: true preservado).
account_suspended403Conta de agente pausada: as leituras seguem, as rotas que mudam algo não.
merchant_required403A conta autenticada não tem perfil de lojista ativo.
verification_required403A operação exige conta verificada — criar a loja é uma delas. GET /api/verification lista o que falta.
verification_requirements_not_met409A conta ainda não cumpre os requisitos: details.missing diz o que falta e details.remaining quanto.
verification_tax_number_in_use409Este CPF/CNPJ já verificou outra conta. Um documento verifica uma conta.
verification_unavailable503Verificação de conta temporariamente indisponível — retry_after: 300.
live_access_required403Criação de chave sk_live_ sem aprovação de acesso à produção.
graduation_pending403Recurso que só destrava depois da graduação da conta de agente: prove um domínio em POST /api/agents/verify-domain e acompanhe em GET /api/agents/status.
domain_required403Receber de terceiros exige domínio verificado — prove um em POST /api/agents/verify-domain.
domain_txt_not_found422O registro TXT do desafio não apareceu ou não confere. Crie o registro e repita depois da propagação.
whatsapp_verification_required403WhatsApp do dono não verificado quando o operador exige verificação.
withdraw_disabled403Saques temporariamente desativados (kill switch global).
external_wallet_disabled403Saques para wallet externa temporariamente desativados.
first_withdraw_tax_number_mismatch403Enquanto a conta não tiver nenhum saque concluído (status sent), todo saque precisa ir para o mesmo CPF/CNPJ que pagou o primeiro depósito concluído da conta. Depois desse primeiro saque, a conta saca para qualquer chave Pix. Conta sem depósito concluído com documento não fica travada, e chaves sk_test_ (sandbox) são isentas. details.anchor_tax_number traz o documento esperado, mascarado.
sandbox_only403simulate-payment chamado em checkout live.
validation_error400Input inválido — details.field quando aplicável; o criar checkout preserva response.errors[].
tax_number_required400CPF/CNPJ obrigatório ausente (payer_tax_number / taxNumber).
amount_out_of_range400Valor fora dos limites do endpoint — details: { min_cents, max_cents } com os bounds do próprio endpoint/modo.
account_limit_exceeded400Limite de conta atingido. details.limit diz qual: "receive_cap" — o teto de recebimento da janela móvel, com { current_level, cap_cents, used_cents, amount_cents, window_days, resets_at, next_level_requirements, kyc_url }; "first_deposit" — a conta ainda não concluiu nenhum depósito pessoal; "per_tx" — teto legado por transação, aplicado só enquanto o teto por nível não estiver ativo. ("cumulative", o teto vitalício, foi removido em 31/07/2026.) Não dependem de a conta ser verificada: verificar libera as ferramentas de lojista e não altera limite nenhum. Ver Limites.
key_limit_exceeded400Limite de gasto da API key — details: { limit: "per_tx" | "daily", limit_cents, used_cents }.
provider_refused400O provedor de liquidação analisou esta cobrança e recusou (pagador em revisão de compliance, endereço bloqueado, split rejeitado). O motivo dele vem em response.errorMessage. É terminal: não repita a chamada. Não confunda com 503 service_unavailable, que significa provedor fora do ar ou sem resposta — esse sim vale repetir. Até 04/08/2026 os dois casos vinham como aquele 503, então a recusa chegava com a instrução errada.
not_found404Rota ou recurso inexistente — inclui recurso de outra conta (ownership nunca é revelado).
conflict409Conflito de estado: transição de checkout inválida, txid duplicado, slug duplicado.
agent_pubkey_exists409Esta chave pública já pertence a uma conta de agente.
username_taken409Nome de usuário já em uso. Escolha outro ou omita para receber o padrão.
idempotency_in_flight409Request com a mesma Idempotency-Key ainda em execução — retry_after: 5.
idempotency_key_reuse422Idempotency-Key reutilizada com um body diferente.
rate_limited429Rate limit por IP, por usuário ou por chave — retry_after até 60.
merchant_rate_limited429Budget agregado do merchant excedido — retry_after até 60.
payer_velocity_limit429Muitas transações para o mesmo CPF/CNPJ do pagador em pouco tempo (máx. 2 por janela deslizante de 30 min; depósitos + checkouts combinados) — details: { window_minutes, max_per_window }, retry_after até o restante da janela.
operator_register_cap_exceeded429Contas-agente demais registradas sob o mesmo código de operador (op_…) dentro de uma janela deslizante — details: { max_per_window, window_hours }, retry_after até a mais antiga sair da janela. Bater nele sem ter criado essas contas significa que o código vazou; só o suporte revoga.
platform_shutdown503Plataforma em manutenção (kill switch global) — retry_after: 300.
agents_disabled503Programa de agentes desligado globalmente (kill switch) — retry_after: 3600. É pausa da plataforma inteira, nunca suspensão da sua conta.
service_unavailable503Dependência de infra indisponível — inclui o fail-closed de rotas wallet_* via API key quando o rate limit não pode ser checado — retry_after: 30. Também é a resposta quando o provedor de liquidação não responde a tempo ao criar um Pix (POST /api/deposit e criação de checkout): repetir vale a pena. Em POST /api/deposit esse caso era 500 até 04/08/2026.
upstream_error502Resposta malformada ou erro do provedor Pix.
internal_error500Erro interno inesperado. Tente novamente em alguns instantes.

Criar checkout

Cria um novo checkout Pix. Retorna o QR code e a URL de pagamento para exibir ao seu cliente.

POST /api/checkouts

Parâmetros

CampoTipoDescrição
amountintegerobrigatórioValor em centavos. Mínimo: 500 (R$ 5,00). Máximo: 600000 (R$ 6.000,00). No trilho depix é o valor de face, antes do desconto do lojista.
payer_tax_numberstringobrigatórioCPF ou CNPJ do pagador. Aceita CPF (11 dígitos) ou CNPJ (14 caracteres, inclusive o novo formato alfanumérico), com ou sem máscara. Deve ser um CPF/CNPJ real e registrado — o processador de pagamentos valida além do dígito verificador ao gerar o QR. Obrigatório apenas no trilho pix; no trilho depix é ignorado.
payment_methodstringopcionalpix (padrão) ou depix. Com depix a cobrança é paga direto em DePix na rede Liquid, sem QR Pix — ver Receber DePix direto. Se o lojista não tiver o recebimento direto ativado, a criação falha com depix_not_enabled (400).
expected_discount_pctintegeropcionalSó no trilho depix: o desconto (0–90) que a sua página mostrou ao cliente. Se o lojista tiver mudado o desconto nesse meio-tempo, a criação falha com discount_changed (409) trazendo os valores atuais, em vez de cobrar um preço diferente do exibido.
descriptionstringopcionalDescrição do pedido. Máximo 500 caracteres. Exibida na página de pagamento.
expires_inintegeropcionalTempo de expiração em segundos. Trilho pix: padrão 1200 (20min), mínimo 300 (5min), máximo 1200 (20min). Trilho depix: padrão 1800 (30min), mínimo 300 (5min), máximo 3600 (1h).
image_urlstringopcionalURL HTTPS da imagem do produto. Exibida na página de pagamento.
callback_urlstringopcionalURL HTTPS que recebe os webhooks do checkout.
redirect_urlstringopcionalURL para redirecionar o cliente após o pagamento.
metadataobjectopcionalDados adicionais do seu sistema (order_id, user_id, etc.). Máximo 4KB. Devolvido nos webhooks.
CPF/CNPJ real e registrado. O processador de pagamentos valida o payer_tax_number além do dígito verificador ao gerar o QR. Um número com checksum válido mas não registrado falha na criação com o erro genérico "Error generating QR Code. Please contact an admin." — se você receber esse erro ao criar, o CPF/CNPJ do pagador quase certamente não é um número real e registrado.

Exemplo

curl -X POST https://api.depixapp.com/api/checkouts \
  -H "Authorization: Bearer $DEPIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 2990,
    "payer_tax_number": "529.982.247-25",
    "description": "Camiseta tamanho M",
    "expires_in": 900,
    "callback_url": "https://minha-loja.com/webhook/depix",
    "metadata": { "order_id": "ORD-123" }
  }'
const res = await fetch("https://api.depixapp.com/api/checkouts", {
  method: "POST",
  headers: {
    "Authorization": "Bearer sk_live_<sua-chave>",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    amount: 2990,
    payer_tax_number: "529.982.247-25",
    description: "Camiseta tamanho M",
    expires_in: 900,
    callback_url: "https://minha-loja.com/webhook/depix",
    metadata: { order_id: "ORD-123" },
  }),
});
const data = await res.json();
console.log(data.id, data.payment_url);
import requests

resp = requests.post(
    "https://api.depixapp.com/api/checkouts",
    headers={"Authorization": "Bearer sk_live_<sua-chave>"},
    json={
        "amount": 2990,
        "payer_tax_number": "529.982.247-25",
        "description": "Camiseta tamanho M",
        "expires_in": 900,
        "callback_url": "https://minha-loja.com/webhook/depix",
        "metadata": {"order_id": "ORD-123"},
    },
)
data = resp.json()
print(data["id"], data["payment_url"])
$ch = curl_init("https://api.depixapp.com/api/checkouts");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer sk_live_<sua-chave>",
        "Content-Type: application/json",
    ],
    CURLOPT_POSTFIELDS => json_encode([
        "amount" => 2990,
        "payer_tax_number" => "529.982.247-25",
        "description" => "Camiseta tamanho M",
        "expires_in" => 900,
        "callback_url" => "https://minha-loja.com/webhook/depix",
        "metadata" => ["order_id" => "ORD-123"],
    ]),
]);
$response = curl_exec($ch);
$data = json_decode($response, true);
echo $data["id"] . " " . $data["payment_url"];
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer sk_live_<sua-chave>");

var payload = new {
    amount = 2990,
    payer_tax_number = "529.982.247-25",
    description = "Camiseta tamanho M",
    expires_in = 900,
    callback_url = "https://minha-loja.com/webhook/depix",
    metadata = new { order_id = "ORD-123" }
};

var res = await client.PostAsync(
    "https://api.depixapp.com/api/checkouts",
    new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json")
);
var json = await res.Content.ReadAsStringAsync();
Console.WriteLine(json);
body := `{"amount":2990,"payer_tax_number":"529.982.247-25","description":"Camiseta tamanho M","expires_in":900,"callback_url":"https://minha-loja.com/webhook/depix","metadata":{"order_id":"ORD-123"}}`

req, _ := http.NewRequest("POST", "https://api.depixapp.com/api/checkouts", strings.NewReader(body))
req.Header.Set("Authorization", "Bearer sk_live_<sua-chave>")
req.Header.Set("Content-Type", "application/json")

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
io.Copy(os.Stdout, resp.Body)
require "net/http"
require "json"

uri = URI("https://api.depixapp.com/api/checkouts")
req = Net::HTTP::Post.new(uri, {
  "Authorization" => "Bearer sk_live_<sua-chave>",
  "Content-Type" => "application/json",
})
req.body = { amount: 2990, payer_tax_number: "529.982.247-25", description: "Camiseta tamanho M", expires_in: 900,
             callback_url: "https://minha-loja.com/webhook/depix",
             metadata: { order_id: "ORD-123" } }.to_json

res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
puts JSON.parse(res.body)
HttpClient client = HttpClient.newHttpClient();
String json = """
    {"amount":2990,"payer_tax_number":"529.982.247-25","description":"Camiseta tamanho M","expires_in":900,
     "callback_url":"https://minha-loja.com/webhook/depix",
     "metadata":{"order_id":"ORD-123"}}""";

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.depixapp.com/api/checkouts"))
    .header("Authorization", "Bearer sk_live_<sua-chave>")
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(json))
    .build();

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
Resposta — 201 Created
{
  "id":          "chk_01jxxxxxxxxxxxxxxxxxxxxxx",
  "status":      "pending",
  "amount":      2990,
  "description": "Camiseta tamanho M",
  "image_url":   null,
  "expires_at":  "2025-06-01T15:30:00.000Z",
  "is_live":     true,
  "payment_url": "https://pay.depixapp.com/chk_01jxxxxxxxxxxxxxxxxxxxxxx",
  "pix": {
    "qr_code": "00020126580014br.gov.bcb.pix..."    // payload EMV para QR code
  }
}

Cobrar direto em DePix: envie "payment_method": "depix" no mesmo endpoint. A resposta troca o bloco pix por um bloco depix com endereço, valor exato e link de pagamento — ver Receber DePix direto.

Formato da resposta: o POST /api/checkouts devolve o checkout plano, na raiz do JSON (como acima). Já o GET /api/checkouts/:id devolve o mesmo objeto embrulhado em { "checkout": { ... } } — ver Consultar checkout.

💡 Exiba o payment_url ao seu cliente ou gere um QR code a partir do pix.qr_code. O QR code é compatível com qualquer app de banco.

Consultar checkout

Retorna os detalhes de um checkout específico.

GET /api/checkouts/:id
curl
curl https://api.depixapp.com/api/checkouts/chk_01jxxxxxxxxxxxxxxxxxxxxxx \
  -H "Authorization: Bearer $DEPIX_API_KEY"
Resposta — 200 OK
{
  "checkout": {
    "id":             "chk_01jxxxxxxxxxxxxxxxxxxxxxx",
    "status":         "completed",     // pending | processing | approved | completed | cancelled | expired
    "amount":         2990,
    "description":    "Camiseta tamanho M",
    "image_url":      null,
    "callback_url":   "https://minha-loja.com/webhook/depix",
    "redirect_url":   null,
    "metadata":       { "order_id": "ORD-123" },
    "expires_at":     "2025-06-01T15:30:00.000Z",      // todas as datas em UTC, formato RFC 3339
    "is_live":        1,
    "created_at":     "2025-06-01T15:00:00.000Z",
    "processing_at":  "2025-06-01T15:02:00.000Z",
    "approved_at":    "2025-06-01T15:03:00.000Z",
    "completed_at":   "2025-06-01T15:22:00.000Z",
    "cancelled_at":   null,
    "blockchain_tx_id": "abc123...def456",  // txid Liquid (presente quando completed)
    "rejection_reasons": [],        // array de motivos quando o pagamento foi devolvido/retido
    "delay_until":     null,        // quando o dinheiro é liberado, se a venda estiver retida
    "vault_hours":     0            // horas que essa venda ficou marcada para esperar (0 = nenhuma; null = sem registro)
  }
}

Formato da resposta: aqui o checkout vem embrulhado em { "checkout": { ... } }, enquanto o POST /api/checkouts devolve o objeto plano na raiz do JSON — atenção ao parsear os dois.

Enquanto o checkout está pending, a resposta também traz pix_payload (o payload EMV do QR Pix); o campo é omitido assim que o status sai de pending. approved_at está sempre presente (null até a aprovação).

Status possíveis

StatusSignificado
pendingAguardando pagamento.
processingO dinheiro do pagador chegou até nós. Pode ficar aqui de segundos a dias — ver "Venda paga que ainda não caiu" abaixo.
approvedPagamento aprovado pelo banco, aguardando liquidação em DePix.
completedPagamento confirmado. DePix na carteira do merchant.
cancelledCancelado/estornado pelo provedor Pix.
expiredPrazo de pagamento expirou.

Venda paga que ainda não caiu

processing quer dizer que o dinheiro do pagador chegou até nós. Antes de cair na sua carteira, ele pode ficar um tempo guardado no Cofre — a proteção que segura o valor por até 14 dias para o caso de o pagamento ser contestado.

Liberar o produto ou serviço em processing é decisão sua. Quem vende conteúdo digital costuma liberar nessa hora; quem envia produto físico ou vende valores altos costuma esperar o completed.

Dois campos contam essa história:

  • delay_until — a data em que o dinheiro cai. É a única data que vale; não calcule created_at + vault_hours. Vem null se a venda não foi retida, e também enquanto a data ainda não chegou do provedor.
  • vault_hours — quantas horas de espera a venda recebeu ao ser criada. 0 = sem espera; null = sem registro (venda de sandbox ou venda paga em DePix).

O prazo diminui conforme a conta ganha tempo de uso e valor recebido:

Momento da contaEspera
1ª vendaAté R$ 100: cai na hora. Acima disso: a espera do nível, abaixo.
2ª à 5ª vendaAté R$ 100: 24 horas. Acima disso: a espera do nível, abaixo.
Da 6ª em dianteVer abaixo. Vendas de até R$ 100 continuam caindo na hora, até somar R$ 500 a cada 14 dias; passando disso, esperam também.
Nível da contaEspera
Nível 014 dias
Nível 110 dias
Nível 27 dias
Nível 34 dias
Nível 43 dias

A tabela é o caso normal. O prazo que vale para cada venda é sempre o delay_until dela.

O sandbox não simula o Cofre: lá o checkout vai de pending direto para completed e nunca passa por processing. Em produção, a mesma venda pode ficar dias em processing.

Motivos de devolução (rejection_reasons)

Quando o pagamento de um checkout é devolvido ou retido pelo provedor, o campo rejection_reasons traz um array com os motivos ([] quando não houve recusa). Novos códigos podem surgir — trate valores desconhecidos de forma genérica.

CódigoSignificado
PAYER_MISMATCHPagamento feito com CPF/CNPJ diferente do informado no checkout.
PAST_DAILY_LIMITLimite diário do pagador excedido.
BLOCKED_USERUsuário bloqueado pelo provedor.
HIGH_VELOCITYMuitas transações do pagador em pouco tempo.

Listar checkouts

Lista os checkouts do merchant com filtros e paginação.

GET /api/checkouts

Query params (todos opcionais)

ParâmetroDescrição
statusFiltrar por status: pending, processing, approved, completed, cancelled, expired.
product_idFiltrar por produto. Ex: prd_xxx.
fromData de início (ISO 8601). Ex: 2025-06-01T00:00:00Z.
toData de fim (ISO 8601).
qBusca por ID ou descrição.
limitNúmero de resultados por página. Padrão: 50. Máximo: 100.
offsetPaginação. Padrão: 0.
curl
curl "https://api.depixapp.com/api/checkouts?status=completed&limit=20" \
  -H "Authorization: Bearer $DEPIX_API_KEY"
Resposta — 200 OK
{
  "checkouts": [
    {
      "id":            "chk_01jxxxxxxxxxxxxxxxxxxxxxx",
      "status":        "completed",
      "amount":        2990,
      "description":   "Camiseta tamanho M",
      "product_name":  "Camiseta Preta",    // null se checkout não vier de um produto
      "metadata":      "{\"order_id\":\"42\"}",  // string JSON, null se ausente
      "created_at":    "2025-06-01T15:00:00.000Z",   // todas as datas em UTC, formato RFC 3339
      "processing_at": "2025-06-01T15:02:14.000Z",
      "approved_at":   "2025-06-01T15:03:00.000Z",
      "expires_at":    "2025-06-01T15:30:00.000Z",
      "is_live":       1,
      "payment_method": "depix",        // "pix" ou "depix" — a trilha em que a venda foi liquidada
      "depix_discount_pct": 10,       // só na trilha depix: desconto oferecido, em %
      "depix_due_cents": 2691,       // só na trilha depix: o valor que o pagador realmente envia
      "rejection_reasons": [],    // array de motivos quando o pagamento foi devolvido/retido
      "delay_until":   null,          // quando o dinheiro é liberado, se a venda estiver retida
      "vault_hours":   0              // horas que essa venda ficou marcada para esperar (0 = nenhuma; null = sem registro)
    }
  ],
  "stats": {
    "total":            47,
    "pending":          2,
    "completed":        40,
    "completed_amount":  189500    // centavos — R$ 1.895,00
  },
  "limit":  20,
  "offset": 0
}
Conciliando vendas em DePix: amount é sempre o preço de tabela. Quando payment_method é "depix", o valor que o cliente realmente envia é depix_due_cents — o preço com o desconto aplicado e ajustado em alguns centavos para baixo, que é como reconhecemos qual pagamento é de qual venda. Somar amount em vendas com desconto superestima cada uma delas. Os dois campos depix_* só aparecem na trilha depix; numa venda em Pix eles não vêm.
Vendas pagas que ainda não caíram: ficam em processing e trazem delay_until e vault_hours — o que cada campo significa está em "Venda paga que ainda não caiu", na seção Consultar checkout. Para conciliar "quanto vendi mas ainda não recebi", liste com ?status=processing e some o amount das linhas com delay_until ou vault_hours maior que zero. A lista é paginada (limit vai até 100): compare com stats.total, que conta o filtro inteiro, ou pegue as próximas páginas por offset — somar uma página só deixa a conciliação menor que a realidade.

Criar produto

Cria um novo produto com valor fixo. Cada produto gera um link de pagamento permanente que pode ser compartilhado com seus clientes.

POST /api/products

Parâmetros

CampoTipoDescrição
namestringobrigatórioNome do produto exibido na UI. 2-80 caracteres.
slugstringopcionalIdentificador na URL. Se omitido, é gerado automaticamente a partir do name. Letras minúsculas, números e hífens. 2-60 caracteres. Não pode começar/terminar com hífen.
amountintegerobrigatórioValor em centavos. Mínimo: 500. Máximo: 600000.
descriptionstringopcionalDescrição do produto. Máximo 500 caracteres.
image_urlstringopcionalURL HTTPS da imagem do produto.
callback_urlstringopcionalURL HTTPS para webhooks. Sobrescreve o default do merchant.
redirect_urlstringopcionalURL de redirecionamento. Sobrescreve o default do merchant.
metadataobjectopcionalDados adicionais. Máximo 4KB. Incluído nos webhooks dos checkouts gerados.
expires_inintegeropcionalTempo de expiração dos checkouts em segundos. Padrão: 1200 (20min). Mínimo: 300 (5min). Máximo: 1200 (20min).
kindstringopcionalproduct (padrão) ou charge. Uma cobrança (charge) é um link de pagamento com vencimento e encargos, servido em pay.depixapp.com/c/{id} — nunca aparece na loja pública, no carrinho, na vitrine nem na listagem padrão de produtos. Imutável depois de criado.
due_datestringcobrançaObrigatório quando kind=charge. Primeiro vencimento, YYYY-MM-DD. É a âncora da recorrência. Pode estar no passado — uma cobrança retroativa nasce vencida, que é o caso de quem cobra o mês anterior.
recurrencestringcobrançanull (cobrança única) ou weekly, monthly, quarterly, semiannual, yearly. Mensal e acima ancoram no dia do vencimento, ajustando para o último dia em meses mais curtos (dia 31 → 28/29 em fevereiro).
late_fine_bpsintegercobrançaMulta única por atraso, em basis points do valor base (200 = 2%). Padrão: 0. Máximo: 2000 (20%).
late_interest_monthly_bpsintegercobrançaJuros ao mês em basis points (100 = 1% a.m.), aplicados pro-rata por dia de atraso. Padrão: 0. Máximo: 1000 (10% a.m.).

Exemplo

curl -X POST https://api.depixapp.com/api/products \
  -H "Authorization: Bearer $DEPIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Camiseta M",
    "slug": "camiseta-m",
    "amount": 2990,
    "description": "Camiseta tamanho M"
  }'
const res = await fetch("https://api.depixapp.com/api/products", {
  method: "POST",
  headers: {
    "Authorization": "Bearer sk_live_<sua-chave>",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "Camiseta M",
    slug: "camiseta-m",
    amount: 2990,
    description: "Camiseta tamanho M",
  }),
});
const data = await res.json();
console.log(data.product.payment_url);
import requests

resp = requests.post(
    "https://api.depixapp.com/api/products",
    headers={"Authorization": "Bearer sk_live_<sua-chave>"},
    json={
        "name": "Camiseta M",
        "slug": "camiseta-m",
        "amount": 2990,
        "description": "Camiseta tamanho M",
    },
)
data = resp.json()
print(data["product"]["payment_url"])
$ch = curl_init("https://api.depixapp.com/api/products");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer sk_live_<sua-chave>",
        "Content-Type: application/json",
    ],
    CURLOPT_POSTFIELDS => json_encode([
        "name" => "Camiseta M",
        "slug" => "camiseta-m",
        "amount" => 2990,
        "description" => "Camiseta tamanho M",
    ]),
]);
$response = curl_exec($ch);
$data = json_decode($response, true);
echo $data["product"]["payment_url"];
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer sk_live_<sua-chave>");

var payload = new { name = "Camiseta M", slug = "camiseta-m", amount = 2990, description = "Camiseta tamanho M" };

var res = await client.PostAsync(
    "https://api.depixapp.com/api/products",
    new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json")
);
Console.WriteLine(await res.Content.ReadAsStringAsync());
body := `{"name":"Camiseta M","slug":"camiseta-m","amount":2990,"description":"Camiseta tamanho M"}`

req, _ := http.NewRequest("POST", "https://api.depixapp.com/api/products", strings.NewReader(body))
req.Header.Set("Authorization", "Bearer sk_live_<sua-chave>")
req.Header.Set("Content-Type", "application/json")

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
io.Copy(os.Stdout, resp.Body)
require "net/http"
require "json"

uri = URI("https://api.depixapp.com/api/products")
req = Net::HTTP::Post.new(uri, {
  "Authorization" => "Bearer sk_live_<sua-chave>",
  "Content-Type" => "application/json",
})
req.body = { name: "Camiseta M", slug: "camiseta-m", amount: 2990, description: "Camiseta tamanho M" }.to_json

res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
puts JSON.parse(res.body)
HttpClient client = HttpClient.newHttpClient();
String json = """
    {"name":"Camiseta M","slug":"camiseta-m","amount":2990,"description":"Camiseta tamanho M"}""";

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.depixapp.com/api/products"))
    .header("Authorization", "Bearer sk_live_<sua-chave>")
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(json))
    .build();

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
Resposta — 201 Created
{
  "product": {
    "id":           "prd_xxx",
    "name":         "Camiseta M",
    "slug":         "camiseta-m",
    "amount":       2990,
    "description":  "Camiseta tamanho M",
    "image_url":    null,
    "callback_url": null,
    "redirect_url": null,
    "metadata":     null,
    "expires_in":   1200,
    "active":       true,
    "is_live":      true,
    "payment_url":  "https://pay.depixapp.com/joao/camiseta-m"
  }
}

Listar produtos

Lista os produtos do merchant com filtros e paginação.

GET /api/products

Query params (todos opcionais)

ParâmetroDescrição
kindFiltrar por tipo: product (padrão), charge ou all. O padrão garante que cobranças nunca apareçam em integrações escritas antes delas; use charge para listar cobranças (cada linha traz charge_state).
activeFiltrar por status: 1 (ativos) ou 0 (inativos).
qBusca por nome, slug ou descrição.
limitNúmero de resultados. Padrão: 50. Máximo: 100.
offsetPaginação. Padrão: 0.
curl
curl "https://api.depixapp.com/api/products?active=1" \
  -H "Authorization: Bearer $DEPIX_API_KEY"
Resposta — 200 OK
{
  "products": [
    {
      "id":          "prd_xxx",
      "name":        "Camiseta M",
      "slug":        "camiseta-m",
      "amount":      2990,
      "description": "Camiseta tamanho M",
      "active":      1,
      "is_live":     1,
      "position":    0,
      "created_at":  "2025-06-01T15:00:00.000Z",  // todas as datas em UTC, formato RFC 3339
      "total_checkouts":     12,
      "completed_checkouts": 5,
      "completed_amount":    14950,
      "settled_count":       5,
      "processing_count":    0
    }
  ],
  "limit":  50,
  "offset": 0
}

position — inteiro ou null. Ordem de exibição na vitrine pública. null = não fixado (ordenado por mais vendidos); inteiro = fixado, exibido na posição indicada (menor primeiro).

payment_url — só em linhas de cobrança (kind=charge). Para produtos o link é https://pay.depixapp.com/{merchant_slug}/{slug}, devolvido pronto na criação (201).

Consultar produto

Retorna os detalhes de um produto específico, incluindo estatísticas de checkouts.

GET /api/products/:id
curl
curl https://api.depixapp.com/api/products/prd_xxx \
  -H "Authorization: Bearer $DEPIX_API_KEY"
Resposta — 200 OK
{
  "product": {
    "id":           "prd_xxx",
    "name":         "Camiseta M",
    "slug":         "camiseta-m",
    "amount":       2990,
    "description":  "Camiseta tamanho M",
    "image_url":    null,
    "callback_url": null,
    "redirect_url": null,
    "metadata":     null,
    "expires_in":   1200,
    "active":       1,
    "is_live":      1,
    "position":     0,
    "created_at":   "2025-06-01T15:00:00.000Z"   // todas as datas em UTC, formato RFC 3339
  },
  "stats": {
    "total":            12,
    "completed":        5,
    "pending":          1,
    "completed_amount": 14950
  }
}

position — inteiro ou null. Ordem de exibição na vitrine pública. null = não fixado (ordenado por mais vendidos); inteiro = fixado, exibido na posição indicada (menor primeiro).

Editar produto

Atualiza um ou mais campos de um produto existente. Envie apenas os campos que deseja alterar.

PATCH /api/products/:id

Parâmetros (todos opcionais)

CampoTipoDescrição
namestringNovo nome do produto. 2-80 caracteres.
slugstringNovo identificador na URL. Mesmas regras da criação.
amountintegerNovo valor em centavos. Mínimo: 500. Máximo: 600000.
descriptionstringNova descrição.
image_urlstringNova URL de imagem.
callback_urlstringNova URL de webhook.
redirect_urlstringNova URL de redirecionamento.
metadataobjectNovos dados adicionais.
expires_inintegerNovo tempo de expiração dos checkouts. Mínimo: 300 (5min). Máximo: 1200 (20min).
curl
curl -X PATCH https://api.depixapp.com/api/products/prd_xxx \
  -H "Authorization: Bearer $DEPIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "amount": 3490, "description": "Camiseta tamanho M - Edição Especial" }'
Resposta — 200 OK
{ "success": true }

A resposta não devolve o produto — consulte Consultar produto para ler o estado atualizado.

Ativar / Desativar produto

Ativa ou desativa um produto. Produtos inativos retornam erro 404 quando acessados pelo link de pagamento.

POST /api/products/:id/activate
POST /api/products/:id/deactivate
curl — ativar
curl -X POST https://api.depixapp.com/api/products/prd_xxx/activate \
  -H "Authorization: Bearer $DEPIX_API_KEY"
curl — desativar
curl -X POST https://api.depixapp.com/api/products/prd_xxx/deactivate \
  -H "Authorization: Bearer $DEPIX_API_KEY"
Resposta — 200 OK
{ "success": true }

Checkouts do produto

Lista os checkouts gerados a partir de um produto específico. Aceita os mesmos filtros da listagem geral de checkouts.

GET /api/products/:id/checkouts
curl
curl "https://api.depixapp.com/api/products/prd_xxx/checkouts?status=completed" \
  -H "Authorization: Bearer $DEPIX_API_KEY"

A resposta segue o mesmo formato da listagem de checkouts.

Cobranças

Uma cobrança é um produto com kind=charge: o mesmo endpoint, o mesmo checkout e os mesmos webhooks, com três diferenças — tem data de vencimento, pode ter multa e juros por atraso, e vive num link privado que nunca aparece na sua loja pública. É o formato certo para aluguel, mensalidade, parcela — qualquer valor que vence numa data e pode atrasar.

Crie com POST /api/products passando kind: "charge" e due_date; liste com GET /api/products?kind=charge. O link vem em payment_url no formato https://pay.depixapp.com/c/prd_xxx — endereçado por id (não por slug), com noindex, e a pré-visualização em apps de mensagem mostra o nome da sua loja e o título da cobrança, nunca o valor. Os demais endpoints de produto (GET, PATCH, ativar/desativar, checkouts do produto) funcionam em cobranças sem nenhuma mudança.

Se você tiver o recebimento direto em DePix ativado, a página da cobrança também oferece Pagar com DePix, ao lado do Pix — mesmo fluxo das páginas de loja e de produto. O valor cobrado no trilho DePix é o total do dia (valor original + multa + juros), com o seu desconto aplicado sobre esse total, e a quitação entra na fila da cobrança igual a um pagamento Pix. Nesse trilho não se pede CPF/CNPJ do pagador.

Como o valor é calculado

O valor não é fixo: ele é calculado no momento em que o pagador abre o link e gera o QR.

Fórmula
dias_de_atraso = dias corridos após o vencimento (fuso America/Sao_Paulo)
multa          = valor_base × late_fine_bps / 10000              // uma vez só
juros          = valor_base × late_interest_monthly_bps / 10000 × dias_de_atraso / 30
total          = min(valor_base + multa + juros, 600000)          // teto por transação

O dia do vencimento não conta como atraso — o atraso começa no dia seguinte. Não há adiamento para fim de semana ou feriado: o Pix funciona 24/7. Os juros são lineares (pro-rata por dia), nunca compostos. Não há correção monetária.

Recorrência e quitação (FIFO)

Com recurrence, a cobrança vira uma série de vencimentos ancorada em due_date, e o mesmo link serve para sempre. Cada pagamento quita a competência mais antiga em aberto; a próxima visita já mostra a seguinte. A competência quitada por cada checkout vai em metadata.charge_cycle, e o webhook traz data.product_id — juntos, dizem exatamente qual mês foi pago.

Duas regras que decidem qual competência um QR cobra, e valem a leitura antes de integrar:

  • Um QR em aberto reserva a competência dele. O valor e a competência são congelados quando o QR é criado, mas a posição na série é resolvida na liquidação. Se dois QRs vivos fossem precificados como a mesma competência e ambos fossem pagos, o segundo pagador levaria a multa e os juros de uma competência que não estava atrasada. Por isso um QR ainda válido conta para a posição: o próximo QR já sai precificado na competência que ele realmente vai quitar. QRs expirados não contam.
  • A posição nunca anda para trás. Conta-se competência que já liquidou alguma vez. Um estorno (MED, reembolso por divergência de CPF) portanto não faz o link recobrar uma competência já paga nem aplicar os encargos do ciclo errado — e também não recobra sozinho a competência estornada. Isso é decisão do lojista: você recebe checkout.cancelled e vê a venda cancelada, e decide o que fazer.

charge_state

Presente em cada linha de GET /api/products?kind=charge e na resposta 201 do checkout de uma cobrança.

Exemplo — cobrança 10 dias em atraso
{
  "settled":              false,      // true = cobrança única já paga (demais campos ausentes)
  "cycle_due_date":       "2026-08-05",  // competência corrente (mais antiga em aberto)
  "days_late":            10,
  "base_cents":           250000,
  "fine_cents":           5000,
  "interest_cents":       833,
  "total_today_cents":    255833,   // o que um QR criado agora cobra
  "capped":               false,      // true = base + encargos passou do teto e foi limitado
  "status":               "late",      // late | due_today | upcoming
  "open_past_due_cycles": 1,         // > 1 = competências acumuladas
  "in_flight":            false       // um Pix pago desta cobrança está liquidando
}

Erros específicos

CódigoHTTPQuando
charge_already_paid409Cobrança única já quitada — não há competência a pagar.
charge_payment_in_progress409Já existe um Pix pago desta cobrança em processamento. Gerar outro QR agora viraria pagamento em dobro.
charge_payment_pending409Um QR ainda válido já reserva a única competência restante (cobrança única). Não é o mesmo que "já foi paga": ninguém pagou nada ainda.
Encargos são sua configuração e sua responsabilidade. O DePix App aplica a fórmula que você definir e valida apenas limites de sanidade (multa até 20%, juros até 10% a.m.). Não prestamos consultoria jurídica sobre o que seu contrato permite cobrar.

Produto público

Retorna os dados públicos de um produto ativo. Não requer autenticação.

GET /api/products/:id/public
curl
curl https://api.depixapp.com/api/products/prd_xxx/public
Resposta — 200 OK
{
  "product": {
    "id":          "prd_xxx",
    "name":        "Camiseta M",
    "slug":        "camiseta-m",
    "amount":      2990,
    "description": "Camiseta tamanho M",
    "image_url":   null
  },
  "merchant": {
    "name":          "Loja do Joao",
    "merchant_slug": "joao",
    "username":      "joao"
  }
}

Checkout do produto

Cria um checkout a partir de um produto ativo. Não requer autenticação. O valor é herdado do produto.

POST /api/products/:id/checkout

Parâmetros

CampoTipoDescrição
payer_tax_numberstringobrigatórioCPF ou CNPJ de quem paga o Pix, com ou sem máscara. Deve ser um CPF/CNPJ real e registrado — o processador de pagamentos valida além do dígito verificador ao gerar o QR. Obrigatório apenas no trilho pix.
payment_methodstringopcionalpix (padrão) ou depix — ver Receber DePix direto. No trilho depix não se envia CPF/CNPJ.
expected_discount_pctintegeropcionalSó no trilho depix: o desconto (0–90) que a sua página mostrou. Divergiu do atual? discount_changed (409).
curl
curl -X POST https://api.depixapp.com/api/products/prd_xxx/checkout \
  -H "Content-Type: application/json" \
  -d '{ "payer_tax_number": "52998224725" }'

A resposta segue o mesmo formato do criar checkout (status 201).

🛈 Os erros aqui são escritos para quem paga. Quem chama este endpoint é um terceiro anônimo, não o lojista. Uma recusa só mantém a mensagem detalhada e o error.details quando ela fala de quem paga, do que a pessoa digitou ou do que está sendo pago — que é o caso de todos os códigos listados acima. Recusas que pertencem à conta do lojista (limite de recebimento, pendências de cadastro, configuração da conta) respondem uma mensagem neutra e sem error.details. O error.code continua sendo o real. Se a sua integração precisa dos números, faça a chamada autenticada como lojista em POST /api/checkouts.

Página do merchant

Retorna os dados públicos do merchant. Não requer autenticação.

GET /api/merchants/:username/public
curl
curl https://api.depixapp.com/api/merchants/joao/public
Resposta — 200 OK
{
  "merchant": {
    "name":          "Loja do Joao",
    "merchant_slug": "joao",
    "username":      "joao"
  }
}

Checkout do merchant

Cria um checkout com valor customizado a partir da página do merchant. Não requer autenticação.

POST /api/merchants/:username/checkout

Parâmetros

CampoTipoDescrição
amountintegerobrigatórioValor em centavos. Mínimo: 500. Máximo: 600000.
payer_tax_numberstringobrigatórioCPF ou CNPJ de quem paga o Pix, com ou sem máscara. Deve ser um CPF/CNPJ real e registrado — o processador de pagamentos valida além do dígito verificador ao gerar o QR. Obrigatório apenas no trilho pix.
payment_methodstringopcionalpix (padrão) ou depix — ver Receber DePix direto. No trilho depix não se envia CPF/CNPJ.
expected_discount_pctintegeropcionalSó no trilho depix: o desconto (0–90) que a sua página mostrou. Divergiu do atual? discount_changed (409).
curl
curl -X POST https://api.depixapp.com/api/merchants/joao/checkout \
  -H "Content-Type: application/json" \
  -d '{ "amount": 5000, "payer_tax_number": "52998224725" }'

A resposta segue o mesmo formato do criar checkout (status 201).

🛈 Os erros aqui são escritos para quem paga. Mesma regra do checkout do produto: recusas que pertencem à conta do lojista respondem uma mensagem neutra e sem error.details. Os números completos só saem na chamada autenticada, em POST /api/checkouts.

Receber DePix direto (Liquid)

Todo checkout pode ser cobrado por um de dois trilhos, escolhido no campo payment_method. O padrão (pix) é o QR Pix de sempre. O alternativo (depix) cobra direto em DePix: quem paga envia DePix de carteira para carteira na rede Liquid, para um endereço exclusivo daquele lojista, e o DePix App acompanha a rede para confirmar o pagamento. Não existe QR Pix, não se pede CPF do pagador, e o dinheiro cai direto na carteira do lojista.

Pagamento na rede não tem volta. Diferente do Pix, um envio errado não pode ser cancelado nem devolvido por nós. Use sempre o valor exato devolvido pela API e envie somente DePix — outra moeda enviada para o mesmo endereço é perdida. Por isso a página de pagamento entrega valor e endereço prontos, sem ninguém digitar nada.

Como o lojista ativa

O recebimento direto é ligado pelo próprio dono da conta no DePix App, em Receber com DePix (confirmação de senha). Ao ativar, o app cria um endereço exclusivo para esses recebimentos — separado do endereço que recebe as liquidações de Pix — e o lojista escolhe um desconto de 0% a 90% para quem pagar por esse trilho. Não existe endpoint de API key para ligar o trilho nem para mudar o desconto: é sempre o dono, logado, com senha.

Enquanto o lojista não ativar, qualquer criação com payment_method: "depix" devolve depix_not_enabled (400) — o trilho Pix continua funcionando normalmente.

Criar uma cobrança em DePix

Mesmos endpoints de sempre (POST /api/checkouts, checkout do produto e checkout do merchant), trocando só o trilho.

curl
curl -X POST https://api.depixapp.com/api/checkouts \
  -H "Authorization: Bearer $DEPIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 9990,
    "payment_method": "depix",
    "description": "Pedido #124",
    "expires_in": 1800,
    "expected_discount_pct": 10
  }'
JavaScript
const res = await fetch("https://api.depixapp.com/api/checkouts", {
  method: "POST",
  headers: {
    "Authorization": "Bearer sk_live_<sua-chave>",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    amount: 9990,
    payment_method: "depix",
    description: "Pedido #124",
    expires_in: 1800,
    expected_discount_pct: 10,
  }),
});
const data = await res.json();
console.log(data.depix.amount, data.depix.uri);
Python
import requests

resp = requests.post(
    "https://api.depixapp.com/api/checkouts",
    headers={"Authorization": "Bearer sk_live_<sua-chave>"},
    json={
        "amount": 9990,
        "payment_method": "depix",
        "description": "Pedido #124",
        "expires_in": 1800,
        "expected_discount_pct": 10,
    },
)
data = resp.json()
print(data["depix"]["amount"], data["depix"]["uri"])
Resposta — 201 Created
{
  "id":             "chk_01jxxxxxxxxxxxxxxxxxxxxxx",
  "status":         "pending",
  "amount":         9990,
  "description":    "Pedido #124",
  "image_url":      null,
  "expires_at":     "2026-07-29T12:30:00.000Z",
  "is_live":        true,
  "payment_url":    "https://pay.depixapp.com/chk_01jxxxxxxxxxxxxxxxxxxxxxx",
  "payment_method": "depix",
  "depix": {
    "address":               "lq1qqw8re6vg9dqfazzsx4h9pkq6trxfmk8n0h0ykr7v9k8xn7pdrjq...",
    "amount_cents":          8991,
    "amount":                "89.91",
    "asset_id":              "02f22f8d9c76ab41661a2729e4752e2c5d1a263012141b86ea98af5472df5189",
    "uri":                   "liquidnetwork:lq1qqw8re6...?amount=89.91&assetid=02f22f8d...&depixid=chk_01jxxx...",
    "discount_pct":          10,
    "original_amount_cents": 9990,
    "detected":              false
  }
}

O checkout em DePix não traz o bloco pix — ele simplesmente não existe nesse trilho. Um checkout Pix, por sua vez, não traz o bloco depix. Sempre olhe payment_method antes de ler o payload de pagamento.

O objeto depix

CampoTipoDescrição
addressstringEndereço Liquid confidencial (lq1…) exclusivo dos recebimentos diretos desse lojista. Em modo teste é um endereço de mentira: não envie nada para ele.
amount_centsintegerValor exato a enviar, em centavos. É o valor de face menos o desconto do lojista e menos um ajuste de até 99 centavos (sempre para baixo) que torna esse valor único entre as cobranças abertas do lojista.
amountstringO mesmo valor no formato que a carteira assina ("89.91"). Exiba e transmita exatamente assim, sem arredondar.
asset_idstringIdentificador do DePix na rede Liquid. Enviar qualquer outra moeda para esse endereço perde o dinheiro.
uristringLink de pagamento com endereço, valor, moeda e o id desta cobrança já embutidos (liquidnetwork:…?amount=…&assetid=…&depixid=…). É o que você entrega para a carteira — assim ninguém digita valor à mão. Qualquer carteira BIP21 ignora o depixid; o DePix App usa ele para reler a cobrança e conferir endereço e situação antes de oferecer o pagamento, então uma URI escrita por outra pessoa é recusada em vez de paga. O link nunca leva o nome da loja: nome dentro da URI é nome na área de transferência do pagador, onde qualquer um escreve. null em modo teste.
discount_pctintegerDesconto do lojista aplicado nesse trilho, de 0 a 90.
original_amount_centsintegerValor de face, antes do desconto e do ajuste de centavos — o mesmo amount do checkout.
detectedbooleantrue quando um pagamento compatível já apareceu na rede mas ainda não foi confirmado. Serve só para a tela dizer "recebemos, confirmando"; o status continua pending e nenhum webhook é disparado ainda.

Fluxo de status

StatusQuando aconteceWebhook
pendingAguardando o pagamento. depix.detected vira true assim que a transação aparece na rede (segundos).
approved1ª confirmação na rede (~1 minuto) e valor casado com essa cobrança. O dinheiro já está na carteira do lojista — este é o ponto seguro para liberar o pedido.checkout.approved
completed2ª confirmação. Terminal.checkout.completed
expiredTerminal, disparado 15 minutos depois de expires_at: é a janela de tolerância para que um pagamento enviado nos últimos segundos ainda confirme e seja creditado.checkout.expired

Os status processing e cancelled não são usados nesse trilho. Enquanto o tempo mostra 0 e o status ainda é pending, continue consultando: é a janela de tolerância acima, não uma cobrança travada.

Por que o valor tem centavos "quebrados"?

O pagamento é identificado pelo valor exato. Para que duas cobranças abertas do mesmo lojista nunca tenham o mesmo valor, a API pode baixar alguns centavos — o valor pode variar até R$ 0,99 para baixo, sempre a favor de quem paga. Por isso o valor cobrado (depix.amount_cents) pode ser alguns centavos menor que o valor de face menos o desconto. Cobre e concilie sempre pelo valor que a API devolveu, nunca por um valor recalculado por você.

Uma consequência prática: pagamento com valor diferente do informado não é creditado automaticamente — ele vira um recebimento não atribuído (o lojista vê no app e recebe o webhook checkout.unmatched_payment). O dinheiro está na carteira do lojista; só a associação automática com o pedido não aconteceu.

Webhooks e conciliação

Nos eventos checkout.* desse trilho, amount continua sendo o valor de face, e o valor efetivamente pago vem em amount_received (com discount_pct e payment_method ao lado). Libere o pedido pelo amount_received. Se você concilia consultando a cobrança em vez de escutar o webhook, o bloco depix do GET /api/checkouts/{id} continua lá depois do pagamentodepix.amount_cents é o valor que entrou (o uri vem null, porque não há mais nada a pagar). Recebimentos que não casam com nenhuma cobrança geram o evento checkout.unmatched_payment, enviado para o default_callback_url do lojista.

Erros
depix_not_enabled          400   lojista não aceita recebimento direto em DePix
depix_busy                 409   sem valor único disponível agora — ofereça o Pix
discount_changed           409   o desconto mudou (details traz os valores atuais)
depix_address_unsupported  400   endereço de recebimento não é um lq1... confidencial
depix_address_conflict     400   o endereço precisa ser exclusivo desse recebimento
invalid_blinding_key       400   a chave de leitura não corresponde ao endereço

Os três últimos só aparecem no fluxo de ativação feito pelo dono da conta no app; uma integração por API key nunca os encontra.

Em modo teste (sk_test_) o trilho DePix não emite destino pagável: o endereço é um placeholder, uri vem null e não há QR. Conclua a cobrança de teste pelo simular pagamento, como no trilho Pix.

Depósito e Saque (scopes wallet_*)

Além de receber por checkouts, uma API key com os scopes wallet_* movimenta o lado "pagar" da conta: gera QR Pix de depósito pessoal (on-ramp BRL → DePix) e cria saques DePix → Pix (off-ramp). É a mesma superfície que a UI humana usa — limites, delays e verificação da conta valem por construção.

Fluxos SDK-first. Depósito e saque foram desenhados para serem consumidos pelo SDK oficial, que espelha a UX humana: sdk.deposit(...) gera o QR e acompanha a liquidação; sdk.withdraw(...) cota, monta a transação, assina do lado do cliente e acompanha a liquidação. O REST abaixo é o contrato completo que o próprio SDK consome — documentado integralmente para quem preferir integrar direto.

Taxas

As taxas são descontadas do valor — o que o pagador envia não é o que o destino recebe. Planeje pelo valor líquido:

FluxoTaxaR$ 100,00 viram
Depósito (BRL → DePix)2% + R$ 0,99R$ 97,01 em DePix
Saque até R$ 100,001% + R$ 1,00R$ 98,00 na chave Pix
Saque acima de R$ 100,002%

As duas faixas de saque se encontram sem degrau: em exatamente R$ 100,00 as duas regras custam R$ 2,00. As taxas podem mudar — a tabela canônica e sempre atualizada é o quadro de taxas em depixapp.com; trate os valores acima como ilustração do formato, não como contrato.

O ativo DePix na Liquid

O DePix é um ativo emitido na Liquid mainnet, com 8 casas decimais e paridade 1:1 com o BRL. O id do ativo é:

02f22f8d9c76ab41661a2729e4752e2c5d1a263012141b86ea98af5472df5189

Você precisa dele sempre que montar a transação Liquid por conta própria em vez de deixar o SDK montar — em especial a saída da taxa de saque, que precisa pagar fee_cents para fee_address como saída DePix explícita (não-blindada) na mesma transação. Pagar o ativo errado, ou pagar blindado, faz o saque falhar e pode perder os fundos.

O fluxo de depósito (on-ramp)

  • 1. POST /api/deposit → QR Pix (qrCopyPaste) + id.
  • 2. O dono da conta paga o QR num app de banco.
  • 3. Acompanhe por polling em GET /api/deposits/:id (5–15s) e/ou pelos webhooks deposit.* até o status terminal depix_sent — o DePix chegou no endereço Liquid informado.

O depósito pessoal conta para a verificação da conta. Alternativa de on-ramp: criar um checkout contra si mesmo — note que pagamentos de checkout não contam para a verificação.

O fluxo de saque (off-ramp)

  • 1. POST /api/withdraw → cotação com depositAddress (endereço Liquid do provedor).
  • 2. Envie o DePix para o depositAddress a partir da sua wallet — a assinatura é sempre do lado do cliente; a API jamais toca chave privada ou custodia fundos.
  • 3. Acompanhe por GET /api/withdrawals/:id e/ou pelos webhooks withdraw.* até sent — o Pix chegou na chave de destino.

Limites

Duas camadas, sempre em conjunto (AND):

  • Limites de conta — herdados da conta do dono e sempre prevalecentes. Cobrem primeiro depósito, valor por transação, atraso nos primeiros depósitos e tetos máximos de depósito e saque. Os valores dependem do nível de verificação da conta e podem mudar — não os codifique. Ao exceder → 400 account_limit_exceeded, com details informando limit_cents/used_cents vigentes.
  • Limites de chave — os limites de gasto que o dono definiu ao criar a chave wallet_write (per_tx_limit_cents, daily_limit_cents; ver Scopes e limites), visíveis em GET /api/api-keys. Ao exceder → 400 key_limit_exceeded (details.limit = per_tx | daily).

Sandbox sintético (sk_test_)

Com uma chave sk_test_, deposit e withdraw respondem com payloads sintéticos marcados "sandbox": true: strings SANDBOX-…-DO-NOT-PAY impagáveis, ids sandbox_*, zero dinheiro, zero chamada ao provedor, zero registro criado. Todos os gates de conta e o limite por transação da chave são checados normalmente — o sandbox ensina os limites reais. Ver Sandbox.

Criar depósito

Gera um QR Pix de depósito pessoal. Quando o Pix é pago, o DePix é entregue no endereço Liquid informado. Exige scope wallet_write. Aceita Idempotency-Key.

POST /api/deposit

Parâmetros

CampoTipoDescrição
amountInCentsintegerobrigatórioValor em centavos. Mínimo: 500 (R$ 5,00). Máximo: 600000 (R$ 6.000,00). Limites da conta e da chave podem restringir mais.
depixAddressstringobrigatórioEndereço Liquid que recebe o DePix quando o Pix liquidar.
payer_tax_numberstringobrigatórioCPF ou CNPJ de quem paga o Pix, com ou sem máscara. Deve ser um CPF/CNPJ real e registrado — o processador de pagamentos valida além do dígito verificador ao gerar o QR.
CPF/CNPJ real e registrado. O processador de pagamentos valida o payer_tax_number além do dígito verificador ao gerar o QR. Um número com checksum válido mas não registrado falha na criação com o erro genérico "Error generating QR Code. Please contact an admin." — se você receber esse erro ao criar, o CPF/CNPJ do pagador quase certamente não é um número real e registrado.

Exemplo

curl -X POST https://api.depixapp.com/api/deposit \
  -H "Authorization: Bearer $DEPIX_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: dep-pedido-42" \
  -d '{
    "amountInCents": 5000,
    "depixAddress": "lq1qq...",
    "payer_tax_number": "529.982.247-25"
  }'
const res = await fetch("https://api.depixapp.com/api/deposit", {
  method: "POST",
  headers: {
    "Authorization": "Bearer sk_live_<sua-chave>",
    "Content-Type": "application/json",
    "Idempotency-Key": "dep-pedido-42",
  },
  body: JSON.stringify({
    amountInCents: 5000,
    depixAddress: "lq1qq...",
    payer_tax_number: "529.982.247-25",
  }),
});
const data = await res.json();
console.log(data.response.qrCopyPaste, data.response.id);
import requests

resp = requests.post(
    "https://api.depixapp.com/api/deposit",
    headers={
        "Authorization": "Bearer sk_live_<sua-chave>",
        "Idempotency-Key": "dep-pedido-42",
    },
    json={
        "amountInCents": 5000,
        "depixAddress": "lq1qq...",
        "payer_tax_number": "529.982.247-25",
    },
)
data = resp.json()
print(data["response"]["qrCopyPaste"], data["response"]["id"])
$ch = curl_init("https://api.depixapp.com/api/deposit");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer sk_live_<sua-chave>",
        "Content-Type: application/json",
        "Idempotency-Key: dep-pedido-42",
    ],
    CURLOPT_POSTFIELDS => json_encode([
        "amountInCents" => 5000,
        "depixAddress" => "lq1qq...",
        "payer_tax_number" => "529.982.247-25",
    ]),
]);
$response = curl_exec($ch);
$data = json_decode($response, true);
echo $data["response"]["qrCopyPaste"];
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer sk_live_<sua-chave>");
client.DefaultRequestHeaders.Add("Idempotency-Key", "dep-pedido-42");

var payload = new {
    amountInCents = 5000,
    depixAddress = "lq1qq...",
    payer_tax_number = "529.982.247-25"
};

var res = await client.PostAsync(
    "https://api.depixapp.com/api/deposit",
    new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json")
);
var json = await res.Content.ReadAsStringAsync();
Console.WriteLine(json);
body := `{"amountInCents":5000,"depixAddress":"lq1qq...","payer_tax_number":"529.982.247-25"}`

req, _ := http.NewRequest("POST", "https://api.depixapp.com/api/deposit", strings.NewReader(body))
req.Header.Set("Authorization", "Bearer sk_live_<sua-chave>")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", "dep-pedido-42")

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
io.Copy(os.Stdout, resp.Body)
require "net/http"
require "json"

uri = URI("https://api.depixapp.com/api/deposit")
req = Net::HTTP::Post.new(uri, {
  "Authorization" => "Bearer sk_live_<sua-chave>",
  "Content-Type" => "application/json",
  "Idempotency-Key" => "dep-pedido-42",
})
req.body = { amountInCents: 5000, depixAddress: "lq1qq...",
             payer_tax_number: "529.982.247-25" }.to_json

res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
puts JSON.parse(res.body)
HttpClient client = HttpClient.newHttpClient();
String json = """
    {"amountInCents":5000,"depixAddress":"lq1qq...","payer_tax_number":"529.982.247-25"}""";

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.depixapp.com/api/deposit"))
    .header("Authorization", "Bearer sk_live_<sua-chave>")
    .header("Content-Type", "application/json")
    .header("Idempotency-Key", "dep-pedido-42")
    .POST(HttpRequest.BodyPublishers.ofString(json))
    .build();

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
Resposta — 200 OK
{
  "async": false,
  "response": {
    "qrCopyPaste": "00020126580014br.gov.bcb.pix...",   // payload EMV para QR code
    "qrImageUrl":  "https://qr.example/qr-id-456.png",
    "id":          "qr-id-456"                          // use no GET /api/deposits/:id
  }
}
Resposta — 200 OK (sandbox, sk_test_)
{
  "async": false,
  "response": {
    "qrCopyPaste": "SANDBOX-DEPIX-TEST-MODE-DO-NOT-PAY-a1b2c3d4e5f60708",
    "qrImageUrl":  null,
    "id":          "sandbox_3uw_a1b2c3d4e5f60708",   // sandbox_<amount36>_<hex> — 5000 → 3uw
    "sandbox":     true
  }
}
Rejeição do provedor chega com HTTP 400. Se o provedor Pix recusar a operação, a resposta é 400 com error.code = "validation_error" e a mensagem do provedor preservada em response.errorMessage (mesmo comportamento do POST /api/withdraw). Programe contra o status HTTP: 2xx = QR emitido, 4xx = recusa. A mensagem humana continua em response.errorMessage.

Status do depósito

Consulta o status de um depósito criado via POST /api/deposit. Ownership é obrigatório: id de outra conta → 404. Faça polling a cada 5–15 segundos até um status terminal — depix_sent é o sucesso terminal.

GET /api/deposits/:id
curl
curl https://api.depixapp.com/api/deposits/qr-id-456 \
  -H "Authorization: Bearer $DEPIX_API_KEY"
Resposta — 200 OK
{
  "id":           "qr-id-456",
  "type":         "deposit",
  "amount_cents": 5000,
  "status":       "depix_sent",
  "created_at":   "2026-07-01T12:00:00.000Z",
  "updated_at":   "2026-07-01T12:34:56.000Z",
  "rejection_reasons": []   // sempre presente; [] quando não houve recusa
}

Status possíveis

StatusTerminalSignificado
pendingQR gerado; Pix ainda não pago.
under_reviewPix pago; pagamento em análise pré-liquidação.
pending_pix2faPix pago; aguardando o pagador completar o 2FA do Pix.
approvedPix aprovado pelo provedor; DePix ainda não enviado.
delayedLiquidação retida pela política de delay (contas novas/valores altos).
will_refundFluxo de reembolso iniciado; o depósito será reembolsado.
errorErro de processamento no provedor. Não é terminal: o provedor ainda deve um desfecho, então continue o polling — o depósito ainda pode virar depix_sent ou refunded.
depix_sentsimSucesso: DePix entregue no endereço Liquid de destino.
refundedsimDepósito reembolsado ao pagador.
canceledsimCancelado pelo provedor.
expiredsimQR expirou sem pagamento.

Motivos de devolução (rejection_reasons)

A resposta sempre traz rejection_reasons como array — [] quando não houve recusa. Assim um agente em polling lê o campo incondicionalmente, sem checar existência. Ele é preenchido quando o pagamento foi devolvido ou retido pelo provedor (tipicamente nos status will_refund, refunded e error). Novos códigos podem surgir — exiba valores desconhecidos como recebidos. Saques não têm rejection_reasons.

CódigoSignificado
PAYER_MISMATCHPagamento feito com CPF/CNPJ diferente do informado no depósito.
PAST_DAILY_LIMITLimite diário do pagador excedido.
BLOCKED_USERUsuário bloqueado pelo provedor.
HIGH_VELOCITYMuitas transações do pagador em pouco tempo.
Sandbox: com sk_test_, um id sandbox_* devolve sempre a resposta sintética fixa { "id": "sandbox_3uw_a1b2c3d4e5f60708", "type": "deposit", "amount_cents": 5000, "status": "depix_sent", "created_at": "2026-01-01T00:00:00.000Z", "updated_at": "2026-01-01T00:00:00.000Z", "sandbox": true, "rejection_reasons": [] } — mesmo shape da resposta live (inclui amount_cents, decodificado do id, e timestamps determinísticos) para exercitar o loop de polling completo em modo test. O amount_cents é null apenas em ids legados sem valor embutido. Qualquer outro id via sk_test_404; ids sandbox_* via chave live ou JWT → 404.

Criar saque

Cota um saque DePix → Pix: a resposta traz o endereço Liquid do provedor para onde você envia o DePix. Depois de transmitir a transação, acompanhe o status. Exige scope wallet_write. Aceita Idempotency-Key.

POST /api/withdraw

Parâmetros

Envie exatamente um entre depositAmountInCents (modo "você envia") e payoutAmountInCents (modo "você recebe") — os mesmos dois modos da UI humana.

CampoTipoDescrição
pixKeystringobrigatórioChave Pix de destino (e-mail, telefone, CPF/CNPJ ou chave aleatória).
depositAmountInCentsintegerum dos doisModo "você envia": quanto DePix você entrega, em centavos. Mínimo: 500. Máximo: 600000 (R$ 6.000,00). Mutuamente exclusivo com payoutAmountInCents.
payoutAmountInCentsintegerum dos doisModo "você recebe": quanto a chave de destino recebe, em centavos. Mínimo: 500. Máximo: 600000. Mutuamente exclusivo com depositAmountInCents.
taxNumberstringobrigatórioCPF ou CNPJ do titular da chave Pix de destino.
refundAddressstringopcionalEndereço Liquid para onde o provedor devolve o DePix caso o Pix não possa ser concluído. Use um endereço que você controla — sem ele, um Pix recusado não tem caminho de volta automático. O checksum é conferido: endereço malformado devolve 400 com error.details.field = "refundAddress", antes de o saque ser cotado. A devolução é do valor que chegou ao provedor (depositAmountInCents da resposta), não do total que saiu da sua carteira: a taxa de plataforma (fee_cents) é uma saída separada e não volta.

Exemplo

curl -X POST https://api.depixapp.com/api/withdraw \
  -H "Authorization: Bearer $DEPIX_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: saque-pedido-42" \
  -d '{
    "pixKey": "alguem@exemplo.com",
    "depositAmountInCents": 10000,
    "taxNumber": "529.982.247-25",
    "refundAddress": "lq1qq..."
  }'
const res = await fetch("https://api.depixapp.com/api/withdraw", {
  method: "POST",
  headers: {
    "Authorization": "Bearer sk_live_<sua-chave>",
    "Content-Type": "application/json",
    "Idempotency-Key": "saque-pedido-42",
  },
  body: JSON.stringify({
    pixKey: "alguem@exemplo.com",
    depositAmountInCents: 10000,
    taxNumber: "529.982.247-25",
    refundAddress: "lq1qq...",
  }),
});
const data = await res.json();
console.log(data.response.withdrawalId, data.response.depositAddress);
import requests

resp = requests.post(
    "https://api.depixapp.com/api/withdraw",
    headers={
        "Authorization": "Bearer sk_live_<sua-chave>",
        "Idempotency-Key": "saque-pedido-42",
    },
    json={
        "pixKey": "alguem@exemplo.com",
        "depositAmountInCents": 10000,
        "taxNumber": "529.982.247-25",
        "refundAddress": "lq1qq...",
    },
)
data = resp.json()
print(data["response"]["withdrawalId"], data["response"]["depositAddress"])
$ch = curl_init("https://api.depixapp.com/api/withdraw");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer sk_live_<sua-chave>",
        "Content-Type: application/json",
        "Idempotency-Key: saque-pedido-42",
    ],
    CURLOPT_POSTFIELDS => json_encode([
        "pixKey" => "alguem@exemplo.com",
        "depositAmountInCents" => 10000,
        "taxNumber" => "529.982.247-25",
        "refundAddress" => "lq1qq...",
    ]),
]);
$response = curl_exec($ch);
$data = json_decode($response, true);
echo $data["response"]["depositAddress"];
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer sk_live_<sua-chave>");
client.DefaultRequestHeaders.Add("Idempotency-Key", "saque-pedido-42");

var payload = new {
    pixKey = "alguem@exemplo.com",
    depositAmountInCents = 10000,
    taxNumber = "529.982.247-25",
    refundAddress = "lq1qq..."
};

var res = await client.PostAsync(
    "https://api.depixapp.com/api/withdraw",
    new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json")
);
var json = await res.Content.ReadAsStringAsync();
Console.WriteLine(json);
body := `{"pixKey":"alguem@exemplo.com","depositAmountInCents":10000,"taxNumber":"529.982.247-25","refundAddress":"lq1qq..."}`

req, _ := http.NewRequest("POST", "https://api.depixapp.com/api/withdraw", strings.NewReader(body))
req.Header.Set("Authorization", "Bearer sk_live_<sua-chave>")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", "saque-pedido-42")

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
io.Copy(os.Stdout, resp.Body)
require "net/http"
require "json"

uri = URI("https://api.depixapp.com/api/withdraw")
req = Net::HTTP::Post.new(uri, {
  "Authorization" => "Bearer sk_live_<sua-chave>",
  "Content-Type" => "application/json",
  "Idempotency-Key" => "saque-pedido-42",
})
req.body = { pixKey: "alguem@exemplo.com", depositAmountInCents: 10000,
             taxNumber: "529.982.247-25", refundAddress: "lq1qq..." }.to_json

res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
puts JSON.parse(res.body)
HttpClient client = HttpClient.newHttpClient();
String json = """
    {"pixKey":"alguem@exemplo.com","depositAmountInCents":10000,"taxNumber":"529.982.247-25","refundAddress":"lq1qq..."}""";

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.depixapp.com/api/withdraw"))
    .header("Authorization", "Bearer sk_live_<sua-chave>")
    .header("Content-Type", "application/json")
    .header("Idempotency-Key", "saque-pedido-42")
    .POST(HttpRequest.BodyPublishers.ofString(json))
    .build();

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
Resposta — 200 OK
{
  "response": {
    "withdrawalId":              "wd-123",           // use no GET de status
    "depositAddress":            "lq1qq2v9wxkyz...", // envie o DePix do saque para cá
    "depositAmountInCents":      9900,               // o que o provedor recebe
    "payoutAmountInCents":       9800,               // o que a chave Pix recebe
    "totalDepositAmountInCents": 10000,              // saída bruta da wallet (provedor + taxa)
    "split":                     { "address": "ex1qfee...", "amountCentavos": 100 },
    "fee_cents":                 100,                // taxa da plataforma — OBRIGATÓRIA na mesma transação
    "fee_address":               "ex1qfee..."        // endereço da taxa (forma não-confidencial)
  }
}

A resposta via API key inclui fee_cents e fee_address: a taxa da plataforma que a sua transação Liquid deve pagar como uma segunda saída explícita (não-blindada, asset DePix) para o fee_address, na mesma transação da saída principal para o depositAddress. Pague o fee_address exatamente como recebido — ele vem na forma não-confidencial (ex1...) de propósito: uma saída confidencial/blindada não pode ser verificada e conta como taxa não paga. A taxa é verificada automaticamente na transação Liquid que paga o saque.

Atenção: Enviar os fundos do saque sem incluir, na mesma transação, o valor da taxa resultará em: erro no saque e perda de fundos.
Resposta — 200 OK (sandbox, sk_test_)
{
  "response": {
    "withdrawalId":         "sandbox_0011223344556677",
    "depositAddress":       "SANDBOX-LIQUID-ADDRESS-DO-NOT-PAY",
    "depositAmountInCents": 9900,                // o que vai ao provedor: o valor digitado menos a nossa taxa
    "payoutAmountInCents":  9800,                // mesma conta do live: ~1% do provedor com piso de R$ 1
    "totalDepositAmountInCents": 10000,          // o que sai da carteira
    "split": { "address": "SANDBOX-LIQUID-FEE-ADDRESS-DO-NOT-PAY", "amountCentavos": 100 },
    "fee_cents":            100,                 // nossa taxa de 1%, no segundo output
    "fee_address":          "SANDBOX-LIQUID-FEE-ADDRESS-DO-NOT-PAY",
    "sandbox":              true
  }
}

Status do saque

Consulta o status de um saque criado via POST /api/withdraw. Ownership é obrigatório: id de outra conta → 404. Faça polling a cada 5–15 segundos até um status terminal — sent é o sucesso terminal.

GET /api/withdrawals/:id
curl
curl https://api.depixapp.com/api/withdrawals/wd-123 \
  -H "Authorization: Bearer $DEPIX_API_KEY"
Resposta — 200 OK
{
  "id":           "wd-123",
  "type":         "withdraw",
  "amount_cents": 10000,                // lado enviado (depositAmountInCents)
  "status":       "sent",
  "created_at":   "2026-07-01T10:00:00.000Z",
  "updated_at":   "2026-07-01T10:00:00.000Z",
  "liquid_txid":  "abab...ab"           // presente após a detecção on-chain da transferência
}

Status possíveis

StatusTerminalSignificado
unsentCriado; o DePix ainda não chegou ao provedor.
sendingDePix recebido; Pix de saída em andamento.
errorO DePix chegou e o Pix de saída falhou. Não é terminal: o provedor está com o dinheiro e ainda deve um desfecho, então continue o polling — o saque ainda pode virar sent (Pix reenviado) ou refunded.
sentsimSucesso: Pix entregue na chave de destino.
refundedsimReembolsado.
cancelledsimCancelado.
expiredsimO DePix nunca chegou — varrido pelo cron.
Sandbox: com sk_test_, um id sandbox_* devolve sempre { "id": "sandbox_7ps_…", "type": "withdraw", "amount_cents": 10000, "status": "confirmed", "created_at": "2026-01-01T00:00:00.000Z", "updated_at": "2026-01-01T00:00:00.000Z", "sandbox": true } — estado sintético fixo, exclusivo do sandbox (status: "confirmed" fica fora do enum live); amount_cents é decodificado do id (null em ids legados sem valor). Qualquer outro id via sk_test_404; ids sandbox_* via chave live ou JWT → 404.

Webhooks

Quando o status de um checkout muda, a API envia um POST para o callback_url que você informou ao criar o checkout (ou configurado no produto/merchant). Depósitos e saques criados via API key também disparam webhooks (eventos deposit.*/withdraw.*) para o default_callback_url do merchant — ver Eventos.

Como funciona

  • O request é enviado com timeout de 30 segundos.
  • Se falhar (resposta não-2xx, timeout ou erro de rede), a API tenta novamente até 5 vezes: após 1 minuto, 10 minutos, 1 hora, 4 horas e 12 horas (6 tentativas no total, cobrindo cerca de 17 horas).
  • Sua endpoint deve responder com status 2xx para confirmar recebimento.
  • O callback_url precisa ser HTTPS e de acesso público (sem IPs privados).

Headers enviados

  • X-DePix-Signature — assinatura HMAC-SHA256 (ver seção Verificar assinatura).
  • X-DePix-Event — nome do evento (ex: checkout.completed).
  • X-DePix-Event-Id — identificador único e estável entre tentativas deste evento (ex: evt_abc123…). É a chave recomendada para deduplicação.
  • X-DePix-Delivery-Attempt — número da tentativa atual (1, 2, … até 6). Muda a cada retry; não use para dedupe.
  • User-Agent — sempre DePix-Webhook/1.0.

Entrega at-least-once e idempotência (obrigatório)

Webhooks são entregues com semântica at-least-once — é o padrão do mercado (Stripe, PayPal, Mercado Pago funcionam da mesma forma). Isso significa que o mesmo evento pode chegar ao seu endpoint mais de uma vez, mesmo que tudo esteja funcionando corretamente. Cenários comuns:

  • Seu servidor processa o webhook mas responde lentamente — nossa API faz timeout em 30s, marca como falha e tenta novamente; você processa duas vezes.
  • Seu servidor responde 200 mas a conexão cai antes da gente ler a resposta — mesma coisa: retry e processamento duplicado.
  • O time de operações reenvia manualmente um evento (via comando administrativo) que você já processou.

Para evitar entregar produto duas vezes, creditar saldo em duplicidade, ou disparar acionamentos múltiplos, seu endpoint precisa ser idempotente. A forma mais simples e robusta é deduplicar por X-DePix-Event-Id: guarde os IDs de eventos já processados e ignore silenciosamente qualquer evento cujo ID já esteja na sua tabela.

// Exemplo de dedupe (Node.js, pseudo-código)
app.post("/webhook", async (req, res) => {
  // 1. Valide a assinatura HMAC primeiro (ver seção Verificar assinatura).

  const eventId = req.headers["x-depix-event-id"];

  // 2. Processe E marque como processado dentro da MESMA transação — se
  // processCheckout falhar, o INSERT também é revertido e o nosso retry
  // consegue entregar o evento novamente.
  try {
    await db.transaction(async (tx) => {
      await tx.query(
        "INSERT INTO processed_webhooks (event_id, received_at) VALUES (?, NOW())",
        [eventId]
      );
      await processCheckout(req.body, tx);
    });
  } catch (err) {
    if (err.code === "ER_DUP_ENTRY") {
      // Já processamos antes — responde 200 e ignora.
      return res.sendStatus(200);
    }
    throw err; // Deixa nossa API tentar novamente.
  }

  res.sendStatus(200);
});

Se preferir não manter uma tabela separada, você também pode deduplicar pelo campo event_id dentro do JSON (data.event_id) — ele carrega o mesmo valor estável do header X-DePix-Event-Id. Não use (data.id, event) como chave de dedupe: um reenvio manual feito pela equipe de operações reusa o mesmo id de checkout e o mesmo nome de evento, então uma chave baseada nessa tupla descartaria silenciosamente o reenvio.

Reduzindo retries

Para evitar entregas duplicadas no caminho feliz, responda o mais rápido possível — alvos comuns ficam abaixo de poucos segundos, para deixar folga para a latência de rede antes do nosso timeout de 30s. O padrão recomendado é: validar a assinatura, responder 200 imediatamente e processar o evento em background (fila, worker, etc.). Isso evita retries causados por timeout do nosso lado.

Eventos

checkout.processing

Disparado quando o pagamento Pix é recebido e a conversão está sendo processada.

{
  "event": "checkout.processing",
  "data": {
    "event_id":       "evt_01jxxxxxxxxxxxxxxxxxxxxxx",
    "id":             "chk_01jxxxxxxxxxxxxxxxxxxxxxx",
    "product_id":     null,
    "status":         "processing",
    "amount":         2990,
    "processing_at":  "2025-06-01T15:02:00.000Z",
    "metadata":       { "order_id": "ORD-123" }
  }
}

checkout.approved

Disparado quando o pagamento é aprovado pelo banco e aguarda a liquidação final em DePix.

{
  "event": "checkout.approved",
  "data": {
    "event_id":     "evt_01jxxxxxxxxxxxxxxxxxxxxxx",
    "id":           "chk_01jxxxxxxxxxxxxxxxxxxxxxx",
    "product_id":   null,
    "status":       "approved",
    "amount":       2990,
    "approved_at":  "2025-06-01T15:05:00.000Z",
    "metadata":     { "order_id": "ORD-123" }
  }
}

checkout.completed

Disparado quando o pagamento é confirmado e o DePix chega na carteira do merchant.

{
  "event": "checkout.completed",
  "data": {
    "event_id":      "evt_01jxxxxxxxxxxxxxxxxxxxxxx",
    "id":            "chk_01jxxxxxxxxxxxxxxxxxxxxxx",
    "product_id":    null,
    "status":        "completed",
    "amount":        2990,
    "completed_at":  "2025-06-01T15:22:00.000Z",
    "metadata":      { "order_id": "ORD-123" }
  }
}

checkout.cancelled

Disparado quando o pagamento do checkout é cancelado, estornado ou entra em reembolso pelo provedor Pix. O payload traz rejection_reasons com o motivo — dá para revogar o pedido e registrar o porquê sem uma consulta extra.

{
  "event": "checkout.cancelled",
  "data": {
    "event_id":      "evt_01jxxxxxxxxxxxxxxxxxxxxxx",
    "id":            "chk_01jxxxxxxxxxxxxxxxxxxxxxx",
    "product_id":    null,
    "status":        "cancelled",
    "amount":        2990,
    "cancelled_at":  "2025-06-01T15:05:00.000Z",
    "rejection_reasons": ["PAYER_MISMATCH"],  // o motivo — mesmo catálogo do GET; [] quando o provedor não informou
    "metadata":      { "order_id": "ORD-123" }
  }
}

checkout.expired

Disparado quando o checkout expira sem receber pagamento.

{
  "event": "checkout.expired",
  "data": {
    "event_id":    "evt_01jxxxxxxxxxxxxxxxxxxxxxx",
    "id":          "chk_01jxxxxxxxxxxxxxxxxxxxxxx",
    "product_id":  null,
    "status":      "expired",
    "amount":      2990,
    "expires_at":  "2025-06-01T15:30:00.000Z",
    "metadata":    { "order_id": "ORD-123" }
  }
}

checkout.unmatched_payment

Só no trilho DePix direto: chegou um pagamento no endereço do lojista que não casa com nenhuma cobrança (valor diferente do informado, pagamento depois da janela de tolerância, ou um segundo pagamento para uma cobrança já quitada). O dinheiro está na carteira do lojista — só a associação automática com o pedido não aconteceu, e a conciliação é uma decisão humana. Como não há checkout, a entrega vai para o default_callback_url do merchant.

{
  "event": "checkout.unmatched_payment",
  "data": {
    "event_id":     "evt_01jxxxxxxxxxxxxxxxxxxxxxx",
    "id":           "dout_9f8e7d6c5b4a",
    "type":         "depix_output",
    "status":       "unattributed",
    "amount_cents": 8997,
    "txid":         "abab…",
    "vout":         1,
    "first_seen_at": "2026-07-29T12:07:00.000Z",
    "reason":       "no_matching_checkout"
  }
}

status é unattributed (nenhuma cobrança aberta com esse valor exato) ou duplicate (segundo pagamento de uma cobrança já quitada). first_seen_at é quando o pagamento foi visto no endereço do lojista — pode ser alguns minutos antes desse aviso — e é o mesmo horário que aparece no app. reason detalha o motivo (no_matching_checkout, duplicate_payment, ambiguous_candidates, value_not_whole_cents, max_attributions_per_tx, transition_lost); concilie pelo id, não por esse texto.

Nos eventos checkout.* do trilho DePix direto vêm também payment_method, amount_received (o valor efetivamente pago, em centavos) e discount_pct. O campo amount continua sendo o valor de face — libere o pedido pelo amount_received.

Eventos deposit.* e withdraw.*

Depósitos e saques criados via API key (scope wallet_write) disparam um evento por transição real de status, nomeado 1:1 com o status cru: deposit.<status> / withdraw.<status>. Operações humanas (dashboard/SPA) não disparam. A entrega vai para o default_callback_url do merchant — sem ele configurado, nada é enviado. Operações sandbox (sk_test_) não criam registros e portanto nunca disparam webhooks.

Criar um depósito ou saque não emite webhook — o primeiro evento que você recebe é a próxima mudança de status. Por isso os eventos de estado inicial (deposit.pending, withdraw.unsent) só aparecem na reversão incomum de volta a esse estado.

EventoDisparado quando
deposit.pendingDepósito aguardando pagamento Pix (estado inicial).
deposit.under_reviewPix pago; pagamento em análise pré-liquidação.
deposit.pending_pix2faAguardando o pagador completar o 2FA do Pix.
deposit.approvedAprovado pelo provedor; DePix ainda não enviado.
deposit.delayedLiquidação retida pela política de delay.
deposit.will_refundFluxo de reembolso iniciado.
deposit.depix_sentSucesso terminal: DePix entregue no endereço de destino.
deposit.refundedReembolsado ao pagador (terminal).
deposit.canceledCancelado pelo provedor (terminal).
deposit.errorErro de processamento no provedor — não terminal, ainda pode virar depix_sent ou refunded.
deposit.expiredQR expirou sem pagamento (terminal).
withdraw.unsentSaque aguardando o envio do DePix (estado inicial).
withdraw.sendingDePix recebido; Pix de saída em andamento.
withdraw.sentSucesso terminal: Pix entregue na chave de destino.
withdraw.refundedReembolsado (terminal).
withdraw.cancelledCancelado (terminal).
withdraw.errorO DePix chegou e o Pix falhou — não terminal, ainda pode virar sent ou refunded.
withdraw.expiredO DePix nunca chegou — varrido pelo cron (terminal).

O payload usa o mesmo shape em inglês dos GETs de status, mais o event_id — a chave de dedupe (mesmo valor do header X-DePix-Event-Id, estável entre retries). Payloads deposit.* sempre trazem rejection_reasons (array, [] quando não houve recusa) — com conteúdo em deposit.refunded, deposit.will_refund e deposit.error; vazio nos demais eventos. Payloads withdraw.* não têm esse campo:

{
  "event": "deposit.depix_sent",
  "data": {
    "id":           "qr-id-456",
    "type":         "deposit",
    "amount_cents": 5000,
    "status":       "depix_sent",
    "created_at":   "2026-07-01T12:00:00.000Z",
    "updated_at":   "2026-07-01T12:34:56.000Z",
    "rejection_reasons": [],   // ex.: ["PAYER_MISMATCH"] em deposit.refunded
    "event_id":     "evt_9f8e7d6c5b4a"
  }
}
{
  "event": "withdraw.sent",
  "data": {
    "id":           "wd-123",
    "type":         "withdraw",
    "amount_cents": 10000,
    "status":       "sent",
    "created_at":   "2026-07-01T10:00:00.000Z",
    "updated_at":   "2026-07-01T10:00:00.000Z",
    "liquid_txid":  "abab...ab",
    "event_id":     "evt_1a2b3c4d5e6f"
  }
}

Verificar assinatura

Cada webhook vem com um header X-DePix-Signature. Sempre valide a assinatura antes de processar o evento — isso garante que o request veio da API do DePix App e não de terceiros.

Formato do header

X-DePix-Signature: t=1717257600,v1=abc123def456...
  • t — timestamp Unix do envio (segundos).
  • v1 — assinatura HMAC-SHA256 em hexadecimal.

Como validar

A assinatura é calculada sobre a string timestamp.payload usando o Webhook Secret da sua conta (disponível em Meu Negócio).

Bash
# Calcular a assinatura esperada
EXPECTED=$(echo -n "${TIMESTAMP}.${RAW_BODY}" | \
  openssl dgst -sha256 -hmac "${WEBHOOK_SECRET}" | awk '{print $2}')

# Comparar com o v1 recebido
if [ "$EXPECTED" = "$RECEIVED_V1" ]; then
  echo "Assinatura válida"
fi
Node.js
import crypto from "node:crypto";

function verifyWebhook(rawBody, sigHeader, secret) {
  const parts = Object.fromEntries(
    sigHeader.split(",").map(p => p.split("=", 2))
  );
  const timestamp = parts["t"];
  const received  = parts["v1"];

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");

  // Use timingSafeEqual to prevent timing attacks
  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(received, "hex");
  if (a.length !== b.length) return false;
  return crypto.timingSafeEqual(a, b);
}

// Exemplo com Express
app.post("/webhook/depix", express.raw({ type: "application/json" }), (req, res) => {
  const sig = req.headers["x-depix-signature"];
  if (!verifyWebhook(req.body.toString(), sig, process.env.DEPIX_WEBHOOK_SECRET)) {
    return res.status(401).send("Assinatura inválida");
  }
  const { event, data } = JSON.parse(req.body);
  // processa o evento...
  res.sendStatus(200);
});
Python
import hmac, hashlib

def verify_webhook(raw_body: str, sig_header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in sig_header.split(","))
    timestamp = parts["t"]
    received = parts["v1"]
    expected = hmac.new(
        secret.encode(),
        f"{timestamp}.{raw_body}".encode(),
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, received)
PHP
function verifyWebhook(string $rawBody, string $sigHeader, string $secret): bool {
    $parts = [];
    foreach (explode(",", $sigHeader) as $pair) {
        [$k, $v] = explode("=", $pair, 2);
        $parts[$k] = $v;
    }
    $expected = hash_hmac("sha256", $parts["t"] . "." . $rawBody, $secret);
    return hash_equals($expected, $parts["v1"]);
}
C#
static bool VerifyWebhook(string rawBody, string sigHeader, string secret) {
    var parts = sigHeader.Split(',')
        .ToDictionary(p => p.Split('=', 2)[0], p => p.Split('=', 2)[1]);
    using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
    var expected = Convert.ToHexString(
        hmac.ComputeHash(Encoding.UTF8.GetBytes($"{parts["t"]}.{rawBody}"))
    ).ToLower();
    return CryptographicOperations.FixedTimeEquals(
        Encoding.UTF8.GetBytes(expected),
        Encoding.UTF8.GetBytes(parts["v1"])
    );
}
Go
func verifyWebhook(rawBody, sigHeader, secret string) bool {
    parts := make(map[string]string)
    for _, p := range strings.Split(sigHeader, ",") {
        kv := strings.SplitN(p, "=", 2)
        parts[kv[0]] = kv[1]
    }
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write([]byte(parts["t"] + "." + rawBody))
    expected := hex.EncodeToString(mac.Sum(nil))
    return hmac.Equal([]byte(expected), []byte(parts["v1"]))
}
Ruby
def verify_webhook(raw_body, sig_header, secret)
  parts = sig_header.split(",").to_h { |p| p.split("=", 2) }
  expected = OpenSSL::HMAC.hexdigest("sha256", secret, "#{parts['t']}.#{raw_body}")
  Rack::Utils.secure_compare(expected, parts["v1"])
end
Java
static boolean verifyWebhook(String rawBody, String sigHeader, String secret)
        throws Exception {
    Map<String, String> parts = new HashMap<>();
    for (String p : sigHeader.split(",")) {
        String[] kv = p.split("=", 2);
        parts.put(kv[0], kv[1]);
    }
    Mac mac = Mac.getInstance("HmacSHA256");
    mac.init(new SecretKeySpec(secret.getBytes(), "HmacSHA256"));
    String expected = HexFormat.of().formatHex(
        mac.doFinal((parts.get("t") + "." + rawBody).getBytes())
    );
    return MessageDigest.isEqual(expected.getBytes(), parts.get("v1").getBytes());
}
Leia o body como raw bytes (antes do parsing JSON). Qualquer reformatação invalida a assinatura.

Logs de entrega

Auditoria somente-leitura das entregas dos eventos checkout.*, deposit.* e withdraw.* às callback URLs desta conta — o que foi entregue, retentado ou falhou. A listagem retorna as 50 tentativas mais recentes (da mais nova para a mais antiga) sem os corpos; busque um log pelo id para ver os payloads de requisição e resposta. Um log de outra conta responde 404: a posse nunca é revelada.

GET /api/webhook-logs
GET /api/webhook-logs/:id
Aceita tanto o JWT do dashboard quanto uma API key com o escopo merchant_read — é a mesma auditoria que o painel mostra, aberta a um agente. Uma conta de agente usa o gêmeo assinado por chave em GET /api/agents/webhook-logs. Pelo MCP, a ferramenta é list_webhook_logs.
curl
curl https://api.depixapp.com/api/webhook-logs \
  -H "Authorization: Bearer $DEPIX_API_KEY"
Resposta — 200 OK (listagem)
{
  "logs": [
    {
      "id":           "wlog_1",
      "checkout_id":  "chk_abc123",
      "event":        "checkout.completed",
      "url":          "https://store.example.com/hook",
      "status_code":  200,
      "error":        null,
      "attempt":      1,
      "sent_at":      "2026-07-22T12:00:00.000Z"
    },
    {
      "id":           "wlog_2",
      "checkout_id":  null,               // eventos deposit.*/withdraw.* não têm checkout
      "event":        "deposit.depix_sent",
      "url":          "https://store.example.com/hook",
      "status_code":  null,               // null = nem chegou a haver resposta HTTP
      "error":        "fetch timeout",
      "attempt":      2,
      "sent_at":      "2026-07-22T11:58:00.000Z"
    }
  ]
}
Resposta — 200 OK (detalhe: inclui os corpos)
{
  "log": {
    "id": "wlog_1", "checkout_id": null, "merchant_id": "mrc_1",
    "event": "deposit.depix_sent", "url": "https://store.example.com/hook", "status_code": 200,
    "request_body":  "{...}",   // o payload assinado enviado (X-DePix-Signature cobre estes bytes)
    "response_body": "{...}",   // o que o receptor respondeu
    "error": null, "attempt": 1, "next_retry_at": null, "sent_at": "2026-07-22T12:00:00.000Z"
  }
}
Erros
insufficient_scope  403   a chave não tem merchant_read
merchant_required   403   a conta não tem perfil de lojista
not_found           404   log não existe ou pertence a outra conta (só no detalhe)

Sandbox

Use chaves do tipo sk_test_... para testar sem movimentar dinheiro real. Checkouts criados com chave test nunca geram Pix real e ficam isolados dos checkouts de produção.

Diferenças do modo test

  • O campo is_live marca o modo: false nas respostas de criação e no GET /api/me, 0 nas consultas de checkouts e produtos.
  • O QR code gerado não é um Pix válido — não pode ser pago num app de banco.
  • Use o endpoint /simulate-payment para marcar o checkout como pago.
  • Webhooks são enviados normalmente — ótimo para testar sua integração de ponta a ponta.

Depósito e saque em modo test

  • POST /api/deposit e POST /api/withdraw com sk_test_ respondem com payloads sintéticos marcados "sandbox": true: strings SANDBOX-…-DO-NOT-PAY impagáveis, ids sandbox_*, e no saque a mesma conta de taxa do live, com os mesmos campos.
  • Zero dinheiro e zero registro: nenhuma chamada ao provedor Pix, nenhuma linha gravada — os contadores econômicos da conta não são afetados.
  • Validações e limites reais são exercitados: os gates da conta e o limite por transação da chave rodam normalmente. O limite diário não acumula (nada é gravado).
  • GET /api/deposits/:id e GET /api/withdrawals/:id com id sandbox_* devolvem um status sintético fixo (depix_sent / confirmed) para treinar o loop de polling.
  • Sem registros → operações sandbox nunca disparam webhooks deposit.*/withdraw.*. Para testar webhooks de ponta a ponta, use checkout + simulate-payment.
  • Chave sk_test_ jamais lê ou escreve dados live: qualquer id que não seja sandbox_*404.
O sandbox é independente de produção. Você pode criar e simular checkouts de teste sem risco.

Simular pagamento

Marca um checkout de teste como pago. Só funciona com chaves sk_test_. Dispara o webhook checkout.completed normalmente.

POST /api/checkouts/:id/simulate-payment
curl
# 1. Crie um checkout de teste
curl -X POST https://api.depixapp.com/api/checkouts \
  -H "Authorization: Bearer $DEPIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "amount": 1000, "payer_tax_number": "529.982.247-25", "callback_url": "https://minha-loja.com/webhook" }'

# 2. Simule o pagamento
curl -X POST https://api.depixapp.com/api/checkouts/chk_01jxxxxxxxxxxxxxxxxxxxxxx/simulate-payment \
  -H "Authorization: Bearer $DEPIX_API_KEY"
Resposta — 200 OK
{ "success": true }

Após a simulação, seu callback_url receberá o evento checkout.completed em alguns segundos — exatamente como num pagamento real.

Verificar chave (GET /api/me)

Retorna as informações do merchant autenticado. Útil para verificar se a API key é válida e consultar dados da conta.

GET /api/me
curl
curl https://api.depixapp.com/api/me \
  -H "Authorization: Bearer $DEPIX_API_KEY"
Resposta — 200 OK
{
  "merchant_id":    "mrc_xxx",
  "name":           "Loja do Joao",
  "username":       "joao",
  "merchant_slug":  "joao",
  "is_live":        true,
  "created_at":     "2025-06-01T00:00:00.000Z"
}

Verificação da conta

Diz se esta conta já pode usar as ferramentas de lojista (checkouts e produtos) e, se não pode, exatamente o que falta. O GET lê o estado; o POST avalia a prova e, quando tudo está satisfeito, destrava as ferramentas. Faça polling do GET enquanto a pessoa cumpre os passos e então chame o POST.

GET /api/verification
POST /api/verification
Aceita o JWT do dashboard ou uma API key sk_, e nenhum escopo é exigido. O POST não leva corpo e é idempotente: uma conta já verificada responde 200. Pelo MCP, quem lê isso é get_onboarding_status, que já traduz os passos em instruções para o humano.
Verificar destrava ferramentas, nunca um limite. Quanto a conta pode receber é decidido pelo nível e pelo Cofre, e não muda ao verificar. Contam só as movimentações feitas depois de o recurso entrar no ar — histórico antigo não verifica conta nenhuma sozinho.

Campos da resposta

CampoDescrição
verifiedtrue quando as ferramentas de lojista estão destravadas nesta conta.
verified_atQuando a verificação fechou (RFC 3339 UTC). null enquanto não verificada.
whatsapp_verified1 assim que a conta verificou o número de WhatsApp, 0 caso contrário. Ele barra o primeiro depósito: uma conta humana precisa passar por esse passo no app antes de verificar. Numa conta de agente é sempre 0 — ela é isenta, porque prova um domínio no lugar.
methodQual prova vale para esta conta. round_trip: receber de um CPF/CNPJ e sacar de volta para o mesmo documento. domain: provar um domínio por DNS TXT (contas de agente — POST /api/agents/verify-domain).
enabledfalse quando a verificação automática está desligada na plataforma inteira — fale com o suporte em vez de repetir.
eligibletrue quando todo requisito está satisfeito e o POST promoveria a conta. A promoção ainda precisa acontecer: conta suspensa nunca verifica.
requirementsO que a prova desta conta exige: deposit_cents, withdraw_cents, min_account_age_days, max_days_between_legs, domain_proof. Os membros que não se aplicam ao método vêm null.
progress / remaining / missingO que já foi feito, o que ainda falta em números e a lista do que está pendente.
stepsO checklist na ordem em que precisa ser cumprido — inclusive trocar um pouco de DePix por L-BTC, que é o que paga a taxa da rede Liquid: sem isso o saque não é sequer transmitido. Renderize steps como veio.
curl
curl https://api.depixapp.com/api/verification \
  -H "Authorization: Bearer $DEPIX_API_KEY"
Resposta — 200 OK (recebeu, ainda falta sacar de volta)
{
  "verified":          false,
  "verified_at":       null,
  "whatsapp_verified": 1,
  "method":            "round_trip",
  "enabled":           true,
  "eligible":          false,
  "requirements": { "deposit_cents": 9500, "withdraw_cents": 8500, "min_account_age_days": 0, "max_days_between_legs": 30, "domain_proof": false },
  "missing":  ["withdraw_leg"],
  "unlocks":  ["checkouts", "products"],
  "steps": [
    { "id": "deposit",      "state": "done",    "target_cents": 9500, "remaining_cents": 0 },
    { "id": "convert_lbtc", "state": "unknown", "target_cents": 500,  "remaining_cents": null },
    { "id": "withdraw",     "state": "pending", "target_cents": 8500, "remaining_cents": 8500 }
  ]
}
Erros do POST
account_blocked                    403   conta suspensa
verification_tax_number_in_use     409   esse CPF/CNPJ já verificou outra conta — um documento verifica uma conta só
(demais 409)                       409   o que falta vem em error.details.missing / error.details.remaining

Editar perfil da loja (PATCH /api/merchants/me)

Atualiza parcialmente o perfil da loja autenticada. Envie apenas os campos que deseja alterar. Aceita tanto o JWT do dashboard quanto uma API key com scope merchant_write.

PATCH /api/merchants/me
Por API key, só estes 5 campos leves. liquid_address (redireciona dinheiro), cnpj e a senha da conta não são editáveis por chave — enviá-los numa requisição autenticada por API key retorna 400 com error.code = "validation_error" e details.field nomeando o campo recusado. O split_address nunca é editável por este endpoint (só admin). Esses campos sensíveis só mudam pelo painel web, pelo dono da conta (endereço Liquid exige senha).

Parâmetros (todos opcionais — envie só o que muda)

CampoTipoDescrição
business_namestringNovo nome do negócio (2–100 caracteres). Alterar rotaciona o merchant_slug público e aposenta o antigo — qualquer link de pagamento ou URL de checkout construído sobre o slug antigo passa a retornar 404. Renomeie ciente disso.
websitestringNovo site da loja (normalizado para https://). Enviar null ou vazio limpa o campo.
logo_urlstringNova URL HTTPS do logo. null ou vazio limpa o campo.
default_callback_urlstringNovo endpoint HTTPS padrão de webhook para eventos deposit.* / withdraw.*. null ou vazio limpa o campo.
default_redirect_urlstringNova URL HTTPS padrão de redirecionamento pós-pagamento dos clientes da loja. null ou vazio limpa o campo.
Todo PATCH por API key notifica o dono por email, nomeando os campos alterados e o id da chave (controle compensatório). Consulte o perfil atual com GET /api/me.
curl
curl -X PATCH https://api.depixapp.com/api/merchants/me \
  -H "Authorization: Bearer $DEPIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "default_redirect_url": "https://loja.exemplo.com/obrigado" }'
Resposta — 200 OK
{
  "success":        true,
  "merchant_slug":  "loja-do-joao"       // muda apenas se business_name mudou
}
Resposta — 400 (campo só do dono, via API key)
{
  "response": { "errorMessage": "Este campo só pode ser alterado pelo dono da conta no painel web." },
  "error": {
    "code":        "validation_error",
    "message":     "This field can only be changed by the account owner in the web dashboard.",
    "request_id":  "gru1::abcd-1234",
    "docs_url":    "https://depixapp.com/docs/en/#errors",
    "details":     { "field": "liquid_address" }
  }
}

Audit log da chave

Toda operação de escrita feita com uma API key é auditada: ação, valor, recurso, request_id, IP, flag de sandbox e replays idempotentes — inclusive negações autenticadas (ex.: insufficient_scope, account_blocked, com a ação sufixada *.denied:<code>). O dono consulta o histórico de cada chave com o endpoint abaixo. Retenção: 90 dias. GETs e respostas 429 não são logados.

GET /api/api-keys/:id/audit
Somente JWT. Este endpoint aceita apenas o JWT do dashboard (login do dono) — nunca uma API key. Uma chave não enxerga o próprio audit log.

Query params (todos opcionais)

ParâmetroDescrição
limitResultados por página. Padrão: 50. Mínimo: 1. Máximo: 100.
offsetPaginação. Padrão: 0.
curl
curl "https://api.depixapp.com/api/api-keys/a1b2c3d4e5f6/audit?limit=50&offset=0" \
  -H "Authorization: Bearer <jwt-do-dashboard>"
Resposta — 200 OK
{
  "audit": [
    {
      "id":           "aud_01jxxxxxxxxxxxxxxxxxxxxxx",
      "action":       "withdraw.create",
      "method":       "POST",
      "path":         "/api/withdraw",
      "status_code":  200,
      "amount_cents": 10000,
      "resource_id":  "wd-123",
      "is_sandbox":   0,
      "is_replay":    0,       // 1 = replay idempotente (handler não rodou)
      "ip":           "203.0.113.9",
      "request_id":   "gru1::iad1::v9x4k-1751476800000-abc123",
      "created_at":   "2026-07-02T14:03:11.000Z"
    }
  ],
  "total": 123
}

Rate limits

A API aplica limites de requisições para garantir estabilidade e proteger contra abusos.

EndpointLimiteEscopo
POST /api/checkouts30 / minpor IP
POST /api/checkouts/:id/simulate-payment60 / min por IP · 30 / min por chaveautenticado (scope merchant_write)
POST /api/products60 / min por IP · 30 / min por chaveautenticado (scope merchant_write)
POST /api/products/featured60 / min por IP · 30 / min por chaveautenticado (scope merchant_write)
PATCH /api/products/:id60 / min por IP · 30 / min por chaveautenticado (scope merchant_write)
POST /api/products/:id/deactivate60 / min por IP · 30 / min por chaveautenticado (scope merchant_write)
POST /api/products/:id/activate60 / min por IP · 30 / min por chaveautenticado (scope merchant_write)
POST /api/deposit20 / min por IP · 2 / min por chaveautenticado (scope wallet_write)
POST /api/withdraw20 / min por IP · 2 / min por chaveautenticado (scope wallet_write)
GET /api/deposits/:id60 / min por IP · 30 / min por chaveautenticado (scope wallet_read)
GET /api/withdrawals/:id60 / min por IP · 30 / min por chaveautenticado (scope wallet_read)
GET /api/api-keys/:id/audit60 / min por IP · 30 / min por usuárioautenticado (JWT)
PATCH /api/merchants/me30 / min por IP · 10 / min por chaveautenticado (scope merchant_write)
GET /api/checkout-page/:id30 / minpor IP (público)
GET /api/pay/:id60 / minpor IP (público)
POST /api/pay/:id/simulate5 / minpor IP (público, só sandbox)
POST /api/merchants/:username/checkout10 / min por IP · 60 / min por lojistapúblico
POST /api/products/:id/checkout10 / min por IP · 60 / min por lojistapúblico
GET /api/products/:id/public30 / minpor IP (público)
GET /api/merchants/:username/public30 / minpor IP (público)
Por merchant (API key)30 / min (padrão) — configurávelcompartilhado entre todos os endpoints da chave; elevável via suporte
Por chave (rate_limit_per_min)Configurável na criação da chavecheck adicional por API key (1–600 req/min)
  • Para requests autenticados com API key, um rate limit adicional é aplicado por merchant (padrão 30 req/min, compartilhado entre todos os endpoints — fale com o suporte se precisar de aumento) e, quando definido na criação, por chave (rate_limit_per_min).
  • As escritas autenticadas alcançáveis por API key — criar/simular checkout e o CRUD de produtos — têm limite por-endpoint (60/min por IP · 30/min por chave), listado na tabela acima. As leituras do lado "receber" (listar checkouts e produtos, GET /api/me, etc.) não têm limite por-endpoint próprio — são cobertas pelo orçamento por merchant (padrão 30 req/min, configurável). O caminho JWT/SPA também é limitado, não só o de API key.
  • Nas rotas de deposit/withdraw, o contador por usuário vale por chave — cada API key tem budget próprio, sem competir com o dono na SPA.
  • Para endpoints públicos (sem auth), o rate limit é aplicado apenas por IP.
  • Quando o limite é atingido, a API retorna 429 com error.code = "rate_limited" (ou "merchant_rate_limited"), o campo error.retry_after e o header Retry-After — aguarde os segundos indicados antes de tentar de novo.
  • Em rotas wallet_* autenticadas por API key, falha de infraestrutura no check de rate limit responde 503 service_unavailable com retry_after (fail-closed) em vez de deixar o request passar.
  • Velocidade por pagador: no máximo 2 QRs por CPF/CNPJ do pagador em uma janela deslizante de 30 minutos (depósitos e checkouts contam juntos). A partir do 3º, a API retorna 429 com error.code = "payer_velocity_limit", details: { window_minutes, max_per_window } e o header Retry-After indicando os segundos até liberar.
Se você precisa de limites maiores para sua integração, entre em contato pelo suporte.

Abrir um ticket

Abre um ticket de suporte. Os endpoints /api/tickets funcionam tanto para usuários humanos (o JWT do dashboard) quanto para agentes de IA (uma chave sk_live_/sk_test_) — nenhum escopo é exigido. Uma requisição assinada com sk_ se comporta exatamente como a com JWT. Cada sessão ou chave só enxerga os tickets que ela mesma criou; os tickets de outro principal são invisíveis.

O suporte responde em até 1 dia útil. É uma fila humana, não um canal em tempo real — faça polling de GET /api/tickets/{id} a cada poucos minutos, não segundos. Para um agente, esse polling é como ele lê a resposta.
POST /api/tickets

Parâmetros

CampoTipoDescrição
subjectstringobrigatórioResumo curto. 4–120 caracteres.
bodystringobrigatórioA mensagem. 1–4000 caracteres.
categorystringopcionalUm de bug, question, account, payment, other. Padrão other.

Campos do ticket

CampoValoresDescrição
statusawaiting_reply · answered · closedawaiting_reply = é a vez do suporte responder (nunca fecha sozinho). answered = o suporte respondeu e aguarda você (fecha sozinho após 2 dias úteis sem resposta do usuário). closed = terminal, embora um ticket fechado automaticamente reabra se você responder em até 7 dias.
opener_typehuman · agentQuem abriu o ticket.
closed_reasonnull · user · admin · autoPor que fechou. null enquanto aberto.
categorybug · question · account · payment · otherDefinido na criação.
curl
curl -X POST https://api.depixapp.com/api/tickets \
  -H "Authorization: Bearer $DEPIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "subject": "Saque preso como pendente",
    "category": "payment",
    "body": "O saque wtd_123 está pendente há 2 horas."
  }'
Resposta — 201 Created
{
  "ticket": {
    "id": "tkt_ab12cd34ef",
    "opener_type": "human",
    "status": "awaiting_reply",
    "subject": "Saque preso como pendente",
    "category": "payment",
    "created_at": "2026-07-22T12:00:00.000Z",
    "last_activity_at": "2026-07-22T12:00:00.000Z",
    "closed_reason": null,
    "closed_at": null
  }
}
Erros
validation_error   400   campo inválido (details.field; irmão legado response.errors[])
ticket_open_cap    429   tickets abertos demais (details.max_open)
unauthorized       401   credencial ausente / inválida

O limite é de 5 tickets abertos simultaneamente (1 para contas suspensas); error.details.max_open traz o teto. Erros de validação também expõem o irmão legado response.errors[] ao lado de error.details.field.

Listar seus tickets

Lista os tickets criados pelo principal que faz a chamada (esta sessão JWT ou esta API key), da atividade mais recente para a mais antiga. Paginado.

GET /api/tickets

Parâmetros de query

CampoTipoDescrição
limitintegeropcionalTamanho da página. Padrão 50.
offsetintegeropcionalLinhas a pular. Padrão 0.
curl
curl "https://api.depixapp.com/api/tickets?limit=50&offset=0" \
  -H "Authorization: Bearer $DEPIX_API_KEY"
Resposta — 200 OK
{
  "tickets": [
    {
      "id": "tkt_ab12cd34ef",
      "opener_type": "human",
      "status": "awaiting_reply",
      "subject": "Saque preso como pendente",
      "category": "payment",
      "created_at": "2026-07-22T12:00:00.000Z",
      "last_activity_at": "2026-07-22T12:00:00.000Z",
      "closed_reason": null,
      "closed_at": null
    }
  ],
  "total": 1,
  "limit": 50,
  "offset": 0
}

Detalhe do ticket & mensagens

Retorna um ticket com toda a thread de mensagens, da mais antiga para a mais recente. Fazer polling deste endpoint é como você (ou um agente) lê uma resposta do suporte.

GET /api/tickets/{id}
curl
curl https://api.depixapp.com/api/tickets/tkt_ab12cd34ef \
  -H "Authorization: Bearer $DEPIX_API_KEY"
Resposta — 200 OK
{
  "ticket": {
    "id": "tkt_ab12cd34ef",
    "opener_type": "human",
    "status": "answered",
    "subject": "Saque preso como pendente",
    "category": "payment",
    "created_at": "2026-07-22T12:00:00.000Z",
    "last_activity_at": "2026-07-22T13:15:00.000Z",
    "closed_reason": null,
    "closed_at": null
  },
  "messages": [
    { "id": "tmsg_1", "sender": "user",  "body": "O saque wtd_123 está pendente há 2 horas.", "created_at": "2026-07-22T12:00:00.000Z" },
    { "id": "tmsg_2", "sender": "admin", "body": "Acabou de liquidar — pode confirmar?", "created_at": "2026-07-22T13:15:00.000Z" }
  ]
}

O sender de uma mensagem é um de user, admin ou system.

Erros
not_found   404   ticket inexistente — ou pertence a outro principal

Um ticket que não é seu retorna o mesmo 404 not_found de um inexistente — a posse nunca é revelada.

Responder

Anexa uma mensagem do usuário à thread. Responder um ticket answered o traz de volta para awaiting_reply. Responder um ticket fechado automaticamente em até 7 dias o reabre. Tickets fechados por um usuário ou por um admin são terminais — uma resposta ali falha com 409 ticket_closed.

POST /api/tickets/{id}/messages

Parâmetros

CampoTipoDescrição
bodystringobrigatórioA resposta. 1–4000 caracteres.
curl
curl -X POST https://api.depixapp.com/api/tickets/tkt_ab12cd34ef/messages \
  -H "Authorization: Bearer $DEPIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "body": "Confirmado, os fundos chegaram. Obrigado!" }'
Resposta — 201 Created
{
  "message": { "id": "tmsg_3", "sender": "user", "body": "Confirmado, os fundos chegaram. Obrigado!", "created_at": "2026-07-22T13:20:00.000Z" },
  "ticket": {
    "id": "tkt_ab12cd34ef",
    "opener_type": "human",
    "status": "awaiting_reply",
    "subject": "Saque preso como pendente",
    "category": "payment",
    "created_at": "2026-07-22T12:00:00.000Z",
    "last_activity_at": "2026-07-22T13:20:00.000Z",
    "closed_reason": null,
    "closed_at": null
  }
}
Erros
validation_error   400   body inválido (details.field)
not_found          404   ticket inexistente (ou não é seu)
ticket_closed      409   ticket fechado por um usuário ou admin (terminal)

Anexar arquivo

Envia um arquivo (imagem, PDF, log ou JSON) para a equipe de suporte — ideal para um print de tela ou um arquivo de diagnóstico ao relatar um bug. Os bytes vão em file_b64 (base64, sem o prefixo data:), até ~3 MB. O arquivo é encaminhado ao suporte, não é armazenado nem servido de volta — a resposta registra apenas o nome e o tipo. Anexar conta como uma resposta: um ticket answered volta para awaiting_reply e um ticket fechado automaticamente em até 7 dias reabre.

POST /api/tickets/{id}/attachments

Parâmetros

CampoTipoDescrição
filenamestringobrigatórioNome do arquivo mostrado ao suporte. 1–200 caracteres.
content_typestringobrigatórioUm de: image/png, image/jpeg, image/webp, application/pdf, text/plain, application/json.
file_b64stringobrigatórioOs bytes do arquivo em base64 (sem o prefixo data:). Máx. ~3 MB decodificado.
captionstringopcionalNota curta exibida junto ao arquivo. Máx. 400 caracteres.
curl
curl -X POST https://api.depixapp.com/api/tickets/tkt_ab12cd34ef/attachments \
  -H "Authorization: Bearer $DEPIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "filename": "erro-checkout.png", "content_type": "image/png", "file_b64": "iVBORw0KGgo…" }'
Resposta — 201 Created
{
  "message": {
    "id": "tmsg_5",
    "sender": "user",
    "body": "erro-checkout.png",
    "created_at": "2026-07-22T14:05:00.000Z",
    "attachment": { "name": "erro-checkout.png", "mime": "image/png" }
  },
  "ticket": {
    "id": "tkt_ab12cd34ef",
    "opener_type": "human",
    "status": "awaiting_reply",
    "subject": "Saque preso como pendente",
    "category": "payment",
    "created_at": "2026-07-22T12:00:00.000Z",
    "last_activity_at": "2026-07-22T14:05:00.000Z",
    "closed_reason": null,
    "closed_at": null
  }
}
Erros
validation_error        400   content_type/file_b64 inválido (details.field)
attachment_too_large    413   arquivo acima de ~3 MB
not_found               404   ticket inexistente (ou não é seu)
ticket_closed           409   ticket fechado por um usuário ou admin (terminal)
attachment_unavailable  503   sem canal de suporte agora — tente de novo ou use texto

Fechar um ticket

Fecha o ticket você mesmo, definindo closed_reason como user. Um ticket fechado pelo usuário é terminal — para continuar a conversa você abre um novo ticket.

POST /api/tickets/{id}/close
curl
curl -X POST https://api.depixapp.com/api/tickets/tkt_ab12cd34ef/close \
  -H "Authorization: Bearer $DEPIX_API_KEY"
Resposta — 200 OK
{
  "ticket": {
    "id": "tkt_ab12cd34ef",
    "opener_type": "human",
    "status": "closed",
    "subject": "Saque preso como pendente",
    "category": "payment",
    "created_at": "2026-07-22T12:00:00.000Z",
    "last_activity_at": "2026-07-22T13:25:00.000Z",
    "closed_reason": "user",
    "closed_at": "2026-07-22T13:25:00.000Z",
    "rating": null,
    "rated_at": null
  }
}
Erros
not_found       404   ticket inexistente (ou não é seu)
ticket_closed   409   já fechado

Avaliar o atendimento

Dá uma nota de 0 a 10 ao atendimento de um ticket já encerrado (0 = pior, 10 = melhor). A avaliação é única: a segunda chamada devolve 409 already_rated, então a primeira nota nunca é sobrescrita em silêncio.

Avaliar apenas registra o dado — não reabre o ticket, não conta como mensagem e não altera last_activity_at.

POST /api/tickets/{id}/rating
CampoTipoObrigatórioDescrição
ratingintegersimNota inteira de 0 a 10. Valores fora da faixa, fracionários ou não numéricos retornam 400 validation_error.
curl
curl -X POST https://api.depixapp.com/api/tickets/tkt_ab12cd34ef/rating \
  -H "Authorization: Bearer $DEPIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"rating": 10}'
Resposta — 200 OK
{
  "ticket": {
    "id": "tkt_ab12cd34ef",
    "opener_type": "human",
    "status": "closed",
    "subject": "Saque preso como pendente",
    "category": "payment",
    "created_at": "2026-07-22T12:00:00.000Z",
    "last_activity_at": "2026-07-22T13:25:00.000Z",
    "closed_reason": "admin",
    "closed_at": "2026-07-22T13:25:00.000Z",
    "rating": 10,
    "rated_at": "2026-07-22T13:31:00.000Z"
  }
}
Erros
validation_error  400   nota ausente, fora de 0–10 ou não inteira
not_found         404   ticket inexistente (ou não é seu)
ticket_open       409   o ticket ainda está aberto
already_rated     409   este ticket já foi avaliado

Contas de agente

Uma conta de agente é um merchant que um agente de IA cria e opera sozinho — sem e-mail, WhatsApp ou captcha. É provisionada e operada inteiramente pelos endpoints /api/agents/* abaixo. A maioria dos agentes os acessa pelo SDK DePix (a classe DepixAgent), que assina cada requisição por você.

Autenticação por requisição assinada

Os endpoints de agente não usam uma API key estática. Cada requisição é assinada com a chave Ed25519 da conta (gerada e guardada localmente pelo agente). Envie estes headers em cada chamada:

HeaderValor
x-agent-public-keyChave pública Ed25519 crua, 64 hex
x-agent-signatureAssinatura Ed25519 da string canônica, 128 hex
x-agent-nonceÚnica por requisição — uso único, válida ~11 min
x-agent-timestampSegundos Unix, dentro de ±300s do horário do servidor
String canônica — unida por quebras de linha, depois assinada com Ed25519
depix-agent-auth:v1
api.depixapp.com
<METHOD>
<path-without-query>
<timestamp>
<nonce>
<sha256hex(raw-request-body)>
O SDK monta e assina isso por você — DepixAgent.create() gera o par de chaves e cada chamada agent.* é assinada automaticamente. A referência abaixo é para quem constrói o próprio cliente.

Onboarding e graduação

Um agente novo começa com uma chave sandbox sk_test_ e uma chave sk_live_ starter (só wallet), limitada a R$100/tx e R$500/dia. A conta gradua — e passa a emitir chaves sk_live_ completas — quando fica verificada. Para um agente, verificar a conta é provar um domínio por DNS: é o mesmo domínio que libera receber de terceiros (checkouts, escopos merchant_*), então uma coisa destrava a outra.

O atraso de 24h (inter_deposit_delay_hours) aplicado aos depósitos 2–5 segura a liquidação (a entrega do DePix) daqueles depósitos — ele não bloqueia criar o próximo depósito, que pode ser criado em seguida.

A posição do depósito é contada por conta, não por canal: um checkout Pix conta igual a um QR pessoal. As 24h valem para depósitos de até R$ 100 — acima disso vale a espera do nível da conta, que é maior. O 1º depósito de até R$ 100 liquida na hora; do 6º em diante valem o teto de recebimento e a faixa instantânea. Um checkout pago pelo trilho DePix (Liquid) fica fora de todas essas regras, por não passar pelo trilho Pix.

Erros de requisição assinada
agent_invalid_signature   401   assinatura não confere
agent_signature_expired   401   timestamp fora de ±300s
agent_replay_detected     401   nonce já usado
agent_unknown_key         401   chave não registrada (rotas autenticadas)
account_suspended         403   conta pausada (rotas mutantes)
agents_disabled           503   kill-switch de onboarding de agente ligado

Registrar um agente

Cria uma conta de agente — um merchant, um endereço Liquid de recebimento e as chaves starter. Exige um operator token (op_…) que um humano obtém conectando uma identidade (GitHub/Google) em https://api.depixapp.com/api/agents/oauth/start — a âncora anti-abuso. O token não é show-once: a mesma página mostra o mesmo token a cada login, então perdê-lo não custa nada — basta entrar de novo.

POST /api/agents/register

Parâmetros

CampoTipoDescrição
namestringobrigatórioNome de exibição. 2–100 caracteres.
operator_tokenstringobrigatórioO token op_… do operador humano.
operator_emailstringobrigatórioE-mail de notificação (nunca um login).
liquid_addressstringobrigatórioEndereço de recebimento da wallet. Imutável após o registro.
usernamestringopcionalHandle minúsculo. Padrão: agent_<prefixo-da-pubkey>.
default_callback_urlstringopcionalURL HTTPS de webhook.
refstringopcionalUsername de indicação, só para atribuição.
curl
curl -X POST https://api.depixapp.com/api/agents/register \
  -H "x-agent-public-key: <64hex>" \
  -H "x-agent-signature: <128hex>" \
  -H "x-agent-nonce: <unique>" -H "x-agent-timestamp: <unix>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My Agent",
    "operator_token": "op_...",
    "operator_email": "me@example.com",
    "liquid_address": "lq1..."
  }'
Resposta — 201 Created
{
  "response": {
    "agent":    { "username": "agent_9f3c2a1b0d", "public_key": "<64hex>", "account_type": "agent" },
    "merchant": { "id": "mrc_...", "merchant_slug": "agent_9f3c2a1b0d", "liquid_address": "lq1...", "webhook_secret": "whsec_..." },
    "keys": {
      "test":         { "id": "...", "key": "sk_test_...", "scopes": "merchant_read merchant_write wallet_read wallet_write" },
      "live_starter": { "id": "...", "key": "sk_live_...", "scopes": "wallet_read wallet_write", "per_tx_limit_cents": 10000, "daily_limit_cents": 50000, "starter": true }
    },
    "graduation": {
      "requires": "domain_proof",                                  // o que falta: provar um domínio
      "verify_domain_endpoint": "POST /api/agents/verify-domain",  // onde provar
      "allowed_tlds_endpoint": "GET /api/agents/domain-tlds"       // TLDs aceitos — consulte antes
    },
    "pacing": {
      "first_deposit_max_cents": 10000,
      "unverified_per_tx_max_cents": 10000,
      "inter_deposit_delay_hours": 24,   // atrasa a liquidação dos depósitos 2–5 — não bloqueia criar o próximo
      "payer_velocity": { "max_per_window": 2, "window_minutes": 30 },
      "verified_per_tx_deposit_max_cents": 600000,
      "verified_per_tx_withdraw_send_max_cents": 600000,     // teto de depositAmountInCents — quanto você envia
      "verified_per_tx_withdraw_receive_max_cents": 600000  // teto de payoutAmountInCents — quanto cai na conta, já sem as taxas
    }
  }
}

As chaves em texto plano e o webhook_secret são devolvidos uma única vez.

Erros
validation_error               400   campo inválido (details.field)
invalid_operator_token         401   operator token inválido
operator_token_revoked         403
agent_pubkey_exists            409   essa chave já tem uma conta
username_taken                 409
operator_register_cap_exceeded 429   teto do operador (details.window_hours) — aguarde retry_after
agents_disabled                503

O 429 é o teto anti-fazenda do próprio operator token: um op_ só abre um punhado de contas-agente dentro de uma janela deslizante. details.max_per_window e details.window_hours trazem o formato exato, e retry_after os segundos até liberar uma vaga. Bater nele sem ter aberto essas contas significa que o token vazou — peça ao operador para revogá-lo. Os dois tetos do registro, este e o por IP, falham fechados: quando a infraestrutura que faz a contagem está indisponível, o endpoint responde 503 em vez de deixar o registro passar.

Criar uma chave

Emite uma nova API key para a conta do agente. Chaves live exigem graduação; escopos merchant_* exigem um domínio verificado.

POST /api/agents/keys

Parâmetros

CampoTipoDescrição
livebooleanopcionalPadrão false. true emite sk_live_ (exige graduação).
scopesstring[]opcionalSubconjunto de merchant_read, merchant_write, wallet_read, wallet_write. Padrão ["merchant_read","merchant_write"].
labelstringopcionalAté 100 caracteres.
per_tx_limit_centsintegeropcionalMín 100. Obrigatório quando a chave tem wallet_write (padrão 10000).
daily_limit_centsintegeropcionalMín 100. Padrão 50000 com wallet_write.
Resposta — 201 Created
{
  "response": {
    "id": "...", "key": "sk_live_...", "prefix": "sk_live_",
    "is_live": true, "scopes": "wallet_read wallet_write",
    "per_tx_limit_cents": 10000, "daily_limit_cents": 50000
  }
}

O key em texto plano é devolvido uma vez. Máximo de 5 chaves ativas por tipo (live / test).

Erros
validation_error   400   scopes / label / limites inválidos, ou limite de 5 chaves
graduation_pending 403   live:true antes da graduação
domain_required    403   escopo merchant_* sem domínio verificado

Revogar uma chave

Revoga uma das chaves da conta. Idempotente — revogar uma chave já revogada ainda retorna sucesso.

POST /api/agents/keys/revoke

Parâmetros

CampoTipoDescrição
idstringobrigatórioO id da chave a revogar. Deve pertencer a este agente.
Resposta — 200 OK
{ "response": { "id": "...", "revoked": true } }

not_found (404) quando a chave não pertence ao merchant deste agente.

Status da conta

Lê o estado da conta e o progresso de graduação. Continua disponível mesmo com a conta suspensa, para o agente conseguir ler o motivo.

Assimetria importante: a suspensão da sua conta mantém esta leitura disponível (200, com o campo reason). Já o kill-switch global do programa de agentes responde 503 agents_disabled até para esta leitura — trate 503 agents_disabled como pausa da plataforma inteira, não como suspensão da sua conta.

GET /api/agents/status
Resposta — 200 OK
{
  "response": {
    "account_status": "active",          // active | suspended
    "graduated": false,
    "graduation": { "blocked_on": "domain_proof" },  // "domain_proof": prove um domínio (é com você)
                                                     // "gate_review": verificado, graduação ainda não caiu — poll
                                                     // null: já graduou
    "keys": [
      { "id": "...", "prefix": "sk_test_", "is_live": false, "starter": false, "scopes": "...", "revoked_at": null }
    ]
    // "reason": "..."  — presente só quando suspensa
  }
}

Logs de entrega de webhook

Auditoria somente-leitura das entregas dos eventos checkout.*, deposit.* e withdraw.* às callback URLs desta conta — o que foi entregue, retentado ou falhou. A listagem retorna as 50 tentativas mais recentes (da mais nova para a mais antiga) sem os corpos; busque um log pelo id para ver os payloads de requisição/resposta. Um log de outra conta responde 404.

GET /api/agents/webhook-logs
GET /api/agents/webhook-logs/:id
curl
curl https://api.depixapp.com/api/agents/webhook-logs \
  -H "x-agent-public-key: <64hex>" \
  -H "x-agent-signature: <128hex>" \
  -H "x-agent-nonce: <unique>" -H "x-agent-timestamp: <unix>"
Resposta — 200 OK (listagem)
{
  "logs": [
    { "id": "...", "checkout_id": null, "event": "deposit.depix_sent", "url": "https://...", "status_code": 200, "error": null, "attempt": 1, "sent_at": "..." }
  ]
}
Resposta — 200 OK (detalhe: inclui os corpos)
{
  "log": {
    "id": "...", "event": "checkout.completed", "url": "https://...", "status_code": 200,
    "request_body": "{...}",   // o payload assinado enviado (X-DePix-Signature cobre estes bytes)
    "response_body": "{...}",  // o que o receptor respondeu
    "error": null, "attempt": 1, "next_retry_at": null, "sent_at": "..."
  }
}
Erros
merchant_required  403   sem perfil de lojista ATIVO (ex.: conta suspensa)
not_found          404   log não existe ou pertence a outra conta

Verificar um domínio

Prova por DNS TXT em duas fases. Libera o recebimento de terceiros (checkouts / escopos merchant_*). O domínio é normalizado para sua raiz registrável (ex.: shop.acme.com.bracme.com.br).

POST /api/agents/verify-domain

Parâmetros

CampoTipoDescrição
domainstringobrigatórioO domínio a verificar. O TLD precisa estar na allowlist.
confirmbooleanopcionalOmita na fase 1 (pegar o token). Envie true na fase 2 (checar o registro TXT).
Fase 1 (sem confirm) — 200 OK
{
  "record_name":  "_depix-verify.acme.com.br",
  "record_value": "depix-verify=<token 32-hex>"
}
Fase 2 (confirm: true, após adicionar o registro TXT) — 200 OK
{ "verified_domain": "acme.com.br", "verified": true }  // verified: a conta ficou verificada — a graduação vem em seguida
Erros
validation_error      400   details.field: "domain"
domain_tld_not_allowed 422   details.allowed_tlds: [...]
domain_free_host      422   vercel.app / netlify.app / github.io / ... negados
domain_txt_not_found  422   TXT ausente/divergente — tente após propagar o DNS

Allowlist de TLDs

Público. Retorna os sufixos de TLD aceitos pela verificação de domínio.

GET /api/agents/domain-tlds
curl
curl https://api.depixapp.com/api/agents/domain-tlds
Resposta — 200 OK
{ "allowed_tlds": [".com", ".net", ".org", ".io", ".ai", ".dev", ".app", ".com.br", ".br", ".store", ".shop", "..."] }

Ligar o trilho DePix

Registra (ou remove) o endereço Liquid confidencial dedicado em que o merchant do agente é pago pelo trilho DePix — assim os checkouts dele passam a ser pagos em DePix on-chain, e não só por Pix. A porta gêmea humana (POST /api/merchants/me/depix-pay) é protegida por senha; um agente não tem senha nem navegador, então prova a intenção com a assinatura do par de chaves, nunca uma Bearer key.

Ligar exige o endereço e a chave de visão (a blinding key privada): a chave é a prova de posse — o endereço é reconstruído a partir dela e precisa bater — e é a única coisa que nos deixa ler os valores que chegam ali. Ela é selada em repouso e nunca é devolvida. O endereço precisa ser confidencial (lq1…) e exclusivo: os endereços de payout e de split são recusados. Desligar não pede chave nenhuma. Exige uma conta verificada (para um agente, o domínio provado) e recusa um merchant suspenso.

POST /api/agents/depix-pay

Parâmetros

CampoTipoDescrição
enabledbooleanobrigatóriotrue registra o endereço dedicado e começa a observá-lo; false desliga e apaga a chave de visão (nenhum outro campo é lido nem exigido).
addressstringcondicionalObrigatório quando enabled é true. Endereço Liquid confidencial (lq1…) dedicado a este recebimento. Um endereço não confidencial (ex1/base58) é recusado — publicaria cada valor recebido. Precisa ser exclusivo: nunca o de payout nem o de split.
blinding_keystringcondicionalObrigatório quando enabled é true. A blinding key privada de 32 bytes desse endereço, em hex. Dá visibilidade só dos valores que chegam àquele script — nenhum poder de gasto, nenhum outro endereço. Enviada uma vez, guardada selada e apagada ao desligar.
derivation_indexintegeropcionalÍndice de derivação do endereço na carteira, devolvido no eco para o índice seguir reservado num restore. Inteiro não negativo.
curl — ligar
curl -X POST https://api.depixapp.com/api/agents/depix-pay \
  -H "x-agent-public-key: <64hex>" \
  -H "x-agent-signature: <128hex>" \
  -H "x-agent-nonce: <unique>" -H "x-agent-timestamp: <unix>" \
  -H "Content-Type: application/json" \
  -d '{
    "enabled": true,
    "address": "lq1qq...",
    "blinding_key": "<64hex>",
    "derivation_index": 12
  }'
curl — desligar
curl -X POST https://api.depixapp.com/api/agents/depix-pay \
  -H "x-agent-public-key: <64hex>" \
  -H "x-agent-signature: <128hex>" \
  -H "x-agent-nonce: <unique>" -H "x-agent-timestamp: <unix>" \
  -H "Content-Type: application/json" \
  -d '{ "enabled": false }'
Resposta — 200 OK (ligado)
{
  "response": {
    "depix_pay_enabled": true,
    "depix_pay_address": "lq1qq...",   // o endereço registrado (em minúsculas)
    "depix_derivation_index": 12,   // o índice enviado, ecoado; null se nenhum
    "depix_discount_pct": 0        // desconto atual do trilho; esta chamada não mexe nele
  }
}
Resposta — 200 OK (desligado)
{
  "response": {
    "depix_pay_enabled": false,
    "view_key_deleted": true,     // ao menos uma chave de visão foi apagada
    "pending_addresses": 0       // endereços que mantiveram a chave por um checkout DePix ainda aberto
  }
}
Erros
validation_error          400   corpo malformado (details.field) — ex.: enabled ausente
depix_address_unsupported 400   endereço não confidencial (não lq1…) ou indecodificável
depix_address_conflict    400   endereço não exclusivo (payout/split) ou já registrado
invalid_blinding_key      400   a chave de visão não corresponde ao endereço
agent_unknown_key         401   assinatura falhou ou chave não registrada
verification_required     403   conta não verificada — para um agente, prove um domínio
account_suspended         403   merchant suspenso (account_blocked se bloqueado)
service_unavailable       503   trilho indisponível (KEK ausente / anti-replay fora do ar)
agents_disabled           503   programa de agentes desligado globalmente

Gateway MCP

O gateway MCP da DePix é um servidor Model Context Protocol hospedado que deixa qualquer cliente MCP — Claude, Cursor, ChatGPT — receber Pix sem custódia: criar checkouts e produtos, e ler status de pagamento. É um cliente fino e stateless na frente desta mesma API REST; não guarda chaves e nunca move dinheiro.

POST https://mcp.depixapp.com/mcp

O transporte é MCP Streamable HTTP (stateless). O pacote @depixapp/mcp também roda localmente via stdio (npx -y @depixapp/mcp).

É um servidor só, em dois níveis de acesso. Conectado a este endpoint hospedado, ele é o lado de receber (sem custódia) e expõe 26 ferramentas. Para guardar, assinar e mover fundos — depósitos, saques, conversões — rode o mesmo pacote na sua máquina (npx -y @depixapp/mcp, stdio): com uma seed local ele expõe 60. O que separa os níveis é quem guarda a seed, não o transporte — este endpoint não guarda nenhuma, e por isso não move fundos.

Conectar um cliente

Autentique com uma API key DePix no header Authorizationsk_test_ para sandbox, sk_live_ para produção. A chave é repassada verbatim à API a cada requisição e nunca é armazenada.

Claude Code (HTTP remoto)
claude mcp add --transport http depix https://mcp.depixapp.com/mcp \
  --header "Authorization: Bearer sk_test_YOUR_KEY"
Cursor — ~/.cursor/mcp.json
{
  "mcpServers": {
    "depix": {
      "url": "https://mcp.depixapp.com/mcp",
      "headers": { "Authorization": "Bearer sk_test_YOUR_KEY" }
    }
  }
}
Claude Desktop (stdio local) — claude_desktop_config.json
{
  "mcpServers": {
    "depix": {
      "command": "npx",
      "args": ["-y", "@depixapp/mcp"],
      "env": { "DEPIX_API_KEY": "sk_test_YOUR_KEY" }
    }
  }
}

claude.ai / ChatGPT conectam por OAuth (sem header custom). Faça login pelo conector e depois vincule esse login à sua conta DePix no app, em Agentes de IA. Sessões OAuth ficam limitadas a escopos de leitura + merchant e nunca movem dinheiro — para isso use uma chave sk_. Teste qualquer conexão com a ferramenta get_account.

Escopos

Cada ferramenta precisa de um escopo na chave: merchant_read, merchant_write, wallet_read. Uma chamada sem o escopo retorna insufficient_scope dizendo o que falta.

Comandos do terminal (modo local)

Rodando o pacote na sua máquina (npx -y @depixapp/mcp), cinco comandos são seus, e não do agente. Nenhum deles é uma ferramenta MCP, de propósito: dois mostram as 12 palavras da carteira — que nunca podem passar pelo contexto do modelo nem por um histórico de conversa — e os outros três decidem com qual conta o servidor age. Como ferramenta, um agente que leu uma página envenenada poderia se promover da conta de teste dele para a sua.

ComandoO que fazQuando usarA garantia
initCria a carteira local — com --restore, importa uma seed de 12 palavras que você já tem — e liga a ela os apps de IA que encontrar na máquina.Uma vez, antes de qualquer ferramenta wallet_* funcionar.Só roda em terminal de verdade: recusa quando a entrada ou a saída não é um terminal. A senha não aparece na tela enquanto você digita e nunca vai para o arquivo de configuração do app.
backupMostra de novo as 12 palavras desta carteira.Quando precisar copiar a seed no papel outra vez.Terminal de verdade ou nada. A senha é digitada toda vez, mesmo numa máquina que abre a carteira sozinha, e a tela é limpa no fim.
loginEntra na sua própria conta DePix pelo navegador (Google ou GitHub) e guarda essa sessão cifrada nesta máquina.Quando o servidor deve agir como você, e não como a conta que o agente abriu para si.Quem faz o login é o navegador, e a resposta volta para esta mesma máquina (127.0.0.1). Nenhum token é impresso, registrado em log ou devolvido numa mensagem de erro.
logoutRemove esse login desta máquina.Ao terminar, ou numa máquina que você não controla mais.Desfaz o login inteiro, inclusive uma escolha account use owner que passaria a apontar para o nada.
account status / account use agent|ownerDiz qual conta está agindo e por quê, ou escolhe uma delas.Sempre que houver dúvida sobre quem está gastando — e depois de um login numa máquina que já tinha conta de agente.Ler e escolher acontecem no seu terminal; nenhum agente troca a identidade. Atenção: DEPIX_API_KEY no ambiente do servidor vence qualquer escolha, e o status avisa quando é esse o caso.

Nas versões 2.8.0 e 2.8.1 do pacote, o login exige DEPIX_WORKOS_CLIENT_ID apontando para a aplicação de entrada da DePix — o identificador embutido nessas duas versões aponta para uma aplicação antiga; a partir da 2.8.2 o correto já vem embutido e o comando funciona sem configuração. Detalhe completo no README do pacote: github.com/depixapp/depix-mcp.

Ferramentas

São 26 ferramentas no endpoint hospedado e 60 rodando o mesmo pacote localmente com uma seed (npx -y @depixapp/mcp). Todos os valores em centavos. O nível hospedado nunca cria depósitos ou saques: sem seed, não há o que assinar.

Gateway — 26 ferramentas (hospedado e local)

FerramentaEscopoO que faz
create_checkoutmerchant_writeCria um checkout Pix (exige payer_tax_number).
get_checkoutmerchant_readBusca um checkout por id.
list_checkoutsmerchant_readLista/filtra checkouts.
wait_for_checkoutmerchant_readPoll no servidor até o estado final; emite progresso.
simulate_checkout_paymentmerchant_writeSó sandbox: marca um checkout como pago.
create_productmerchant_writeCria um link de pagamento reutilizável.
list_productsmerchant_readLista/filtra produtos.
get_productmerchant_readBusca um produto + agregados.
update_productmerchant_writeAtualiza campos do produto.
activate_product / deactivate_productmerchant_writeLiga/desliga um produto.
set_featured_productsmerchant_writeFixa produtos na vitrine.
list_product_checkoutsmerchant_readCheckouts de um produto.
get_accountmerchant_readVerifica a chave / a conexão.
get_deposit_statuswallet_readLê o status de um depósito.
get_withdrawal_statuswallet_readLê o status de um saque.
get_onboarding_statusmerchant_readNarra o que ainda falta para a conta ir ao ar: a escada ordenada de passos (criar a carteira, verificar o WhatsApp, depositar, trocar um pouco por L-BTC, sacar de volta, criar a loja), cada um com título e instrução em PT+EN para repassar ao humano, um link direto do app e os números atuais. Quando todos os passos estão prontos, dispara a verificação sozinho.
update_merchant_profilemerchant_writeAltera os campos leves do perfil da loja — business_name, logo_url, website, default_redirect_url, default_callback_url. Só o que você passa muda. Os campos que redirecionam dinheiro não estão aqui, por construção (PATCH /api/merchants/me).
get_vault_statuswallet_readLê a posição da conta no Cofre: se o mecanismo está ativo, quanto tempo um depósito novo fica retido, o nível de confiança e o teto de recebimento da janela móvel com quanto ainda sobra.
list_webhook_logsmerchant_readLê as entregas de webhook recentes: o evento, o endpoint, o status HTTP que ele devolveu ou o erro de transporte, a tentativa e quando saiu — da mais nova para a mais antiga. Passe id para uma entrega só, com os corpos (GET /api/webhook-logs).
open_support_ticketnenhumAbre um ticket de suporte; o corpo vira a primeira mensagem (POST /api/tickets).
get_support_ticketnenhumLê um ticket e suas mensagens — é assim que o agente vê a resposta.
list_support_ticketsnenhumLista os tickets abertos por esta mesma chave ou sessão.
reply_support_ticketnenhumResponde num ticket aberto.
close_support_ticketnenhumFecha um ticket.
attach_support_ticket_filenenhumAnexa um arquivo a um ticket.

Só no modo local — mais 34

Rodando o pacote na máquina onde o agente vive (npx -y @depixapp/mcp, stdio), o mesmo servidor ganha mais 34 ferramentas: 29 wallet_* — saldos e endereços, on/off-ramp Pix, envio, cotação e conversão, stablecoin entre redes, Lightning (liquidado por swaps Boltz), gift cards, limites de gasto, recuperação e diagnóstico — e 5 de conta. Elas assinam dentro do próprio processo, com uma seed que nunca sai dali; nenhuma exporta a seed nem afrouxa os limites.

Saldo sempre fresco; leitura nunca quebra. Toda leitura sincroniza com a rede antes de responder, e todo gasto sincroniza antes e depois — um pagamento recebido já aparece na consulta de saldo seguinte, sem você pedir nada. Se a rede falhar, a resposta vem assim mesmo, com o último estado conhecido e o aviso stale (ou post_sync_failed, quando a falha foi no sincronismo depois de o dinheiro já ter saído — o dinheiro andou, só a foto ficou velha).

FerramentaO que faz
wallet_syncForça um refresh explícito. Raramente é preciso — leituras e gastos já sincronizam sozinhos. Com rescan: true faz uma varredura fria desde o zero, para quando os saldos parecem dessincronizados (transações faltando, valores velhos): isso pode levar MINUTOS. Não assina nada.
wallet_list_utxosLista as moedas não gastas da carteira — por moeda: ativo, valor em unidades base, o txid:vout que a criou, o endereço que a guarda, altura do bloco e confirmações. Só lê: não assina, não gasta, não reserva.
register_accountCria a conta DePix e suas chaves de API dentro deste processo, na máquina do operador — sem painel, sem reiniciar, sem colar chave em config. Pede o código op_ do humano e uma carteira já inicializada (o endereço de recebimento é o dela). As chaves ficam cifradas nessa máquina; a resposta traz só fatos públicos (usuário, slug da loja, limites, ids das chaves), nunca os segredos. Ativa a chave sandbox por padrão (POST /api/agents/register).
agent_statusLê o andamento da conta de agente: ativa ou suspensa, quantos depósitos pessoais liquidaram, se já graduou para chaves live e o que ainda trava, e as chaves com id, prefixo, escopos e revogação — nunca o segredo (GET /api/agents/status).
verify_domainProva o controle de um domínio por DNS TXT, em duas fases: sem confirm devolve o nome e o valor do registro a criar, para o humano adicionar no provedor de DNS; com confirm: true, depois da propagação, o servidor resolve e grava o domínio como verificado (POST /api/agents/verify-domain).
configure_depix_railLiga ou desliga o recebimento em DePix direto na Liquid. Ligando, deriva um endereço dedicado desta carteira e o registra no backend para que o DePix que chegar ali seja creditado. Você passa só enabled — o endereço e a chave de visão privada são derivados e enviados aqui dentro; a chave nunca aparece na resposta (POST /api/agents/depix-pay).
activate_keyEscolhe com qual das duas chaves da conta o servidor passa a autenticar: test (sandbox, sem dinheiro real) ou live (a chave inicial de produção). As duas já existem desde o register_account; nada é emitido e nenhum segredo aparece. A escolha fica salva no cofre cifrado, sobrevive a reinícios, e a carteira a adota na chamada seguinte. Em live, depósitos são cobranças Pix reais — confirme com o operador antes.

Código e referência completa: github.com/depixapp/depix-mcp (@depixapp/mcp).

SDK da wallet

@depixapp/sdk é uma wallet Liquid não-custodial para Node — a seed é gerada e cifrada localmente, cada assinatura acontece no lado do agente, e o backend nunca guarda uma chave. Traz duas classes:

ClassePropósito
DepixWalletMover dinheiro: depositar, sacar, converter, enviar, saldos.
DepixAgentSelf-onboarding: registrar uma conta e gerenciar API keys (opera os endpoints de agente).
Instalar
npm install @depixapp/sdk

Só ESM. Node ≥ 22.4, Linux / macOS. Valores on-chain são bigint em sats de 8 casas (R$1,00 = 100_000_000n DePix); valores Pix são inteiros em centavos de BRL.

Início rápido

Cria uma wallet, financia com Pix, converte para L-BTC — tudo client-side.

create → deposit → convert
import { DepixWallet } from "@depixapp/sdk";

// cria + faz backup (headless) + abre o gate de recebimento
const { wallet } = await DepixWallet.create({
  passphrase: process.env.DEPIX_WALLET_PASSPHRASE, // ≥ 12 chars
  mnemonicSecured: true,
});
await wallet.confirmBackup();

// depósito: o dono paga o QR (precisa de DEPIX_API_KEY)
const dep = await wallet.deposit({ amountCents: 1000, payerTaxNumber: "OWNER_CPF" });
console.log("Pague isto:", dep.qrCopyPaste);
await wallet.waitForDeposit(dep.id, { timeoutMs: 15 * 60_000 }); // sempre limite esperas humanas

// converte R$5 de DePix para L-BTC (client-side)
await wallet.convert({ from: "DEPIX", to: "LBTC", amount: 500_000_000n });
await wallet.close();

Referência da wallet

DepixWallet.create() / open() / restore() retornam uma wallet. Seus métodos de dinheiro e leitura:

MétodoO que faz
deposit({ amountCents, payerTaxNumber })Cria um depósito Pix → { id, qrCopyPaste }. O dono paga; creditado líquido de taxas.
withdraw({ pixKey, recipientTaxNumber, amountCents, mode })Saque Pix. mode: "send" | "payout".
convert({ from, to, amount, … })Converte entre DEPIX / LBTC / USDT / BTC entre redes. amount é bigint em sats.
Pagar / receber via LightningPaga e recebe faturas Lightning. A liquidação passa por submarine swaps da Boltz — o DePix não é emitido na Lightning, então toda perna Lightning é um swap contra L-BTC. Compras de gift card liquidam da mesma forma.
send({ asset, amountSats, address })Envio on-chain de DEPIX / USDT / LBTC.
quote(intent)Cotações de rota (read-only) para uma conversão.
getBalances()Saldos por ativo + uma estimativa em BRL.
getReceiveAddress()Um endereço Liquid novo de recebimento (após confirmar o backup).
waitForDeposit(id, { timeoutMs })Faz poll de um depósito até liquidar. Sempre passe timeoutMs.
Os guardrails rodam antes de cada assinatura — padrão R$100/tx e R$500 por 24h corridas, imutáveis em runtime. A única rota custodial (USDt cross-network via SideShift) é sempre revelada por custodial: true.

Onboarding do agente

DepixAgent opera os endpoints de agente — gera a identidade Ed25519 e assina cada requisição. Crie com DepixAgent.create() (ou open() para recarregar).

MétodoO que faz
register({ name, operatorToken, operatorEmail, liquidAddress, … })Cria a conta → merchant + chaves starter.
status()Status da conta + progresso de graduação + chaves.
createKey({ live, scopes, … })Emite uma nova chave (devolvida uma vez).
revokeKey(id)Revoga uma chave.
rotateWebhookSecret()Rotaciona o secret de assinatura de webhook.

Erros e runtime

Todo erro estende DepixSdkError com um .code estável; refine com isDepixSdkError(err, code?). Códigos comuns:

Códigos que vale tratar
BACKUP_REQUIRED         sem receber/depositar até confirmar o backup
API_KEY_REQUIRED        deposit/withdraw precisam de DEPIX_API_KEY
graduation_pending      chave live pedida antes da graduação
domain_required         escopo merchant sem domínio verificado
GUARDRAIL_PER_TX_LIMIT  valor acima do guardrail por transação
GUARDRAIL_DAILY_LIMIT   valor acima do guardrail de 24h corridas
MULTIPLE_ROUTES_AVAILABLE  escolha um route id de quote() e repita
POLL_TIMEOUT            um wait*() estourou seu timeoutMs

A spec agent-facing vive nestes próprios docs e no manifesto /.well-known/agent.json. O código do engine mora em github.com/depixapp/depix-mcp — é o mesmo motor de carteira, agora desenvolvido e publicado ali. O @depixapp/sdk é a linhagem congelada no npm: a linha 1.2.x continua funcionando, mas o sucessor é o @depixapp/mcp.