> ## 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.

# Fiscal (Brasil)

> Notas fiscais e política fiscal no Brasil.

O módulo Fiscal trata as obrigações de nota fiscal envolvidas no repasse de comissões a afiliados. Todo afiliado é pessoa jurídica; o módulo cobre a **Nota Fiscal (NF)** emitida pelo afiliado e a **política fiscal** configurável por organização. Tudo é Brasil-específico e foi desenhado para que a organização (dona do programa) cumpra suas obrigações sobre os pagamentos.

Os valores são sempre inteiros em **centavos**; alíquotas e percentuais em **basis points** (bps; 100 bps = 1%). Para entender de onde vem o valor a pagar, veja [Payouts](/docs/conceitos/payouts) e [Comissões](/docs/conceitos/comissoes).

## Exigência de nota fiscal

Todo afiliado é pessoa jurídica. A exigência de NF não vem de um regime declarado pelo afiliado: ela é definida pela **política fiscal** da organização, via o campo `nfMode`. Quando `nfMode` é `affiliate_uploads`, o afiliado envia a NF (upload de PDF/XML) antes da liquidação do payout.

<Info>
  A exigência de NF é uma decisão da organização (política `nfMode`), não do afiliado. Com `nfMode: "affiliate_uploads"`, todo payout passa pelo gate de NF: a liquidação só acontece depois que a NF é validada.
</Info>

## Política fiscal

Cada organização tem uma política fiscal. O default, quando você ainda não configurou nada, é `{ nfMode: "affiliate_uploads" }`.

<ParamField body="nfMode" type="string" default="affiliate_uploads">
  Modo de emissão da NF. **Apenas `affiliate_uploads` é aceito**: o afiliado (ou você, via API) envia o PDF/XML antes da liquidação. Qualquer outro valor é rejeitado com `400`.
</ParamField>

Consulte ou atualize com `GET /settings/fiscal-policy` e `PUT /settings/fiscal-policy`. Atualizar exige papel `owner`/`admin` e registra a mudança no evento `settings.updated`.

<CodeGroup>
  ```bash GET política theme={null}
  curl https://api.userepass.com/settings/fiscal-policy \
    -H 'Authorization: Bearer rstr_...'
  ```

  ```bash PUT política theme={null}
  curl -X PUT https://api.userepass.com/settings/fiscal-policy \
    -H 'Authorization: Bearer rstr_...' \
    -H 'Content-Type: application/json' \
    -d '{ "nfMode": "affiliate_uploads" }'
  ```

  ```json Resposta theme={null}
  {
    "nfMode": "affiliate_uploads"
  }
  ```
</CodeGroup>

## Nota Fiscal (PJ)

A entidade central é a nota fiscal (`invoice`, prefixo `inv_`). Ela **não nasce** neste módulo: é criada junto com a execução de um [Payout](/docs/conceitos/payouts), quando a política exige NF (`nfMode: "affiliate_uploads"`). O módulo Fiscal cobre o restante do ciclo: envio, validação, rejeição, consulta e download.

### Ciclo de vida

```mermaid theme={null}
stateDiagram-v2
    [*] --> pending: run de payout (NF exigida)<br/>invoice.created
    pending --> validated: submit válido<br/>invoice.submitted + invoice.validated
    pending --> rejected: submit inválido<br/>invoice.submitted + invoice.rejected
    rejected --> validated: re-submit corrigido OU validate<br/>invoice.validated
    rejected --> rejected: re-submit ainda inválido<br/>invoice.rejected
    validated --> rejected: rejeição manual (reject)<br/>invoice.rejected (manual)
    validated --> validated: revalidação ainda válida<br/>invoice.validated (revalidated)
    pending --> canceled: cancel do payout<br/>invoice.canceled
    validated --> canceled: cancel do payout<br/>invoice.canceled
    rejected --> canceled: cancel do payout<br/>invoice.canceled
    canceled --> [*]
```

| Estado      | Significado                                                                                              |
| ----------- | -------------------------------------------------------------------------------------------------------- |
| `pending`   | Obrigação criada com o payout; NF ainda não enviada.                                                     |
| `validated` | NF enviada e aprovada na validação automática. **Libera a liquidação do payout.**                        |
| `rejected`  | NF reprovada (automática por divergência, ou manual pelo operador). Reabre a pendência; permite reenvio. |
| `canceled`  | Payout cancelado; a obrigação morre junto. Terminal.                                                     |

<Note>
  A liquidação (PIX) de um payout com `invoice` associada só ocorre depois que ela está `validated`. Enquanto não estiver, o payout permanece `scheduled`. Payouts sem invoice (modo diferente de `affiliate_uploads`) não são segurados por esse gate.
</Note>

### Enviar a NF (submit)

`POST /invoices/:invoiceId/submit` recebe o arquivo da NF e os metadados declarados. É **multipart**: uma única parte de arquivo (PDF/XML, no máximo 5 MB) mais os campos de texto.

