> ## Documentation Index
> Fetch the complete documentation index at: https://userepass.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Payouts

> Ciclos de pagamento, prévia, execução e comprovantes.

Um **Payout** (repasse) agrega todas as [Comissões](/docs/conceitos/comissoes) aprovadas e não pagas de um [Afiliado](/docs/conceitos/afiliados) em um único pagamento, valida elegibilidade (perfil fiscal completo, sem fraude em aberto, sem nota fiscal pendente, saldo acima do mínimo), agenda e executa via provedor PIX. Cada payout é um fato financeiro independente, com **snapshot congelado** do destino (chave PIX) no momento da criação, e marca atomicamente todas as comissões incluídas como pagas quando liquidado.

O ciclo tem dois passos distintos:

1. **Fechamento**: `POST /payouts/run`, manual, com dry-run por padrão. Cria os payouts `scheduled` e reserva as comissões.
2. **Execução**: liquida os payouts `scheduled` via PIX, em lote.

<Info>
  IDs de payout usam o prefixo `pay_` (ULID): `pay_01J9X...`. Valores monetários são sempre **inteiros em centavos** (`amountCents`) na moeda `BRL`.
</Info>

## Ciclo de vida

Um payout nasce `scheduled` no fechamento, é promovido a `processing` quando a execução inicia e termina em `completed` ou `failed`. O operador pode cancelar (`scheduled`) ou reagendar (`failed`).

```mermaid theme={null}
stateDiagram-v2
    [*] --> scheduled: payouts/run (dryRun=false)<br/>payout.created
    scheduled --> processing: execução inicia<br/>payout.processing
    scheduled --> canceled: cancel (operador)<br/>payout.canceled
    scheduled --> scheduled: execução pula<br/>(NF não validada)
    processing --> completed: PIX ok<br/>payout.completed + commission.paid
    processing --> failed: PIX erro<br/>payout.failed
    failed --> scheduled: retry (operador)<br/>payout.retried
    completed --> [*]
    canceled --> [*]
```

| Status       | Significado                                                                                |
| ------------ | ------------------------------------------------------------------------------------------ |
| `scheduled`  | Criado e reservou as comissões; aguarda execução. Estado inicial.                          |
| `processing` | Execução iniciada (pagamento PIX em andamento).                                            |
| `completed`  | Pagamento liquidado; comissões marcadas `paid`. **Terminal.**                              |
| `failed`     | Execução falhou; razão registrada em `failureReason`. Pode voltar a `scheduled` via retry. |
| `canceled`   | Fechamento desfeito; comissões liberadas. **Terminal.**                                    |

<Note>
  Um payout só pode ser cancelado enquanto está `scheduled`. Se a execução já o tiver promovido para `processing`, o cancel falha com `409`. Isso evita que um cancelamento aconteça depois que o pagamento já começou.
</Note>

## Fechamento de ciclo

O fechamento agrega o saldo de cada afiliado e decide quem é elegível. Use `POST /payouts/preview` (ou `POST /payouts/run` com `dryRun: true`) para inspecionar o resultado **sem persistir nada**. As duas chamadas retornam exatamente o mesmo resumo. Para efetivar, chame `POST /payouts/run` com `dryRun: false`.

<Warning>
  `dryRun` tem default `true`. Um `POST /payouts/run` sem corpo (ou sem `dryRun`) **não cria payouts**: apenas calcula a prévia. Você precisa enviar `dryRun: false` explicitamente para fechar o ciclo.
</Warning>

Cada elegível da prévia traz também `destination` (chave PIX/método atual do afiliado), para você conferir "quem recebe onde" antes de efetivar.

### Lote do fechamento (histórico de ciclos)

Um fechamento real cria um **lote** (`payout_batch`, prefixo `cyc_`) que agrupa os payouts daquele run. É o histórico de ciclos: liste com `GET /payout-batches` e abra um com `GET /payout-batches/:id` (`expand=payouts,invoices`). O `status` do lote é derivado dos payouts-membro: `processing` enquanto algum estiver agendado/processando, `completed` quando todos liquidam, `partial` se houver falha/cancelamento. Para a contabilidade, `GET /payouts/accounting-report?from=…&to=…` devolve uma linha por payout liquidado no período (afiliado, valor, comprovante, situação da NF).

