Pular para o conteúdo
LeztyPay docs
Painel

Solicitar saque

Um POST, com Idempotency-Key obrigatória, e o dinheiro sai para a chave PIX escolhida.

#!/usr/bin/env bash
# Saca R$ 50,00 para uma chave PIX já cadastrada e verificada.
# O id da chave sai de `GET /v1/pix-keys`.
#   LM_PIX_KEY_ID=pk_01j... ./create-withdrawal.sh
set -euo pipefail

curl --fail-with-body -sS "$LM_API_URL/v1/withdrawals" \
  -H "Authorization: Bearer $LM_API_KEY" \
  -H "Idempotency-Key: saque-2026-09-17-001" \
  -H "Content-Type: application/json" \
  -d "{
    \"amount\": 5000,
    \"pix_key_id\": \"$LM_PIX_KEY_ID\",
    \"description\": \"repasse semanal\"
  }"

O fluxo completo, escolhendo a chave e conferindo o saldo antes:

// Saque completo: escolhe a chave PIX, confere o saldo e pede o saque.
//   LM_API_URL=... LM_API_KEY=... node create-withdrawal.mjs
const apiUrl = process.env.LM_API_URL;
const apiKey = process.env.LM_API_KEY;
const auth = { Authorization: `Bearer ${apiKey}` };

// `GET /v1/pix-keys` só devolve chave verificada e ativa, que é a única que saca.
const chaves = await (await fetch(`${apiUrl}/v1/pix-keys`, { headers: auth })).json();
const chave = chaves.data[0];

if (chave === undefined) {
  console.error('nenhuma chave PIX verificada; cadastre uma no painel antes de sacar');
  process.exit(1);
}

const saldo = await (await fetch(`${apiUrl}/v1/balance`, { headers: auth })).json();
console.log('disponível:', saldo.available, 'em reserva:', saldo.reserve);

const res = await fetch(`${apiUrl}/v1/withdrawals`, {
  method: 'POST',
  headers: {
    ...auth,
    // Sem este header o pedido é recusado com 400 missing_idempotency_key.
    'Idempotency-Key': 'saque-2026-09-17-001',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    amount: 5000,
    pix_key_id: chave.id,
    description: 'repasse semanal',
  }),
});

const saque = await res.json();

if (!res.ok) {
  // `details` traz o número que faltou: saldo disponível, taxa, limite usado.
  console.error(res.status, saque.error.code, saque.error.details ?? {});
  process.exit(1);
}

// A taxa é cobrada por fora: sai `total` do saldo, não `amount`.
console.log(saque.id, saque.status);
console.log('valor:', saque.amount, 'taxa:', saque.fee, 'sai do saldo:', saque.total);

A taxa é cobrada por fora

amount é o que chega na sua conta bancária. fee é a taxa do saque. total, que é a soma dos dois, é o que sai do seu saldo.

Então pedir o saque do seu available inteiro falha: falta a taxa. O que cabe é available menos fee.

As pré-condições

Todas são conferidas no momento do pedido, e cada uma tem o seu erro:

  • Conta apta a operar. Senão 403 seller_not_allowed.
  • KYC aprovado, em produção. Senão 403 kyc_required. No ambiente de teste o KYC não é exigido.
  • Saque não travado pela nossa operação. Senão 403 payout_locked.
  • Chave PIX verificada e ativa. Senão 409 pix_key_not_verified. Ver Chave PIX.
  • Valor mínimo de R$ 1,00 e saldo suficiente para valor mais taxa. Senão 409 insufficient_balance, com o disponível e a taxa em details.
  • Dentro do limite diário, em valor e em quantidade (no máximo 20 saques por dia). Senão 409 daily_limit_exceeded, e details.reason diz qual dos dois estourou.
  • Nenhum outro saque em andamento. Senão 409 invalid_state com details.reason igual a withdrawal_in_progress.

Um saque por vez

Essa última merece parágrafo próprio, porque é a que mais surpreende. Enquanto um saque seu não chegar ao fim, nenhum outro é aceito.

O motivo é honesto: dois saques concorrentes contra o mesmo saldo viram corrida, e corrida com dinheiro vira saque a descoberto. Com um por vez, a conta fecha sempre.

Na prática, se você faz repasses em lote, enfileire e mande um de cada vez, esperando o estado final de cada um.

O ciclo de vida

Status O que quer dizer
awaiting Recebido, aguardando aprovação.
approved Aprovado, na fila para ser enviado.
rejected Recusado na aprovação. O valor volta para o saldo.
processing Enviado ao processador, aguardando a liquidação.
successful Pago. end_to_end_id traz o identificador da transferência PIX.
failed Não foi pago. O valor volta para o saldo, e failure_reason diz o motivo.
cancelled Cancelado por você enquanto ainda estava em awaiting.

Dependendo da configuração da sua conta, o saque nasce direto em approved e pula a espera.

Cada transição entrega um evento no seu webhook: withdrawal.awaiting, withdrawal.approved, withdrawal.rejected, withdrawal.processing, withdrawal.successful, withdrawal.failed e withdrawal.cancelled. successful e failed são os dois estados finais que interessam.

Cancelar

Só em awaiting, e não precisa de Idempotency-Key:

POST /v1/withdrawals/{id}/cancel

Saque que já saiu de awaiting responde 409 invalid_state. Não existe cancelamento depois do envio ao processador: a partir de processing, o resultado é successful ou failed, e você espera.

Quando o resultado demora

processing pode durar. Enquanto o processador não confirmar, o saque fica nesse estado e nós continuamos consultando.

O que fazer: nada, além de escutar o evento. Não crie outro saque, não pergunte em laço apertado, não trate demora como falha. O valor está preso em pending_withdraw e vai para um dos dois estados finais.

Próximo passo

Você já tem o ciclo do dinheiro inteiro. Para produção, veja Webhooks e depois o checklist de go-live.

Referência: Solicita um saque (exige o header Idempotency-Key), Consulta um saque, Lista os saques do seller no mode atual e Cancela um saque ainda em awaiting.