<ParamField body="file" type="file" required>
  O documento. MIME em `application/pdf`, `application/xml` ou `text/xml`. Máximo 5 MB (excedente → `413`).
</ParamField>

<ParamField body="nfNumber" type="string" required>
  Número da NF (1 a 60 caracteres).
</ParamField>

<ParamField body="nfSeries" type="string">
  Série da NF (1 a 20 caracteres). Opcional.
</ParamField>

<ParamField body="issuerCnpj" type="string" required>
  CNPJ do emissor. Aceita formatação (`12.345.678/0001-90`); normalizado para 14 dígitos. Menos de 14 dígitos → `400`.
</ParamField>

<ParamField body="declaredAmountCents" type="integer" required>
  Valor declarado da NF em centavos (inteiro ≥ 1).
</ParamField>

<ParamField body="recipientName" type="string" required>
  Tomador declarado (1 a 255 caracteres). Deve bater com a razão social da organização.
</ParamField>

```bash cURL theme={null}
curl -X POST https://api.userepass.com/invoices/inv_01J8.../submit \
  -H 'Authorization: Bearer rstr_...' \
  -H 'Idempotency-Key: a1b2c3d4-...' \
  -F 'file=@nota-fiscal.pdf;type=application/pdf' \
  -F 'nfNumber=12345' \
  -F 'nfSeries=1' \
  -F 'issuerCnpj=12.345.678/0001-90' \
  -F 'declaredAmountCents=150000' \
  -F 'recipientName=Minha Loja LTDA'
```

O submit é aceito apenas a partir dos estados `pending` ou `rejected` (caso contrário, `409`). Ele armazena o arquivo com os metadados declarados, roda a validação na hora e emite dois eventos: `invoice.submitted` e, conforme o resultado, `invoice.validated` ou `invoice.rejected`.

<Tip>
  O upload é guardado **antes** da validação. Uma NF reprovada por divergência ainda fica armazenada; o reenvio sobrescreve o arquivo. O `Idempotency-Key` é suportado em todas as rotas POST. Veja [Idempotência](/docs/convencoes/idempotencia).
</Tip>

### Validação automática

A validação compara os **metadados declarados** contra os dados de cadastro. O conteúdo do arquivo não é lido: apenas os campos enviados são confrontados.

| Verificação                           | Falha                    |
| ------------------------------------- | ------------------------ |
| CNPJ do emissor = CNPJ do afiliado    | `issuer_cnpj_mismatch`   |
| Afiliado tem CNPJ cadastrado          | `affiliate_cnpj_missing` |
| Valor declarado = valor do payout     | `amount_mismatch`        |
| Tomador = razão social da organização | `recipient_mismatch`     |

Nomes são normalizados (trim, lowercase, espaços colapsados) e CNPJs reduzidos a dígitos antes da comparação. Quando há falha, a invoice vai para `rejected`, os códigos são gravados em `validationErrors` e o reenvio é permitido.

### Revalidar e rejeitar

<AccordionGroup>
  <Accordion title="Revalidar: POST /invoices/:id/validate">
    Reroda a validação com os metadados **já gravados** e os dados **atuais** de afiliado, payout e organização. Útil quando o operador corrige, por exemplo, o CNPJ do afiliado no cadastro depois de uma rejeição automática, sem reenviar o arquivo. Exige envio prévio (`issuerCnpj`/`declaredAmountCents`/`recipientName` preenchidos) e invoice não `canceled`, senão `409`. Emite `invoice.validated` ou `invoice.rejected` com `revalidated: true`. Exige papel `owner`/`admin`.
  </Accordion>

  <Accordion title="Rejeitar manualmente: POST /invoices/:id/reject">
    Rejeição manual do operador, com motivo. Aceita origem `validated` ou `rejected`; não pode rejeitar `pending` (nunca enviada) nem `canceled` (`409`). Reabre a pendência e emite `invoice.rejected` com `manual: true` e o `reason`. Exige papel `owner`/`admin`.
  </Accordion>

  <Accordion title="Cobrar novamente: POST /invoices/:id/remind">
    Lembra o afiliado de enviar a NF **pendente** (só vale em `pending`, senão `409`). Não muda o estado da nota: dispara a notificação e registra `invoice.reminder_sent` no event store. Exige papel `owner`/`admin`. Para o arquivo de NFs por competência, `GET /invoices` aceita `created_after`/`created_before`.
  </Accordion>

  <Accordion title="Consultar e baixar: GET /invoices, GET /invoices/:id, GET /invoices/:id/file">
    A listagem é paginada por cursor (veja [Paginação](/docs/convencoes/paginacao)) e aceita os filtros `affiliate_id`, `payout_id` e `status`. O download (`/file`) devolve uma **URL temporária** (válida por 15 minutos) e exige papel `owner`/`admin`. Documentos ficam guardados por no mínimo 5 anos.
  </Accordion>