### Agregação líquida

O payout soma todas as comissões `approved`, sem payout associado e fora de revisão de fraude, incluindo clawbacks (comissões negativas). O `amountCents` é o **líquido** e precisa ser `> 0` para o payout ser criado.

| Cenário                                       | Resultado                                                         |
| --------------------------------------------- | ----------------------------------------------------------------- |
| Comissão `9000` + clawback `-1000`            | Payout de `8000`, `commissionCount: 2`                            |
| Saldo `< minimumPayoutCents` (default `5000`) | Bloqueado `below_minimum`, acumula para o próximo ciclo           |
| Saldo `<= 0` (clawbacks ≥ comissões)          | Bloqueado `non_positive_balance`, débito rola para ciclos futuros |

<CodeGroup>
  ```bash Preview theme={null}
  curl https://api.userepass.com/payouts/preview \
    -H "Authorization: Bearer rstr_..." \
    -H "Content-Type: application/json" \
    -d '{ "programId": "prog_01J9X..." }'
  ```

  ```bash Run (efetivo) theme={null}
  curl https://api.userepass.com/payouts/run \
    -H "Authorization: Bearer rstr_..." \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: run-2026-06-13" \
    -d '{ "dryRun": false }'
  ```
</CodeGroup>

```json Resposta (run dryRun=false) theme={null}
{
  "dryRun": false,
  "totals": { "eligibleAmountCents": 23000, "eligibleCount": 2, "blockedCount": 1 },
  "eligible": [
    {
      "affiliateId": "aff_01J9X...",
      "affiliateName": "Maria Silva",
      "method": "pix",
      "amountCents": 8000,
      "commissionCount": 2
    }
  ],
  "blocked": [
    {
      "affiliateId": "aff_01J9Y...",
      "affiliateName": "João Souza",
      "reason": "below_minimum",
      "amountCents": 3200,
      "commissionCount": 1
    }
  ],
  "payouts": [
    {
      "id": "pay_01J9Z...",
      "affiliateId": "aff_01J9X...",
      "status": "scheduled",
      "method": "pix",
      "amountCents": 8000,
      "commissionCount": 2
    }
  ]
}
```

<Tip>
  O body de `preview` e `run` aceita `programId` opcional para restringir o fechamento aos afiliados de um único [Programa](/docs/conceitos/programas-e-regras). Sem ele, o ciclo cobre toda a organização.
</Tip>

### Threshold mínimo e política

A política de payout fica na configuração da organização, consultável em `GET /settings/payout-policy` e ajustável via `PUT /settings/payout-policy` (vale para os próximos ciclos).

<ParamField body="cycle" type="string" default="monthly">
  `monthly`, `biweekly` ou `manual`. Hoje serve como configuração de referência: o fechamento é sempre disparado manualmente via `POST /payouts/run`.
</ParamField>

<ParamField body="closingDay" type="integer" default="1">
  Dia de fechamento (1 a 28). Também serve como configuração de referência.
</ParamField>

<ParamField body="minimumPayoutCents" type="integer" default="5000">
  Valor mínimo de saque em centavos. Saldo abaixo disso bloqueia com `below_minimum` e acumula para o próximo ciclo. Default `5000` (R\$ 50,00).
</ParamField>

<ParamField body="defaultMethod" type="string" default="pix">
  Método de pagamento padrão sugerido para novos afiliados.
</ParamField>

### Reserva (sem dupla cobrança)

No fechamento real, a criação de cada payout **reserva** as comissões incluídas. Se alguma comissão deixou de ser pagável entre o cálculo e a criação (refund, ban, outro run concorrente), o fechamento é abortado com `409 conflict`. Basta re-rodar. Reexecutar o run não duplica: comissões já reservadas saem do conjunto pagável, então um segundo run não encontra nada elegível.

## Bloqueios

Um afiliado é excluído do ciclo (aparece em `blocked`, sem payout criado) por um destes motivos:

