# Fiscal

NF-e (modelo 55), Declaração de Conteúdo, registro de XML e download de DANFE/PDF. **Um documento fiscal por pedido.**

> Pré-requisitos: leia primeiro [Pedidos: visão geral](/guia/pedidos). O pedido precisa estar em estado fiscal-elegível (`preparing` em diante — ou, em pedidos de produção, qualquer estado da fábrica) antes de qualquer operação aqui.

---

## Conceitos

O objeto que `GET /orders/{id}/fiscal` devolve — o(s) documento(s) já registrado(s) e a disponibilidade da Declaração:

```jsonc
{
  "invoices": [                  // 0 ou 1 documento por pedido
    {
      "id": "5b4f…",
      "type": "nfe",             // ← nfe | declaration
      "status": "completed",     // ← pending | validating | valid | invalid | processing | completed | error
      "invoice_key": "3526…",    // chave de acesso (44 dígitos, NF-e)
      "invoice_number": "1234",  // só display
      "invoice_value": 249.9,    // só display
      "has_pdf": true,           // ← DANFE/PDF pronto p/ download (olhe aqui, não no status)
      "error_message": null,     // motivo quando a validação fiscal falha (status error/invalid)
      "created_at": "2026-04-26T15:10:00Z"
    }
  ],
  "declaration_available": true  // loja pode emitir Declaração de Conteúdo
}
```

---

## Quem emite o quê

Quem emite a NF-e é a sua loja, não a plataforma — e isso faz sentido: a nota nasce dos seus dados fiscais, do seu CNPJ e do seu regime tributário, então ela sai do seu ERP e é autorizada pela SEFAZ no seu nome. O papel da plataforma vem depois que a nota existe: **guardar a chave** (e, idealmente, o XML), **gerar o DANFE** a partir do XML, e **disponibilizar para download** quando o cliente ou o transportador precisar. Ou seja, você cuida da emissão; a gente cuida de hospedar e entregar.

A Declaração de Conteúdo é diferente: a plataforma **gera o PDF dela** para você, em background, a partir dos dados do pedido. É uma alternativa à NF-e para casos onde a loja não emite nota fiscal (típico em PF, MEI sem inscrição estadual, ou em pedidos de baixo valor).

Você usa **um ou outro por pedido**:

```mermaid
flowchart TD
    A["Pedido em preparing+"] --> B{"Sua loja emite<br/>NF-e?"}
    B -->|sim| C["Emite no ERP da loja"]
    C --> D{"Tem o XML<br/>autorizado?"}
    D -->|"sim, em mãos"| E["POST /fiscal/nfe/key<br/>com invoice_xml"]
    D -->|"XML em arquivo separado"| G["POST /invoice/upload-xml<br/>multipart"]
    E --> H["DANFE gerado<br/>em background"]
    G --> H
    B -->|"não, loja PF"| I["POST /fiscal/declaration"]
    I --> J["Declaração gerada<br/>em background"]
    H --> K["GET /fiscal — polling até has_pdf=true"]
    J --> K
    K --> L["GET /fiscal/&lcub;invoiceId&rcub;/pdf<br/>URL S3 15 min"]
```

---

## Pré-condições

**O pedido precisa estar num estado fiscal-elegível**: qualquer estado de produção (`production_started`, `awaiting_materials`, `production_in_progress`, `production_finished`) ou `preparing` em diante (`ready_to_ship`, `shipped`, `delivered`, `ready_for_pickup`, `picked_up`). Tentar emitir em `paid` retorna **422**:

```json
{
  "success": false,
  "code": 422,
  "message_code": "VALIDATION_ERROR",
  "errors": {
    "status": ["O pedido precisa estar em preparação antes de emitir documentos fiscais. Status atual: paid"]
  }
}
```

**Declaração de Conteúdo exige feature da loja.** A primeira chamada que você deve fazer é a consulta:

<a class="api-route" href="/reference/pedidos#tag/fiscal/GET/api/v1/sellers/orders/{id}/fiscal"><span class="api-method api-method-get">GET</span><span class="api-path">/api/v1/sellers/orders/{id}/fiscal</span></a>

```json
{
  "data": {
    "invoices": [],
    "declaration_available": true
  }
}
```

- `invoices[]`: documentos já emitidos para esse pedido (vazio se for o primeiro).
- `declaration_available`: se sua loja tem a feature `fiscal_declaration` ativa. Se vier `false`, só resta NF-e.