</AccordionGroup>

### Fluxo completo: do nascimento à liquidação

```mermaid theme={null}
sequenceDiagram
    participant Cli as Você (API)
    participant Pay as Payouts
    participant Inv as Fiscal (NF)

    Cli->>Pay: POST /payouts/run (dryRun=false)
    Note over Pay: bloqueia se NF anterior pendente/rejeitada<br/>ou sem CNPJ
    Pay->>Inv: cria NF pending
    Pay-->>Inv: invoice.created
    Cli->>Inv: POST /invoices/:id/submit (PDF/XML + campos)
    Inv->>Inv: valida CNPJ, valor, tomador
    alt dados válidos
        Inv-->>Cli: validated (invoice.submitted + invoice.validated)
    else falha de validação
        Inv-->>Cli: rejected + validationErrors
        Cli->>Inv: corrige cadastro e POST .../validate
        Inv-->>Cli: validated (invoice.validated, revalidated)
    end
    Note over Pay,Inv: NF != validated → payout aguarda em scheduled
    Pay->>Pay: NF validated → PIX → payout completed
```

<Warning>
  Uma NF `pending` ou `rejected` de um payout anterior do mesmo afiliado **bloqueia um novo run** (motivo `pending_invoice`). Quando a NF é exigida e o afiliado está **sem CNPJ cadastrado**, o payout também é bloqueado (`incomplete_payout_profile`), pois o CNPJ é insumo da validação. Veja [Payouts](/docs/conceitos/payouts).
</Warning>

## Retenção de documentos e LGPD

<AccordionGroup>
  <Accordion title="Guarda mínima de 5 anos">
    Todos os documentos fiscais (NFs, comprovantes, relatórios) ficam disponíveis por no mínimo 5 anos (prazo decadencial tributário). O download é feito via URL temporária, válida por 15 minutos.
  </Accordion>

  <Accordion title="LGPD">
    Dados fiscais e financeiros são retidos pelo prazo legal mesmo após a exclusão de conta solicitada pelo afiliado; os demais dados pessoais são anonimizados na exclusão. A base legal é registrada por categoria de dado.
  </Accordion>
</AccordionGroup>

## Eventos de domínio

Toda mudança de estado emite um evento, que você pode assinar via webhook. Os relatórios e consultas (GET) são somente leitura e não emitem eventos. Veja o [catálogo de eventos](/docs/webhooks/catalogo-de-eventos) e o [Event Store](/docs/conceitos/event-store).

| Evento              | Quando                                                                                |
| ------------------- | ------------------------------------------------------------------------------------- |
| `invoice.created`   | No run de payout, quando a obrigação nasce `pending` (emitido por Payouts).           |
| `invoice.submitted` | No submit, sempre: antes do resultado da validação.                                   |
| `invoice.validated` | Submit aprovado ou revalidação válida (com `revalidated: true`).                      |
| `invoice.rejected`  | Submit/revalidação inválida (`validationErrors`) ou rejeição manual (`manual: true`). |
| `invoice.canceled`  | No cancel do payout (emitido por Payouts).                                            |
| `settings.updated`  | No `PUT /settings/fiscal-policy` (`key: "fiscal_policy"`, com `before`/`after`).      |

## Resumo das rotas

| Método | Caminho                         | Papel           | Idempotência |
| ------ | ------------------------------- | --------------- | ------------ |
| POST   | `/invoices/:invoiceId/submit`   | qualquer membro | sim          |
| GET    | `/invoices`                     | qualquer membro | n/a          |
| GET    | `/invoices/:invoiceId`          | qualquer membro | n/a          |
| POST   | `/invoices/:invoiceId/validate` | owner/admin     | sim          |
| POST   | `/invoices/:invoiceId/reject`   | owner/admin     | sim          |
| GET    | `/invoices/:invoiceId/file`     | owner/admin     | n/a          |
| GET    | `/settings/fiscal-policy`       | qualquer membro | n/a          |
| PUT    | `/settings/fiscal-policy`       | owner/admin     | n/a          |

Todas as rotas exigem autenticação por sessão ou API key. Para os campos exatos de request e response, veja a aba **Referência da API**.

## Próximos passos

<CardGroup cols={2}>
  <Card title="Payouts" icon="money-bill-transfer" href="/docs/conceitos/payouts">
    Onde a invoice nasce e o gate de liquidação por NF validada.
  </Card>

  <Card title="Comissões" icon="percent" href="/docs/conceitos/comissoes">
    A origem dos valores que viram payout e, depois, NF.
  </Card>

  <Card title="Afiliados" icon="user" href="/docs/conceitos/afiliados">
    Onde o CNPJ do afiliado é cadastrado.
  </Card>

  <Card title="Idempotência" icon="rotate" href="/docs/convencoes/idempotencia">
    Como reenviar com segurança o submit e demais POSTs.
  </Card>
</CardGroup>