| `reason`                    | Condição                                                                           |
| --------------------------- | ---------------------------------------------------------------------------------- |
| `banned`                    | Afiliado com status `banned`.                                                      |
| `incomplete_payout_profile` | Sem `pixKey`, ou (NF exigida) sem `cnpj`.                                          |
| `fraud_review_open`         | Existe revisão de [fraude](/docs/conceitos/conversoes-e-fraude) aberta para o afiliado. |
| `pending_invoice`           | NF exigida tem nota de payout anterior não resolvida (pendente/rejeitada).         |
| `non_positive_balance`      | Saldo `<= 0` (clawbacks ≥ comissões).                                              |
| `below_minimum`             | Saldo abaixo de `minimumPayoutCents`.                                              |

<Note>
  A nota fiscal **do próprio ciclo** nunca se bloqueia: o cálculo de elegibilidade roda antes da criação da invoice, então só NFs de payouts anteriores não resolvidas geram `pending_invoice`. NF cancelada (junto com o payout) não conta como pendência.
</Note>

## Snapshot do destino

No fechamento, o payout congela o `destination`: `{ method, pixKey?, pixKeyType? }`, copiado do perfil do afiliado.

Trocar a chave PIX do afiliado **depois** não afeta payouts já criados. Os métodos `pix`, `bank_transfer`, `wise` e `paypal` são aceitos e congelados no snapshot; atualmente, apenas **PIX** tem caminho de liquidação disponível.

## Nota fiscal antes do pagamento

Quando a política fiscal exige NF (`nfMode: "affiliate_uploads"`), o fechamento cria uma invoice `pending` junto do payout, e a execução **só liquida depois que a NF fica `validated`**. Enquanto a NF não é validada, a execução pula o payout (permanece `scheduled`).

```mermaid theme={null}
sequenceDiagram
    actor Op as Operador
    actor Af as Afiliado
    participant Run as payouts/run
    participant Exec as Execução
    participant Inv as Invoice (fiscal)

    Op->>Run: run (dryRun=false)
    Run-->>Run: cria payout scheduled + invoice pending
    Exec->>Exec: 1a passagem: NF pending -> pula (scheduled)
    Af->>Inv: envia NF (PDF/XML) -> validação
    Inv-->>Inv: invoice -> validated
    Exec->>Exec: 2a passagem: NF validated -> liquida (completed)
```

Veja [Fiscal](/docs/conceitos/fiscal) para o detalhe da política `nfMode`, do upload e da validação da NF.

## Execução em lote (PIX)

A execução processa os payouts `scheduled` em lote. Para cada payout liquidado, marca **todas** as comissões incluídas como `paid`, com um `paidAt` único.

```mermaid theme={null}
sequenceDiagram
    participant Exec as Execução
    participant Pay as payouts
    participant Gw as PIX
    participant Comm as comissões

    loop cada payout scheduled
        Exec->>Pay: invoice != validated? -> pula (fica scheduled)
        Exec->>Pay: status -> processing + payout.processing
        Exec->>Gw: envia pagamento (valor, moeda, destino)
        alt PIX ok
            Exec->>Pay: completed + comissões paid
            Pay->>Comm: status = paid (todas do payout)
            Note over Pay: payout.completed + commission.paid (xN)
        else PIX erro
            Exec->>Pay: failed + payout.failed
        end
    end
```

<Note>
  Uma falha (`failed`) **mantém** as comissões reservadas (associadas ao payout, não pagas, não liberadas) até um retry ou cancel. Não há cobrança ao afiliado em saldo não positivo: o débito rola para o próximo ciclo.
</Note>

## Operações do payout

Todas as rotas exigem autenticação via API key (`Authorization: Bearer rstr_...`). As operações que mudam estado (`run`, `retry`, `cancel` e o `PUT /settings/payout-policy`) exigem papel **owner** ou **admin**; `preview`, listagens e detalhe estão abertos a qualquer membro. As rotas POST aceitam `Idempotency-Key` (replays retornam a resposta armazenada com `Idempotent-Replay: true`).