---

## NF-e: dois caminhos

A NF-e nasce no seu ERP. O que muda é **o que você tem em mãos** quando vai registrar:

### Caminho 1: XML completo em string

Se o seu ERP devolveu o XML autorizado junto com a chave, mande tudo de uma vez:

<a class="api-route" href="/reference/pedidos#tag/fiscal/POST/api/v1/sellers/orders/{id}/fiscal/nfe/key"><span class="api-method api-method-post">POST</span><span class="api-path">/api/v1/sellers/orders/{id}/fiscal/nfe/key</span></a>

```json
{
  "type": "nfe",
  "invoice_key": "35260411222333000144550010000012341123456789",
  "invoice_number": "1234",
  "invoice_value": 249.90,
  "invoice_xml": "<?xml version=\"1.0\"?><nfeProc>...</nfeProc>"
}
```

| Campo | Obrigatório | O que é |
|---|---|---|
| `type` | sim | Sempre `nfe` por enquanto. |
| `invoice_key` | sim | Chave de acesso de 44 caracteres (aqui a validação é só de tamanho; o dígito mod-11 é conferido nos fluxos de lookup/upload de XML). |
| `invoice_xml` | **sim** | XML autorizado. A partir dele a plataforma gera o DANFE em background. |
| `invoice_number` | não | Número da nota, só para exibição. |
| `invoice_value` | não | Valor total, só para exibição. |

Retorno **201 Created**. O DANFE entra na fila e fica pronto em alguns segundos.

### Caminho 2: upload manual do XML

Quando você tem o arquivo .xml em mãos mas **não tem ainda a chave registrada na plataforma** (cenário típico: XML salvo do email do contador, exportado de outro sistema). Mande o arquivo direto:

<a class="api-route" href="/reference/pedidos#tag/fiscal/POST/api/v1/sellers/orders/{id}/invoice/upload-xml"><span class="api-method api-method-post">POST</span><span class="api-path">/api/v1/sellers/orders/{id}/invoice/upload-xml</span></a>

`multipart/form-data` com o campo `xml` contendo o arquivo (máx 1 MB, extensão `.xml`):

```http
POST /api/v1/sellers/orders/ORD-000123/invoice/upload-xml
Content-Type: multipart/form-data; boundary=----X

------X
Content-Disposition: form-data; name="xml"; filename="nfe.xml"
Content-Type: application/xml

<?xml version="1.0"?>...
------X--
```

A chave é **extraída do próprio XML** (atributo `Id="NFe..."` em `infNFe`) e validada por mod-11. Resposta igual ao caminho 1.

Erros típicos:
- **422**: XML sem chave, chave inválida, arquivo vazio, extensão errada.
- **409 `chave_already_used`**: essa NF-e já está vinculada a outro pedido.
- **409 `invoice_already_validated`**: refaça o invoice antes de reenviar.

---

## Declaração de Conteúdo

Para lojas sem NF-e. **A plataforma gera o PDF a partir dos dados do pedido**; você só dispara:

<a class="api-route" href="/reference/pedidos#tag/fiscal/POST/api/v1/sellers/orders/{id}/fiscal/declaration"><span class="api-method api-method-post">POST</span><span class="api-path">/api/v1/sellers/orders/{id}/fiscal/declaration</span></a>

Sem body. Resposta **202 Accepted**:

```json
{
  "success": true,
  "code": 202,
  "data": {
    "invoice": { "id": "8c7e...", "type": "declaration", "status": "pending", "has_pdf": false }
  }
}
```

A partir daí, polling até `has_pdf=true` no `GET /fiscal`.

Erros típicos:
- **403**: `declaration_available=false` na sua loja. Peça ativação da feature `fiscal_declaration`.
- **422**: pedido em status que não permite emissão fiscal (típico: `paid`).

---

## Polling do PDF

NF-e (com XML) e Declaração geram PDF em background. O padrão é o mesmo para os dois:

```mermaid
sequenceDiagram
    autonumber
    participant I as Integrador
    participant A as API
    participant W as Worker
    I->>A: POST /fiscal/declaration<br/>(ou /fiscal/nfe/key com XML)
    A-->>I: 202 / 201
    A->>W: dispara job
    Note over W: gera PDF
    loop polling (a cada N segundos)
      I->>A: GET /fiscal
      A-->>I: { invoices: [{ has_pdf: false }] }
    end
    W-->>A: PDF pronto
    I->>A: GET /fiscal
    A-->>I: { invoices: [{ has_pdf: true }] }
    I->>A: GET /fiscal/{invoiceId}/pdf
    A-->>I: { url, expires_in: "15 minutos" }
```

