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

{
  "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:

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:

{
  "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:

GET/api/v1/sellers/orders/{id}/fiscal

{
  "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:

POST/api/v1/sellers/orders/{id}/fiscal/nfe/key

{
  "type": "nfe",
  "invoice_key": "35260411222333000144550010000012341123456789",
  "invoice_number": "1234",
  "invoice_value": 249.90,
  "invoice_xml": "<?xml version=\"1.0\"?><nfeProc>...</nfeProc>"
}
CampoObrigatórioO que é
typesimSempre nfe por enquanto.
invoice_keysimChave 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_xmlsimXML autorizado. A partir dele a plataforma gera o DANFE em background.
invoice_numbernãoNúmero da nota, só para exibição.
invoice_valuenãoValor 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:

POST/api/v1/sellers/orders/{id}/invoice/upload-xml

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

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:

POST/api/v1/sellers/orders/{id}/fiscal/declaration

Sem body. Resposta 202 Accepted:

{
  "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:

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

GET/api/v1/sellers/orders/{id}/fiscal/{invoiceId}/pdf

{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:

{
  "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)

GET/api/v1/sellers/orders/{id}/fiscal/{invoiceId}/view

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:

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:

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

Se declaration_available=true, dispara:

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.