<Steps>
  <Step title="Listar e inspecionar">
    `GET /payouts` lista com paginação por cursor e filtros `affiliate_id`, `status`, `method`. `GET /payouts/:payoutId` traz o detalhe e aceita `expand[]=commissions` (anexa até 100 comissões do payout).
  </Step>

  <Step title="Cancelar">
    `POST /payouts/:payoutId/cancel` só é permitido em `scheduled`. Libera as comissões (voltam a `approved` não pago) e cancela a invoice associada. Qualquer outro estado retorna `409 conflict`.
  </Step>

  <Step title="Reagendar">
    `POST /payouts/:payoutId/retry` só é permitido em `failed`. Limpa `failureReason`/`failedAt` e repõe o status `scheduled` para uma nova tentativa de liquidação. O retry é sempre disparado manualmente.
  </Step>

  <Step title="Comprovante">
    `GET /payouts/:payoutId/receipt` retorna o comprovante do provedor **apenas** quando o payout está `completed` e há `providerReceiptUrl`; caso contrário, `404 resource_not_found`.
  </Step>
</Steps>

<CodeGroup>
  ```bash Listar theme={null}
  curl "https://api.userepass.com/payouts?status=scheduled&limit=20" \
    -H "Authorization: Bearer rstr_..."
  ```

  ```bash Cancelar theme={null}
  curl -X POST https://api.userepass.com/payouts/pay_01J9Z.../cancel \
    -H "Authorization: Bearer rstr_..."
  ```

  ```bash Retry theme={null}
  curl -X POST https://api.userepass.com/payouts/pay_01J9Z.../retry \
    -H "Authorization: Bearer rstr_..."
  ```

  ```bash Comprovante theme={null}
  curl https://api.userepass.com/payouts/pay_01J9Z.../receipt \
    -H "Authorization: Bearer rstr_..."
  ```
</CodeGroup>

Para os campos completos de requisição e resposta de cada endpoint, veja a aba **Referência da API**.

### Erros comuns

| Situação                                         | HTTP | `code`               |
| ------------------------------------------------ | ---- | -------------------- |
| Payout inexistente                               | 404  | `resource_not_found` |
| Comprovante ausente (não `completed` ou sem URL) | 404  | `resource_not_found` |
| Cancelar payout não `scheduled`                  | 409  | `conflict`           |
| Retry de payout não `failed`                     | 409  | `conflict`           |
| Comissões deixaram de ser pagáveis durante o run | 409  | `conflict`           |
| Body/query inválidos                             | 400  | `parameter_invalid`  |

Veja o envelope completo em [Erros](/docs/convencoes/erros).

## Eventos de domínio

Cada mudança de estado emite eventos. O ciclo completo gera, na ordem:

| Evento              | Quando                                                                            |
| ------------------- | --------------------------------------------------------------------------------- |
| `payout.created`    | Fechamento real cria o payout. Payload inclui `commissionIds[]`.                  |
| `invoice.created`   | Junto do `payout.created`, quando a NF é exigida (`nfMode: "affiliate_uploads"`). |
| `payout.processing` | A execução do pagamento inicia.                                                   |
| `payout.completed`  | O pagamento PIX é confirmado.                                                     |
| `commission.paid`   | Junto do `payout.completed`, uma por comissão paga.                               |
| `payout.failed`     | O pagamento PIX retorna erro (payload com `reason`).                              |
| `payout.retried`    | Operador reenfileira payout `failed`.                                             |
| `payout.canceled`   | Operador cancela payout `scheduled` (payload com `commissionIds[]` liberadas).    |
| `invoice.canceled`  | Junto do `payout.canceled`, se havia invoice.                                     |
| `settings.updated`  | `PUT /settings/payout-policy` altera a política.                                  |

Assine esses eventos via [Webhooks](/docs/webhooks/visao-geral) ou consulte o histórico no [Event store](/docs/conceitos/event-store).

## Próximos passos

<CardGroup cols={2}>
  <Card title="Comissões" icon="coins" href="/docs/conceitos/comissoes">
    O que entra no saldo de um payout e como clawbacks afetam o líquido.
  </Card>

  <Card title="Fiscal" icon="receipt" href="/docs/conceitos/fiscal">
    Política `nfMode`, upload de NF e o gate de validação antes da liquidação.
  </Card>

  <Card title="Refund e clawback" icon="rotate-left" href="/docs/guias/refund-e-clawback">
    Como estornos reduzem o saldo a pagar nos próximos ciclos.
  </Card>

  <Card title="Catálogo de eventos" icon="list" href="/docs/webhooks/catalogo-de-eventos">
    Payloads dos eventos `payout.*` e `commission.paid`.
  </Card>
</CardGroup>