> **Cadência recomendada:** primeiros 30 segundos a cada 3s, depois a cada 10s, com timeout em 2 minutos. PDFs simples ficam prontos em ~5s; declarações com muitos itens podem levar mais.

---

## Baixar o PDF

<a class="api-route" href="/reference/pedidos#tag/fiscal/GET/api/v1/sellers/orders/{id}/fiscal/{invoiceId}/pdf"><span class="api-method api-method-get">GET</span><span class="api-path">/api/v1/sellers/orders/{id}/fiscal/{invoiceId}/pdf</span></a>

`{invoiceId}` é o `id` que veio em `invoices[]` na resposta de `/fiscal`. A rota é a mesma para DANFE e Declaração; o `filename` já vem com o prefixo certo (`danfe_` ou `declaracao_`).

Quando o PDF está pronto:

```json
{
  "success": true,
  "code": 200,
  "data": {
    "url": "https://s3.amazonaws.com/.../danfe_ORD-000123.pdf?X-Amz-Signature=...",
    "filename": "danfe_ORD-000123.pdf",
    "expires_in": "15 minutos"
  }
}
```

A `url` é S3 assinada, válida por **15 minutos**. Se expirar, chame a rota de novo, que gera uma nova URL. Não armazene a URL.

Quando ainda não está pronto, retorna **202 Accepted** com o status atual (não é um erro, é só sinal de "tente de novo").

---

## Consulta de dados (sem PDF)

<a class="api-route" href="/reference/pedidos#tag/fiscal/GET/api/v1/sellers/orders/{id}/fiscal/{invoiceId}/view"><span class="api-method api-method-get">GET</span><span class="api-path">/api/v1/sellers/orders/{id}/fiscal/{invoiceId}/view</span></a>

Devolve os dados completos do invoice (chave, número, valor, status, metadata da declaração) **sem o PDF**. Útil para telas de detalhe fiscal no painel.

---

## Cenários comuns

### "Acabei de emitir a NF-e no ERP, quero registrar agora"

ERP devolveu chave + XML:

```http
POST /api/v1/sellers/orders/ORD-000123/fiscal/nfe/key
{ "type": "nfe", "invoice_key": "...", "invoice_xml": "<?xml...?>" }
```

Depois faça polling do `/fiscal` até `has_pdf=true` e baixe.

### "Loja é MEI, vai usar Declaração"

Confirme primeiro:

```http
GET /api/v1/sellers/orders/ORD-000123/fiscal
```

Se `declaration_available=true`, dispara:

```http
POST /api/v1/sellers/orders/ORD-000123/fiscal/declaration
```

Polling → download.

### "O cliente está reclamando que não recebeu o DANFE, quero reenviar"

A URL expirou. Faça `GET /fiscal/{invoiceId}/pdf` de novo, que vai gerar uma URL nova.

### "Errei a NF-e, emiti outra no ERP, quero corrigir"

A rota `/fiscal/nfe/key` aceita ser chamada de novo no mesmo pedido **se o invoice ainda não foi validado**. Se já foi validado, vai bater **409 `invoice_already_validated`**; nesse caso é caso de operação (contate suporte para reabrir o invoice).

---

## Cuidados

**Não chame `/fiscal` em loop curto.** Para acompanhar geração de PDF, cadência de 3–10 segundos é suficiente. Loop em ms gasta quota sem benefício.

**`invoice_xml` é obrigatório.** Esquecer o XML no `/fiscal/nfe/key` retorna 422. Se você ainda não tem o XML registrado na plataforma, use `/invoice/upload-xml` para enviar o arquivo.

**Um pedido carrega um único documento fiscal — NF-e ou Declaração, nunca os dois.** Isso é proposital: o documento fiscal é o que acompanha a mercadoria, e ter dois competindo pelo mesmo pedido só geraria ambiguidade na hora de imprimir, despachar e mostrar pro cliente. Por isso você escolhe um caminho no começo. Se emitiu um e precisa trocar, não é algo que você desfaz pela API — é caso de operação (contate o suporte).

**Não armazene as URLs S3.** Elas têm assinatura curta. Sempre chame `/fiscal/{invoiceId}/pdf` na hora de servir o download, pois a URL anterior pode estar expirada.
