# API do FazNota — v3 — Documentação completa (para LLMs) > Versão v3 (recomendada). Gerado automaticamente a partir da documentação. Base da API: https://api.mysebr.com.br/nfemyse-v3/rest. Auth: header Authorization: Token . Emissão assíncrona (recibo + polling). --- # API do FazNota — v3 (Recomendada) Fonte: https://faznota.com.br/api/v3/ API REST para emissão e gestão de Nota Fiscal Eletrônica (NFe), NFCe, NFSe, clientes e produtos. Versão 3, recomendada para novas integrações. ## Comece por aqui ## O que a API do FazNota faz A **API de emissão de notas do FazNota** permite automatizar a emissão de: - **NFe** — Nota Fiscal Eletrônica (modelo 55) - **NFCe** — Nota Fiscal de Consumidor Eletrônica (modelo 65) - **NFSe** — Nota Fiscal de Serviço Eletrônica (municipal) - **Notas de fornecedor** — manifestação do destinatário (MD-e) Além disso, oferece **gestão de clientes e produtos** com configurações fiscais completas. A API foi projetada para integrar-se a sistemas existentes (ERPs, e-commerces, PDVs) e transferir para a plataforma toda a responsabilidade de emissão e comunicação com a SEFAZ. ## Como a documentação está organizada Glossário fiscal, fluxo assíncrono de emissão e estrutura padrão de resposta. Códigos de status internos, tratamento de erros, paginação e idempotência. Receitas passo a passo para emitir, cancelar, corrigir e sincronizar. Implementações completas em cURL, JS, Python, PHP, Java, C#, Go e Ruby. ## Convenções desta documentação - **Sempre HTTP 200.** A API retorna `200 OK` mesmo em erros; o status real está em `body.status` (códigos `001`–`999`). - **Toda emissão é assíncrona.** Você recebe um `recibo` e consulta depois. Veja [Fluxo assíncrono](/api/v3/conceitos/fluxo-assincrono/). - **Cada resposta inclui um `X-Request-Id`** (também em `meta.request_id`). Cite esse valor ao acionar o suporte. ## Suporte - 📧 **Suporte técnico:** [suporte@myse.com.br](mailto:suporte@myse.com.br) - 🌐 **Site institucional:** [myse.com.br](https://myse.com.br) - 🔎 **Status da plataforma:** consulte o endpoint `GET /health` --- # Autenticação Fonte: https://faznota.com.br/api/v3/autenticacao/ Como autenticar requisições na API do FazNota usando o Token único da empresa. A API do FazNota utiliza **autenticação por Token único por empresa**. O token é gerado no Painel FazNota e enviado no header `Authorization` de cada requisição. ## Como obter seu Token 1. Acesse o **Painel FazNota** com suas credenciais. 2. Navegue até **Meus Dados** → aba **Integração**. 3. Gere ou copie o Token da empresa. Se você é uma **empresa administradora** com acesso ao Painel de Gestão de Contas, pode gerar tokens individuais para cada empresa vinculada. ## Cabeçalhos obrigatórios Toda requisição autenticada deve incluir: ```http Authorization: Token Content-Type: application/json ``` Em requisições com corpo (POST/PUT), o `Content-Type` é obrigatório. ## Exemplo de chamada autenticada ```bash curl https://api.mysebr.com.br/nfemyse-v3/rest/nfe \ -H "Authorization: Token abc123def456ghi789jkl012mno345pq" \ -H "Content-Type: application/json" ``` ## Endpoints que NÃO exigem autenticação | Endpoint | Propósito | |---|---| | `GET /health` | Health check da plataforma. Use para uptime monitor. | ## Respostas de erro de autenticação | HTTP | `error.status` | `error.message` | O que fazer | |---|---|---|---| | `401` | `401` | `"Token não autorizado."` | Verifique se o header está correto e se o Token não foi revogado. | | `403` | `403` | `"Este contrato está inapto para realizar transações de API..."` | Contrato pendente. Entre em contato com o FazNota para regularizar. | | `429` | `429` | `"Você atingiu o limite de consumo de API..."` | Aguarde a janela informada na mensagem ou no header `X-RateLimit-Reset`. | Exemplo de resposta `401`: ```json { "error": { "status": 401, "message": "Token não autorizado." } } ``` ## Boas práticas ### Use variáveis de ambiente ```bash # .env (NUNCA commit este arquivo) MYSE_API_TOKEN=abc123def456ghi789jkl012mno345pq MYSE_API_BASE=https://api.mysebr.com.br/nfemyse-v3/rest ``` ```javascript const token = process.env.MYSE_API_TOKEN; const base = process.env.MYSE_API_BASE; ``` ### Rotação periódica Considere rotacionar o Token periodicamente (a cada 6–12 meses). Para rotação sem indisponibilidade: 1. Gere um novo Token no painel (sem revogar o atual). 2. Atualize a variável de ambiente nas instâncias da sua aplicação. 3. Quando confirmar que tudo continua funcionando, revogue o Token antigo no painel. ### Centralize o cliente HTTP Evite espalhar `fetch`/`curl` pelo código. Crie uma camada que injeta automaticamente o header `Authorization` e trata erros comuns: ```typescript // src/lib/myse-api.ts const BASE = process.env.MYSE_API_BASE!; const TOKEN = process.env.MYSE_API_TOKEN!; export async function mApi( method: string, path: string, body?: unknown ): Promise { const res = await fetch(`${BASE}${path}`, { method, headers: { Authorization: `Token ${TOKEN}`, 'Content-Type': 'application/json', }, body: body ? JSON.stringify(body) : undefined, }); return res.json(); } ``` ### Não compartilhe Token entre ambientes Tenha tokens separados para homologação e produção (quando aplicável) e não use o mesmo Token em diferentes integrações — isso facilita revogar uma sem afetar as demais. --- # Boas práticas de consumo Fonte: https://faznota.com.br/api/v3/boas-praticas/ Diretrizes para integrações estáveis, performáticas e que respeitam os limites operacionais da plataforma. Esta página reúne as melhores práticas para consumir a API do FazNota de forma estável, performática e que respeite os limites operacionais da plataforma. ## 1. Idempotência **Sempre envie `numero-origem`** em emissões. Veja [Idempotência](/api/v3/padroes/idempotencia/). ```json { "numero-origem": "PED-2026-00042", ... } ``` Sem ele, retries em caso de timeout podem gerar duplicatas. ## 2. Polling responsável Para consultar recibo após emissão: - **Intervalo mínimo: 2 segundos** entre consultas do mesmo recibo. - **Use backoff exponencial:** 2s → 3s → 4.5s → 7s → 10s → 15s → 23s → 30s (cap). - **Pare em status terminal** (`004`, `010`, `900`, `999`). - **Timeout total razoável:** 5 minutos. Após isso, acione o suporte. ```typescript async function polling(recibo, token) { const TERM = ['004', '010', '900', '999']; let wait = 2000; const start = Date.now(); while (Date.now() - start < 5 * 60_000) { const json = await mApi('GET', `/nfe/nota/${recibo}`, undefined, token); if (TERM.includes(json.status)) return json; await sleep(wait); wait = Math.min(wait * 1.5, 30_000); } throw new Error('Timeout'); } ``` ## 3. Cache local Dados que **não mudam com frequência** podem ser cacheados: | Recurso | TTL recomendado | |---|---| | Listagem de clientes | 1 hora | | Listagem de produtos | 1 hora | | Cliente individual (`GET /clientes/{doc}`) | 1 hora | | Produto individual (`GET /produtos/{ref}`) | 1 hora | | NFes emitidas (consulta por recibo) | Sem cache durante emissão; cache longo após `004`/`010` | Use Redis, Memcached ou cache em memória da aplicação. ## 4. Retry com backoff Apenas em erros transitórios (HTTP 5xx, timeout, erro de rede). **Não retente** em erros de validação (`status: "999"` com mensagem clara): ```typescript async function comRetry(fn, maxTentativas = 3) { for (let i = 1; i <= maxTentativas; i++) { try { return await fn(); } catch (err) { // Se for ApiError de validação, propaga (não retentar) if (err instanceof ApiError && err.payload?.status === '999') throw err; if (i === maxTentativas) throw err; await sleep(1000 * Math.pow(2, i - 1)); // 1s, 2s, 4s } } } ``` ## 5. Timeouts Defina timeouts adequados no seu cliente HTTP: | Operação | Timeout recomendado | |---|---| | `GET /health` | 5 segundos | | Listagens (`GET /nfe`, etc.) | 30 segundos | | Consulta por recibo | 15 segundos | | Emissão (POST) | 30 segundos | | Cadastros (POST/PUT clientes/produtos) | 30 segundos | Timeout mínimo recomendado: **10 segundos**. Menor que isso pode causar falsos timeouts. ## 6. Concorrência limitada Evite enviar dezenas de requisições em paralelo no mesmo token: ```typescript // ❌ Ruim const promises = clientes.map((c) => mApi('POST', '/clientes', c)); await Promise.all(promises); // ✅ Bom — limite de 5 simultâneas const limit = pLimit(5); await Promise.all(clientes.map((c) => limit(() => mApi('POST', '/clientes', c)))); ``` ## 7. Headers de rate limit Monitore os headers em cada resposta: ```javascript function checarRateLimit(res) { const remaining = parseInt(res.headers.get('x-ratelimit-remaining') || '0', 10); const reset = parseInt(res.headers.get('x-ratelimit-reset') || '0', 10); if (remaining < 100) { console.warn(`Rate limit baixo: ${remaining} restantes. Reset em ${new Date(reset * 1000)}`); // Considere pausar emissões em massa } } ``` ## 8. Sincronização incremental Evite percorrer todo o histórico a cada sincronização. Use marcadores: - **NFSe:** filtro `dataInicial`. - **Outros:** salve o último `id` sincronizado e pare quando alcançá-lo. Veja [Sincronizar clientes e produtos](/api/v3/recipes/sincronizar/) para padrões completos. ## 9. Não polling sobre listas **Evite:** ```typescript // ❌ Polling agressivo de listagens setInterval(() => mApi('GET', '/nfe?page=1&page_size=100'), 5000); ``` **Prefira:** - Poll apenas o que precisa: o `recibo` específico, não a lista inteira. - Use cache local com TTL de minutos para listagens. ## 10. Tratamento correto de erros - **HTTP 401/403/429:** trate antes de qualquer lógica de domínio. - **HTTP 200 + `status: "999"`:** erro de domínio. Veja `data.erro`. - **HTTP 200 + `status: "050"`:** **não é erro**. É proteção; use o recibo retornado. - **HTTP 200 + `status: "900"`:** rejeição SEFAZ; veja `data.sefaz.mensagem`. Veja [Tratamento de erros](/api/v3/padroes/erros/) para detalhe. ## 11. Use `X-Request-Id` no suporte Toda resposta traz `meta.request_id` e o header `X-Request-Id` com o mesmo valor. **Cite esse valor** sempre que abrir ticket. Sem ele, a investigação fica difícil. ## 12. Versionamento da API Hoje a API está em **v3** (`nfemyse-v3` no path). Mudanças significativas de contrato serão anunciadas com antecedência via aviso prévio (ver [Políticas operacionais](/api/v3/politicas-operacionais/)). ## Checklist de integração madura - [ ] Token armazenado em variável de ambiente ou cofre de segredos - [ ] Cliente HTTP centralizado com headers padrão e timeouts - [ ] `numero-origem` único em todas as emissões - [ ] Polling com intervalo mínimo de 2s e backoff exponencial - [ ] Tratamento de `status: "999"`, `"900"`, `"050"` - [ ] Cache local de clientes e produtos (TTL 1h) - [ ] Concorrência limitada (≤5 simultâneas no mesmo token) - [ ] Retry só em erros transitórios, com backoff - [ ] Monitoramento de `X-RateLimit-Remaining` - [ ] Log estruturado com `meta.request_id` em cada operação - [ ] Health check externo apontando para `GET /health` --- # Changelog Fonte: https://faznota.com.br/api/v3/changelog/ Histórico de mudanças relevantes da API do FazNota e desta documentação. Esta página registra as alterações relevantes da API de emissão de notas do FazNota e desta documentação. Mudanças seguem o padrão **SemVer**: alterações breaking incrementam o major (v3 → v4) e recebem [comunicação prévia](/api/v3/politicas-operacionais/). ## 2026-08-06 — Resumo diário e download de XMLs por período Mudança **puramente aditiva**: dois recursos novos, nenhum endpoint existente foi alterado. ### Adicionado - **`GET /resumo?data-inicial=&data-final=`** — totais por dia de NFe e NFCe emitidas, canceladas e denegadas, mais as falhas de emissão. Reflete tudo que a empresa emitiu, inclusive pela tela do ERP. Período máximo de 92 dias. Atenção: `canceladas` é **subconjunto** de `emitidas` — não some os dois campos. Veja [Resumo diário de emissões](/api/v3/recipes/resumo-diario/). - **`POST /xml/solicitacao`** e **`GET /xml/solicitacao/{protocolo}`** — download em lote dos XMLs de um período, em fluxo assíncrono (solicita → consulta protocolo → baixa do link temporário, válido por 7 dias). Período máximo de 92 dias, até 3 solicitações simultâneas por empresa. O arquivo também é enviado por e-mail. Veja [Baixar XMLs por período](/api/v3/recipes/baixar-xml-periodo/). - **Status novos** no envelope: `006` (processado sem resultado), `007` (falha no processamento assíncrono) e `400` (parâmetro inválido — erro do chamador, repetir a requisição não resolve). ## 2026-08-04 — NFSe: informações complementares e ISS retido Mudança **puramente aditiva**. Requisições que não enviem os novos campos continuam se comportando exatamente como antes. ### Adicionado - **Campo `informacoes-complementares`** (NFSe, opcional, até 2.000 caracteres) em `POST /nfse/emissao` e `PUT /nfse/{id}`. Texto livre que sai na seção **"Informações Complementares"** do DANFSe/XML. É uma seção **diferente** da discriminação do serviço (`observacao`) — os dois convivem na mesma nota. Omitido: nota emitida sem informações complementares (comportamento anterior). - **Campo `iss-retido`** (NFSe, opcional) em `POST /nfse/emissao` e `PUT /nfse/{id}`. `"1"` = ISS retido pelo tomador (substituição tributária), `"2"` = não retido. **Padrão `"2"`** quando omitido (comportamento anterior). É informação da nota, não do item; mesmo com ISS retido a alíquota e o valor do ISS continuam no XML. Valor diferente de `"1"`/`"2"` é rejeitado com mensagem de validação. Veja [Emitir uma NFSe](/api/v3/recipes/emitir-nfse/). ## 2026-05-13 — Modernização de DX (v3 mantida) Esta release **mantém compatibilidade total** com o contrato existente. Nada quebra. Novas capacidades são puramente aditivas. ### Adicionado - **Header `X-Request-Id`** em todas as respostas. Idêntico ao novo campo `meta.request_id` no body. Cite ao acionar o suporte. - **Headers `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`** em respostas autenticadas. Permitem auto-regulação do consumo. - **Bloco `meta`** em todas as respostas com `request_id` e `timestamp` (ISO-8601 UTC). - **Endpoint `GET /health`** sem autenticação. Retorna `200 OK` quando aplicação + banco saudáveis; `503` quando degradado. - **Paginação por query string** (`?page=&page_size=`) em todas as listagens, como alternativa ao path-based legado. Veja [Paginação](/api/v3/padroes/paginacao/). - **Campo `numero-origem`** (com hífen) aceito em emissões, equivalente ao legado `numero_origem` (com underscore). Os dois formatos funcionam. - **Header `Access-Control-Expose-Headers`** expõe `X-Request-Id` e os `X-RateLimit-*` para clientes browser conseguirem lê-los. ### Melhorado - **Mensagens de erro internas** mais seguras: stack traces e detalhes de infraestrutura não vazam mais ao cliente. Mensagens de validação (em português, começando com `"Campo @..."`) permanecem **idênticas**. - **Logs estruturados** (interno) via SLF4J + Logback. Cada log contém `requestId=` para correlação. Sem impacto no cliente. - **Health check de DB** real em `GET /health` (não apenas ping de aplicação). - **OPTIONS preflight** respondido imediatamente sem exigir autenticação, melhorando integrações browser. ### Corrigido (interno) - Bug histórico que impedia gravação no log de uso da API (`tb_uso_api`). Logs passam a ser gravados corretamente. - Vulnerabilidade SQL injection no provider de log (uso de PreparedStatement). - Duplicação de headers CORS nos resources (centralizado no filter). ### Documentação - **Nova documentação completa** com a estrutura de 12 seções (esta versão). - **Spec OpenAPI 3.0.3** publicada em [`openapi/nota-fiscal.yaml`](/api/v3/referencia/). - 7 recipes práticos cobrindo os principais fluxos. - Coleção Postman/Insomnia oficial. ## Versões anteriores A documentação consolidada começou nesta versão. Mudanças anteriores estão registradas no Painel FazNota e em comunicados por e-mail aos integradores. ## Política de breaking changes A v3 está congelada em contrato — **nenhuma mudança quebrante** será introduzida sem: 1. **Comunicação prévia** com prazo adequado (mínimo 90 dias). 2. **Janela de retrocompatibilidade** com versão antiga funcionando em paralelo. 3. **Documentação clara** do que mudou e como migrar. Veja [Políticas operacionais](/api/v3/politicas-operacionais/) para o regramento completo. ## Como acompanhar - 📧 **E-mail:** os contatos cadastrados no Painel FazNota recebem comunicados. - 🌐 **Painel:** avisos importantes aparecem ao logar. - 📄 **Esta página:** consulte periodicamente. --- # Código de benefício fiscal (cBenef) Fonte: https://faznota.com.br/api/v3/conceitos/codigo-beneficio-fiscal/ O que é o código de benefício fiscal (cBenef), quando é exigido e como informá-lo na configuração fiscal do produto na API v3. O **código de benefício fiscal** (`cBenef`) identifica, na nota, qual benefício de ICMS está sendo aplicado ao item — redução de base de cálculo, isenção, diferimento, etc. É um código de **8 caracteres** (padrão: 2 letras da UF + 6 dígitos, ex.: `SP020110`), definido na **tabela de benefícios fiscais de cada estado (UF)**. ## Quando é exigido A exigência depende da combinação **CST/CSOSN do ICMS + NCM + CFOP + UF** da operação. Regra prática: | Situação | Resultado na SEFAZ | |---|---| | CST **com** benefício fiscal + código **informado** | ✅ Autoriza | | CST **com** benefício fiscal + código **não** informado | ❌ Rejeição: *"CST com benefício fiscal e não informado o código de benefício fiscal"* | | CST **sem** benefício fiscal + código **informado** | ❌ Rejeição: *"Informado código de benefício fiscal para CST sem benefício"* | ## Como informar O código vai na **configuração fiscal do produto**, dentro do bloco de alíquotas do ICMS **por estado** — campo `codigo-beneficiario`: ```json "configuracoes-fiscais": [ { "cfop": "5.102", "configuracoes": { "icms": { "estados": [ { "estado": "SP", "icms-cst": "20", "aliquotas": { "aliquota-icms": "18", "percentual-reducao-bc": "96.71666666", "codigo-beneficiario": "SP020110" } } ] } } } ] ``` ## Formato e validação - **8 caracteres** (ex.: `SP020110`). A API aceita **vazio** (sem benefício) **ou** exatamente 8 caracteres — qualquer outro tamanho é rejeitado na validação com uma mensagem clara. - É **por estado**: cada entrada de `icms.estados[]` tem seu próprio `codigo-beneficiario`, pois o benefício varia por UF. --- # Estrutura padrão de resposta Fonte: https://faznota.com.br/api/v3/conceitos/estrutura-resposta/ Como a API do FazNota monta seus payloads de resposta e como interpretá-los. A API do FazNota segue um padrão **uniforme** de resposta para todos os endpoints, exceto os health checks e respostas de erro de baixo nível (401, 404, 405, 429). ## Formato base ```json { "status": "001", "descricao": "Nota registrada com sucesso.", "data": { ... }, "meta": { "request_id": "req_01HXY8ZG3J4K5M6N7P8Q9R0S1T", "timestamp": "2026-05-13T14:32:01.234Z" } } ``` | Campo | Tipo | Descrição | |---|---|---| | `status` | string | Código interno de status (`001`–`999`). Veja [tabela](/api/v3/padroes/codigos-status/). | | `descricao` | string | Descrição humana do status (em português). | | `data` | object \| array \| omitted | Carga útil. Tipo depende do endpoint. | | `meta` | object | Metadados sempre presentes para rastreabilidade. | | `meta.request_id` | string | Idêntico ao header `X-Request-Id`. | | `meta.timestamp` | string (ISO-8601 UTC) | Momento em que a resposta foi gerada. | ## Convenção: HTTP 200 + `body.status` A API do FazNota adota uma **convenção de canal único de status** para erros de domínio: operações que chegaram à camada de aplicação (passaram autenticação e parser) sempre retornam **HTTP `200 OK`**, com o resultado real no campo `body.status`. Apenas falhas de **infraestrutura, autenticação ou roteamento** retornam HTTP diferente de 200 — ver tabela abaixo. ### Por que essa convenção? 1. **Resposta sempre lida pelo cliente.** Bibliotecas HTTP populares (axios, OkHttp, RestSharp) lançam exception em 4xx/5xx por padrão. Com a convenção `200 + body.status`, o cliente sempre acessa o body e decide como tratar — sem `try/catch` obrigatório para erros de negócio. 2. **Não confunde validação com erro de servidor.** Um CFOP inválido não é "erro do servidor", é uma resposta de negócio. Misturar com 5xx polui métricas de SRE. 3. **Idempotência clara.** `status: "050"` (duplicidade) é uma resposta válida — não é erro; retornar o recibo original é o comportamento correto. 4. **Compatibilidade entre clients.** Integradores em PHP, Ruby, Python, Java tratam o body com o mesmo código, sem ramificações por HTTP status. ### Quando a API retorna HTTP ≠ 200 | Cenário | HTTP | Body | |---|---|---| | Sucesso, erro de validação (`999`), duplicidade (`050`), rejeição SEFAZ (`900`), recurso não existe (`500`) | `200 OK` | `{ status, descricao, data?, meta }` | | Token ausente, inválido, ou contrato inapto | `401` | `{ error: { status, message } }` | | Limite de consumo atingido (rate limit) | `429` | `{ error: { status, message } }` | | Endpoint inexistente (URL errada) | `404` | `{ error: { status, message } }` | | Método HTTP inválido | `405` | `{ error: { status, message } }` | ### Padrão de tratamento ```javascript const res = await fetch(url, { method, headers, body }); // 1. Erro estrutural (auth, rate limit, URL errada) if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error(`HTTP ${res.status}: ${err.error?.message}`); } // 2. Resposta normal: avaliar body.status const json = await res.json(); switch (json.status) { case '001': case '002': case '003': case '004': case '005': case '010': return json; // sucesso ou em processamento case '050': return json; // duplicidade — use o recibo original case '900': throw new RejeicaoSefazError(json); // SEFAZ recusou (corrigir e re-emitir) case '999': throw new ErroValidacaoError(json); // erro de validação ou interno case '500': throw new ReciboNaoExisteError(json); // recibo inválido default: throw new Error(`Status desconhecido: ${json.status}`); } ``` ## Padrão para emissões (com recibo) Endpoints que iniciam um processamento assíncrono retornam: ```json { "status": "001", "descricao": "Nota registrada com sucesso.", "data": { "recibo": "rec_abc123def456" }, "meta": { ... } } ``` Use o `recibo` em `GET //` para consultar o resultado. ## Padrão para consultas em lista ```json { "status": "005", "descricao": "Busca de listagem de notas gerada com sucesso.", "data": [ { "status": "004", "descricao": "...", "data": { ... } }, { "status": "004", "descricao": "...", "data": { ... } } ], "meta": { ... } } ``` Note que em listagens cada item também tem seu próprio bloco `status`/`descricao`/`data`, representando o status individual do documento (emitido, cancelado, etc). ## Padrão para erros ### Erro de validação ou domínio (`status: 999`) ```json { "status": "999", "descricao": "Ocorreu um erro interno com a nota.", "data": { "erro": "Campo nfe@chave deve ter 44 caracteres." }, "meta": { "request_id": "req_01HX...", "timestamp": "..." } } ``` - Mensagens de **validação** (campo `data.erro`) são em português e descrevem exatamente o problema. Padrão: `"Campo "`. - Mensagens de **erros internos não-controlados** retornam uma frase genérica: `"Erro interno ao processar a requisição. Tente novamente em instantes ou entre em contato com o suporte informando o request-id."` - Cite o `meta.request_id` ao acionar o suporte. ### Duplicidade (`status: 050`) Quando um `numero-origem` já foi usado antes, o sistema retorna o **recibo original** da emissão anterior, evitando duplicatas: ```json { "status": "050", "descricao": "Já existe uma nota com este número de origem em nosso sistema.", "data": { "recibo": "rec_original_abc" }, "meta": { ... } } ``` Veja [Idempotência](/api/v3/padroes/idempotencia/) para entender por que isso protege sua integração. ### Erros de autenticação (401, 403, 429) Esses são os únicos casos em que o HTTP status code não é `200`. Formato: ```json { "error": { "status": 401, "message": "Token não autorizado." } } ``` | HTTP | Quando ocorre | |---|---| | `401` | Token ausente ou inválido. | | `403` | Contrato inapto para realizar transações. | | `429` | Limite de consumo atingido (proteção operacional). | ## Headers de resposta Toda resposta inclui: | Header | Sempre presente? | Descrição | |---|---|---| | `X-Request-Id` | ✅ Sempre | Idêntico a `meta.request_id`. Cite no suporte. | | `X-RateLimit-Limit` | Autenticadas | Limite total na janela. | | `X-RateLimit-Remaining` | Autenticadas | Restante na janela. | | `X-RateLimit-Reset` | Autenticadas | Unix timestamp do reset. | | `Content-Type` | ✅ Sempre | `application/json; charset=UTF-8` | | `Access-Control-*` | ✅ Sempre | CORS aberto (`*`). | ## Tratamento recomendado no cliente ```javascript async function call(method, path, body, token) { const res = await fetch(`https://api.mysebr.com.br/nfemyse-v3/rest${path}`, { method, headers: { Authorization: `Token ${token}`, 'Content-Type': 'application/json', }, body: body ? JSON.stringify(body) : undefined, }); // Erros HTTP "duros" (401, 429, 5xx) if (!res.ok) { const err = await res.json().catch(() => ({})); throw new ApiError(res.status, err.error?.message ?? 'Erro HTTP', err); } const json = await res.json(); // Erros de domínio (HTTP 200 com status 999) if (json.status === '999') { throw new ApiError( 200, json.data?.erro ?? json.descricao, json, json.meta?.request_id ); } // Duplicidade — não é erro, é retorno controlado if (json.status === '050') { console.warn('numero-origem duplicado, retornando recibo original', json); } return json; } ``` --- # Fluxo assíncrono de emissão Fonte: https://faznota.com.br/api/v3/conceitos/fluxo-assincrono/ Como funciona o fluxo emissão → recibo → polling → resultado, e como implementar consultas eficientes. Toda emissão de documento fiscal na API do FazNota é **assíncrona**. O motivo é simples: a comunicação com a SEFAZ pode levar de segundos a minutos, dependendo do volume e da disponibilidade do servidor estadual. A API responde imediatamente com um `recibo` e processa a emissão em segundo plano. ## Diagrama do fluxo ```mermaid sequenceDiagram participant App as Sua aplicação participant API as API do FazNota participant SEFAZ as SEFAZ App->>+API: POST /nfe/emissao API-->>-App: status=001, recibo=abc123 Note over API,SEFAZ: Processamento assíncrono API->>+SEFAZ: Envia XML SEFAZ-->>-API: Autorização loop Polling com backoff (intervalo mínimo 2s) App->>+API: GET /nfe/nota/abc123 API-->>-App: status=002 (pendente) → 003 (emitindo) → 004 (emitida) end Note over App: status=004 → buscar DANFE/XML ``` ## Códigos de status durante o ciclo de vida Após enviar uma emissão, o `status` retornado pelas consultas evolui assim: ``` 001 (registrado) → 002 (pendente) → 003 (emitindo) → 004 (emitida) ✅ ↘ 010 (cancelada) ↘ 900 (rejeitada SEFAZ — ou SEFAZ lenta, ver nota abaixo) ↘ 999 (erro interno) # 900 pode voltar para 004 sozinho: se sefaz.codigo for 217/105, a SEFAZ ainda está # processando e o resultado se resolve em minutos (reconciliação automática, até 3h). ``` Veja a [tabela completa](/api/v3/padroes/codigos-status/) para o detalhamento. ## Como implementar o polling ### Regras de ouro ### Exemplo em JavaScript ```javascript async function aguardarEmissao(recibo, token) { const url = `https://api.mysebr.com.br/nfemyse-v3/rest/nfe/nota/${recibo}`; const TERMINAIS = ['004', '010', '999']; // SEFAZ lenta/instável: 900 com um desses códigos NÃO é definitivo, continue consultando. // Ver: https://faznota.com.br/api/v3/conceitos/instabilidade-sefaz/ const SEFAZ_CODIGO_TRANSITORIO = ['217', '105']; let intervalo = 2000; // 2 segundos iniciais const intervaloMax = 30000; // 30 segundos no máximo const inicio = Date.now(); const timeoutTotal = 5 * 60 * 1000; // 5 minutos while (Date.now() - inicio < timeoutTotal) { const res = await fetch(url, { headers: { Authorization: `Token ${token}` }, }); const json = await res.json(); const ehTerminal = TERMINAIS.includes(json.status) || (json.status === '900' && !SEFAZ_CODIGO_TRANSITORIO.includes(json.data?.sefaz?.codigo)); if (ehTerminal) { return json; } await new Promise((r) => setTimeout(r, intervalo)); // Backoff exponencial até 30s intervalo = Math.min(intervalo * 1.5, intervaloMax); } throw new Error(`Timeout consultando recibo ${recibo}`); } ``` ### Exemplo em Python ```python import time import requests def aguardar_emissao(recibo, token, timeout=300): url = f"https://api.mysebr.com.br/nfemyse-v3/rest/nfe/nota/{recibo}" headers = {"Authorization": f"Token {token}"} terminais = {"004", "010", "999"} # SEFAZ lenta/instável: 900 com um desses códigos NÃO é definitivo, continue consultando. # Ver: https://faznota.com.br/api/v3/conceitos/instabilidade-sefaz/ sefaz_codigo_transitorio = {"217", "105"} intervalo = 2 inicio = time.time() while time.time() - inicio < timeout: r = requests.get(url, headers=headers, timeout=30) data = r.json() codigo_sefaz = (data.get("data") or {}).get("sefaz", {}).get("codigo") eh_terminal = data["status"] in terminais or ( data["status"] == "900" and codigo_sefaz not in sefaz_codigo_transitorio ) if eh_terminal: return data time.sleep(intervalo) intervalo = min(intervalo * 1.5, 30) raise TimeoutError(f"Timeout consultando recibo {recibo}") ``` ## Outros endpoints assíncronos O mesmo padrão se aplica a: | Operação | Endpoint de emissão | Endpoint de consulta | |---|---|---| | Emitir NFe | `POST /nfe/emissao` | `GET /nfe/nota/{recibo}` | | Carta de correção | `POST /nfe/correcao` | `GET /nfe/correcao/{recibo}` | | Cancelar NFe | `POST /nfe/cancelamento` | `GET /nfe/cancelamento/{recibo}` | | Emitir NFCe | `POST /nfce/emissao` | `GET /nfce/cupom/{recibo}` | | Cancelar NFCe | `POST /nfce/cancelamento` | `GET /nfce/cancelamento/{recibo}` | | Emitir NFSe | `POST /nfse/emissao` | `GET /nfse/nota/{recibo}` | | Reconhecimento fornecedor | `POST /nffornecedor/reconhecimento` | `GET /nffornecedor/{recibo}` | ## Headers úteis durante o polling Toda resposta da API retorna headers que ajudam a rastrear e regular o consumo: | Header | Uso | |---|---| | `X-Request-Id` | Cite no suporte ao reportar problemas. | | `X-RateLimit-Limit` | Limite total de requisições na janela atual. | | `X-RateLimit-Remaining` | Quanto resta. Use para ajustar o ritmo. | | `X-RateLimit-Reset` | Unix timestamp em que o limite reseta. | Se `X-RateLimit-Remaining` ficar baixo, aumente o intervalo entre consultas. --- # Glossário fiscal Fonte: https://faznota.com.br/api/v3/conceitos/glossario/ Termos fiscais e técnicos usados nesta documentação e no payload da API. ## Documentos fiscais | Termo | Significado | |---|---| | **NFe** | Nota Fiscal Eletrônica (modelo 55). Documento fiscal que substitui a nota fiscal modelo 1/1A em papel; emitida e armazenada eletronicamente, autorizada pela SEFAZ. | | **NFCe** | Nota Fiscal de Consumidor Eletrônica (modelo 65). Substitui o cupom fiscal em operações de varejo presencial; emitida em tempo real. | | **NFSe** | Nota Fiscal de Serviço Eletrônica. Emitida pelo município (não pela SEFAZ estadual) para prestadores de serviço. | | **CC-e** | Carta de Correção Eletrônica. Permite corrigir alguns erros de uma NFe já autorizada (sem alterar valores). | | **MD-e** | Manifestação do Destinatário. Evento em que o destinatário declara a ciência, confirmação, desconhecimento ou operação não realizada de uma NFe recebida. | | **DANFE** | Documento Auxiliar da Nota Fiscal Eletrônica. Representação gráfica simplificada da NFe (PDF/A4). | | **XML** | Arquivo da NFe propriamente dita. É o documento fiscal — o DANFE é só uma representação visual. | ## Identificadores | Termo | Significado | |---|---| | **Chave de acesso** | Identificador único da NFe/NFCe (44 caracteres numéricos). Codifica UF, AAMM, CNPJ emitente, modelo, série, número e código. | | **Número** | Numeração sequencial da nota dentro da série. | | **Série** | Subdivisão da numeração (1–999). Permite paralelismo (ex.: série 1 para online, série 65 para PDV). | | **Recibo** | Identificador retornado pela API do FazNota após uma emissão. Use para consultar o resultado posterior (`GET //`). | | **`numero-origem`** | Identificador único definido pelo integrador para evitar duplicidade. Idempotência. | ## Tributação | Termo | Significado | |---|---| | **CFOP** | Código Fiscal de Operações e Prestações (4 dígitos). Indica a natureza da operação (venda dentro do estado, fora, devolução, etc). | | **CST** | Código de Situação Tributária. Classifica como o tributo incide na operação. | | **NCM** | Nomenclatura Comum do Mercosul. Classificação fiscal do produto (8 dígitos). | | **ICMS** | Imposto sobre Circulação de Mercadorias e Serviços. Estadual. | | **IPI** | Imposto sobre Produtos Industrializados. Federal. | | **PIS / COFINS** | Contribuições federais sobre receita/faturamento. | | **ISS** | Imposto Sobre Serviços de Qualquer Natureza. Municipal. Aplica-se a NFSe. | | **FCP** | Fundo de Combate à Pobreza. Adicional ao ICMS em alguns produtos. | | **ICMS-ST** | ICMS por Substituição Tributária. Pago antecipadamente por um contribuinte da cadeia. | ## Órgãos | Termo | Significado | |---|---| | **SEFAZ** | Secretaria da Fazenda (estadual). Autoriza NFe e NFCe. | | **SEFAZ Virtual** | Servidor da SEFAZ que processa as notas. Cada UF tem o seu, com ambiente de homologação e produção. | ## Status (API do FazNota) Códigos retornados em `body.status`. Veja [tabela completa](/api/v3/padroes/codigos-status/). | Código | Significado | |---|---| | `001` | Registro realizado (aguardando processamento) | | `002` | Pendente de processamento | | `003` | Em processo de emissão | | `004` | Documento emitido | | `005` | Consulta realizada (busca) | | `010` | Documento cancelado | | `050` | Duplicidade de `numero-origem` | | `900` | Rejeição pela SEFAZ | | `999` | Erro interno | ## Tipos comuns | Termo | Valores | |---|---| | `tipo` (NF) | `S` = Saída, `E` = Entrada | | `finalidade` (NFe) | `N` = Normal, `C` = Complementar, `A` = Ajuste, `D` = Devolução | | `tipo` (Cliente) | `F` = Pessoa Física, `J` = Pessoa Jurídica | | `tipo-inscricao-estadual` | `1` = Contribuinte ICMS, `2` = Contribuinte isento, `9` = Não Contribuinte | | `modalidade` (Transporte) | `0`–`4`, `9` (sem ocorrência de transporte) | | `tp_pagamento` (NFCe) | `dinheiro`, `credito`, `debito`, `outros` | | `codigo-reconhecimento` (Fornecedor) | `210200` (Confirmação), `210210` (Ciência), `210220` (Desconhecimento), `210240` (Operação não realizada) | --- # IBS / CBS (Reforma Tributária) Fonte: https://faznota.com.br/api/v3/conceitos/ibs-cbs/ Como informar os tributos IBS e CBS da reforma tributária na emissão de NFe pela API v3. A **API v3 já contempla os novos tributos da reforma tributária** — **IBS** (Imposto sobre Bens e Serviços) e **CBS** (Contribuição sobre Bens e Serviços). Eles fazem parte da **configuração fiscal do produto**, no bloco `cbs-ibs`. ## Onde entra o bloco `cbs-ibs` O `cbs-ibs` é um objeto dentro da configuração fiscal do produto, **ao lado** de `ipi`, `pis-cofins` e `icms` — tanto no cadastro do produto (`POST /produtos`) quanto no produto enviado inline na emissão (`POST /nfe/emissao`): ```json "configuracoes-fiscais": [ { "cfop": "5.102", "configuracoes": { "ipi": { "...": "..." }, "pis-cofins": { "...": "..." }, "cbs-ibs": { "cbs-ibs-cst": "000", "classificacao": "000001", "aliquotas": { "percentual-cbs": "0.9", "percentual-diferimento-cbs": "0", "percentual-devolucao-cbs": "0", "percentual-ibs": "0.1", "percentual-diferimento-ibs": "0", "percentual-devolucao-ibs": "0" } }, "icms": { "...": "..." } } } ] ``` ## Campos | Campo | Padrão | Descrição | |---|---|---| | `cbs-ibs-cst` | `"000"` | CST do IBS/CBS | | `classificacao` | `"000001"` | Código de classificação tributária do IBS/CBS | | `aliquotas.percentual-cbs` | `"0.9"` | Alíquota da CBS (%) | | `aliquotas.percentual-diferimento-cbs` | `"0"` | Diferimento da CBS (%) | | `aliquotas.percentual-devolucao-cbs` | `"0"` | Devolução da CBS (%) | | `aliquotas.percentual-ibs` | `"0.1"` | Alíquota do IBS (%) | | `aliquotas.percentual-diferimento-ibs` | `"0"` | Diferimento do IBS (%) | | `aliquotas.percentual-devolucao-ibs` | `"0"` | Devolução do IBS (%) | ## Observações - Os valores (CST, classificação, alíquotas) devem seguir as tabelas oficiais da reforma tributária conforme o seu produto e operação. A API **não infere** esses valores — ela usa o que você enviar (ou o padrão). - O bloco vale por **configuração de CFOP** do produto (cada entrada de `configuracoes-fiscais` tem seu próprio `cbs-ibs`). - Consulte também a [Referência da API](/api/v3/referencia/) (schema `configuracoes.cbs-ibs`) e o guia [Emitir NFe](/api/v3/recipes/emitir-nfe/). --- # SEFAZ lenta ou instável Fonte: https://faznota.com.br/api/v3/conceitos/instabilidade-sefaz/ O que acontece e o que fazer quando a autorização demora mais que o normal ou a SEFAZ está indisponível. A emissão de documentos fiscais pela API é **assíncrona**: o `POST` de emissão devolve um **recibo**, e o resultado deve ser acompanhado pela consulta (`GET .../{recibo}`). Na operação normal, a SEFAZ autoriza em poucos segundos. Em alguns momentos (instabilidade ou lentidão da SEFAZ), a autorização pode levar **mais de 15 segundos**. Nessa janela, enquanto a SEFAZ ainda não deu uma resposta definitiva, a consulta do recibo devolve o status de **processamento**: ```json { "status": "003", "descricao": "A nota está em processo de emissão." } ``` ## A regra de ouro: `003` é pendente; só reemita em rejeição definitiva | `status` | Significado | Ação do integrador | |---|---|---| | `003` | **Em processamento** — SEFAZ ainda não respondeu (inclui lentidão/instabilidade) | Aguardar e seguir consultando o mesmo recibo — **não reemitir** | | `004` | **Autorizada** (protocolo e XML disponíveis) | Concluir a venda / imprimir DANFE | | `010` | **Cancelada** | — | | `900` | **Rejeição definitiva** da SEFAZ | Corrigir o dado apontado em `sefaz.mensagem` e emitir uma **nova** nota | ## O que o nosso sistema faz automaticamente Você **não precisa (e não deve) reenviar a nota**. Nosso sistema possui **reconciliação automática**: um processo interno re-consulta a situação da nota diretamente na SEFAZ (pela chave de acesso) e atualiza o resultado assim que houver resposta definitiva: - Se a SEFAZ **autorizou** → a consulta do recibo passa a devolver `status: "004"`, com protocolo de autorização e XML definitivo — como em qualquer emissão normal. - Se a SEFAZ **rejeitou de fato** → a consulta passa a devolver `status: "900"` com `sefaz.codigo`/`sefaz.mensagem` refletindo a rejeição real, para correção e reenvio. Na maioria dos casos a situação se resolve **em poucos minutos**. A janela de reconciliação automática cobre até **3 horas**. ## Ação necessária do integrador 1. **Continue consultando o mesmo recibo** enquanto o status for `003` (ou, na salvaguarda, `900` com `sefaz.codigo` `217`/`105`), até obter um resultado definitivo (`004` autorizada ou `900` com código de rejeição real). Sugestão de polling: a cada 2–5 s no primeiro minuto; depois a cada 30–60 s. 2. **Nunca reenvie a mesma venda enquanto o status for `003`** (nem, na salvaguarda, `900` com `217`/`105`). A nota pode já ter sido autorizada na SEFAZ — reenviar gera **duplicidade de documento fiscal** (duas notas válidas para a mesma venda). 3. **Só considere a venda concluída** (impressão de DANFE, baixa no seu sistema) quando obtiver `status: "004"` (autorizada). 4. Se receber uma **rejeição definitiva** (`900` com `sefaz.codigo` ≠ `217`/`105`, ex.: erro cadastral ou tributário), corrija o dado apontado em `sefaz.mensagem` e **aí sim** emita uma nova nota. 5. Se uma nota permanecer em `003` (ou `900`+`217`/`105`) por **mais de 3 horas**, **não reemita por conta própria** — acione o [suporte](/api/v3/faq/#suporte) informando o recibo e a chave de acesso, para verificação manual. ## Resumo por situação | Situação | `status` | `sefaz.codigo` | Ação | |---|---|---|---| | Em processamento (SEFAZ lenta / sem retorno) | `003` | — | Aguardar e repetir a consulta — **não reemitir** | | Autorizada | `004` | `100` | Concluir a venda / imprimir DANFE | | Rejeição definitiva | `900` | ≠ `100`/`217`/`105` | Corrigir o dado e emitir nova nota | | Cancelada | `010` | — | — | | Salvaguarda: `900` transitório | `900` | `217` (ou `105`) | Tratar como pendente — aguardar, **não reemitir** | | Pendente por mais de 3h | `003` (ou `900`+`217`/`105`) | — | Contatar o suporte com recibo + chave — **não reemitir** | ## Checando isso no código do seu cliente Trate `003` (e `001`/`002`) como pendente. Como salvaguarda, se receber `900`, verifique `data.sefaz.codigo` antes de tratar como terminal — `217`/`105` continuam sendo transitórios: ```typescript const SEFAZ_TRANSITORIO = ['217', '105']; function classificar(json: { status: string; data?: { sefaz?: { codigo?: string } } }) { const codigoSefaz = json.data?.sefaz?.codigo; // 001/002/003 = em processamento => pendente, continue consultando if (['001', '002', '003'].includes(json.status)) return 'pendente'; // salvaguarda: 900 com codigo transitorio ainda NAO é definitivo if (json.status === '900' && codigoSefaz && SEFAZ_TRANSITORIO.includes(codigoSefaz)) { return 'pendente'; } if (['004', '010'].includes(json.status)) return 'sucesso'; if (['500', '900', '999'].includes(json.status)) return 'falha'; return 'falha'; } ``` Veja também [Fluxo assíncrono](/api/v3/conceitos/fluxo-assincrono/) para o exemplo completo de polling com backoff. --- # Código de Exemplo — Visão geral Fonte: https://faznota.com.br/api/v3/exemplos/ Implementações completas da API do FazNota em múltiplas linguagens, com autenticação, retry, polling e tratamento de erro. Esta seção traz **implementações completas e prontas para produção** em várias linguagens. Cada exemplo cobre o ciclo completo: - Carregamento do Token a partir de variável de ambiente - Cliente HTTP centralizado com headers padrão e timeout - Idempotência via `numero-origem` - Polling com backoff exponencial - Retry em erros transitórios - Tratamento de `status: "050"`, `"900"`, `"999"` - Captura do `X-Request-Id` para logs ## Linguagens disponíveis ## Padrão dos exemplos Cada página segue a mesma estrutura: 1. **Setup** — instalação de dependências e variáveis de ambiente. 2. **Cliente HTTP centralizado** — com captura de `X-Request-Id` e tratamento de erro. 3. **Helper de polling reutilizável** — chamado por todos os cenários assíncronos. 4. **Cenários por documento fiscal:** - Emitir NFC-e (com polling) - Emitir NF-e (com transporte completo, faturas) - Emitir NFS-e (Nota Fiscal de Serviço) - Cancelar NF-e, NFC-e, NFS-e - Lançar carta de correção (CC-e) - Reconhecer nota de fornecedor (MD-e) 5. **Retry com backoff** — apenas em erros transitórios. 6. **Listar com paginação** — loop completo. 7. **Tratamento de erros** — captura de `X-Request-Id` e propagação adequada. ## Matriz de cobertura (todos os 40 endpoints da API) ### Documentos fiscais | Cenário | cURL | JS/TS | Python | PHP | Java | C# | Go | Ruby | |---|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:| | **NF-e** Emissão (transporte, faturas) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | **NF-e** Alteração (PUT) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | **NF-e** Cancelamento | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | **NF-e** Carta de correção (CC-e) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | **NF-e** Consulta completa por recibo | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | **NF-e** Listagem paginada | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | **NFC-e** Emissão | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | **NFC-e** Alteração (PUT) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | **NFC-e** Cancelamento | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | **NFC-e** Consulta completa por recibo | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | **NFC-e** Listagem paginada | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | **NFS-e** Emissão | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | **NFS-e** Alteração (PUT) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | **NFS-e** Cancelamento | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | **NFS-e** Listagem com `dataInicial` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | **Fornecedor** Listagem | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | **Fornecedor** Reconhecimento (MD-e) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ### Clientes (CRUD) | Cenário | cURL | JS/TS | Python | PHP | Java | C# | Go | Ruby | |---|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:| | Cadastrar pessoa física | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Cadastrar pessoa jurídica | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Consultar por CPF/CNPJ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Buscar por nome (+ top_clientes) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Atualizar | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Excluir | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ### Produtos (CRUD) | Cenário | cURL | JS/TS | Python | PHP | Java | C# | Go | Ruby | |---|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:| | Cadastrar produto (com config fiscal) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Cadastrar serviço (tipo S) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Consultar por referência | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Buscar por nome | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Paginação com busca | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Atualizar | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Excluir | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ### Infraestrutura e padrões | Cenário | cURL | JS/TS | Python | PHP | Java | C# | Go | Ruby | |---|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:| | Health check (sem auth) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Cliente HTTP centralizado | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Helper de polling reutilizável | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Retry com backoff | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Captura `X-Request-Id` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Tratamento estruturado de erros | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ## Variáveis de ambiente comuns Todos os exemplos esperam: ```bash export MYSE_API_BASE="https://api.mysebr.com.br/nfemyse-v3/rest" export MYSE_API_TOKEN="seu_token_aqui" ``` --- # Exemplos em C# (.NET) Fonte: https://faznota.com.br/api/v3/exemplos/csharp/ Implementação completa da API do FazNota em .NET 6+ com HttpClient. ## Setup Requer **.NET 6+**. ```xml ``` ```bash # .env MYSE_API_BASE=https://api.mysebr.com.br/nfemyse-v3/rest MYSE_API_TOKEN=seu_token_aqui ``` ## Cliente HTTP centralizado ```csharp using System.Net.Http.Json; using System.Text.Json; using System.Text.Json.Nodes; namespace Myse; public class ApiError : Exception { public int HttpStatus { get; } public JsonNode? Payload { get; } public string? RequestId { get; } public ApiError(int httpStatus, string message, JsonNode? payload, string? requestId) : base(message) { HttpStatus = httpStatus; Payload = payload; RequestId = requestId; } } public class MyseApi { private readonly HttpClient _http; public MyseApi(string baseUrl, string token) { _http = new HttpClient { BaseAddress = new Uri(baseUrl), Timeout = TimeSpan.FromSeconds(30), }; _http.DefaultRequestHeaders.Add("Authorization", $"Token {token}"); } public async Task CallAsync( HttpMethod method, string path, object? body = null, CancellationToken ct = default) { var req = new HttpRequestMessage(method, path); if (body != null) { req.Content = JsonContent.Create(body); } var res = await _http.SendAsync(req, ct); var requestId = res.Headers.TryGetValues("X-Request-Id", out var v) ? v.FirstOrDefault() : null; var bodyStr = await res.Content.ReadAsStringAsync(ct); var json = JsonNode.Parse(bodyStr); if (!res.IsSuccessStatusCode) { var msg = json?["error"]?["message"]?.ToString() ?? $"HTTP {(int)res.StatusCode}"; throw new ApiError((int)res.StatusCode, msg, json, requestId); } if (json?["status"]?.ToString() == "999") { var msg = json["data"]?["erro"]?.ToString() ?? json["descricao"]?.ToString() ?? "erro"; var rid = json["meta"]?["request_id"]?.ToString() ?? requestId; throw new ApiError(200, msg, json, rid); } return json ?? throw new InvalidOperationException("Resposta vazia"); } } ``` ## Cenário: emitir NFCe com polling ```csharp public class EmissorNFCe { private readonly MyseApi _api; private static readonly HashSet Terminais = new() { "004", "010", "900", "999" }; public EmissorNFCe(MyseApi api) => _api = api; public async Task EmitirAsync(Pedido p, CancellationToken ct = default) { var body = new { serie = "65", cfop = "5102", numero_origem = $"PED-{p.Id}", tp_pagamento = p.FormaPagamento, cliente = p.CpfCnpjCliente, itens = p.Itens.Select(i => new { produto = i.Sku, quantidade = i.Qtd.ToString(), valor_unitario = i.Preco.ToString("F2"), }), }; var emissao = await _api.CallAsync(HttpMethod.Post, "/nfce/emissao", body, ct); var status = emissao["status"]?.ToString(); if (status != "001" && status != "050") { throw new InvalidOperationException( $"Emissão falhou: {emissao["descricao"]}" ); } var recibo = emissao["data"]!["recibo"]!.ToString(); Console.WriteLine($"[emissao] recibo={recibo} request_id={emissao["meta"]?["request_id"]}"); // Polling var wait = TimeSpan.FromSeconds(5); var inicio = DateTime.UtcNow; while (DateTime.UtcNow - inicio < TimeSpan.FromMinutes(5)) { await Task.Delay(wait, ct); var consulta = await _api.CallAsync(HttpMethod.Get, $"/nfce/cupom/{recibo}", null, ct); var st = consulta["status"]?.ToString(); Console.WriteLine($"[polling] status={st}"); if (Terminais.Contains(st!)) { if (st == "004") return consulta["data"]!; throw new InvalidOperationException($"Status terminal não-sucesso: {st}"); } wait = TimeSpan.FromMilliseconds(Math.Min(wait.TotalMilliseconds * 1.5, 30_000)); } throw new TimeoutException($"Timeout aguardando recibo {recibo}"); } } ``` ## Retry com backoff ```csharp public static async Task ComRetryAsync(Func> fn, int maxTentativas = 3) { Exception? last = null; for (int i = 1; i <= maxTentativas; i++) { try { return await fn(); } catch (ApiError e) when (e.Payload?["status"]?.ToString() == "999") { throw; // validação, não retentar } catch (Exception e) { last = e; if (i == maxTentativas) throw; await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, i - 1))); } } throw last!; } // Uso: var nfce = await ComRetryAsync(() => emissor.EmitirAsync(pedido)); ``` ## Helper de polling reutilizável ```csharp public static class FiscalHelpers { private static readonly HashSet Terminais = new() { "004", "010", "900", "999" }; public static async Task AguardarReciboAsync( MyseApi api, string pathBase, string recibo, TimeSpan? timeout = null, CancellationToken ct = default) { var max = timeout ?? TimeSpan.FromMinutes(5); var wait = TimeSpan.FromSeconds(5); var inicio = DateTime.UtcNow; while (DateTime.UtcNow - inicio < max) { await Task.Delay(wait, ct); var resp = await api.CallAsync(HttpMethod.Get, $"{pathBase}/{recibo}", null, ct); if (Terminais.Contains(resp["status"]?.ToString() ?? "")) return resp; wait = TimeSpan.FromMilliseconds(Math.Min(wait.TotalMilliseconds * 1.5, 30_000)); } throw new TimeoutException($"Timeout aguardando recibo {recibo}"); } public static void ValidarMotivo(string motivo) { if (motivo.Length < 15 || motivo.Length > 255) throw new ArgumentException("motivo deve ter entre 15 e 255 caracteres"); } } ``` ## Emitir NF-e (com transporte completo) ```csharp public async Task EmitirNFeAsync(Pedido p, CancellationToken ct = default) { var body = new { serie = "1", cfop = "5102", numero_origem = $"PED-{p.Id}", tipo = "S", finalidade = "N", valor_despesas = "0.00", cliente = p.CpfCnpjCliente, transporte = new { modalidade = "0", transportadora = p.Transportadora, placa_veiculo = p.Placa, valor_frete = p.Frete ?? "0.00", valor_seguro = "0.00", peso_bruto = p.PesoBruto, peso_liquido = p.PesoLiquido, quantidade_volume = "1", especie_volume = "Caixa", marca_volume = "Embalagem", }, itens = p.Itens.Select(i => new { produto = i.Sku, quantidade = i.Qtd.ToString(), valor_unitario = i.Preco.ToString("F2"), }), faturas = p.Faturas?.Select(f => new { valor = f.Valor.ToString("F2"), data = f.Data, }) ?? Enumerable.Empty(), }; var emissao = await _api.CallAsync(HttpMethod.Post, "/nfe/emissao", body, ct); var status = emissao["status"]?.ToString(); if (status != "001" && status != "050") throw new InvalidOperationException($"Emissão falhou: {emissao["descricao"]}"); var recibo = emissao["data"]!["recibo"]!.ToString(); var resultado = await FiscalHelpers.AguardarReciboAsync(_api, "/nfe/nota", recibo, ct: ct); if (resultado["status"]?.ToString() != "004") throw new InvalidOperationException($"Status terminal não-sucesso: {resultado["status"]}"); return resultado["data"]!; } ``` ## Cancelar NF-e ```csharp public async Task CancelarNFeAsync(string chave, string motivo, CancellationToken ct = default) { FiscalHelpers.ValidarMotivo(motivo); var cancel = await _api.CallAsync(HttpMethod.Post, "/nfe/cancelamento", new { chave, motivo }, ct); if (cancel["status"]?.ToString() != "001") throw new InvalidOperationException(cancel["descricao"]?.ToString()); return await FiscalHelpers.AguardarReciboAsync(_api, "/nfe/cancelamento", cancel["data"]!["recibo"]!.ToString(), ct: ct); } ``` ## Lançar carta de correção (CC-e) ```csharp /// /// O campo "motivo" na CC-e é o TEXTO da correção (xCorrecao). /// public async Task CartaCorrecaoAsync(string chave, string textoCorrecao, CancellationToken ct = default) { FiscalHelpers.ValidarMotivo(textoCorrecao); var cce = await _api.CallAsync(HttpMethod.Post, "/nfe/correcao", new { chave, motivo = textoCorrecao }, ct); if (cce["status"]?.ToString() != "001") throw new InvalidOperationException(cce["descricao"]?.ToString()); return await FiscalHelpers.AguardarReciboAsync(_api, "/nfe/correcao", cce["data"]!["recibo"]!.ToString(), ct: ct); } ``` ## Cancelar NFC-e ```csharp public async Task CancelarNFCeAsync(string chave, string motivo, CancellationToken ct = default) { FiscalHelpers.ValidarMotivo(motivo); var cancel = await _api.CallAsync(HttpMethod.Post, "/nfce/cancelamento", new { chave, motivo }, ct); if (cancel["status"]?.ToString() != "001") throw new InvalidOperationException(cancel["descricao"]?.ToString()); return await FiscalHelpers.AguardarReciboAsync(_api, "/nfce/cancelamento", cancel["data"]!["recibo"]!.ToString(), ct: ct); } ``` ## Emitir NFS-e (Nota Fiscal de Serviço) ```csharp public async Task EmitirNFSeAsync(Servico s, CancellationToken ct = default) { var body = new { numero_origem = $"OS-{s.Id}", observacao = s.Observacao, nome_contato = s.NomeContato, telefone_contato = s.TelefoneContato, data_fim = s.DataFim, cliente = s.CpfCnpjCliente, itens = s.Itens.Select(i => new { produto = i.CodigoServico, quantidade = i.Qtd.ToString(), valor_unitario = i.Preco.ToString("F2"), }), }; var emissao = await _api.CallAsync(HttpMethod.Post, "/nfse/emissao", body, ct); var status = emissao["status"]?.ToString(); if (status != "001" && status != "050") throw new InvalidOperationException($"Emissão NFSe falhou: {emissao["descricao"]}"); var recibo = emissao["data"]!["recibo"]!.ToString(); var resultado = await FiscalHelpers.AguardarReciboAsync(_api, "/nfse/nota", recibo, ct: ct); if (resultado["status"]?.ToString() != "004") throw new InvalidOperationException($"Status terminal não-sucesso: {resultado["status"]}"); return resultado["data"]!; } ``` ## Cancelar NFS-e ```csharp public async Task CancelarNFSeAsync(string chave, string motivo, CancellationToken ct = default) { FiscalHelpers.ValidarMotivo(motivo); return await _api.CallAsync(HttpMethod.Post, "/nfse/cancelamento", new { chave, motivo }, ct); } ``` ## Reconhecer nota de fornecedor (MD-e) ```csharp /// /// Códigos de reconhecimento: /// "210200" — Confirmação da operação (terminal, irreversível) /// "210210" — Ciência da operação (provisório, 15 dias) /// "210220" — Desconhecimento da operação /// "210240" — Operação não realizada /// public async Task ReconhecerNotaFornecedorAsync( string chave, string codigo, CancellationToken ct = default) { var validos = new HashSet { "210200", "210210", "210220", "210240" }; if (!validos.Contains(codigo)) throw new ArgumentException($"Código inválido: {codigo}"); var resp = await _api.CallAsync(HttpMethod.Post, "/nffornecedor/reconhecimento", new { chave, codigo_reconhecimento = codigo }, ct); if (resp["status"]?.ToString() != "001") throw new InvalidOperationException(resp["descricao"]?.ToString()); return await FiscalHelpers.AguardarReciboAsync(_api, "/nffornecedor", resp["data"]!["recibo"]!.ToString(), ct: ct); } ``` ## Listar com paginação ```csharp public async Task> ListarTodosClientesAsync(CancellationToken ct = default) { var todos = new List(); int page = 1; int pageSize = 100; while (true) { var resp = await _api.CallAsync(HttpMethod.Get, $"/clientes?page={page}&page_size={pageSize}", null, ct); if (resp["status"]?.ToString() != "005") break; if (resp["data"] is not JsonArray arr || arr.Count == 0) break; todos.AddRange(arr.Select(x => x!)); if (arr.Count < pageSize) break; page++; await Task.Delay(200, ct); } return todos; } ``` ## Tratamento de erros + logging estruturado ```csharp try { var nfce = await emissor.EmitirAsync(pedido); _logger.LogInformation("NFCe emitida: {Chave}", nfce["sefaz"]?["chave"]); } catch (ApiError e) { _logger.LogError( "API Error httpStatus={Status} requestId={RequestId} msg={Message}", e.HttpStatus, e.RequestId, e.Message ); throw; } ``` ## Health check (sem autenticação) ```csharp using var http = new HttpClient(); var res = await http.GetAsync($"{baseUrl}/health"); var health = await res.Content.ReadFromJsonAsync(); // { status: "ok", database: "ok", timestamp: "..." } ``` ## Alterar nota antes da emissão (PUT) Disponível em NF-e (`/nfe/{id}`), NFC-e (`/nfce/{id}`) e NFS-e (`/nfse/{id}`). **Só funciona enquanto a nota ainda não foi submetida para emissão.** ```csharp public async Task AlterarNotaAsync(string tipo, string id, object dados, CancellationToken ct = default) { // tipo: "nfe" | "nfce" | "nfse" return await _api.CallAsync(HttpMethod.Put, $"/{tipo}/{id}", dados, ct); } // Uso: await AlterarNotaAsync("nfe", "12345", new { serie = "1", cfop = "5102", cliente = "12345678000199", itens = new[] { new { produto = "SKU-001", quantidade = "3", valor_unitario = "89.90" } } }); ``` ## Consulta detalhada por recibo ```csharp var nfeCompleta = await _api.CallAsync(HttpMethod.Get, $"/nfe/nota/{recibo}/completa", null); var nfceCompleta = await _api.CallAsync(HttpMethod.Get, $"/nfce/cupom/{recibo}/completa", null); ``` ## Listagens ```csharp var nfes = await _api.CallAsync(HttpMethod.Get, "/nfe?page=1&page_size=20", null); var nfces = await _api.CallAsync(HttpMethod.Get, "/nfce?page=1&page_size=50", null); var nfses = await _api.CallAsync(HttpMethod.Get, "/nfse?dataInicial=2026-05-01&page=1&page_size=20", null); var fornecedor = await _api.CallAsync(HttpMethod.Get, "/nffornecedor?page=1&page_size=20", null); // Forma legada (path-based) var nfesLegado = await _api.CallAsync(HttpMethod.Get, "/nfe/paginacao/1/20", null); ``` ## Clientes (CRUD completo) ```csharp public async Task CadastrarPFAsync(PessoaFisica pf, CancellationToken ct = default) { return await _api.CallAsync(HttpMethod.Post, "/clientes", new { tipo = "F", nome = pf.Nome, cpf = pf.Cpf, rg = pf.Rg, consumidor_final = "1", contato = new { email = pf.Email, telefone = pf.Telefone }, endereco = pf.Endereco, }, ct); } public async Task CadastrarPJAsync(PessoaJuridica pj, CancellationToken ct = default) { return await _api.CallAsync(HttpMethod.Post, "/clientes", new { tipo = "J", razao_social = pj.RazaoSocial, nome_fantasia = pj.NomeFantasia, cnpj = pj.Cnpj, inscricao_municipal = pj.InscricaoMunicipal, tipo_inscricao_estadual = pj.Contribuinte ?? "9", inscricao_estadual = pj.InscricaoEstadual ?? "ISENTO", consumidor_final = "0", contato = new { email = pj.Email, telefone = pj.Telefone }, endereco = pj.Endereco, }, ct); } // Consultar por CPF/CNPJ var cliente = await _api.CallAsync(HttpMethod.Get, "/clientes/12345678000199", null); // Buscar por nome var encontrados = await _api.CallAsync(HttpMethod.Get, "/clientes/busca/exemplo", null); var top = await _api.CallAsync(HttpMethod.Get, "/clientes/busca/top_clientes", null); // Atualizar await _api.CallAsync(HttpMethod.Put, "/clientes/12345678000199", new { tipo = "J", razao_social = "Empresa Exemplo (Novo Nome) Ltda" }); // Excluir await _api.CallAsync(HttpMethod.Delete, "/clientes/12345678000199", null); ``` ## Produtos (CRUD completo) ```csharp public async Task CadastrarProdutoAsync(Produto p, CancellationToken ct = default) { return await _api.CallAsync(HttpMethod.Post, "/produtos", new { referencia = p.Sku, nome = p.Nome, valor = p.Valor.ToString("F2"), medida = p.Medida ?? "UN", categoria = p.Categoria, ncm = p.Ncm, origem = p.Origem ?? "0", cfop_preferencial = p.Cfop ?? "5102", tipo_produto = "P", configuracoes_fiscais = p.ConfiguracoesFiscais ?? new object[] { }, }, ct); } public async Task CadastrarServicoAsync(Servico s, CancellationToken ct = default) { return await _api.CallAsync(HttpMethod.Post, "/produtos", new { referencia = s.Sku, nome = s.Nome, valor = s.Valor.ToString("F2"), medida = "H", tipo_produto = "S", codigo_servico = s.CodigoServico, }, ct); } // Consultar var produto = await _api.CallAsync(HttpMethod.Get, "/produtos/SKU-001", null); var porNome = await _api.CallAsync(HttpMethod.Get, "/produtos/nome/Camiseta", null); // Listar paginado com busca (use _todos para não filtrar) var todos = await _api.CallAsync(HttpMethod.Get, "/produtos/paginacao/1/20/_todos", null); var filtrados = await _api.CallAsync(HttpMethod.Get, "/produtos/paginacao/1/20/Camiseta", null); // Atualizar await _api.CallAsync(HttpMethod.Put, "/produtos/SKU-001", new { referencia = "SKU-001", nome = "Camiseta Polo M (Novo)", valor = "99.90" }); // Excluir await _api.CallAsync(HttpMethod.Delete, "/produtos/SKU-001", null); ``` --- # Exemplos em cURL Fonte: https://faznota.com.br/api/v3/exemplos/curl/ Chamadas completas à API do FazNota via curl, com extração de campos e polling em shell script. ## Setup ```bash export MYSE_API_BASE="https://api.mysebr.com.br/nfemyse-v3/rest" export MYSE_API_TOKEN="seu_token_aqui" ``` ## Health check ```bash curl -i "$MYSE_API_BASE/health" ``` ## Emitir NFCe e aguardar resultado (polling) ```bash #!/bin/bash set -euo pipefail BASE="$MYSE_API_BASE" TOKEN="$MYSE_API_TOKEN" # 1. Emitir RESP=$(curl -sS -X POST "$BASE/nfce/emissao" \ -H "Authorization: Token $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "serie": "65", "cfop": "5102", "numero-origem": "VENDA-'$(date +%s)'", "tp_pagamento": "dinheiro", "itens": [ { "produto": "SKU-001", "quantidade": "1", "valor-unitario": "10.00" } ] }') echo "Resposta emissão: $RESP" STATUS=$(echo "$RESP" | jq -r .status) RECIBO=$(echo "$RESP" | jq -r .data.recibo) REQ_ID=$(echo "$RESP" | jq -r .meta.request_id) if [ "$STATUS" != "001" ] && [ "$STATUS" != "050" ]; then echo "Erro na emissão (request_id=$REQ_ID): $RESP" >&2 exit 1 fi echo "Recibo: $RECIBO (request_id=$REQ_ID)" # 2. Polling WAIT=5 TERM_REGEX='^(004|010|900|999)$' for i in $(seq 1 30); do sleep $WAIT RESP=$(curl -sS "$BASE/nfce/cupom/$RECIBO" \ -H "Authorization: Token $TOKEN") STATUS=$(echo "$RESP" | jq -r .status) echo "[$i] status=$STATUS" if [[ $STATUS =~ $TERM_REGEX ]]; then echo "Status terminal: $STATUS" echo "$RESP" | jq . exit 0 fi # Backoff até 30s WAIT=$(echo "$WAIT * 1.5" | bc | awk '{ printf "%d", $1 < 30 ? $1 : 30 }') done echo "Timeout aguardando recibo $RECIBO" >&2 exit 1 ``` ## Listar NFes com paginação ```bash PAGE=1 SIZE=20 while :; do RESP=$(curl -sS "$MYSE_API_BASE/nfe?page=$PAGE&page_size=$SIZE" \ -H "Authorization: Token $MYSE_API_TOKEN") STATUS=$(echo "$RESP" | jq -r .status) [ "$STATUS" != "005" ] && { echo "Erro: $RESP"; break; } COUNT=$(echo "$RESP" | jq '.data | length') echo "Página $PAGE: $COUNT registros" echo "$RESP" | jq -c '.data[]' [ "$COUNT" -lt "$SIZE" ] && break PAGE=$((PAGE + 1)) done ``` ## Helper de polling reutilizável ```bash # Função genérica: aguarda recibo até status terminal. # Uso: aguardar_recibo /nfe/nota $RECIBO aguardar_recibo() { local PATH_BASE="$1" local RECIBO="$2" local TERM_REGEX='^(004|010|900|999)$' local WAIT=5 for i in $(seq 1 30); do sleep $WAIT RESP=$(curl -sS "$MYSE_API_BASE${PATH_BASE}/$RECIBO" \ -H "Authorization: Token $MYSE_API_TOKEN") STATUS=$(echo "$RESP" | jq -r .status) echo "[$i] status=$STATUS" if [[ $STATUS =~ $TERM_REGEX ]]; then echo "$RESP" return 0 fi WAIT=$(echo "$WAIT * 1.5" | bc | awk '{ printf "%d", $1 < 30 ? $1 : 30 }') done echo "Timeout aguardando recibo $RECIBO" >&2 return 1 } ``` ## Emitir NF-e (com transporte completo) ```bash RESP=$(curl -sS -X POST "$MYSE_API_BASE/nfe/emissao" \ -H "Authorization: Token $MYSE_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "serie": "1", "cfop": "5102", "numero-origem": "PED-'$(date +%s)'", "tipo": "S", "finalidade": "N", "valor-despesas": "0.00", "cliente": "12345678000199", "transporte": { "modalidade": "0", "transportadora": "98765432000111", "placa-veiculo": "ABC-1D23", "valor-frete": "150.00", "valor-seguro": "0.00", "peso-bruto": "5.5", "peso-liquido": "5.0", "quantidade-volume": "1", "especie-volume": "Caixa", "marca-volume": "Embalagem padrao" }, "itens": [ { "produto": "SKU-001", "quantidade": "2", "valor-unitario": "89.90" } ], "faturas": [ { "valor": "179.80", "data": "2026-06-15" } ] }') RECIBO=$(echo "$RESP" | jq -r .data.recibo) echo "Recibo NF-e: $RECIBO" aguardar_recibo /nfe/nota "$RECIBO" ``` ## Cancelar NF-e ```bash RESP=$(curl -sS -X POST "$MYSE_API_BASE/nfe/cancelamento" \ -H "Authorization: Token $MYSE_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "chave": "35260512345678000199550010000000011000000010", "motivo": "Cancelamento solicitado pelo cliente — pedido refeito." }') RECIBO=$(echo "$RESP" | jq -r .data.recibo) aguardar_recibo /nfe/cancelamento "$RECIBO" ``` ## Lançar carta de correção (CC-e) ```bash RESP=$(curl -sS -X POST "$MYSE_API_BASE/nfe/correcao" \ -H "Authorization: Token $MYSE_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "chave": "35260512345678000199550010000000011000000010", "motivo": "Correcao da descricao do item 3 — modelo correto: XYZ-2." }') RECIBO=$(echo "$RESP" | jq -r .data.recibo) aguardar_recibo /nfe/correcao "$RECIBO" ``` ## Cancelar NFC-e ```bash RESP=$(curl -sS -X POST "$MYSE_API_BASE/nfce/cancelamento" \ -H "Authorization: Token $MYSE_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "chave": "35260512345678000199650010000000011000000010", "motivo": "Cancelamento de cupom solicitado pelo cliente." }') RECIBO=$(echo "$RESP" | jq -r .data.recibo) aguardar_recibo /nfce/cancelamento "$RECIBO" ``` ## Emitir NFS-e (Nota Fiscal de Serviço) ```bash RESP=$(curl -sS -X POST "$MYSE_API_BASE/nfse/emissao" \ -H "Authorization: Token $MYSE_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "numero-origem": "OS-'$(date +%s)'", "observacao": "Servico de consultoria realizado em maio/2026.", "nome-contato": "Joao Silva", "telefone-contato": "11987654321", "data-fim": "2026-05-31", "cliente": "12345678000199", "itens": [ { "produto": "SRV-CONSULT", "quantidade": "10", "valor-unitario": "250.00" } ] }') RECIBO=$(echo "$RESP" | jq -r .data.recibo) aguardar_recibo /nfse/nota "$RECIBO" ``` ## Cancelar NFS-e ```bash curl -sS -X POST "$MYSE_API_BASE/nfse/cancelamento" \ -H "Authorization: Token $MYSE_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "chave": "chave_nfse_aqui", "motivo": "Cancelamento solicitado pelo tomador — servico nao realizado." }' ``` ## Reconhecer nota de fornecedor (MD-e) ```bash # Códigos: # 210200 — Confirmação da operação (terminal, irreversível) # 210210 — Ciência da operação (provisório, 15 dias) # 210220 — Desconhecimento da operação # 210240 — Operação não realizada RESP=$(curl -sS -X POST "$MYSE_API_BASE/nffornecedor/reconhecimento" \ -H "Authorization: Token $MYSE_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "chave": "35260512345678000199550010000000011000000010", "codigo-reconhecimento": "210200" }') RECIBO=$(echo "$RESP" | jq -r .data.recibo) echo "Recibo MD-e: $RECIBO" # Consultar resultado: curl -sS "$MYSE_API_BASE/nffornecedor/$RECIBO" \ -H "Authorization: Token $MYSE_API_TOKEN" | jq . ``` ## Capturar headers de rate limit ```bash curl -sS -D - "$MYSE_API_BASE/nfe?page=1&page_size=1" \ -H "Authorization: Token $MYSE_API_TOKEN" \ -o /dev/null \ | grep -i -E "x-request-id|x-ratelimit" ``` Saída esperada: ``` X-Request-Id: req_01HXY8ZG3J4K5M6N7P8Q9R0S1T X-RateLimit-Limit: 150000 X-RateLimit-Remaining: 149873 X-RateLimit-Reset: 1715600000 ``` ## Tratamento de erro em shell ```bash call_api() { local METHOD="$1" local PATH="$2" local BODY="${3:-}" local CURL_ARGS=(-sS -X "$METHOD" -H "Authorization: Token $MYSE_API_TOKEN" -H "Content-Type: application/json" -D /tmp/myse-headers.txt) [ -n "$BODY" ] && CURL_ARGS+=(-d "$BODY") RESP=$(curl "${CURL_ARGS[@]}" "$MYSE_API_BASE$PATH") REQ_ID=$(grep -i "x-request-id:" /tmp/myse-headers.txt | awk '{print $2}' | tr -d '\r\n') STATUS=$(echo "$RESP" | jq -r .status) case "$STATUS" in 001|002|003|004|005|010) echo "$RESP" ;; 050) echo "Duplicidade (use recibo retornado): $RESP" >&2 echo "$RESP" ;; 900) echo "Rejeição SEFAZ (request_id=$REQ_ID): $RESP" >&2 exit 2 ;; 999) echo "Erro interno (request_id=$REQ_ID): $RESP" >&2 exit 3 ;; *) echo "Status desconhecido $STATUS (request_id=$REQ_ID): $RESP" >&2 exit 99 ;; esac } ``` ## Health check (sem autenticação) ```bash curl -i "$MYSE_API_BASE/health" # HTTP/1.1 200 OK # {"status":"ok","database":"ok","timestamp":"2026-05-13T14:32:01Z"} ``` ## Alterar nota antes da emissão (PUT) Disponível em NF-e (`/nfe/{id}`), NFC-e (`/nfce/{id}`) e NFS-e (`/nfse/{id}`). **Só funciona enquanto a nota ainda não foi submetida para emissão.** ```bash # Substitua o caminho e o ID conforme o documento curl -X PUT "$MYSE_API_BASE/nfe/12345" \ -H "Authorization: Token $MYSE_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "serie": "1", "cfop": "5102", "cliente": "12345678000199", "itens": [ { "produto": "SKU-001", "quantidade": "3", "valor-unitario": "89.90" } ] }' ``` ## Consulta detalhada (NF-e/NFC-e completa por recibo) Diferente do polling rápido (`/nota/{recibo}`), retorna **todos** os dados: itens, cliente, transporte, faturas, notas referenciadas, dados SEFAZ. ```bash # NF-e completa curl "$MYSE_API_BASE/nfe/nota/$RECIBO/completa" \ -H "Authorization: Token $MYSE_API_TOKEN" | jq . # NFC-e completa curl "$MYSE_API_BASE/nfce/cupom/$RECIBO/completa" \ -H "Authorization: Token $MYSE_API_TOKEN" | jq . ``` ## Listagens (paginação) ```bash # NF-es paginadas curl "$MYSE_API_BASE/nfe?page=1&page_size=20" \ -H "Authorization: Token $MYSE_API_TOKEN" # NFC-es curl "$MYSE_API_BASE/nfce?page=1&page_size=50" \ -H "Authorization: Token $MYSE_API_TOKEN" # NFS-es com filtro de data (ISO YYYY-MM-DD) curl "$MYSE_API_BASE/nfse?dataInicial=2026-05-01&page=1&page_size=20" \ -H "Authorization: Token $MYSE_API_TOKEN" # Notas de fornecedor curl "$MYSE_API_BASE/nffornecedor?page=1&page_size=20" \ -H "Authorization: Token $MYSE_API_TOKEN" # Forma legada (path-based) — mantida por compatibilidade curl "$MYSE_API_BASE/nfe/paginacao/1/20" \ -H "Authorization: Token $MYSE_API_TOKEN" ``` ## Clientes (CRUD completo) ```bash # Cadastrar pessoa física curl -X POST "$MYSE_API_BASE/clientes" \ -H "Authorization: Token $MYSE_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "tipo": "F", "nome": "Joao da Silva", "cpf": "12345678901", "rg": "MG1234567", "consumidor-final": "1", "contato": { "email": "joao@exemplo.com.br", "telefone": "11987654321" }, "endereco": { "cep": "01310100", "rua": "Av. Paulista", "numero": "1000", "bairro": "Bela Vista", "cidade": "Sao Paulo", "estado": "SP" } }' # Cadastrar pessoa jurídica curl -X POST "$MYSE_API_BASE/clientes" \ -H "Authorization: Token $MYSE_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "tipo": "J", "razao-social": "Empresa Exemplo Ltda", "nome-fantasia": "Empresa Exemplo", "cnpj": "12345678000199", "inscricao-municipal": "1234567", "tipo-inscricao-estadual": "1", "inscricao-estadual": "123456789", "consumidor-final": "0", "contato": { "email": "contato@empresa.com.br", "telefone": "1133334444" }, "endereco": { "cep": "01310100", "rua": "Av. Paulista", "numero": "1000", "bairro": "Bela Vista", "cidade": "Sao Paulo", "estado": "SP" } }' # Consultar por CPF (11 dígitos) ou CNPJ (14 dígitos) curl "$MYSE_API_BASE/clientes/12345678000199" \ -H "Authorization: Token $MYSE_API_TOKEN" # Buscar por nome (parcial) curl "$MYSE_API_BASE/clientes/busca/exemplo" \ -H "Authorization: Token $MYSE_API_TOKEN" # Top 20 clientes mais relevantes da empresa curl "$MYSE_API_BASE/clientes/busca/top_clientes" \ -H "Authorization: Token $MYSE_API_TOKEN" # Atualizar curl -X PUT "$MYSE_API_BASE/clientes/12345678000199" \ -H "Authorization: Token $MYSE_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "tipo": "J", "razao-social": "Empresa Exemplo (Novo Nome) Ltda" }' # Excluir curl -X DELETE "$MYSE_API_BASE/clientes/12345678000199" \ -H "Authorization: Token $MYSE_API_TOKEN" ``` ## Produtos (CRUD completo) ```bash # Cadastrar produto (com configuração fiscal por CFOP/estado) curl -X POST "$MYSE_API_BASE/produtos" \ -H "Authorization: Token $MYSE_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "referencia": "SKU-001", "nome": "Camiseta Polo Tamanho M", "valor": "89.90", "medida": "UN", "categoria": "Vestuario", "ncm": "61051000", "origem": "0", "cfop-preferencial": "5102", "tipo-produto": "P", "configuracoes-fiscais": [{ "cfop": "5102", "configuracoes": { "ipi": { "ipi-cst": "53", "ipi-aliquota": "0.00" }, "pis-cofins": { "pis-cst": "01", "pis-aliquota": "1.65", "cofins-cst": "01", "cofins-aliquota": "7.60" }, "icms": { "estados": [{ "estado": "SP", "icms-cst": "00", "modalidade-base-calculo": "0", "aliquotas": { "aliquota-icms": "18.00" } }] } } }] }' # Cadastrar serviço (tipo S, com código de serviço municipal) curl -X POST "$MYSE_API_BASE/produtos" \ -H "Authorization: Token $MYSE_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "referencia": "SRV-CONSULT", "nome": "Consultoria tecnica (hora)", "valor": "250.00", "medida": "H", "tipo-produto": "S", "codigo-servico": "1.04" }' # Consultar por referência curl "$MYSE_API_BASE/produtos/SKU-001" \ -H "Authorization: Token $MYSE_API_TOKEN" # Buscar por nome (1º resultado) curl "$MYSE_API_BASE/produtos/nome/Camiseta" \ -H "Authorization: Token $MYSE_API_TOKEN" # Listar paginado com busca por nome (legado) # Use _todos para não filtrar curl "$MYSE_API_BASE/produtos/paginacao/1/20/_todos" \ -H "Authorization: Token $MYSE_API_TOKEN" curl "$MYSE_API_BASE/produtos/paginacao/1/20/Camiseta" \ -H "Authorization: Token $MYSE_API_TOKEN" # Atualizar curl -X PUT "$MYSE_API_BASE/produtos/SKU-001" \ -H "Authorization: Token $MYSE_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "referencia": "SKU-001", "nome": "Camiseta Polo M (Novo)", "valor": "99.90" }' # Excluir curl -X DELETE "$MYSE_API_BASE/produtos/SKU-001" \ -H "Authorization: Token $MYSE_API_TOKEN" ``` --- # Exemplos em Go Fonte: https://faznota.com.br/api/v3/exemplos/go/ Implementação completa da API do FazNota em Go 1.21+ com net/http stdlib. ## Setup Requer **Go 1.21+**. Sem dependências externas (usa stdlib). ```bash export MYSE_API_BASE="https://api.mysebr.com.br/nfemyse-v3/rest" export MYSE_API_TOKEN="seu_token_aqui" ``` ## Cliente HTTP centralizado ```go package myse import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "os" "time" ) type ApiResponse struct { Status string `json:"status"` Descricao string `json:"descricao"` Data json.RawMessage `json:"data,omitempty"` Meta *Meta `json:"meta,omitempty"` } type Meta struct { RequestID string `json:"request_id"` Timestamp string `json:"timestamp"` } type ApiError struct { HTTPStatus int Message string Payload json.RawMessage RequestID string } func (e *ApiError) Error() string { return fmt.Sprintf("[%d] %s (request_id=%s)", e.HTTPStatus, e.Message, e.RequestID) } type Client struct { Base string Token string HTTP *http.Client } func New() *Client { return &Client{ Base: os.Getenv("MYSE_API_BASE"), Token: os.Getenv("MYSE_API_TOKEN"), HTTP: &http.Client{Timeout: 30 * time.Second}, } } func (c *Client) Call(ctx context.Context, method, path string, body any) (*ApiResponse, error) { var reader io.Reader if body != nil { b, err := json.Marshal(body) if err != nil { return nil, err } reader = bytes.NewReader(b) } req, err := http.NewRequestWithContext(ctx, method, c.Base+path, reader) if err != nil { return nil, err } req.Header.Set("Authorization", "Token "+c.Token) req.Header.Set("Content-Type", "application/json") res, err := c.HTTP.Do(req) if err != nil { return nil, err } defer res.Body.Close() bodyBytes, _ := io.ReadAll(res.Body) requestID := res.Header.Get("X-Request-Id") if res.StatusCode >= 400 { return nil, &ApiError{ HTTPStatus: res.StatusCode, Message: fmt.Sprintf("HTTP %d", res.StatusCode), Payload: bodyBytes, RequestID: requestID, } } var apiResp ApiResponse if err := json.Unmarshal(bodyBytes, &apiResp); err != nil { return nil, err } if apiResp.Status == "999" { // Extrai erro do data var data map[string]any json.Unmarshal(apiResp.Data, &data) msg, _ := data["erro"].(string) if msg == "" { msg = apiResp.Descricao } rid := requestID if apiResp.Meta != nil && apiResp.Meta.RequestID != "" { rid = apiResp.Meta.RequestID } return nil, &ApiError{HTTPStatus: 200, Message: msg, Payload: bodyBytes, RequestID: rid} } return &apiResp, nil } ``` ## Cenário: emitir NFCe com polling ```go package main import ( "context" "encoding/json" "fmt" "math" "time" ) var terminais = map[string]bool{"004": true, "010": true, "900": true, "999": true} func EmitirNFCe(ctx context.Context, c *myse.Client, pedido Pedido) (json.RawMessage, error) { body := map[string]any{ "serie": "65", "cfop": "5102", "numero-origem": fmt.Sprintf("PED-%d", pedido.ID), "tp_pagamento": pedido.FormaPagamento, "cliente": pedido.CpfCnpjCliente, "itens": buildItens(pedido.Itens), } emissao, err := c.Call(ctx, "POST", "/nfce/emissao", body) if err != nil { return nil, err } if emissao.Status != "001" && emissao.Status != "050" { return nil, fmt.Errorf("emissão falhou: %s", emissao.Descricao) } var emissaoData map[string]string json.Unmarshal(emissao.Data, &emissaoData) recibo := emissaoData["recibo"] fmt.Printf("[emissao] recibo=%s request_id=%s\n", recibo, emissao.Meta.RequestID) wait := 5 * time.Second deadline := time.Now().Add(5 * time.Minute) for time.Now().Before(deadline) { time.Sleep(wait) consulta, err := c.Call(ctx, "GET", "/nfce/cupom/"+recibo, nil) if err != nil { return nil, err } fmt.Printf("[polling] status=%s\n", consulta.Status) if terminais[consulta.Status] { if consulta.Status == "004" { return consulta.Data, nil } return nil, fmt.Errorf("status terminal não-sucesso: %s", consulta.Status) } wait = time.Duration(math.Min(float64(wait)*1.5, float64(30*time.Second))) } return nil, fmt.Errorf("timeout aguardando recibo %s", recibo) } func buildItens(itens []Item) []map[string]string { out := make([]map[string]string, len(itens)) for i, it := range itens { out[i] = map[string]string{ "produto": it.SKU, "quantidade": fmt.Sprintf("%d", it.Qtd), "valor-unitario": fmt.Sprintf("%.2f", it.Preco), } } return out } ``` ## Retry com backoff ```go func ComRetry[T any](ctx context.Context, fn func() (T, error), max int) (T, error) { var zero T var lastErr error for i := 1; i <= max; i++ { result, err := fn() if err == nil { return result, nil } // Erros de validação: não retentar if apiErr, ok := err.(*myse.ApiError); ok { var p map[string]any json.Unmarshal(apiErr.Payload, &p) if p["status"] == "999" { return zero, err } } lastErr = err if i < max { time.Sleep(time.Duration(math.Pow(2, float64(i-1))) * time.Second) } } return zero, lastErr } ``` ## Helper de polling reutilizável ```go var terminais = map[string]bool{"004": true, "010": true, "900": true, "999": true} func AguardarRecibo(ctx context.Context, c *myse.Client, pathBase, recibo string, timeout time.Duration) (*myse.ApiResponse, error) { if timeout == 0 { timeout = 5 * time.Minute } wait := 5 * time.Second inicio := time.Now() for time.Since(inicio) < timeout { time.Sleep(wait) resp, err := c.Call(ctx, "GET", pathBase+"/"+recibo, nil) if err != nil { return nil, err } if terminais[resp.Status] { return resp, nil } wait = time.Duration(math.Min(float64(wait)*1.5, float64(30*time.Second))) } return nil, fmt.Errorf("timeout aguardando recibo %s", recibo) } func validarMotivo(motivo string) error { if len(motivo) < 15 || len(motivo) > 255 { return fmt.Errorf("motivo deve ter entre 15 e 255 caracteres") } return nil } ``` ## Emitir NF-e (com transporte completo) ```go func EmitirNFe(ctx context.Context, c *myse.Client, pedido Pedido) (json.RawMessage, error) { faturas := make([]map[string]string, 0, len(pedido.Faturas)) for _, f := range pedido.Faturas { faturas = append(faturas, map[string]string{ "valor": fmt.Sprintf("%.2f", f.Valor), "data": f.Data, }) } body := map[string]any{ "serie": "1", "cfop": "5102", "numero-origem": fmt.Sprintf("PED-%d", pedido.ID), "tipo": "S", "finalidade": "N", "valor-despesas": "0.00", "cliente": pedido.CpfCnpjCliente, "transporte": map[string]string{ "modalidade": "0", "transportadora": pedido.Transportadora, "placa-veiculo": pedido.Placa, "valor-frete": pedido.Frete, "valor-seguro": "0.00", "peso-bruto": pedido.PesoBruto, "peso-liquido": pedido.PesoLiquido, "quantidade-volume": "1", "especie-volume": "Caixa", "marca-volume": "Embalagem", }, "itens": buildItens(pedido.Itens), "faturas": faturas, } emissao, err := c.Call(ctx, "POST", "/nfe/emissao", body) if err != nil { return nil, err } if emissao.Status != "001" && emissao.Status != "050" { return nil, fmt.Errorf("emissão falhou: %s", emissao.Descricao) } var d map[string]string json.Unmarshal(emissao.Data, &d) resultado, err := AguardarRecibo(ctx, c, "/nfe/nota", d["recibo"], 0) if err != nil { return nil, err } if resultado.Status != "004" { return nil, fmt.Errorf("status terminal não-sucesso: %s", resultado.Status) } return resultado.Data, nil // inclui sefaz.chave, url-danfe, url-xml } ``` ## Cancelar NF-e ```go func CancelarNFe(ctx context.Context, c *myse.Client, chave, motivo string) (*myse.ApiResponse, error) { if err := validarMotivo(motivo); err != nil { return nil, err } cancel, err := c.Call(ctx, "POST", "/nfe/cancelamento", map[string]string{ "chave": chave, "motivo": motivo, }) if err != nil { return nil, err } if cancel.Status != "001" { return nil, fmt.Errorf(cancel.Descricao) } var d map[string]string json.Unmarshal(cancel.Data, &d) return AguardarRecibo(ctx, c, "/nfe/cancelamento", d["recibo"], 0) } ``` ## Lançar carta de correção (CC-e) ```go // O campo "motivo" na CC-e é o TEXTO da correção (xCorrecao). func CartaCorrecaoNFe(ctx context.Context, c *myse.Client, chave, textoCorrecao string) (*myse.ApiResponse, error) { if err := validarMotivo(textoCorrecao); err != nil { return nil, err } cce, err := c.Call(ctx, "POST", "/nfe/correcao", map[string]string{ "chave": chave, "motivo": textoCorrecao, }) if err != nil { return nil, err } if cce.Status != "001" { return nil, fmt.Errorf(cce.Descricao) } var d map[string]string json.Unmarshal(cce.Data, &d) return AguardarRecibo(ctx, c, "/nfe/correcao", d["recibo"], 0) } ``` ## Cancelar NFC-e ```go func CancelarNFCe(ctx context.Context, c *myse.Client, chave, motivo string) (*myse.ApiResponse, error) { if err := validarMotivo(motivo); err != nil { return nil, err } cancel, err := c.Call(ctx, "POST", "/nfce/cancelamento", map[string]string{ "chave": chave, "motivo": motivo, }) if err != nil { return nil, err } if cancel.Status != "001" { return nil, fmt.Errorf(cancel.Descricao) } var d map[string]string json.Unmarshal(cancel.Data, &d) return AguardarRecibo(ctx, c, "/nfce/cancelamento", d["recibo"], 0) } ``` ## Emitir NFS-e (Nota Fiscal de Serviço) ```go func EmitirNFSe(ctx context.Context, c *myse.Client, servico Servico) (json.RawMessage, error) { itens := make([]map[string]string, 0, len(servico.Itens)) for _, i := range servico.Itens { itens = append(itens, map[string]string{ "produto": i.CodigoServico, "quantidade": fmt.Sprintf("%d", i.Qtd), "valor-unitario": fmt.Sprintf("%.2f", i.Preco), }) } body := map[string]any{ "numero-origem": fmt.Sprintf("OS-%d", servico.ID), "observacao": servico.Observacao, "nome-contato": servico.NomeContato, "telefone-contato": servico.TelefoneContato, "data-fim": servico.DataFim, "cliente": servico.CpfCnpjCliente, "itens": itens, } emissao, err := c.Call(ctx, "POST", "/nfse/emissao", body) if err != nil { return nil, err } if emissao.Status != "001" && emissao.Status != "050" { return nil, fmt.Errorf("emissão NFSe falhou: %s", emissao.Descricao) } var d map[string]string json.Unmarshal(emissao.Data, &d) resultado, err := AguardarRecibo(ctx, c, "/nfse/nota", d["recibo"], 0) if err != nil { return nil, err } if resultado.Status != "004" { return nil, fmt.Errorf("status terminal não-sucesso: %s", resultado.Status) } return resultado.Data, nil } ``` ## Cancelar NFS-e ```go func CancelarNFSe(ctx context.Context, c *myse.Client, chave, motivo string) (*myse.ApiResponse, error) { if err := validarMotivo(motivo); err != nil { return nil, err } return c.Call(ctx, "POST", "/nfse/cancelamento", map[string]string{ "chave": chave, "motivo": motivo, }) } ``` ## Reconhecer nota de fornecedor (MD-e) ```go // Códigos de reconhecimento: // "210200" — Confirmação da operação (terminal, irreversível) // "210210" — Ciência da operação (provisório, 15 dias) // "210220" — Desconhecimento da operação // "210240" — Operação não realizada func ReconhecerNotaFornecedor(ctx context.Context, c *myse.Client, chave, codigo string) (*myse.ApiResponse, error) { validos := map[string]bool{"210200": true, "210210": true, "210220": true, "210240": true} if !validos[codigo] { return nil, fmt.Errorf("código inválido: %s", codigo) } resp, err := c.Call(ctx, "POST", "/nffornecedor/reconhecimento", map[string]string{ "chave": chave, "codigo-reconhecimento": codigo, }) if err != nil { return nil, err } if resp.Status != "001" { return nil, fmt.Errorf(resp.Descricao) } var d map[string]string json.Unmarshal(resp.Data, &d) return AguardarRecibo(ctx, c, "/nffornecedor", d["recibo"], 0) } ``` ## Listar com paginação ```go func ListarTodosClientes(ctx context.Context, c *myse.Client) ([]json.RawMessage, error) { var todos []json.RawMessage page := 1 pageSize := 100 for { path := fmt.Sprintf("/clientes?page=%d&page_size=%d", page, pageSize) resp, err := c.Call(ctx, "GET", path, nil) if err != nil { return nil, err } if resp.Status != "005" { break } var batch []json.RawMessage json.Unmarshal(resp.Data, &batch) if len(batch) == 0 { break } todos = append(todos, batch...) if len(batch) < pageSize { break } page++ time.Sleep(200 * time.Millisecond) } return todos, nil } ``` ## Tratamento de erros ```go func main() { ctx := context.Background() c := myse.New() data, err := EmitirNFCe(ctx, c, pedido) if err != nil { if apiErr, ok := err.(*myse.ApiError); ok { log.Printf("API error: status=%d request_id=%s msg=%s", apiErr.HTTPStatus, apiErr.RequestID, apiErr.Message) } os.Exit(1) } log.Printf("NFCe emitida: %s", string(data)) } ``` ## Health check (sem autenticação) ```go res, _ := http.Get(myse.Base + "/health") defer res.Body.Close() var health map[string]string json.NewDecoder(res.Body).Decode(&health) // map[status:ok database:ok timestamp:...] ``` ## Alterar nota antes da emissão (PUT) Disponível em NF-e (`/nfe/{id}`), NFC-e (`/nfce/{id}`) e NFS-e (`/nfse/{id}`). **Só funciona enquanto a nota ainda não foi submetida para emissão.** ```go // tipo: "nfe" | "nfce" | "nfse" func AlterarNota(ctx context.Context, c *myse.Client, tipo, id string, dados any) (*myse.ApiResponse, error) { return c.Call(ctx, "PUT", fmt.Sprintf("/%s/%s", tipo, id), dados) } // Uso: AlterarNota(ctx, c, "nfe", "12345", map[string]any{ "serie": "1", "cfop": "5102", "cliente": "12345678000199", "itens": []map[string]string{ {"produto": "SKU-001", "quantidade": "3", "valor-unitario": "89.90"}, }, }) ``` ## Consulta detalhada por recibo ```go nfeCompleta, _ := c.Call(ctx, "GET", "/nfe/nota/"+recibo+"/completa", nil) nfceCompleta, _ := c.Call(ctx, "GET", "/nfce/cupom/"+recibo+"/completa", nil) ``` ## Listagens ```go nfes, _ := c.Call(ctx, "GET", "/nfe?page=1&page_size=20", nil) nfces, _ := c.Call(ctx, "GET", "/nfce?page=1&page_size=50", nil) nfses, _ := c.Call(ctx, "GET", "/nfse?dataInicial=2026-05-01&page=1&page_size=20", nil) fornecedor, _ := c.Call(ctx, "GET", "/nffornecedor?page=1&page_size=20", nil) // Forma legada (path-based) nfesLegado, _ := c.Call(ctx, "GET", "/nfe/paginacao/1/20", nil) ``` ## Clientes (CRUD completo) ```go type PessoaFisica struct { Nome, Cpf, Rg, Email, Telefone string Endereco map[string]string } func CadastrarPF(ctx context.Context, c *myse.Client, pf PessoaFisica) (*myse.ApiResponse, error) { return c.Call(ctx, "POST", "/clientes", map[string]any{ "tipo": "F", "nome": pf.Nome, "cpf": pf.Cpf, "rg": pf.Rg, "consumidor-final": "1", "contato": map[string]string{"email": pf.Email, "telefone": pf.Telefone}, "endereco": pf.Endereco, }) } type PessoaJuridica struct { RazaoSocial, NomeFantasia, Cnpj, InscricaoMunicipal string Contribuinte, InscricaoEstadual, Email, Telefone string Endereco map[string]string } func CadastrarPJ(ctx context.Context, c *myse.Client, pj PessoaJuridica) (*myse.ApiResponse, error) { contribuinte := pj.Contribuinte if contribuinte == "" { contribuinte = "9" } ie := pj.InscricaoEstadual if ie == "" { ie = "ISENTO" } return c.Call(ctx, "POST", "/clientes", map[string]any{ "tipo": "J", "razao-social": pj.RazaoSocial, "nome-fantasia": pj.NomeFantasia, "cnpj": pj.Cnpj, "inscricao-municipal": pj.InscricaoMunicipal, "tipo-inscricao-estadual": contribuinte, "inscricao-estadual": ie, "consumidor-final": "0", "contato": map[string]string{"email": pj.Email, "telefone": pj.Telefone}, "endereco": pj.Endereco, }) } // Consultar por CPF/CNPJ cliente, _ := c.Call(ctx, "GET", "/clientes/12345678000199", nil) // Buscar por nome encontrados, _ := c.Call(ctx, "GET", "/clientes/busca/exemplo", nil) top, _ := c.Call(ctx, "GET", "/clientes/busca/top_clientes", nil) // Atualizar c.Call(ctx, "PUT", "/clientes/12345678000199", map[string]string{ "tipo": "J", "razao-social": "Empresa Exemplo (Novo Nome) Ltda", }) // Excluir c.Call(ctx, "DELETE", "/clientes/12345678000199", nil) ``` ## Produtos (CRUD completo) ```go type Produto struct { Sku, Nome, Medida, Categoria, Ncm, Origem, Cfop string Valor float64 ConfiguracoesFiscais []any } func CadastrarProduto(ctx context.Context, c *myse.Client, p Produto) (*myse.ApiResponse, error) { medida := p.Medida if medida == "" { medida = "UN" } origem := p.Origem if origem == "" { origem = "0" } cfop := p.Cfop if cfop == "" { cfop = "5102" } return c.Call(ctx, "POST", "/produtos", map[string]any{ "referencia": p.Sku, "nome": p.Nome, "valor": fmt.Sprintf("%.2f", p.Valor), "medida": medida, "categoria": p.Categoria, "ncm": p.Ncm, "origem": origem, "cfop-preferencial": cfop, "tipo-produto": "P", "configuracoes-fiscais": p.ConfiguracoesFiscais, }) } type Servico struct { Sku, Nome, CodigoServico string Valor float64 } func CadastrarServico(ctx context.Context, c *myse.Client, s Servico) (*myse.ApiResponse, error) { return c.Call(ctx, "POST", "/produtos", map[string]any{ "referencia": s.Sku, "nome": s.Nome, "valor": fmt.Sprintf("%.2f", s.Valor), "medida": "H", "tipo-produto": "S", "codigo-servico": s.CodigoServico, }) } // Consultar produto, _ := c.Call(ctx, "GET", "/produtos/SKU-001", nil) porNome, _ := c.Call(ctx, "GET", "/produtos/nome/Camiseta", nil) // Listar paginado com busca (use _todos para não filtrar) todos, _ := c.Call(ctx, "GET", "/produtos/paginacao/1/20/_todos", nil) filtrados, _ := c.Call(ctx, "GET", "/produtos/paginacao/1/20/Camiseta", nil) // Atualizar c.Call(ctx, "PUT", "/produtos/SKU-001", map[string]string{ "referencia": "SKU-001", "nome": "Camiseta Polo M (Novo)", "valor": "99.90", }) // Excluir c.Call(ctx, "DELETE", "/produtos/SKU-001", nil) ``` --- # Exemplos em Java Fonte: https://faznota.com.br/api/v3/exemplos/java/ Implementação completa da API do FazNota em Java 11+ com HttpClient nativo. ## Setup Requer **Java 11+** (HttpClient nativo) e uma lib JSON (usando Jackson aqui). ```xml com.fasterxml.jackson.core jackson-databind 2.16.0 ``` ```bash export MYSE_API_BASE="https://api.mysebr.com.br/nfemyse-v3/rest" export MYSE_API_TOKEN="seu_token_aqui" ``` ## Cliente HTTP centralizado ```java package com.example.myse; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; import java.util.Map; public class MyseApi { public static class ApiError extends RuntimeException { public final int httpStatus; public final JsonNode payload; public final String requestId; public ApiError(int s, String m, JsonNode p, String r) { super(m); this.httpStatus = s; this.payload = p; this.requestId = r; } } private static final String BASE = System.getenv("MYSE_API_BASE"); private static final String TOKEN = System.getenv("MYSE_API_TOKEN"); private static final ObjectMapper MAPPER = new ObjectMapper(); private static final HttpClient CLIENT = HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(10)) .build(); public static JsonNode call(String method, String path, Map body) throws Exception { HttpRequest.BodyPublisher publisher = (body == null) ? HttpRequest.BodyPublishers.noBody() : HttpRequest.BodyPublishers.ofString(MAPPER.writeValueAsString(body)); HttpRequest req = HttpRequest.newBuilder() .uri(URI.create(BASE + path)) .timeout(Duration.ofSeconds(30)) .header("Authorization", "Token " + TOKEN) .header("Content-Type", "application/json") .method(method, publisher) .build(); HttpResponse res = CLIENT.send(req, HttpResponse.BodyHandlers.ofString()); String requestId = res.headers().firstValue("X-Request-Id").orElse(null); JsonNode json = MAPPER.readTree(res.body()); if (res.statusCode() >= 400) { String msg = json.path("error").path("message").asText("HTTP " + res.statusCode()); throw new ApiError(res.statusCode(), msg, json, requestId); } if ("999".equals(json.path("status").asText())) { String msg = json.path("data").path("erro").asText( json.path("descricao").asText("erro") ); String rid = json.path("meta").path("request_id").asText(requestId); throw new ApiError(200, msg, json, rid); } return json; } } ``` ## Cenário: emitir NFCe com polling ```java public class EmissorNFCe { private static final Set TERMINAIS = Set.of("004", "010", "900", "999"); public static JsonNode emitir(Pedido p) throws Exception { Map body = Map.of( "serie", "65", "cfop", "5102", "numero-origem", "PED-" + p.getId(), "tp_pagamento", p.getFormaPagamento(), "cliente", p.getCpfCnpjCliente(), "itens", p.getItens().stream().map(i -> Map.of( "produto", i.getSku(), "quantidade", String.valueOf(i.getQtd()), "valor-unitario", String.valueOf(i.getPreco()) )).toList() ); JsonNode emissao = MyseApi.call("POST", "/nfce/emissao", body); String status = emissao.path("status").asText(); if (!"001".equals(status) && !"050".equals(status)) { throw new RuntimeException("Emissão falhou: " + emissao.path("descricao").asText()); } String recibo = emissao.path("data").path("recibo").asText(); System.out.printf("[emissao] recibo=%s request_id=%s%n", recibo, emissao.path("meta").path("request_id").asText()); // Polling long inicio = System.currentTimeMillis(); long wait = 2000; while (System.currentTimeMillis() - inicio < 5 * 60_000L) { Thread.sleep(wait); JsonNode consulta = MyseApi.call("GET", "/nfce/cupom/" + recibo, null); String st = consulta.path("status").asText(); System.out.println("[polling] status=" + st); if (TERMINAIS.contains(st)) { if ("004".equals(st)) return consulta.path("data"); throw new RuntimeException("Status terminal não-sucesso: " + st); } wait = Math.min((long)(wait * 1.5), 30_000L); } throw new RuntimeException("Timeout aguardando recibo " + recibo); } } ``` ## Retry com backoff ```java public static T comRetry(Callable fn, int maxTentativas) throws Exception { Exception last = null; for (int i = 1; i <= maxTentativas; i++) { try { return fn.call(); } catch (MyseApi.ApiError e) { if (e.payload != null && "999".equals(e.payload.path("status").asText())) { throw e; // validação: não retentar } last = e; } catch (Exception e) { last = e; } if (i < maxTentativas) { Thread.sleep(1000L * (long) Math.pow(2, i - 1)); } } throw last; } ``` ## Helper de polling reutilizável ```java public class FiscalHelpers { static final Set TERMINAIS = Set.of("004", "010", "900", "999"); /** Polling com backoff exponencial até status terminal. */ public static JsonNode aguardarRecibo(String pathBase, String recibo) throws Exception { return aguardarRecibo(pathBase, recibo, 5 * 60_000L); } public static JsonNode aguardarRecibo(String pathBase, String recibo, long timeoutMs) throws Exception { long inicio = System.currentTimeMillis(); long wait = 2000; while (System.currentTimeMillis() - inicio < timeoutMs) { Thread.sleep(wait); JsonNode resp = MyseApi.call("GET", pathBase + "/" + recibo, null); if (TERMINAIS.contains(resp.path("status").asText())) return resp; wait = Math.min((long)(wait * 1.5), 30_000L); } throw new RuntimeException("Timeout aguardando recibo " + recibo); } private static void validarMotivo(String motivo) { if (motivo.length() < 15 || motivo.length() > 255) { throw new IllegalArgumentException("motivo deve ter entre 15 e 255 caracteres"); } } } ``` ## Emitir NF-e (com transporte completo) ```java public static JsonNode emitirNFe(Pedido p) throws Exception { Map body = new HashMap<>(); body.put("serie", "1"); body.put("cfop", "5102"); body.put("numero-origem", "PED-" + p.getId()); body.put("tipo", "S"); body.put("finalidade", "N"); body.put("valor-despesas", "0.00"); body.put("cliente", p.getCpfCnpjCliente()); Map transporte = new HashMap<>(); transporte.put("modalidade", "0"); transporte.put("transportadora", p.getTransportadora()); transporte.put("placa-veiculo", p.getPlaca()); transporte.put("valor-frete", p.getFrete()); transporte.put("valor-seguro", "0.00"); transporte.put("peso-bruto", p.getPesoBruto()); transporte.put("peso-liquido", p.getPesoLiquido()); transporte.put("quantidade-volume", "1"); transporte.put("especie-volume", "Caixa"); transporte.put("marca-volume", "Embalagem"); body.put("transporte", transporte); body.put("itens", p.getItens().stream().map(i -> Map.of( "produto", i.getSku(), "quantidade", String.valueOf(i.getQtd()), "valor-unitario", String.valueOf(i.getPreco()) )).toList()); if (p.getFaturas() != null) { body.put("faturas", p.getFaturas().stream().map(f -> Map.of( "valor", String.valueOf(f.getValor()), "data", f.getData() )).toList()); } JsonNode emissao = MyseApi.call("POST", "/nfe/emissao", body); String status = emissao.path("status").asText(); if (!"001".equals(status) && !"050".equals(status)) { throw new RuntimeException("Emissão falhou: " + emissao.path("descricao").asText()); } String recibo = emissao.path("data").path("recibo").asText(); JsonNode resultado = FiscalHelpers.aguardarRecibo("/nfe/nota", recibo); if (!"004".equals(resultado.path("status").asText())) { throw new RuntimeException("Status terminal não-sucesso: " + resultado.path("status")); } return resultado.path("data"); // inclui sefaz.chave, url-danfe, url-xml } ``` ## Cancelar NF-e ```java public static JsonNode cancelarNFe(String chave, String motivo) throws Exception { FiscalHelpers.validarMotivo(motivo); JsonNode cancel = MyseApi.call("POST", "/nfe/cancelamento", Map.of("chave", chave, "motivo", motivo)); if (!"001".equals(cancel.path("status").asText())) { throw new RuntimeException(cancel.path("descricao").asText()); } return FiscalHelpers.aguardarRecibo("/nfe/cancelamento", cancel.path("data").path("recibo").asText()); } ``` ## Lançar carta de correção (CC-e) ```java /** * O campo "motivo" na CC-e é o TEXTO da correção (xCorrecao). */ public static JsonNode cartaCorrecaoNFe(String chave, String textoCorrecao) throws Exception { FiscalHelpers.validarMotivo(textoCorrecao); JsonNode cce = MyseApi.call("POST", "/nfe/correcao", Map.of("chave", chave, "motivo", textoCorrecao)); if (!"001".equals(cce.path("status").asText())) { throw new RuntimeException(cce.path("descricao").asText()); } return FiscalHelpers.aguardarRecibo("/nfe/correcao", cce.path("data").path("recibo").asText()); } ``` ## Cancelar NFC-e ```java public static JsonNode cancelarNFCe(String chave, String motivo) throws Exception { FiscalHelpers.validarMotivo(motivo); JsonNode cancel = MyseApi.call("POST", "/nfce/cancelamento", Map.of("chave", chave, "motivo", motivo)); if (!"001".equals(cancel.path("status").asText())) { throw new RuntimeException(cancel.path("descricao").asText()); } return FiscalHelpers.aguardarRecibo("/nfce/cancelamento", cancel.path("data").path("recibo").asText()); } ``` ## Emitir NFS-e (Nota Fiscal de Serviço) ```java public static JsonNode emitirNFSe(Servico s) throws Exception { Map body = new HashMap<>(); body.put("numero-origem", "OS-" + s.getId()); body.put("observacao", s.getObservacao()); body.put("nome-contato", s.getNomeContato()); body.put("telefone-contato", s.getTelefoneContato()); body.put("data-fim", s.getDataFim()); body.put("cliente", s.getCpfCnpjCliente()); body.put("itens", s.getItens().stream().map(i -> Map.of( "produto", i.getCodigoServico(), "quantidade", String.valueOf(i.getQtd()), "valor-unitario", String.valueOf(i.getPreco()) )).toList()); JsonNode emissao = MyseApi.call("POST", "/nfse/emissao", body); String status = emissao.path("status").asText(); if (!"001".equals(status) && !"050".equals(status)) { throw new RuntimeException("Emissão NFSe falhou: " + emissao.path("descricao")); } String recibo = emissao.path("data").path("recibo").asText(); JsonNode resultado = FiscalHelpers.aguardarRecibo("/nfse/nota", recibo); if (!"004".equals(resultado.path("status").asText())) { throw new RuntimeException("Status terminal não-sucesso: " + resultado.path("status")); } return resultado.path("data"); } ``` ## Cancelar NFS-e ```java public static JsonNode cancelarNFSe(String chave, String motivo) throws Exception { FiscalHelpers.validarMotivo(motivo); return MyseApi.call("POST", "/nfse/cancelamento", Map.of("chave", chave, "motivo", motivo)); } ``` ## Reconhecer nota de fornecedor (MD-e) ```java /** * Códigos de reconhecimento: * "210200" — Confirmação da operação (terminal, irreversível) * "210210" — Ciência da operação (provisório, 15 dias) * "210220" — Desconhecimento da operação * "210240" — Operação não realizada */ public static JsonNode reconhecerNotaFornecedor(String chave, String codigo) throws Exception { Set validos = Set.of("210200", "210210", "210220", "210240"); if (!validos.contains(codigo)) { throw new IllegalArgumentException("Código inválido: " + codigo); } JsonNode resp = MyseApi.call("POST", "/nffornecedor/reconhecimento", Map.of("chave", chave, "codigo-reconhecimento", codigo)); if (!"001".equals(resp.path("status").asText())) { throw new RuntimeException(resp.path("descricao").asText()); } return FiscalHelpers.aguardarRecibo("/nffornecedor", resp.path("data").path("recibo").asText()); } ``` ## Listar com paginação ```java public static List listarTodosClientes() throws Exception { List todos = new ArrayList<>(); int page = 1; int pageSize = 100; while (true) { JsonNode resp = MyseApi.call("GET", "/clientes?page=" + page + "&page_size=" + pageSize, null); if (!"005".equals(resp.path("status").asText())) break; JsonNode data = resp.path("data"); if (!data.isArray() || data.size() == 0) break; data.forEach(todos::add); if (data.size() < pageSize) break; page++; Thread.sleep(200); } return todos; } ``` ## Tratamento de erros ```java try { JsonNode nfce = EmissorNFCe.emitir(pedido); System.out.println("NFCe emitida: " + nfce.path("sefaz").path("chave").asText()); } catch (MyseApi.ApiError e) { logger.error("API error: status={} requestId={} msg={}", e.httpStatus, e.requestId, e.getMessage()); throw e; } ``` ## Health check (sem autenticação) ```java HttpRequest req = HttpRequest.newBuilder() .uri(URI.create(BASE + "/health")) .timeout(Duration.ofSeconds(5)) .build(); HttpResponse res = CLIENT.send(req, HttpResponse.BodyHandlers.ofString()); JsonNode health = new ObjectMapper().readTree(res.body()); // { "status": "ok", "database": "ok", "timestamp": "..." } ``` ## Alterar nota antes da emissão (PUT) Disponível em NF-e (`/nfe/{id}`), NFC-e (`/nfce/{id}`) e NFS-e (`/nfse/{id}`). **Só funciona enquanto a nota ainda não foi submetida para emissão.** ```java public static JsonNode alterarNota(String tipo, String id, Map dados) throws Exception { // tipo: "nfe" | "nfce" | "nfse" return MyseApi.call("PUT", "/" + tipo + "/" + id, dados); } // Uso: alterarNota("nfe", "12345", Map.of( "serie", "1", "cfop", "5102", "cliente", "12345678000199", "itens", List.of(Map.of( "produto", "SKU-001", "quantidade", "3", "valor-unitario", "89.90" )) )); ``` ## Consulta detalhada por recibo ```java JsonNode nfeCompleta = MyseApi.call("GET", "/nfe/nota/" + recibo + "/completa", null); JsonNode nfceCompleta = MyseApi.call("GET", "/nfce/cupom/" + recibo + "/completa", null); ``` ## Listagens ```java JsonNode nfes = MyseApi.call("GET", "/nfe?page=1&page_size=20", null); JsonNode nfces = MyseApi.call("GET", "/nfce?page=1&page_size=50", null); JsonNode nfses = MyseApi.call("GET", "/nfse?dataInicial=2026-05-01&page=1&page_size=20", null); JsonNode fornecedor = MyseApi.call("GET", "/nffornecedor?page=1&page_size=20", null); // Forma legada (path-based) JsonNode nfesLegado = MyseApi.call("GET", "/nfe/paginacao/1/20", null); ``` ## Clientes (CRUD completo) ```java public static JsonNode cadastrarPF(PessoaFisica pf) throws Exception { Map body = new HashMap<>(); body.put("tipo", "F"); body.put("nome", pf.getNome()); body.put("cpf", pf.getCpf()); body.put("rg", pf.getRg()); body.put("consumidor-final", "1"); body.put("contato", Map.of( "email", pf.getEmail(), "telefone", pf.getTelefone() )); body.put("endereco", pf.getEndereco()); return MyseApi.call("POST", "/clientes", body); } public static JsonNode cadastrarPJ(PessoaJuridica pj) throws Exception { Map body = new HashMap<>(); body.put("tipo", "J"); body.put("razao-social", pj.getRazaoSocial()); body.put("nome-fantasia", pj.getNomeFantasia()); body.put("cnpj", pj.getCnpj()); body.put("inscricao-municipal", pj.getInscricaoMunicipal()); body.put("tipo-inscricao-estadual", pj.getContribuinte() != null ? pj.getContribuinte() : "9"); body.put("inscricao-estadual", pj.getInscricaoEstadual() != null ? pj.getInscricaoEstadual() : "ISENTO"); body.put("consumidor-final", "0"); body.put("contato", Map.of( "email", pj.getEmail(), "telefone", pj.getTelefone() )); body.put("endereco", pj.getEndereco()); return MyseApi.call("POST", "/clientes", body); } // Consultar por CPF/CNPJ JsonNode cliente = MyseApi.call("GET", "/clientes/12345678000199", null); // Buscar por nome JsonNode encontrados = MyseApi.call("GET", "/clientes/busca/exemplo", null); JsonNode top = MyseApi.call("GET", "/clientes/busca/top_clientes", null); // Atualizar MyseApi.call("PUT", "/clientes/12345678000199", Map.of( "tipo", "J", "razao-social", "Empresa Exemplo (Novo Nome) Ltda" )); // Excluir MyseApi.call("DELETE", "/clientes/12345678000199", null); ``` ## Produtos (CRUD completo) ```java public static JsonNode cadastrarProduto(Produto p) throws Exception { Map body = new HashMap<>(); body.put("referencia", p.getSku()); body.put("nome", p.getNome()); body.put("valor", String.valueOf(p.getValor())); body.put("medida", p.getMedida() != null ? p.getMedida() : "UN"); body.put("categoria", p.getCategoria()); body.put("ncm", p.getNcm()); body.put("origem", p.getOrigem() != null ? p.getOrigem() : "0"); body.put("cfop-preferencial", p.getCfop() != null ? p.getCfop() : "5102"); body.put("tipo-produto", "P"); if (p.getConfiguracoesFiscais() != null) { body.put("configuracoes-fiscais", p.getConfiguracoesFiscais()); } return MyseApi.call("POST", "/produtos", body); } public static JsonNode cadastrarServico(Servico s) throws Exception { return MyseApi.call("POST", "/produtos", Map.of( "referencia", s.getSku(), "nome", s.getNome(), "valor", String.valueOf(s.getValor()), "medida", "H", "tipo-produto", "S", "codigo-servico", s.getCodigoServico() )); } // Consultar JsonNode produto = MyseApi.call("GET", "/produtos/SKU-001", null); JsonNode porNome = MyseApi.call("GET", "/produtos/nome/Camiseta", null); // Listar paginado com busca (use _todos para não filtrar) JsonNode todos = MyseApi.call("GET", "/produtos/paginacao/1/20/_todos", null); JsonNode filtrados = MyseApi.call("GET", "/produtos/paginacao/1/20/Camiseta", null); // Atualizar MyseApi.call("PUT", "/produtos/SKU-001", Map.of( "referencia", "SKU-001", "nome", "Camiseta Polo M (Novo)", "valor", "99.90" )); // Excluir MyseApi.call("DELETE", "/produtos/SKU-001", null); ``` --- # Exemplos em JavaScript / TypeScript Fonte: https://faznota.com.br/api/v3/exemplos/javascript/ Implementação completa da API do FazNota em Node.js (18+) com fetch nativo. Inclui versão TypeScript tipada. ## Setup Requer **Node.js 18+** (que tem `fetch` nativo). ```bash # .env (use dotenv ou similar) MYSE_API_BASE=https://api.mysebr.com.br/nfemyse-v3/rest MYSE_API_TOKEN=seu_token_aqui ``` ## Cliente HTTP centralizado ```javascript // myse-api.js const BASE = process.env.MYSE_API_BASE; const TOKEN = process.env.MYSE_API_TOKEN; export class ApiError extends Error { constructor(httpStatus, message, payload, requestId) { super(message); this.httpStatus = httpStatus; this.payload = payload; this.requestId = requestId; } } export async function mApi(method, path, body, options = {}) { const url = `${BASE}${path}`; const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? 30_000); try { const res = await fetch(url, { method, headers: { Authorization: `Token ${TOKEN}`, 'Content-Type': 'application/json', }, body: body ? JSON.stringify(body) : undefined, signal: controller.signal, }); const requestId = res.headers.get('x-request-id') ?? undefined; if (!res.ok) { const errBody = await res.json().catch(() => ({})); throw new ApiError( res.status, errBody?.error?.message ?? `HTTP ${res.status}`, errBody, requestId ); } const json = await res.json(); if (json.status === '999') { throw new ApiError( 200, json.data?.erro ?? json.descricao, json, json.meta?.request_id ?? requestId ); } return json; } finally { clearTimeout(timeout); } } ``` ```typescript // myse-api.ts const BASE = process.env.MYSE_API_BASE!; const TOKEN = process.env.MYSE_API_TOKEN!; export interface Meta { request_id: string; timestamp: string; } export interface ApiResponse { status: string; descricao: string; data?: T; meta?: Meta; } export class ApiError extends Error { constructor( public readonly httpStatus: number, message: string, public readonly payload: unknown, public readonly requestId?: string ) { super(message); this.name = 'ApiError'; } } export async function mApi( method: 'GET' | 'POST' | 'PUT' | 'DELETE', path: string, body?: unknown, options: { timeoutMs?: number } = {} ): Promise> { const controller = new AbortController(); const timeout = setTimeout( () => controller.abort(), options.timeoutMs ?? 30_000 ); try { const res = await fetch(`${BASE}${path}`, { method, headers: { Authorization: `Token ${TOKEN}`, 'Content-Type': 'application/json', }, body: body ? JSON.stringify(body) : undefined, signal: controller.signal, }); const requestId = res.headers.get('x-request-id') ?? undefined; if (!res.ok) { const errBody = (await res.json().catch(() => ({}))) as { error?: { message?: string }; }; throw new ApiError( res.status, errBody?.error?.message ?? `HTTP ${res.status}`, errBody, requestId ); } const json = (await res.json()) as ApiResponse; if (json.status === '999') { throw new ApiError( 200, (json.data as { erro?: string })?.erro ?? json.descricao, json, json.meta?.request_id ?? requestId ); } return json; } finally { clearTimeout(timeout); } } ``` ## Cenário: emitir NFCe completa ```javascript const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); async function emitirNFCe(pedido) { // 1. Emitir const emissao = await mApi('POST', '/nfce/emissao', { serie: '65', cfop: '5102', 'numero-origem': `PED-${pedido.id}`, tp_pagamento: pedido.formaPagamento, cliente: pedido.cpfCnpjCliente, itens: pedido.itens.map((i) => ({ produto: i.sku, quantidade: String(i.qtd), 'valor-unitario': String(i.preco), })), }); if (emissao.status !== '001' && emissao.status !== '050') { throw new Error(`Emissão falhou: ${emissao.descricao}`); } const recibo = emissao.data.recibo; console.log(`[emissao] recibo=${recibo} request_id=${emissao.meta.request_id}`); // 2. Polling com backoff const terminais = new Set(['004', '010', '900', '999']); let wait = 2000; const inicio = Date.now(); while (Date.now() - inicio < 5 * 60_000) { await sleep(wait); const consulta = await mApi('GET', `/nfce/cupom/${recibo}`); console.log(`[polling] status=${consulta.status}`); if (terminais.has(consulta.status)) { if (consulta.status === '004') { return consulta.data; // Inclui sefaz.chave, url-danfe, url-xml } throw new Error(`Status terminal não-sucesso: ${consulta.status}`); } wait = Math.min(wait * 1.5, 30_000); } throw new Error(`Timeout aguardando recibo ${recibo}`); } ``` ## Retry com backoff em erros transitórios ```javascript async function comRetry(fn, max = 3) { for (let i = 1; i <= max; i++) { try { return await fn(); } catch (err) { // Não retentar erros de validação if (err instanceof ApiError && err.payload?.status === '999') throw err; if (i === max) throw err; await sleep(1000 * Math.pow(2, i - 1)); // 1s, 2s, 4s } } } // Uso const nfe = await comRetry(() => emitirNFCe(pedido)); ``` ## Helper de polling reutilizável ```javascript const TERMINAIS = new Set(['004', '010', '900', '999']); async function aguardarRecibo(pathBase, recibo, timeoutMs = 5 * 60_000) { let wait = 2000; const inicio = Date.now(); while (Date.now() - inicio < timeoutMs) { await sleep(wait); const resp = await mApi('GET', `${pathBase}/${recibo}`); if (TERMINAIS.has(resp.status)) return resp; wait = Math.min(wait * 1.5, 30_000); } throw new Error(`Timeout aguardando recibo ${recibo}`); } ``` ## Emitir NF-e (com transporte completo) ```javascript async function emitirNFe(pedido) { const emissao = await mApi('POST', '/nfe/emissao', { serie: '1', cfop: '5102', 'numero-origem': `PED-${pedido.id}`, tipo: 'S', finalidade: 'N', 'valor-despesas': '0.00', cliente: pedido.cpfCnpjCliente, transporte: { modalidade: '0', transportadora: pedido.transportadora, 'placa-veiculo': pedido.placa, 'valor-frete': pedido.frete ?? '0.00', 'valor-seguro': '0.00', 'peso-bruto': pedido.pesoBruto, 'peso-liquido': pedido.pesoLiquido, 'quantidade-volume': '1', 'especie-volume': 'Caixa', 'marca-volume': 'Embalagem', }, itens: pedido.itens.map((i) => ({ produto: i.sku, quantidade: String(i.qtd), 'valor-unitario': String(i.preco), })), faturas: pedido.faturas?.map((f) => ({ valor: String(f.valor), data: f.data })) ?? [], }); if (!['001', '050'].includes(emissao.status)) { throw new Error(`Emissão falhou: ${emissao.descricao}`); } const resultado = await aguardarRecibo('/nfe/nota', emissao.data.recibo); if (resultado.status !== '004') { throw new Error(`Status terminal não-sucesso: ${resultado.status}`); } return resultado.data; // inclui sefaz.chave, url-danfe, url-xml } ``` ## Cancelar NF-e ```javascript async function cancelarNFe(chave, motivo) { if (motivo.length < 15 || motivo.length > 255) { throw new Error('Motivo deve ter entre 15 e 255 caracteres'); } const cancel = await mApi('POST', '/nfe/cancelamento', { chave, motivo }); if (cancel.status !== '001') throw new Error(cancel.descricao); return aguardarRecibo('/nfe/cancelamento', cancel.data.recibo); } ``` ## Lançar carta de correção (CC-e) ```javascript async function cartaCorrecaoNFe(chave, textoCorrecao) { if (textoCorrecao.length < 15 || textoCorrecao.length > 255) { throw new Error('Texto da correção deve ter entre 15 e 255 caracteres'); } // Nota: o campo "motivo" na CC-e é o TEXTO da correção (xCorrecao). const cce = await mApi('POST', '/nfe/correcao', { chave, motivo: textoCorrecao }); if (cce.status !== '001') throw new Error(cce.descricao); return aguardarRecibo('/nfe/correcao', cce.data.recibo); } ``` ## Cancelar NFC-e ```javascript async function cancelarNFCe(chave, motivo) { if (motivo.length < 15 || motivo.length > 255) { throw new Error('Motivo deve ter entre 15 e 255 caracteres'); } const cancel = await mApi('POST', '/nfce/cancelamento', { chave, motivo }); if (cancel.status !== '001') throw new Error(cancel.descricao); return aguardarRecibo('/nfce/cancelamento', cancel.data.recibo); } ``` ## Emitir NFS-e (Nota Fiscal de Serviço) ```javascript async function emitirNFSe(servico) { const emissao = await mApi('POST', '/nfse/emissao', { 'numero-origem': `OS-${servico.id}`, observacao: servico.observacao, 'nome-contato': servico.nomeContato, 'telefone-contato': servico.telefoneContato, 'data-fim': servico.dataFim, cliente: servico.cpfCnpjCliente, itens: servico.itens.map((i) => ({ produto: i.codigoServico, quantidade: String(i.qtd), 'valor-unitario': String(i.preco), })), }); if (!['001', '050'].includes(emissao.status)) { throw new Error(`Emissão NFSe falhou: ${emissao.descricao}`); } const resultado = await aguardarRecibo('/nfse/nota', emissao.data.recibo); if (resultado.status !== '004') { throw new Error(`Status terminal não-sucesso: ${resultado.status}`); } return resultado.data; } ``` ## Cancelar NFS-e ```javascript async function cancelarNFSe(chave, motivo) { if (motivo.length < 15 || motivo.length > 255) { throw new Error('Motivo deve ter entre 15 e 255 caracteres'); } return mApi('POST', '/nfse/cancelamento', { chave, motivo }); } ``` ## Reconhecer nota de fornecedor (MD-e) ```javascript /** * Códigos de reconhecimento: * '210200' — Confirmação da operação (terminal, irreversível) * '210210' — Ciência da operação (provisório, 15 dias) * '210220' — Desconhecimento da operação * '210240' — Operação não realizada */ async function reconhecerNotaFornecedor(chave, codigoReconhecimento) { const VALIDOS = new Set(['210200', '210210', '210220', '210240']); if (!VALIDOS.has(codigoReconhecimento)) { throw new Error(`Código inválido: ${codigoReconhecimento}`); } const resp = await mApi('POST', '/nffornecedor/reconhecimento', { chave, 'codigo-reconhecimento': codigoReconhecimento, }); if (resp.status !== '001') throw new Error(resp.descricao); return aguardarRecibo('/nffornecedor', resp.data.recibo); } ``` ## Sincronizar todos os clientes ```javascript async function listarTodosClientes() { const todos = []; let page = 1; const pageSize = 100; while (true) { const resp = await mApi('GET', `/clientes?page=${page}&page_size=${pageSize}`); if (resp.status !== '005' || !Array.isArray(resp.data)) break; todos.push(...resp.data); if (resp.data.length < pageSize) break; page++; await sleep(200); } return todos; } ``` ## Tratamento de erros completo ```javascript try { const nfe = await emitirNFCe(pedido); console.log('NFCe emitida:', nfe.sefaz?.chave); } catch (err) { if (err instanceof ApiError) { console.error( `API Error HTTP=${err.httpStatus} requestId=${err.requestId}`, err.message, err.payload ); // Logar para suporte: requestId é o que importa } else { console.error('Erro inesperado:', err); } } ``` ## Health check (sem autenticação) ```javascript async function health() { const res = await fetch(`${BASE}/health`); return res.json(); // { status: 'ok', database: 'ok', timestamp: '...' } } ``` ## Alterar nota antes da emissão (PUT) Disponível em NF-e (`/nfe/{id}`), NFC-e (`/nfce/{id}`) e NFS-e (`/nfse/{id}`). **Só funciona enquanto a nota ainda não foi submetida para emissão.** ```javascript async function alterarNota(tipo, id, dadosAtualizados) { // tipo: 'nfe' | 'nfce' | 'nfse' return mApi('PUT', `/${tipo}/${id}`, dadosAtualizados); } // Uso: await alterarNota('nfe', '12345', { serie: '1', cfop: '5102', cliente: '12345678000199', itens: [{ produto: 'SKU-001', quantidade: '3', 'valor-unitario': '89.90' }], }); ``` ## Consulta detalhada por recibo ```javascript // NF-e completa (inclui itens, transporte, cliente, sefaz) const nfeCompleta = await mApi('GET', `/nfe/nota/${recibo}/completa`); // NFC-e completa const nfceCompleta = await mApi('GET', `/nfce/cupom/${recibo}/completa`); ``` ## Listagens ```javascript // NF-es paginadas const nfes = await mApi('GET', '/nfe?page=1&page_size=20'); // NFC-es const nfces = await mApi('GET', '/nfce?page=1&page_size=50'); // NFS-es com filtro de data const nfses = await mApi( 'GET', '/nfse?dataInicial=2026-05-01&page=1&page_size=20' ); // Notas de fornecedor const fornecedor = await mApi('GET', '/nffornecedor?page=1&page_size=20'); // Forma legada (path-based) const nfesLegado = await mApi('GET', '/nfe/paginacao/1/20'); ``` ## Clientes (CRUD completo) ```javascript // Cadastrar pessoa física async function cadastrarPF(dados) { return mApi('POST', '/clientes', { tipo: 'F', nome: dados.nome, cpf: dados.cpf, rg: dados.rg, 'consumidor-final': '1', contato: { email: dados.email, telefone: dados.telefone }, endereco: dados.endereco, }); } // Cadastrar pessoa jurídica async function cadastrarPJ(dados) { return mApi('POST', '/clientes', { tipo: 'J', 'razao-social': dados.razaoSocial, 'nome-fantasia': dados.nomeFantasia, cnpj: dados.cnpj, 'inscricao-municipal': dados.inscricaoMunicipal, 'tipo-inscricao-estadual': dados.contribuinte ?? '9', 'inscricao-estadual': dados.inscricaoEstadual ?? 'ISENTO', 'consumidor-final': dados.consumidorFinal ?? '0', contato: { email: dados.email, telefone: dados.telefone }, endereco: dados.endereco, }); } // Consultar por CPF/CNPJ const cliente = await mApi('GET', '/clientes/12345678000199'); // Buscar por nome (parcial) const encontrados = await mApi('GET', '/clientes/busca/exemplo'); // Top 20 clientes mais relevantes const top = await mApi('GET', '/clientes/busca/top_clientes'); // Atualizar await mApi('PUT', '/clientes/12345678000199', { tipo: 'J', 'razao-social': 'Empresa Exemplo (Novo Nome) Ltda', }); // Excluir await mApi('DELETE', '/clientes/12345678000199'); ``` ## Produtos (CRUD completo) ```javascript // Cadastrar produto com configuração fiscal async function cadastrarProduto(produto) { return mApi('POST', '/produtos', { referencia: produto.sku, nome: produto.nome, valor: String(produto.valor), medida: produto.medida ?? 'UN', categoria: produto.categoria, ncm: produto.ncm, origem: produto.origem ?? '0', 'cfop-preferencial': produto.cfop ?? '5102', 'tipo-produto': 'P', 'configuracoes-fiscais': produto.configFiscal ?? [], }); } // Cadastrar serviço async function cadastrarServico(servico) { return mApi('POST', '/produtos', { referencia: servico.sku, nome: servico.nome, valor: String(servico.valor), medida: 'H', 'tipo-produto': 'S', 'codigo-servico': servico.codigoServico, }); } // Consultar por referência const produto = await mApi('GET', '/produtos/SKU-001'); // Buscar por nome (1º resultado) const porNome = await mApi('GET', '/produtos/nome/Camiseta'); // Listar paginado com busca (use _todos para não filtrar) const todos = await mApi('GET', '/produtos/paginacao/1/20/_todos'); const filtrados = await mApi('GET', '/produtos/paginacao/1/20/Camiseta'); // Atualizar await mApi('PUT', '/produtos/SKU-001', { referencia: 'SKU-001', nome: 'Camiseta Polo M (Novo)', valor: '99.90', }); // Excluir await mApi('DELETE', '/produtos/SKU-001'); ``` --- # Exemplos em PHP Fonte: https://faznota.com.br/api/v3/exemplos/php/ Implementação completa da API do FazNota em PHP 8+ com Guzzle. ## Setup ```bash composer require guzzlehttp/guzzle vlucas/phpdotenv ``` ```bash # .env MYSE_API_BASE=https://api.mysebr.com.br/nfemyse-v3/rest MYSE_API_TOKEN=seu_token_aqui ``` ## Cliente HTTP centralizado ```php client = new Client([ 'base_uri' => $_ENV['MYSE_API_BASE'], 'timeout' => 30, 'headers' => [ 'Authorization' => 'Token ' . $_ENV['MYSE_API_TOKEN'], 'Content-Type' => 'application/json', ], 'http_errors' => false, ]); } public function call(string $method, string $path, ?array $body = null): array { $options = $body ? ['json' => $body] : []; $res = $this->client->request($method, ltrim($path, '/'), $options); $requestId = $res->getHeaderLine('X-Request-Id') ?: null; $bodyStr = (string) $res->getBody(); $json = json_decode($bodyStr, true) ?? []; // Erro HTTP duro if ($res->getStatusCode() >= 400) { throw new ApiError( $res->getStatusCode(), $json['error']['message'] ?? "HTTP {$res->getStatusCode()}", $json, $requestId ); } // Erro de domínio if (($json['status'] ?? null) === '999') { throw new ApiError( 200, $json['data']['erro'] ?? $json['descricao'] ?? 'erro', $json, $json['meta']['request_id'] ?? $requestId ); } return $json; } } ``` ## Cenário: emitir NFCe com polling ```php '65', 'cfop' => '5102', 'numero-origem' => 'PED-' . $pedido['id'], 'tp_pagamento' => $pedido['forma_pagamento'], 'cliente' => $pedido['cpf_cnpj_cliente'], 'itens' => array_map(fn($i) => [ 'produto' => $i['sku'], 'quantidade' => (string) $i['qtd'], 'valor-unitario' => (string) $i['preco'], ], $pedido['itens']), ]; $emissao = $api->call('POST', '/nfce/emissao', $body); if (!in_array($emissao['status'], ['001', '050'])) { throw new \RuntimeException("Emissão falhou: {$emissao['descricao']}"); } $recibo = $emissao['data']['recibo']; error_log("[emissao] recibo=$recibo request_id={$emissao['meta']['request_id']}"); // Polling $terminais = ['004', '010', '900', '999']; $wait = 5.0; $inicio = microtime(true); while (microtime(true) - $inicio < 300) { usleep((int) ($wait * 1_000_000)); $consulta = $api->call('GET', "/nfce/cupom/$recibo"); error_log("[polling] status={$consulta['status']}"); if (in_array($consulta['status'], $terminais)) { if ($consulta['status'] === '004') { return $consulta['data']; } throw new \RuntimeException("Status terminal não-sucesso: {$consulta['status']}"); } $wait = min($wait * 1.5, 30); } throw new \RuntimeException("Timeout aguardando recibo $recibo"); } ``` ## Retry com backoff ```php function comRetry(callable $fn, int $max = 3): mixed { for ($i = 1; $i <= $max; $i++) { try { return $fn(); } catch (ApiError $e) { if (($e->payload['status'] ?? null) === '999') throw $e; if ($i === $max) throw $e; sleep((int) pow(2, $i - 1)); } } } // Uso $nfce = comRetry(fn() => emitirNFCe($api, $pedido)); ``` ## Helper de polling reutilizável ```php call('GET', "$pathBase/$recibo"); if (in_array($resp['status'], TERMINAIS)) return $resp; $wait = min($wait * 1.5, 30); } throw new \RuntimeException("Timeout aguardando recibo $recibo"); } ``` ## Emitir NF-e (com transporte completo) ```php '1', 'cfop' => '5102', 'numero-origem' => 'PED-' . $pedido['id'], 'tipo' => 'S', 'finalidade' => 'N', 'valor-despesas' => '0.00', 'cliente' => $pedido['cpf_cnpj_cliente'], 'transporte' => [ 'modalidade' => '0', 'transportadora' => $pedido['transportadora'], 'placa-veiculo' => $pedido['placa'], 'valor-frete' => $pedido['frete'] ?? '0.00', 'valor-seguro' => '0.00', 'peso-bruto' => $pedido['peso_bruto'], 'peso-liquido' => $pedido['peso_liquido'], 'quantidade-volume' => '1', 'especie-volume' => 'Caixa', 'marca-volume' => 'Embalagem', ], 'itens' => array_map(fn($i) => [ 'produto' => $i['sku'], 'quantidade' => (string) $i['qtd'], 'valor-unitario' => (string) $i['preco'], ], $pedido['itens']), 'faturas' => array_map(fn($f) => [ 'valor' => (string) $f['valor'], 'data' => $f['data'], ], $pedido['faturas'] ?? []), ]; $emissao = $api->call('POST', '/nfe/emissao', $body); if (!in_array($emissao['status'], ['001', '050'])) { throw new \RuntimeException("Emissão falhou: {$emissao['descricao']}"); } $resultado = aguardarRecibo($api, '/nfe/nota', $emissao['data']['recibo']); if ($resultado['status'] !== '004') { throw new \RuntimeException("Status terminal não-sucesso: {$resultado['status']}"); } return $resultado['data']; // inclui sefaz.chave, url-danfe, url-xml } ``` ## Cancelar NF-e ```php 255) { throw new \InvalidArgumentException('motivo deve ter entre 15 e 255 caracteres'); } $cancel = $api->call('POST', '/nfe/cancelamento', compact('chave', 'motivo')); if ($cancel['status'] !== '001') throw new \RuntimeException($cancel['descricao']); return aguardarRecibo($api, '/nfe/cancelamento', $cancel['data']['recibo']); } ``` ## Lançar carta de correção (CC-e) ```php 255) { throw new \InvalidArgumentException('texto deve ter entre 15 e 255 caracteres'); } // O campo "motivo" na CC-e é o TEXTO da correção (xCorrecao). $cce = $api->call('POST', '/nfe/correcao', ['chave' => $chave, 'motivo' => $textoCorrecao]); if ($cce['status'] !== '001') throw new \RuntimeException($cce['descricao']); return aguardarRecibo($api, '/nfe/correcao', $cce['data']['recibo']); } ``` ## Cancelar NFC-e ```php 255) { throw new \InvalidArgumentException('motivo deve ter entre 15 e 255 caracteres'); } $cancel = $api->call('POST', '/nfce/cancelamento', compact('chave', 'motivo')); if ($cancel['status'] !== '001') throw new \RuntimeException($cancel['descricao']); return aguardarRecibo($api, '/nfce/cancelamento', $cancel['data']['recibo']); } ``` ## Emitir NFS-e (Nota Fiscal de Serviço) ```php 'OS-' . $servico['id'], 'observacao' => $servico['observacao'], 'nome-contato' => $servico['nome_contato'] ?? null, 'telefone-contato' => $servico['telefone_contato'] ?? null, 'data-fim' => $servico['data_fim'], 'cliente' => $servico['cpf_cnpj_cliente'], 'itens' => array_map(fn($i) => [ 'produto' => $i['codigo_servico'], 'quantidade' => (string) $i['qtd'], 'valor-unitario' => (string) $i['preco'], ], $servico['itens']), ]; $emissao = $api->call('POST', '/nfse/emissao', $body); if (!in_array($emissao['status'], ['001', '050'])) { throw new \RuntimeException("Emissão NFSe falhou: {$emissao['descricao']}"); } $resultado = aguardarRecibo($api, '/nfse/nota', $emissao['data']['recibo']); if ($resultado['status'] !== '004') { throw new \RuntimeException("Status terminal não-sucesso: {$resultado['status']}"); } return $resultado['data']; } ``` ## Cancelar NFS-e ```php 255) { throw new \InvalidArgumentException('motivo deve ter entre 15 e 255 caracteres'); } return $api->call('POST', '/nfse/cancelamento', compact('chave', 'motivo')); } ``` ## Reconhecer nota de fornecedor (MD-e) ```php call('POST', '/nffornecedor/reconhecimento', [ 'chave' => $chave, 'codigo-reconhecimento' => $codigo, ]); if ($resp['status'] !== '001') throw new \RuntimeException($resp['descricao']); return aguardarRecibo($api, '/nffornecedor', $resp['data']['recibo']); } ``` ## Listar com paginação ```php function listarTodosClientes(MyseApi $api): array { $todos = []; $page = 1; $pageSize = 100; while (true) { $resp = $api->call('GET', "/clientes?page=$page&page_size=$pageSize"); if ($resp['status'] !== '005' || !is_array($resp['data'] ?? null)) break; $todos = array_merge($todos, $resp['data']); if (count($resp['data']) < $pageSize) break; $page++; usleep(200_000); } return $todos; } ``` ## Tratamento de erros ```php try { $nfce = emitirNFCe($api, $pedido); echo "NFCe emitida: " . ($nfce['sefaz']['chave'] ?? '???') . "\n"; } catch (ApiError $e) { error_log(json_encode([ 'event' => 'api_error', 'http_status' => $e->httpStatus, 'request_id' => $e->requestId, 'message' => $e->getMessage(), ])); throw $e; } ``` ## Variação: cURL puro (sem Guzzle) ```php function curlCall(string $method, string $path, ?array $body = null): array { $ch = curl_init($_ENV['MYSE_API_BASE'] . $path); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => [ 'Authorization: Token ' . $_ENV['MYSE_API_TOKEN'], 'Content-Type: application/json', ], CURLOPT_TIMEOUT => 30, CURLOPT_POSTFIELDS => $body ? json_encode($body) : null, CURLOPT_HEADER => true, ]); $resp = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); curl_close($ch); $headers = substr($resp, 0, $headerSize); $body = substr($resp, $headerSize); return ['status' => $code, 'headers' => $headers, 'body' => json_decode($body, true)]; } ``` ## Health check (sem autenticação) ```php "ok", "database" => "ok", "timestamp" => "..."] ``` ## Alterar nota antes da emissão (PUT) Disponível em NF-e (`/nfe/{id}`), NFC-e (`/nfce/{id}`) e NFS-e (`/nfse/{id}`). **Só funciona enquanto a nota ainda não foi submetida para emissão.** ```php call('PUT', "/$tipo/$id", $dados); } // Uso: alterarNota($api, 'nfe', '12345', [ 'serie' => '1', 'cfop' => '5102', 'cliente' => '12345678000199', 'itens' => [ ['produto' => 'SKU-001', 'quantidade' => '3', 'valor-unitario' => '89.90'], ], ]); ``` ## Consulta detalhada por recibo ```php call('GET', "/nfe/nota/$recibo/completa"); $nfceCompleta = $api->call('GET', "/nfce/cupom/$recibo/completa"); ``` ## Listagens ```php call('GET', '/nfe?page=1&page_size=20'); $nfces = $api->call('GET', '/nfce?page=1&page_size=50'); $nfses = $api->call('GET', '/nfse?dataInicial=2026-05-01&page=1&page_size=20'); $fornecedor = $api->call('GET', '/nffornecedor?page=1&page_size=20'); // Forma legada (path-based) $nfesLegado = $api->call('GET', '/nfe/paginacao/1/20'); ``` ## Clientes (CRUD completo) ```php call('POST', '/clientes', [ 'tipo' => 'F', 'nome' => $dados['nome'], 'cpf' => $dados['cpf'], 'rg' => $dados['rg'] ?? null, 'consumidor-final' => '1', 'contato' => [ 'email' => $dados['email'] ?? null, 'telefone' => $dados['telefone'] ?? null, ], 'endereco' => $dados['endereco'], ]); } function cadastrarPJ(MyseApi $api, array $dados): array { return $api->call('POST', '/clientes', [ 'tipo' => 'J', 'razao-social' => $dados['razao_social'], 'nome-fantasia' => $dados['nome_fantasia'] ?? null, 'cnpj' => $dados['cnpj'], 'inscricao-municipal' => $dados['inscricao_municipal'] ?? null, 'tipo-inscricao-estadual' => $dados['contribuinte'] ?? '9', 'inscricao-estadual' => $dados['inscricao_estadual'] ?? 'ISENTO', 'consumidor-final' => $dados['consumidor_final'] ?? '0', 'contato' => [ 'email' => $dados['email'] ?? null, 'telefone' => $dados['telefone'] ?? null, ], 'endereco' => $dados['endereco'], ]); } // Consultar por CPF/CNPJ $cliente = $api->call('GET', '/clientes/12345678000199'); // Buscar por nome $encontrados = $api->call('GET', '/clientes/busca/exemplo'); $top = $api->call('GET', '/clientes/busca/top_clientes'); // Atualizar $api->call('PUT', '/clientes/12345678000199', [ 'tipo' => 'J', 'razao-social' => 'Empresa Exemplo (Novo Nome) Ltda', ]); // Excluir $api->call('DELETE', '/clientes/12345678000199'); ``` ## Produtos (CRUD completo) ```php call('POST', '/produtos', [ 'referencia' => $produto['sku'], 'nome' => $produto['nome'], 'valor' => (string) $produto['valor'], 'medida' => $produto['medida'] ?? 'UN', 'categoria' => $produto['categoria'] ?? null, 'ncm' => $produto['ncm'], 'origem' => $produto['origem'] ?? '0', 'cfop-preferencial' => $produto['cfop'] ?? '5102', 'tipo-produto' => 'P', 'configuracoes-fiscais' => $produto['config_fiscal'] ?? [], ]); } function cadastrarServico(MyseApi $api, array $servico): array { return $api->call('POST', '/produtos', [ 'referencia' => $servico['sku'], 'nome' => $servico['nome'], 'valor' => (string) $servico['valor'], 'medida' => 'H', 'tipo-produto' => 'S', 'codigo-servico' => $servico['codigo_servico'], ]); } // Consultar por referência $produto = $api->call('GET', '/produtos/SKU-001'); // Buscar por nome (1º resultado) $porNome = $api->call('GET', '/produtos/nome/Camiseta'); // Listar paginado com busca (use _todos para não filtrar) $todos = $api->call('GET', '/produtos/paginacao/1/20/_todos'); $filtrados = $api->call('GET', '/produtos/paginacao/1/20/Camiseta'); // Atualizar $api->call('PUT', '/produtos/SKU-001', [ 'referencia' => 'SKU-001', 'nome' => 'Camiseta Polo M (Novo)', 'valor' => '99.90', ]); // Excluir $api->call('DELETE', '/produtos/SKU-001'); ``` --- # Exemplos em Python Fonte: https://faznota.com.br/api/v3/exemplos/python/ Implementação completa da API do FazNota em Python 3.10+ com requests. ## Setup ```bash pip install requests python-dotenv ``` ```bash # .env MYSE_API_BASE=https://api.mysebr.com.br/nfemyse-v3/rest MYSE_API_TOKEN=seu_token_aqui ``` ## Cliente HTTP centralizado ```python # myse_api.py import os import time from dataclasses import dataclass from typing import Any, Optional import requests from dotenv import load_dotenv load_dotenv() BASE = os.environ["MYSE_API_BASE"] TOKEN = os.environ["MYSE_API_TOKEN"] @dataclass class ApiError(Exception): http_status: int message: str payload: Any request_id: Optional[str] = None def __str__(self): return f"[{self.http_status}] {self.message} (request_id={self.request_id})" def m_api(method: str, path: str, body: dict | None = None, timeout: int = 30) -> dict: headers = { "Authorization": f"Token {TOKEN}", "Content-Type": "application/json", } res = requests.request( method, f"{BASE}{path}", json=body, headers=headers, timeout=timeout ) request_id = res.headers.get("X-Request-Id") # Erros HTTP duros if not res.ok: try: payload = res.json() msg = payload.get("error", {}).get("message", f"HTTP {res.status_code}") except Exception: payload = {} msg = f"HTTP {res.status_code}" raise ApiError(res.status_code, msg, payload, request_id) json = res.json() # Erro de domínio if json.get("status") == "999": raise ApiError( 200, json.get("data", {}).get("erro") or json.get("descricao", "erro"), json, (json.get("meta") or {}).get("request_id") or request_id, ) return json ``` ## Cenário: emitir NFCe com polling ```python import time def emitir_nfce(pedido: dict) -> dict: # 1. Emissão body = { "serie": "65", "cfop": "5102", "numero-origem": f"PED-{pedido['id']}", "tp_pagamento": pedido["forma_pagamento"], "cliente": pedido["cpf_cnpj_cliente"], "itens": [ { "produto": i["sku"], "quantidade": str(i["qtd"]), "valor-unitario": str(i["preco"]), } for i in pedido["itens"] ], } emissao = m_api("POST", "/nfce/emissao", body) if emissao["status"] not in ("001", "050"): raise RuntimeError(f"Emissão falhou: {emissao['descricao']}") recibo = emissao["data"]["recibo"] print(f"[emissao] recibo={recibo} request_id={emissao['meta']['request_id']}") # 2. Polling TERMINAIS = {"004", "010", "900", "999"} wait = 5.0 inicio = time.time() while time.time() - inicio < 5 * 60: time.sleep(wait) consulta = m_api("GET", f"/nfce/cupom/{recibo}") print(f"[polling] status={consulta['status']}") if consulta["status"] in TERMINAIS: if consulta["status"] == "004": return consulta["data"] raise RuntimeError(f"Status terminal não-sucesso: {consulta['status']}") wait = min(wait * 1.5, 30) raise TimeoutError(f"Timeout aguardando recibo {recibo}") ``` ## Retry em erros transitórios ```python def com_retry(fn, max_tentativas=3): for i in range(1, max_tentativas + 1): try: return fn() except ApiError as e: if e.payload and e.payload.get("status") == "999": raise # Validação: não retentar if i == max_tentativas: raise time.sleep(2 ** (i - 1)) # 1s, 2s, 4s except (requests.Timeout, requests.ConnectionError): if i == max_tentativas: raise time.sleep(2 ** (i - 1)) # Uso nfce = com_retry(lambda: emitir_nfce(pedido)) ``` ## Helper de polling reutilizável ```python TERMINAIS = {"004", "010", "900", "999"} def aguardar_recibo(path_base: str, recibo: str, timeout: int = 300) -> dict: """Polling com backoff exponencial até status terminal.""" wait = 5.0 inicio = time.time() while time.time() - inicio < timeout: time.sleep(wait) resp = m_api("GET", f"{path_base}/{recibo}") if resp["status"] in TERMINAIS: return resp wait = min(wait * 1.5, 30) raise TimeoutError(f"Timeout aguardando recibo {recibo}") ``` ## Emitir NF-e (com transporte completo) ```python def emitir_nfe(pedido: dict) -> dict: body = { "serie": "1", "cfop": "5102", "numero-origem": f"PED-{pedido['id']}", "tipo": "S", "finalidade": "N", "valor-despesas": "0.00", "cliente": pedido["cpf_cnpj_cliente"], "transporte": { "modalidade": "0", "transportadora": pedido["transportadora"], "placa-veiculo": pedido["placa"], "valor-frete": pedido.get("frete", "0.00"), "valor-seguro": "0.00", "peso-bruto": pedido["peso_bruto"], "peso-liquido": pedido["peso_liquido"], "quantidade-volume": "1", "especie-volume": "Caixa", "marca-volume": "Embalagem", }, "itens": [ { "produto": i["sku"], "quantidade": str(i["qtd"]), "valor-unitario": str(i["preco"]), } for i in pedido["itens"] ], "faturas": [ {"valor": str(f["valor"]), "data": f["data"]} for f in pedido.get("faturas", []) ], } emissao = m_api("POST", "/nfe/emissao", body) if emissao["status"] not in ("001", "050"): raise RuntimeError(f"Emissão falhou: {emissao['descricao']}") resultado = aguardar_recibo("/nfe/nota", emissao["data"]["recibo"]) if resultado["status"] != "004": raise RuntimeError(f"Status terminal não-sucesso: {resultado['status']}") return resultado["data"] # inclui sefaz.chave, url-danfe, url-xml ``` ## Cancelar NF-e ```python def cancelar_nfe(chave: str, motivo: str) -> dict: assert 15 <= len(motivo) <= 255, "motivo deve ter entre 15 e 255 caracteres" cancel = m_api("POST", "/nfe/cancelamento", {"chave": chave, "motivo": motivo}) if cancel["status"] != "001": raise RuntimeError(cancel["descricao"]) return aguardar_recibo("/nfe/cancelamento", cancel["data"]["recibo"]) ``` ## Lançar carta de correção (CC-e) ```python def carta_correcao_nfe(chave: str, texto_correcao: str) -> dict: """O campo 'motivo' na CC-e é o TEXTO da correção (xCorrecao).""" assert 15 <= len(texto_correcao) <= 255, "texto deve ter 15-255 chars" cce = m_api("POST", "/nfe/correcao", {"chave": chave, "motivo": texto_correcao}) if cce["status"] != "001": raise RuntimeError(cce["descricao"]) return aguardar_recibo("/nfe/correcao", cce["data"]["recibo"]) ``` ## Cancelar NFC-e ```python def cancelar_nfce(chave: str, motivo: str) -> dict: assert 15 <= len(motivo) <= 255, "motivo deve ter 15-255 chars" cancel = m_api("POST", "/nfce/cancelamento", {"chave": chave, "motivo": motivo}) if cancel["status"] != "001": raise RuntimeError(cancel["descricao"]) return aguardar_recibo("/nfce/cancelamento", cancel["data"]["recibo"]) ``` ## Emitir NFS-e (Nota Fiscal de Serviço) ```python def emitir_nfse(servico: dict) -> dict: body = { "numero-origem": f"OS-{servico['id']}", "observacao": servico["observacao"], "nome-contato": servico.get("nome_contato"), "telefone-contato": servico.get("telefone_contato"), "data-fim": servico["data_fim"], "cliente": servico["cpf_cnpj_cliente"], "itens": [ { "produto": i["codigo_servico"], "quantidade": str(i["qtd"]), "valor-unitario": str(i["preco"]), } for i in servico["itens"] ], } emissao = m_api("POST", "/nfse/emissao", body) if emissao["status"] not in ("001", "050"): raise RuntimeError(f"Emissão NFSe falhou: {emissao['descricao']}") resultado = aguardar_recibo("/nfse/nota", emissao["data"]["recibo"]) if resultado["status"] != "004": raise RuntimeError(f"Status terminal não-sucesso: {resultado['status']}") return resultado["data"] ``` ## Cancelar NFS-e ```python def cancelar_nfse(chave: str, motivo: str) -> dict: assert 15 <= len(motivo) <= 255, "motivo deve ter 15-255 chars" return m_api("POST", "/nfse/cancelamento", {"chave": chave, "motivo": motivo}) ``` ## Reconhecer nota de fornecedor (MD-e) ```python CODIGOS_RECONHECIMENTO = {"210200", "210210", "210220", "210240"} # 210200 — Confirmação da operação (terminal, irreversível) # 210210 — Ciência da operação (provisório, 15 dias) # 210220 — Desconhecimento da operação # 210240 — Operação não realizada def reconhecer_nota_fornecedor(chave: str, codigo: str) -> dict: if codigo not in CODIGOS_RECONHECIMENTO: raise ValueError(f"Código inválido: {codigo}") resp = m_api("POST", "/nffornecedor/reconhecimento", { "chave": chave, "codigo-reconhecimento": codigo, }) if resp["status"] != "001": raise RuntimeError(resp["descricao"]) return aguardar_recibo("/nffornecedor", resp["data"]["recibo"]) ``` ## Listar com paginação ```python def listar_todos_clientes() -> list: todos = [] page = 1 page_size = 100 while True: resp = m_api("GET", f"/clientes?page={page}&page_size={page_size}") if resp["status"] != "005" or not isinstance(resp.get("data"), list): break todos.extend(resp["data"]) if len(resp["data"]) < page_size: break page += 1 time.sleep(0.2) return todos ``` ## Tratamento de erros ```python try: nfce = emitir_nfce(pedido) print("NFCe emitida:", nfce.get("sefaz", {}).get("chave")) except ApiError as e: print(f"API Error: {e}") # Log para suporte com request_id logger.error( "API call failed", extra={ "request_id": e.request_id, "http_status": e.http_status, "payload": e.payload, }, ) except TimeoutError as e: print(f"Timeout: {e}") ``` ## Variação: usando httpx (async) ```python import httpx async def m_api_async(method, path, body=None): async with httpx.AsyncClient(timeout=30) as client: res = await client.request( method, f"{BASE}{path}", json=body, headers={"Authorization": f"Token {TOKEN}"}, ) # ... mesma lógica de tratamento ``` ## Health check (sem autenticação) ```python import requests def health() -> dict: res = requests.get(f"{BASE}/health", timeout=5) return res.json() # {"status": "ok", "database": "ok", "timestamp": "..."} ``` ## Alterar nota antes da emissão (PUT) Disponível em NF-e (`/nfe/{id}`), NFC-e (`/nfce/{id}`) e NFS-e (`/nfse/{id}`). **Só funciona enquanto a nota ainda não foi submetida para emissão.** ```python def alterar_nota(tipo: str, id_nota: str, dados: dict) -> dict: """tipo: 'nfe' | 'nfce' | 'nfse'""" return m_api("PUT", f"/{tipo}/{id_nota}", dados) # Uso: alterar_nota("nfe", "12345", { "serie": "1", "cfop": "5102", "cliente": "12345678000199", "itens": [{"produto": "SKU-001", "quantidade": "3", "valor-unitario": "89.90"}], }) ``` ## Consulta detalhada por recibo ```python nfe_completa = m_api("GET", f"/nfe/nota/{recibo}/completa") nfce_completa = m_api("GET", f"/nfce/cupom/{recibo}/completa") ``` ## Listagens ```python # NF-es paginadas nfes = m_api("GET", "/nfe?page=1&page_size=20") # NFC-es nfces = m_api("GET", "/nfce?page=1&page_size=50") # NFS-es com filtro de data nfses = m_api("GET", "/nfse?dataInicial=2026-05-01&page=1&page_size=20") # Notas de fornecedor fornecedor = m_api("GET", "/nffornecedor?page=1&page_size=20") # Forma legada (path-based) nfes_legado = m_api("GET", "/nfe/paginacao/1/20") ``` ## Clientes (CRUD completo) ```python def cadastrar_pf(dados: dict) -> dict: return m_api("POST", "/clientes", { "tipo": "F", "nome": dados["nome"], "cpf": dados["cpf"], "rg": dados.get("rg"), "consumidor-final": "1", "contato": { "email": dados.get("email"), "telefone": dados.get("telefone"), }, "endereco": dados["endereco"], }) def cadastrar_pj(dados: dict) -> dict: return m_api("POST", "/clientes", { "tipo": "J", "razao-social": dados["razao_social"], "nome-fantasia": dados.get("nome_fantasia"), "cnpj": dados["cnpj"], "inscricao-municipal": dados.get("inscricao_municipal"), "tipo-inscricao-estadual": dados.get("contribuinte", "9"), "inscricao-estadual": dados.get("inscricao_estadual", "ISENTO"), "consumidor-final": dados.get("consumidor_final", "0"), "contato": { "email": dados.get("email"), "telefone": dados.get("telefone"), }, "endereco": dados["endereco"], }) # Consultar por CPF/CNPJ cliente = m_api("GET", "/clientes/12345678000199") # Buscar por nome encontrados = m_api("GET", "/clientes/busca/exemplo") top = m_api("GET", "/clientes/busca/top_clientes") # Atualizar m_api("PUT", "/clientes/12345678000199", { "tipo": "J", "razao-social": "Empresa Exemplo (Novo Nome) Ltda", }) # Excluir m_api("DELETE", "/clientes/12345678000199") ``` ## Produtos (CRUD completo) ```python def cadastrar_produto(produto: dict) -> dict: return m_api("POST", "/produtos", { "referencia": produto["sku"], "nome": produto["nome"], "valor": str(produto["valor"]), "medida": produto.get("medida", "UN"), "categoria": produto.get("categoria"), "ncm": produto["ncm"], "origem": produto.get("origem", "0"), "cfop-preferencial": produto.get("cfop", "5102"), "tipo-produto": "P", "configuracoes-fiscais": produto.get("config_fiscal", []), }) def cadastrar_servico(servico: dict) -> dict: return m_api("POST", "/produtos", { "referencia": servico["sku"], "nome": servico["nome"], "valor": str(servico["valor"]), "medida": "H", "tipo-produto": "S", "codigo-servico": servico["codigo_servico"], }) # Consultar por referência produto = m_api("GET", "/produtos/SKU-001") # Buscar por nome (1º resultado) por_nome = m_api("GET", "/produtos/nome/Camiseta") # Listar paginado com busca (use _todos para não filtrar) todos = m_api("GET", "/produtos/paginacao/1/20/_todos") filtrados = m_api("GET", "/produtos/paginacao/1/20/Camiseta") # Atualizar m_api("PUT", "/produtos/SKU-001", { "referencia": "SKU-001", "nome": "Camiseta Polo M (Novo)", "valor": "99.90", }) # Excluir m_api("DELETE", "/produtos/SKU-001") ``` --- # Exemplos em Ruby Fonte: https://faznota.com.br/api/v3/exemplos/ruby/ Implementação completa da API do FazNota em Ruby 3+ com net/http stdlib. ## Setup Requer **Ruby 3+**. Sem dependências externas (stdlib). ```bash export MYSE_API_BASE="https://api.mysebr.com.br/nfemyse-v3/rest" export MYSE_API_TOKEN="seu_token_aqui" ``` ## Cliente HTTP centralizado ```ruby # myse_api.rb require 'net/http' require 'json' require 'uri' module Myse class ApiError < StandardError attr_reader :http_status, :payload, :request_id def initialize(http_status, message, payload = nil, request_id = nil) @http_status = http_status @payload = payload @request_id = request_id super(message) end end class Client BASE = ENV.fetch('MYSE_API_BASE') TOKEN = ENV.fetch('MYSE_API_TOKEN') def call(method, path, body = nil, timeout: 30) uri = URI.parse("#{BASE}#{path}") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = (uri.scheme == 'https') http.read_timeout = timeout req_class = { 'GET' => Net::HTTP::Get, 'POST' => Net::HTTP::Post, 'PUT' => Net::HTTP::Put, 'DELETE' => Net::HTTP::Delete, }.fetch(method.upcase) req = req_class.new(uri.request_uri) req['Authorization'] = "Token #{TOKEN}" req['Content-Type'] = 'application/json' req.body = body.to_json if body res = http.request(req) request_id = res['x-request-id'] json = JSON.parse(res.body) rescue {} if res.code.to_i >= 400 msg = json.dig('error', 'message') || "HTTP #{res.code}" raise ApiError.new(res.code.to_i, msg, json, request_id) end if json['status'] == '999' msg = json.dig('data', 'erro') || json['descricao'] || 'erro' rid = json.dig('meta', 'request_id') || request_id raise ApiError.new(200, msg, json, rid) end json end end end ``` ## Cenário: emitir NFCe com polling ```ruby require_relative 'myse_api' TERMINAIS = %w[004 010 900 999].freeze def emitir_nfce(client, pedido) body = { serie: '65', cfop: '5102', 'numero-origem' => "PED-#{pedido[:id]}", tp_pagamento: pedido[:forma_pagamento], cliente: pedido[:cpf_cnpj_cliente], itens: pedido[:itens].map do |i| { produto: i[:sku], quantidade: i[:qtd].to_s, 'valor-unitario' => i[:preco].to_s, } end, } emissao = client.call('POST', '/nfce/emissao', body) unless %w[001 050].include?(emissao['status']) raise "Emissão falhou: #{emissao['descricao']}" end recibo = emissao.dig('data', 'recibo') puts "[emissao] recibo=#{recibo} request_id=#{emissao.dig('meta', 'request_id')}" wait = 5.0 inicio = Time.now while Time.now - inicio < 5 * 60 sleep(wait) consulta = client.call('GET', "/nfce/cupom/#{recibo}") puts "[polling] status=#{consulta['status']}" if TERMINAIS.include?(consulta['status']) return consulta['data'] if consulta['status'] == '004' raise "Status terminal não-sucesso: #{consulta['status']}" end wait = [wait * 1.5, 30].min end raise "Timeout aguardando recibo #{recibo}" end ``` ## Retry com backoff ```ruby def com_retry(max_tentativas: 3) (1..max_tentativas).each do |i| begin return yield rescue Myse::ApiError => e raise if e.payload&.dig('status') == '999' raise if i == max_tentativas sleep(2 ** (i - 1)) # 1s, 2s, 4s end end end # Uso nfce = com_retry { emitir_nfce(client, pedido) } ``` ## Helper de polling reutilizável ```ruby TERMINAIS = %w[004 010 900 999].freeze def aguardar_recibo(client, path_base, recibo, timeout_sec: 300) wait = 5.0 inicio = Time.now while Time.now - inicio < timeout_sec sleep(wait) resp = client.call('GET', "#{path_base}/#{recibo}") return resp if TERMINAIS.include?(resp['status']) wait = [wait * 1.5, 30].min end raise "Timeout aguardando recibo #{recibo}" end def validar_motivo!(motivo) unless (15..255).include?(motivo.length) raise ArgumentError, 'motivo deve ter entre 15 e 255 caracteres' end end ``` ## Emitir NF-e (com transporte completo) ```ruby def emitir_nfe(client, pedido) body = { 'serie' => '1', 'cfop' => '5102', 'numero-origem' => "PED-#{pedido[:id]}", 'tipo' => 'S', 'finalidade' => 'N', 'valor-despesas' => '0.00', 'cliente' => pedido[:cpf_cnpj_cliente], 'transporte' => { 'modalidade' => '0', 'transportadora' => pedido[:transportadora], 'placa-veiculo' => pedido[:placa], 'valor-frete' => pedido[:frete] || '0.00', 'valor-seguro' => '0.00', 'peso-bruto' => pedido[:peso_bruto], 'peso-liquido' => pedido[:peso_liquido], 'quantidade-volume' => '1', 'especie-volume' => 'Caixa', 'marca-volume' => 'Embalagem', }, 'itens' => pedido[:itens].map do |i| { 'produto' => i[:sku], 'quantidade' => i[:qtd].to_s, 'valor-unitario' => i[:preco].to_s, } end, 'faturas' => (pedido[:faturas] || []).map do |f| { 'valor' => f[:valor].to_s, 'data' => f[:data] } end, } emissao = client.call('POST', '/nfe/emissao', body) unless %w[001 050].include?(emissao['status']) raise "Emissão falhou: #{emissao['descricao']}" end resultado = aguardar_recibo(client, '/nfe/nota', emissao.dig('data', 'recibo')) unless resultado['status'] == '004' raise "Status terminal não-sucesso: #{resultado['status']}" end resultado['data'] # inclui sefaz.chave, url-danfe, url-xml end ``` ## Cancelar NF-e ```ruby def cancelar_nfe(client, chave, motivo) validar_motivo!(motivo) cancel = client.call('POST', '/nfe/cancelamento', { chave: chave, motivo: motivo }) raise cancel['descricao'] unless cancel['status'] == '001' aguardar_recibo(client, '/nfe/cancelamento', cancel.dig('data', 'recibo')) end ``` ## Lançar carta de correção (CC-e) ```ruby # O campo 'motivo' na CC-e é o TEXTO da correção (xCorrecao). def carta_correcao_nfe(client, chave, texto_correcao) validar_motivo!(texto_correcao) cce = client.call('POST', '/nfe/correcao', { chave: chave, motivo: texto_correcao }) raise cce['descricao'] unless cce['status'] == '001' aguardar_recibo(client, '/nfe/correcao', cce.dig('data', 'recibo')) end ``` ## Cancelar NFC-e ```ruby def cancelar_nfce(client, chave, motivo) validar_motivo!(motivo) cancel = client.call('POST', '/nfce/cancelamento', { chave: chave, motivo: motivo }) raise cancel['descricao'] unless cancel['status'] == '001' aguardar_recibo(client, '/nfce/cancelamento', cancel.dig('data', 'recibo')) end ``` ## Emitir NFS-e (Nota Fiscal de Serviço) ```ruby def emitir_nfse(client, servico) body = { 'numero-origem' => "OS-#{servico[:id]}", 'observacao' => servico[:observacao], 'nome-contato' => servico[:nome_contato], 'telefone-contato' => servico[:telefone_contato], 'data-fim' => servico[:data_fim], 'cliente' => servico[:cpf_cnpj_cliente], 'itens' => servico[:itens].map do |i| { 'produto' => i[:codigo_servico], 'quantidade' => i[:qtd].to_s, 'valor-unitario' => i[:preco].to_s, } end, } emissao = client.call('POST', '/nfse/emissao', body) unless %w[001 050].include?(emissao['status']) raise "Emissão NFSe falhou: #{emissao['descricao']}" end resultado = aguardar_recibo(client, '/nfse/nota', emissao.dig('data', 'recibo')) unless resultado['status'] == '004' raise "Status terminal não-sucesso: #{resultado['status']}" end resultado['data'] end ``` ## Cancelar NFS-e ```ruby def cancelar_nfse(client, chave, motivo) validar_motivo!(motivo) client.call('POST', '/nfse/cancelamento', { chave: chave, motivo: motivo }) end ``` ## Reconhecer nota de fornecedor (MD-e) ```ruby # Códigos de reconhecimento: # '210200' — Confirmação da operação (terminal, irreversível) # '210210' — Ciência da operação (provisório, 15 dias) # '210220' — Desconhecimento da operação # '210240' — Operação não realizada CODIGOS_RECONHECIMENTO = %w[210200 210210 210220 210240].freeze def reconhecer_nota_fornecedor(client, chave, codigo) unless CODIGOS_RECONHECIMENTO.include?(codigo) raise ArgumentError, "Código inválido: #{codigo}" end resp = client.call('POST', '/nffornecedor/reconhecimento', { chave: chave, 'codigo-reconhecimento' => codigo, }) raise resp['descricao'] unless resp['status'] == '001' aguardar_recibo(client, '/nffornecedor', resp.dig('data', 'recibo')) end ``` ## Listar com paginação ```ruby def listar_todos_clientes(client) todos = [] page = 1 page_size = 100 loop do resp = client.call('GET', "/clientes?page=#{page}&page_size=#{page_size}") break unless resp['status'] == '005' && resp['data'].is_a?(Array) todos.concat(resp['data']) break if resp['data'].size < page_size page += 1 sleep(0.2) end todos end ``` ## Tratamento de erros ```ruby require 'logger' logger = Logger.new($stdout) client = Myse::Client.new begin nfce = emitir_nfce(client, pedido) logger.info("NFCe emitida: #{nfce.dig('sefaz', 'chave')}") rescue Myse::ApiError => e logger.error({ event: 'api_error', http_status: e.http_status, request_id: e.request_id, message: e.message, }.to_json) raise end ``` ## Health check (sem autenticação) ```ruby uri = URI.parse("#{Myse::Client::BASE}/health") res = Net::HTTP.get_response(uri) health = JSON.parse(res.body) # {"status" => "ok", "database" => "ok", "timestamp" => "..."} ``` ## Alterar nota antes da emissão (PUT) Disponível em NF-e (`/nfe/{id}`), NFC-e (`/nfce/{id}`) e NFS-e (`/nfse/{id}`). **Só funciona enquanto a nota ainda não foi submetida para emissão.** ```ruby def alterar_nota(client, tipo, id, dados) # tipo: 'nfe' | 'nfce' | 'nfse' client.call('PUT', "/#{tipo}/#{id}", dados) end # Uso: alterar_nota(client, 'nfe', '12345', { serie: '1', cfop: '5102', cliente: '12345678000199', itens: [{ produto: 'SKU-001', quantidade: '3', 'valor-unitario' => '89.90' }], }) ``` ## Consulta detalhada por recibo ```ruby nfe_completa = client.call('GET', "/nfe/nota/#{recibo}/completa") nfce_completa = client.call('GET', "/nfce/cupom/#{recibo}/completa") ``` ## Listagens ```ruby nfes = client.call('GET', '/nfe?page=1&page_size=20') nfces = client.call('GET', '/nfce?page=1&page_size=50') nfses = client.call('GET', '/nfse?dataInicial=2026-05-01&page=1&page_size=20') fornecedor = client.call('GET', '/nffornecedor?page=1&page_size=20') # Forma legada (path-based) nfes_legado = client.call('GET', '/nfe/paginacao/1/20') ``` ## Clientes (CRUD completo) ```ruby def cadastrar_pf(client, dados) client.call('POST', '/clientes', { tipo: 'F', nome: dados[:nome], cpf: dados[:cpf], rg: dados[:rg], 'consumidor-final' => '1', contato: { email: dados[:email], telefone: dados[:telefone] }, endereco: dados[:endereco], }) end def cadastrar_pj(client, dados) client.call('POST', '/clientes', { tipo: 'J', 'razao-social' => dados[:razao_social], 'nome-fantasia' => dados[:nome_fantasia], cnpj: dados[:cnpj], 'inscricao-municipal' => dados[:inscricao_municipal], 'tipo-inscricao-estadual' => dados[:contribuinte] || '9', 'inscricao-estadual' => dados[:inscricao_estadual] || 'ISENTO', 'consumidor-final' => '0', contato: { email: dados[:email], telefone: dados[:telefone] }, endereco: dados[:endereco], }) end # Consultar por CPF/CNPJ cliente = client.call('GET', '/clientes/12345678000199') # Buscar por nome encontrados = client.call('GET', '/clientes/busca/exemplo') top = client.call('GET', '/clientes/busca/top_clientes') # Atualizar client.call('PUT', '/clientes/12345678000199', { tipo: 'J', 'razao-social' => 'Empresa Exemplo (Novo Nome) Ltda', }) # Excluir client.call('DELETE', '/clientes/12345678000199') ``` ## Produtos (CRUD completo) ```ruby def cadastrar_produto(client, produto) client.call('POST', '/produtos', { referencia: produto[:sku], nome: produto[:nome], valor: produto[:valor].to_s, medida: produto[:medida] || 'UN', categoria: produto[:categoria], ncm: produto[:ncm], origem: produto[:origem] || '0', 'cfop-preferencial' => produto[:cfop] || '5102', 'tipo-produto' => 'P', 'configuracoes-fiscais' => produto[:config_fiscal] || [], }) end def cadastrar_servico(client, servico) client.call('POST', '/produtos', { referencia: servico[:sku], nome: servico[:nome], valor: servico[:valor].to_s, medida: 'H', 'tipo-produto' => 'S', 'codigo-servico' => servico[:codigo_servico], }) end # Consultar produto = client.call('GET', '/produtos/SKU-001') por_nome = client.call('GET', '/produtos/nome/Camiseta') # Listar paginado com busca (use _todos para não filtrar) todos = client.call('GET', '/produtos/paginacao/1/20/_todos') filtrados = client.call('GET', '/produtos/paginacao/1/20/Camiseta') # Atualizar client.call('PUT', '/produtos/SKU-001', { referencia: 'SKU-001', nome: 'Camiseta Polo M (Novo)', valor: '99.90', }) # Excluir client.call('DELETE', '/produtos/SKU-001') ``` --- # FAQ & Troubleshooting Fonte: https://faznota.com.br/api/v3/faq/ Perguntas frequentes e resolução dos problemas mais comuns. ## Perguntas gerais ### A API do FazNota tem ambiente de sandbox/homologação? A documentação atual descreve o ambiente de produção (`api.mysebr.com.br`). Para testes sem efeito fiscal, entre em contato com o suporte sobre disponibilidade de ambiente de homologação. ### Posso ter mais de um Token por empresa? Atualmente cada empresa tem um Token único de integração, gerado no Painel FazNota. Para isolar integrações distintas (ex.: ERP vs e-commerce), você pode usar tokens de empresas vinculadas (se acessível via Painel de Gestão de Contas). ### A API é versionada? Sim. A versão atual é **v3**, expressa no path (`/nfemyse-v3/rest/...`). Mudanças significativas de contrato seguem [comunicação prévia](/api/v3/politicas-operacionais/). ### Por que a API sempre retorna HTTP 200? É uma **convenção intencional de canal único de status**: a aplicação responde em `HTTP 200` sempre que processou a requisição, e o resultado real está em `body.status`. Apenas falhas estruturais (auth, rate limit, URL errada) retornam HTTP ≠ 200. Os principais motivos da convenção: - **Resposta sempre lida pelo cliente** — bibliotecas como `axios` lançam exception em 4xx/5xx por padrão; com `200 + body.status`, o cliente sempre acessa o body sem `try/catch` obrigatório para erros de negócio. - **Validação não é erro de servidor** — um CFOP inválido é resposta de negócio, não 5xx. Mistura com 5xx polui métricas de SRE. - **Duplicidade (`050`) é resposta válida** — retornar o recibo original é o comportamento correto. Veja [Estrutura de resposta](/api/v3/conceitos/estrutura-resposta/) para o padrão completo de tratamento. ### Posso usar a API de um frontend (browser)? Tecnicamente o CORS está aberto. Mas **não** é recomendado: o Token é credencial sensível e não deve ser exposto ao browser. Sempre faça as chamadas a partir do seu backend. ## Autenticação ### Recebo `HTTP 401` mesmo com Token correto - Verifique se o header é exatamente `Authorization: Token ` (não `Bearer`). - Confirme se o Token foi rotacionado no painel — o antigo é revogado. - Verifique se há espaços extras ou quebras de linha no Token. ### Recebo `HTTP 403 "contrato inapto"` Contrato da empresa precisa ser regularizado. Entre em contato com o suporte ou o time comercial do FazNota. ### Recebo `HTTP 429` Você atingiu o limite de consumo da janela atual. Veja `X-RateLimit-Reset` para saber quando retomar. Reduza o ritmo de envio e implemente backoff exponencial. ## Emissão de notas ### Recebi `status: "001"` mas a consulta retorna `status: "002"` há muito tempo Esperado durante o processamento (alguns segundos a minutos). Continue o polling com intervalo de 2s+ e backoff. Se passar de 5 minutos, acione o suporte com o `meta.request_id` e o recibo. ### Recebi `status: "050"` (duplicidade) **Não é erro.** Você está enviando o mesmo `numero-origem` de uma emissão anterior. A API retorna o recibo da emissão original para você consultar. Veja [Idempotência](/api/v3/padroes/idempotencia/). ### Recebi `status: "900"` (rejeição SEFAZ) **Antes de qualquer coisa, verifique `data.sefaz.codigo`.** Se for `217` ou `105`, **não é uma rejeição** — é a SEFAZ ainda processando a nota (lenta/instável). Continue consultando o mesmo recibo; o resultado se resolve sozinho em minutos (reconciliação automática, janela de até 3h). **Não reemita** — isso duplica o documento fiscal. Veja [SEFAZ lenta ou instável](/api/v3/conceitos/instabilidade-sefaz/) para o detalhe completo. Para qualquer outro `data.sefaz.codigo`, aí sim é rejeição definitiva. Algumas mensagens comuns: | Código aprox. | Significado | |---|---| | Rejeição 539 | Duplicidade na SEFAZ (chave já existe lá) | | Rejeição 539/610 | Numeração já utilizada | | Rejeição 538/611 | NCM inválido | | Rejeição 778 | CFOP incompatível com operação | | Rejeição 233/237 | Inscrição estadual do destinatário inválida | Corrija o dado indicado na mensagem e emita uma nova nota (não é o caso de retentar a mesma). ### `Campo nfe@chave deve ter 44 caracteres.` A chave de acesso da NFe tem **exatamente 44 dígitos numéricos**. Não inclua prefixos como `NFe`, espaços ou hífens — apenas os 44 dígitos. ### `Campo nfe@motivo deve ter entre 15 e 255 caracteres.` Para cancelamentos e cartas de correção, o motivo precisa ter entre 15 e 255 caracteres. Mensagens curtas como `"Cancelar"` ou `"Teste"` são rejeitadas. ### `Campo nfe@cliente documento não pertence a um cliente cadastrado.` O CPF/CNPJ informado em `cliente` (como string) não está cadastrado. Soluções: 1. Cadastrar o cliente antes via `POST /clientes`. 2. Enviar o cliente inline como objeto no body da emissão. ### `Campo nfe@transporte@placa-veiculo inválido.` A placa precisa estar no formato `AAA-9999` (antigo) ou `AAA-9A99` (Mercosul). Tudo maiúsculo. Exemplo válido: `ABC-1234` ou `ABC-1D23`. ## Consultas e listagens ### Listagem retorna sempre os mesmos 100 (ou 50) registros Você não está paginando. Adicione `?page=1&page_size=20` (e iterations subsequentes). Veja [Paginação](/api/v3/padroes/paginacao/). ### `page_size=10000` retorna só 500 O limite é capado em 500 por chamada (1000 em clientes/produtos). Use loops com páginas menores. ### Como saber quantas páginas tem no total? A API não retorna o total. Pare quando uma página vier com **menos itens que o `page_size`** solicitado. ## Performance ### As consultas estão lentas - Verifique se você está usando `/nota/{recibo}/completa` quando bastaria `/nota/{recibo}` (versão simples). - Cache localmente listagens de clientes/produtos. - Evite consultar uma listagem grande para depois filtrar localmente — use os endpoints específicos (`/clientes/{doc}`, `/produtos/{ref}`). ### Como melhorar performance de polling? Use backoff exponencial. Após `004`/`010`/`999`, **pare**. Para `900`, confira `data.sefaz.codigo` antes de parar — veja [SEFAZ lenta ou instável](/api/v3/conceitos/instabilidade-sefaz/). ```typescript let wait = 2000; const TERM = ['004', '010', '999']; const SEFAZ_TRANSITORIO = ['217', '105']; while (true) { const json = await fetch(...).then(r => r.json()); const codigoSefaz = json.data?.sefaz?.codigo; const parar = TERM.includes(json.status) || (json.status === '900' && !SEFAZ_TRANSITORIO.includes(codigoSefaz)); if (parar) break; await sleep(wait); wait = Math.min(wait * 1.5, 30_000); } ``` ## Suporte ### Como abrir um ticket? Envie para [suporte@myse.com.br](mailto:suporte@myse.com.br) com: 1. **Descrição clara** do problema. 2. **`meta.request_id`** (ou `X-Request-Id`) de pelo menos uma requisição com falha. 3. **Horário aproximado** do ocorrido. 4. **Trecho do payload** enviado (omita o Token). 5. **Resposta recebida** (status interno + descricao + data.erro). ### O que NÃO incluir no ticket - ❌ Seu Token completo (envie apenas primeiros e últimos 6 caracteres se precisar) - ❌ Senhas do Painel FazNota - ❌ Dados pessoais de clientes/produtos não relacionados ao problema --- # Integração com IA / LLMs Fonte: https://faznota.com.br/api/v3/integracao-ia/ Recursos para integrar a API v3 do FazNota usando assistentes de IA (Claude, ChatGPT, Copilot) — arquivos llms.txt e um bloco de contexto pronto para colar. Vai gerar a integração com a ajuda de um assistente de IA (Claude, ChatGPT, Copilot, Cursor…)? Esta página reúne o material pronto para o modelo entender a API v3 e produzir código correto de primeira. ## Arquivos prontos para IA Índice curado de toda a documentação v3, em Markdown, no padrão [llmstxt.org](https://llmstxt.org). Aponte seu agente para ele.
**`https://faznota.com.br/api/llms.txt`**
Toda a documentação v3 concatenada em um único Markdown. Cole no seu assistente para dar contexto total de uma vez.
**`https://faznota.com.br/api/llms-full.txt`**
## Como usar 1. **Contexto rápido:** cole a URL do `llms-full.txt` (ou o conteúdo dele) no seu assistente e peça: *"Com base nesta documentação, gere a integração de emissão de NFe em ``."* 2. **Bloco mínimo:** se preferir algo curto, copie o **bloco de contexto** abaixo — é o essencial e estável da API. ## Bloco de contexto (copie e cole no seu LLM) ```text Você vai integrar com a API de Nota Fiscal do FazNota (v3). Regras essenciais: BASE E AUTENTICAÇÃO - Base URL: https://api.mysebr.com.br/nfemyse-v3/rest - Auth: header em TODAS as requisições -> Authorization: Token - O TOKEN é a chave de integração da empresa (não expor no frontend; chamar do backend). MODELO ASSÍNCRONO (emissão -> recibo -> polling) 1) POST /nfe/emissao (ou /nfce/emissao, /nfse/emissao) devolve um "recibo". 2) Consultar o resultado por polling em GET /nfe/nota/{recibo} (NFCe: /nfce/cupom/{recibo}). 3) Intervalo mínimo 2s entre consultas, com backoff exponencial (2s,3s,4.5s,...ate 30s). Parar em estado terminal. Timeout total ~5 min. ENVELOPE DE RESPOSTA (sempre HTTP 200 nos endpoints de negócio) { "status": "", "descricao": "", "data": { ... }, "meta": { "request_id": "...", "timestamp": "..." } } - Guarde meta.request_id (e o header X-Request-Id) nos seus logs — agiliza o suporte. - Headers X-RateLimit-Limit/Remaining/Reset indicam o consumo permitido. STATUS DE EMISSÃO (campo status; decida pelo data.sefaz.codigo quando houver) - 001 registrada (recibo gerado) | 002/003 em processamento | 004 autorizada - 010 cancelada | 050 duplicidade (mesmo numero-origem -> retorna o recibo original) - 006 processado sem resultado | 007 falha no processamento assíncrono (ambos finais) - 400 parâmetro inválido (erro do chamador — repetir a requisição não resolve) - 900 rejeição da SEFAZ (corrigir e reemitir). IMPORTANTE: se vier 900 com data.sefaz.codigo 217 ou 105, NÃO é rejeição — a SEFAZ ainda está processando; continue consultando o mesmo recibo e NÃO reemita (duplica documento fiscal). IDEMPOTÊNCIA - Envie sempre "numero-origem" (seu id único da venda). Reenvio com o mesmo valor NÃO gera outra nota — retorna o recibo da emissão original. PAYLOAD DE EMISSÃO DE NFe (POST /nfe/emissao) — campos principais { "serie": "1", // obrigatório "cfop": "5102", // obrigatório na prática "numero-origem": "PED-2026-0001", // recomendado (idempotência) "tipo": "S", // S saída (default) | E entrada "finalidade": "N", // N normal | C complementar | A ajuste | D devolução "cliente": "12345678000199", // CPF/CNPJ de cliente cadastrado OU objeto com dados "itens": [ // obrigatório, >=1 { "produto": "SKU-001", "quantidade": "2", "valor-unitario": "89.90" } // "produto" pode ser objeto com referencia + configuracoes-fiscais (ipi, pis-cofins, // cbs-ibs (IBS/CBS reforma tributária), icms.estados[].aliquotas...) ], "transporte": { "modalidade": "9" }, // 9 = sem frete (default) "faturas": [ { "valor": "179.80", "data": "2026-08-15" } ] // opcional } Cliente inline exige endereço completo (no mínimo cidade e estado/UF). ENDPOINTS PRINCIPAIS - NFe: POST /nfe/emissao | GET /nfe/nota/{recibo}[/completa] | PUT /nfe/{id} | POST /nfe/cancelamento | POST /nfe/correcao | GET /nfe (lista, ?page&page_size) - NFCe: POST /nfce/emissao | GET /nfce/cupom/{recibo}[/completa] | POST /nfce/cancelamento - NFSe: POST /nfse/emissao | GET /nfse/nota/{recibo} | POST /nfse/cancelamento - nffornecedor: POST /nffornecedor/reconhecimento | GET /nffornecedor/{recibo} - clientes: GET/POST /clientes | GET/PUT/DELETE /clientes/{doc} | GET /clientes/busca/{nome} - produtos: GET/POST /produtos | GET/PUT/DELETE /produtos/{ref} - resumo: GET /resumo?data-inicial=AAAA-MM-DD&data-final=AAAA-MM-DD (máx 92 dias) totais/dia de NFe e NFCe emitidas/canceladas/denegadas + falhas. ATENÇÃO: canceladas é SUBCONJUNTO de emitidas — não some os dois. - xml em lote (assíncrono): POST /xml/solicitacao {tipo:NFe|NFCe, data-inicial, data-final, email?} -> data.protocolo; depois GET /xml/solicitacao/{protocolo} -> 002 processando | 005 disponivel (data.url-download, expira em 7 dias) | 006 sem-notas (final) | 007 falha (final). A url-download só aceita GET. ERROS - Erro de negócio vem em data.erro (texto claro e acionável). HTTP != 200 só em auth (401/403), rate limit (429) e URL inválida. EXEMPLO cURL (emissão) curl -X POST https://api.mysebr.com.br/nfemyse-v3/rest/nfe/emissao \ -H "Authorization: Token SEU_TOKEN" -H "Content-Type: application/json" \ -d '{ "serie":"1","cfop":"5102","numero-origem":"PED-1","cliente":"12345678000199", "itens":[{"produto":"SKU-001","quantidade":"1","valor-unitario":"10.00"}] }' ``` --- # Códigos de status interno Fonte: https://faznota.com.br/api/v3/padroes/codigos-status/ Tabela completa dos códigos retornados em `body.status` pela API do FazNota. A API do FazNota adota uma **convenção de canal único de status**: enquanto a aplicação processar a requisição (passar autenticação e parser), a resposta é sempre `HTTP 200`, e o resultado real fica em `body.status` (3 dígitos, `001`–`999`). Esse código distingue sucesso, pendência, erro de validação, rejeição da SEFAZ e erros internos. Apenas falhas estruturais (auth, rate limit, URL errada) retornam HTTP ≠ 200 — veja [Estrutura de resposta](/api/v3/conceitos/estrutura-resposta/) para o detalhe.
## Tabela completa | Código | Significado | Quando aparece | |--------|-------------|----------------| | `001` | **Registrado.** A operação foi recebida e está na fila para processamento. | Resposta imediata de qualquer emissão/cancelamento/CC-e. | | `002` | **Pendente de processamento.** Na fila, ainda não começou. | Polling do recibo logo após emissão. | | `003` | **Em processo de emissão.** Está sendo processada agora. | Polling enquanto SEFAZ ainda não respondeu. | | `004` | **Documento emitido.** Status terminal de sucesso. | Polling após autorização SEFAZ. | | `005` | **Consulta realizada.** Status para endpoints `GET` de listagem/busca. | Listagens, buscas por documento/nome/referência. | | `010` | **Documento cancelado.** Status terminal após cancelamento. | Polling após processamento de cancelamento. | | `050` | **Duplicidade de `numero-origem`.** A operação não criou nova nota; retorna o recibo da emissão original. | Emissão com `numero-origem` já usado. Veja [Idempotência](/api/v3/padroes/idempotencia/). | | `500` | **Recibo não existe.** O recibo informado não está na base. | `GET //` com recibo inválido. | | `900` | **Rejeição definitiva da SEFAZ** — corrija o dado apontado em `sefaz.mensagem` e emita nova nota. (O processamento aparece como `003`, não `900`. Salvaguarda: se vier `900` com `sefaz.codigo` `217`/`105`, é transitório — trate como pendente. Veja [SEFAZ lenta ou instável](/api/v3/conceitos/instabilidade-sefaz/).) | Polling após SEFAZ **recusar** a nota (CST inválido, NCM errado, etc.). | | `999` | **Erro interno.** Erro de validação do payload OU erro inesperado. Veja `data.erro`. | Imediatamente após qualquer requisição com erro. | ## Códigos por tipo de operação ### Emissão (POST) Imediatamente após enviar uma emissão (NFe, NFCe, NFSe): | Código | Cenário | |---|---| | `001` | OK, processando. Comece o polling. | | `050` | `numero-origem` duplicado; use o recibo retornado. | | `999` | Erro de validação (veja `data.erro`). | ### Consulta de recibo (GET) Durante o polling do recibo: | Código | Cenário | Ação | |---|---|---| | `001` | Acabou de ser registrada. | Aguarde, consulte novamente. | | `002` | Pendente. | Aguarde, consulte novamente. | | `003` | Emitindo. | Aguarde, consulte novamente. | | `004` | **Emitida** ✅ | Pegue `data.sefaz.url-danfe` e `data.sefaz.url-xml`. | | `010` | **Cancelada** | Não emita de novo. | | `500` | Recibo inválido. | Verifique se o recibo foi salvo corretamente. | | `900` | **Rejeitada SEFAZ (definitiva)** | Corrija o dado indicado em `data.sefaz.mensagem` e emita nova nota. (O processamento em andamento aparece como `003`, não `900`. Salvaguarda: se vier `900` com `data.sefaz.codigo` `217`/`105`, **aguarde e consulte de novo, não reemita** — veja [SEFAZ lenta ou instável](/api/v3/conceitos/instabilidade-sefaz/).) | | `999` | Erro interno. | Cite `meta.request_id` no suporte. | ### Listagem (GET) | Código | Cenário | |---|---| | `005` | OK. `data` traz a lista. | | `999` | Erro interno. | ### Cancelamento / Carta de correção (POST) Imediatamente após enviar: | Código | Cenário | |---|---| | `001` | OK, processando. Comece o polling em `GET //cancelamento/{recibo}` ou `GET //correcao/{recibo}`. | | `999` | Erro de validação. | ## Padrão para clientes ```typescript const TERMINAIS_SUCESSO = ['004', '010']; const TERMINAIS_FALHA = ['500', '999']; // "900" tratado à parte — veja abaixo const PENDENTES = ['001', '002', '003']; const SEFAZ_CODIGO_TRANSITORIO = ['217', '105']; // SEFAZ lenta: não é rejeição definitiva function classificar(json: { status: string; data?: { sefaz?: { codigo?: string } } }): 'sucesso' | 'falha' | 'pendente' { const { status, data } = json; if (status === '900') { const codigoSefaz = data?.sefaz?.codigo; // SEFAZ lenta/instável: ainda não é definitivo, continue consultando o mesmo recibo if (codigoSefaz && SEFAZ_CODIGO_TRANSITORIO.includes(codigoSefaz)) return 'pendente'; return 'falha'; // rejeição real — corrija o dado e emita nova nota } if (TERMINAIS_SUCESSO.includes(status)) return 'sucesso'; if (TERMINAIS_FALHA.includes(status)) return 'falha'; if (PENDENTES.includes(status)) return 'pendente'; if (status === '050') return 'sucesso'; // duplicidade é retorno controlado if (status === '005') return 'sucesso'; // consulta OK return 'falha'; } ``` --- # Tratamento de erros Fonte: https://faznota.com.br/api/v3/padroes/erros/ Como reconhecer, classificar e tratar erros retornados pela API do FazNota. A API do FazNota retorna erros em duas formas distintas: 1. **Erros HTTP** (`401`, `403`, `404`, `405`, `429`) — autenticação, autorização e rate limit. 2. **Erros de domínio** (HTTP `200` + `body.status: "999"`) — validação ou erros internos. ## Erros HTTP ### `401 Unauthorized` Token ausente, inválido ou expirado. ```json { "error": { "status": 401, "message": "Token não autorizado." } } ``` **Ação:** revise o header `Authorization`. Se o Token foi rotacionado, atualize. ### `403 Forbidden` Contrato inapto. ```json { "error": { "status": 403, "message": "Este contrato está inapto..." } } ``` **Ação:** entre em contato com o FazNota para regularizar o contrato. ### `404 Not Found` Recurso não existe (endpoint errado). ```json { "error": { "status": 404, "message": "Resource not found." } } ``` **Ação:** confira o path. Se for consulta por recibo, prefira o `body.status: "500"` (que vem com mais contexto). ### `405 Method Not Allowed` Método HTTP errado para o endpoint. ```json { "error": { "status": 405, "message": "The specified HTTP method is not allowed..." } } ``` **Ação:** confira se você está usando o verbo correto (GET, POST, PUT, DELETE). ### `429 Too Many Requests` Limite de consumo atingido. ```json { "error": { "status": 429, "message": "Você atingiu o limite de consumo de API..." } } ``` **Ação:** 1. Pare de enviar requisições imediatamente. 2. Leia o header `X-RateLimit-Reset` (Unix timestamp) para saber quando retomar. 3. Implemente backoff exponencial em retries. ## Erros de domínio (`status: 999`) Sempre HTTP `200`. O detalhe está em `data.erro`: ```json { "status": "999", "descricao": "Ocorreu um erro interno com a nota.", "data": { "erro": "Campo nfe@chave deve ter 44 caracteres." }, "meta": { "request_id": "req_01HXY8ZG3J4K5M6N7P8Q9R0S1T", "timestamp": "2026-05-13T14:32:01.234Z" } } ``` ### Mensagens de validação Padrão: `"Campo @ "`. Exemplos comuns: | Mensagem | Significado | |---|---| | `Campo nfe@chave não pode ser vazio.` | Falta o campo. | | `Campo nfe@chave deve ter 44 caracteres.` | Tamanho incorreto. | | `Campo nfe@serie inválido.` | Não é numérico ou tem >3 dígitos (limite 1-999). | | `Campo nfe@serie permite séries apenas entre 1 a 999.` | Valor fora do range. | | `Campo nfe@motivo deve ter entre 15 e 255 caracteres.` | Texto curto/longo demais. | | `Campo nfe@transporte@placa-veiculo inválido.` | Não bate com regex `AAA-9999` ou Mercosul. | | `Campo nfe@cliente documento não pertence a um cliente cadastrado.` | CPF/CNPJ não está cadastrado. | | `Campo nfe@itens não pode ser vazio.` | Array vazio. | ### Mensagens de erro interno Mensagens **não-controladas** (vindas de exceções inesperadas) são substituídas por uma frase genérica para não vazar detalhes da infraestrutura: ```json { "status": "999", "data": { "erro": "Erro interno ao processar a requisição. Tente novamente em instantes ou entre em contato com o suporte informando o request-id." }, "meta": { "request_id": "req_..." } } ``` ## Status terminais que não são "erro técnico" mas falha de negócio Estes códigos não são erros da API, mas indicam que a operação **não deu certo**. Trate-os no nível de negócio: | `status` | Significado | Tratamento | |---|---|---| | `050` | Duplicidade de `numero-origem` | Não é erro; é proteção. Use o recibo retornado. | | `500` | Recibo não existe | Verifique se você salvou o recibo correto. | | `900` | Rejeição SEFAZ | Veja `data.sefaz.codigo` e `data.sefaz.mensagem`. Corrija e emita nova nota. | ## Padrão de tratamento recomendado ```typescript class ApiError extends Error { constructor( public httpStatus: number, message: string, public payload: unknown, public requestId?: string ) { super(message); } } async function call(method, path, body, token) { const res = await fetch(`${BASE}${path}`, { method, headers: { Authorization: `Token ${token}`, 'Content-Type': 'application/json', }, body: body ? JSON.stringify(body) : undefined, }); const json = await res.json().catch(() => ({})); // Erro HTTP if (!res.ok) { const reqId = res.headers.get('x-request-id') ?? json?.meta?.request_id; throw new ApiError(res.status, json.error?.message ?? `HTTP ${res.status}`, json, reqId); } // Erro de domínio if (json.status === '999') { throw new ApiError(200, json.data?.erro ?? 'Erro interno', json, json.meta?.request_id); } return json; } ``` ## Quando acionar o suporte | Sintoma | Acionar suporte? | Como | |---|---|---| | Mensagem de validação clara | ❌ Corrija o payload. | — | | `status: "050"` | ❌ Use o recibo retornado. | — | | `status: "900"` com `sefaz.mensagem` claro | ❌ Corrija e emita de novo. | — | | `status: "999"` com mensagem **genérica** | ✅ Sim | Cite `meta.request_id`. | | HTTP `401` persistente após gerar Token novo | ✅ Sim | Cite `X-Request-Id`. | | Padrão repetitivo de `429` em volume normal | ✅ Sim | Cite janela e Token. | | Polling que nunca termina (`002` por > 5 min) | ✅ Sim | Cite recibo e `meta.request_id`. | --- # Idempotência (numero-origem) Fonte: https://faznota.com.br/api/v3/padroes/idempotencia/ Como o campo `numero-origem` previne emissões duplicadas mesmo em caso de retry ou falha de rede. A API do FazNota usa o campo `numero-origem` (também aceito como `numero_origem`, legado) para garantir **idempotência** nas emissões. Esse é um dos campos mais importantes da integração — e o mais ignorado por quem está começando. ## Por que importa Imagine este cenário: 1. Sua aplicação envia `POST /nfe/emissao` com um pedido. 2. A API do FazNota processa e emite a NFe com sucesso. 3. **Mas** a resposta se perde na rede (timeout no cliente, falha intermediária). 4. Sua aplicação interpreta como "falha" e **tenta de novo** com o mesmo pedido. Sem idempotência, você teria **duas NFes emitidas** para o mesmo pedido. Com idempotência, a segunda chamada retorna o **recibo original**, sem duplicar a emissão. ## Como funciona Em qualquer emissão (NFe, NFCe, NFSe), envie no body: ```json { "numero-origem": "PED-2026-00042", ... } ``` Se você já enviou uma emissão com esse mesmo `numero-origem` (no escopo da sua empresa), a API do FazNota retorna: ```json { "status": "050", "descricao": "Já existe uma nota com este número de origem em nosso sistema.", "data": { "recibo": "rec_da_emissao_original" }, "meta": { ... } } ``` Note que **HTTP 200** + `status: "050"`. Não é erro — é proteção. ## Como escolher um bom `numero-origem` | Estratégia | Exemplo | Comentário | |---|---|---| | ID do pedido no seu sistema | `"PED-2026-00042"` | Direto e legível. **Recomendado.** | | UUID v4 | `"550e8400-e29b-41d4-a716-446655440000"` | Garantido único, mas opaco. | | Composto | `"loja:42|pedido:00042|tentativa:1"` | OK, mas evite informações que possam mudar. | ## Padrão de retry seguro ```typescript async function emitirComRetry(payload, token, maxTentativas = 3) { for (let tentativa = 1; tentativa <= maxTentativas; tentativa++) { try { const res = await fetch(`${BASE}/nfe/emissao`, { method: 'POST', headers: { Authorization: `Token ${token}`, 'Content-Type': 'application/json', }, body: JSON.stringify(payload), }); const json = await res.json(); // Sucesso ou duplicidade (idempotência funcionando) if (json.status === '001' || json.status === '050') { return json.data.recibo; } // Erro de validação: não adianta retentar if (json.status === '999') { throw new Error(`Validação: ${json.data?.erro}`); } } catch (err) { if (tentativa === maxTentativas) throw err; // Backoff exponencial: 1s, 2s, 4s await new Promise((r) => setTimeout(r, 1000 * Math.pow(2, tentativa - 1))); } } } ``` O ponto crucial: o `payload` **deve ter o mesmo `numero-origem`** em todas as tentativas. Se mudar, a idempotência se perde. ## Escopo do `numero-origem` - **Por empresa.** Cada empresa (cada Token) tem seu próprio espaço de `numero-origem`. - **Por tipo de documento.** Vou poder usar `"PED-001"` simultaneamente em NFe e em NFCe — são tabelas diferentes. (Recomenda-se evitar para clareza.) - **Permanente.** Uma vez registrado, o `numero-origem` permanece reservado para aquela emissão. ## E se eu não enviar `numero-origem`? A emissão funciona normalmente, mas **sem proteção contra duplicidade**. Em caso de retry, você pode criar duas NFes para o mesmo pedido. ## Formatos aceitos A API aceita **ambos** os formatos do campo, por compatibilidade: ```json { "numero-origem": "PED-001" } // ✅ Preferido (consistente com outros campos) { "numero_origem": "PED-001" } // ✅ Aceito (legado) ``` Em novas integrações, prefira `numero-origem` (com hífen) — segue o padrão dos demais campos da API. --- # Paginação Fonte: https://faznota.com.br/api/v3/padroes/paginacao/ Como paginar respostas de listagem na API do FazNota (via query string ou via path). A API do FazNota oferece **duas formas** de paginar listagens. Ambas convivem; em novas integrações, prefira **query string**. ## Forma 1 — Query string (recomendada) Disponível em todos os endpoints de listagem: ```http GET /nfe?page=1&page_size=20 GET /nfce?page=2&page_size=50 GET /nfse?page=1&page_size=20&dataInicial=2026-05-01 GET /nffornecedor?page=1&page_size=20 GET /clientes?page=1&page_size=100 GET /produtos?page=1&page_size=100 ``` | Parâmetro | Tipo | Default | Limite | |---|---|---|---| | `page` | inteiro | `0` (sem paginação) | — | | `page_size` | inteiro | `0` (sem paginação) | 500 (1000 para clientes/produtos) | Quando ambos são omitidos ou zero, o endpoint retorna o **comportamento legado** (geralmente os 100 ou 50 registros mais recentes — varia por endpoint). ### Exemplo ```bash curl "https://api.mysebr.com.br/nfemyse-v3/rest/nfe?page=1&page_size=20" \ -H "Authorization: Token $TOKEN" ``` ## Forma 2 — Path (legado) Mantida por compatibilidade com integrações antigas: ```http GET /nfe/paginacao/{pagina}/{quantidade} GET /nfce/paginacao/{pagina}/{quantidade} GET /produtos/paginacao/{pagina}/{quantidade}/{busca} ``` `{pagina}` começa em **1**. `{quantidade}` é o tamanho da página. Em `/produtos/paginacao/.../{busca}`, use `_todos` no campo `busca` para não filtrar. ### Exemplo ```bash curl "https://api.mysebr.com.br/nfemyse-v3/rest/nfe/paginacao/1/20" \ -H "Authorization: Token $TOKEN" ``` ## Comparação | Aspecto | Query string | Path (legado) | |---|---|---| | Sintaxe | `?page=1&page_size=20` | `/paginacao/1/20` | | Disponível em | Todos os listings | NFe, NFCe, Produtos | | Filtros adicionais | Combina com outras query (ex.: `&dataInicial=...`) | Não | | Cache HTTP | Melhor (query é parte do cache key padrão) | Funciona | | Recomendação | ✅ Novas integrações | Integrações existentes | ## Limites e capping - O `page_size` **é capado** automaticamente em 500 (ou 1000 para clientes/produtos). - Solicitar `page_size=99999` resultará em **500/1000 registros**, não 99999. - Para downloads em massa, use paginação iterativa, não tente puxar tudo em uma chamada. ## Padrão de iteração recomendado ```javascript async function listarTudo(token) { const todos = []; let pagina = 1; const tamanho = 100; while (true) { const res = await fetch( `https://api.mysebr.com.br/nfemyse-v3/rest/clientes?page=${pagina}&page_size=${tamanho}`, { headers: { Authorization: `Token ${token}` } } ); const json = await res.json(); if (json.status !== '005' || !Array.isArray(json.data)) break; if (json.data.length === 0) break; todos.push(...json.data); // Se veio menos do que pedimos, é a última página if (json.data.length < tamanho) break; pagina++; // Pequena pausa para não estourar rate limit await new Promise((r) => setTimeout(r, 100)); } return todos; } ``` ## Total de páginas A API do FazNota **não retorna `total` ou `page_count`** atualmente. Pare de paginar quando receber **uma página com menos itens que o `page_size`** solicitado, ou um array vazio. ## Sincronização incremental Para sincronizar NFSes (e em breve outros recursos) sem percorrer todo o histórico, use o filtro `dataInicial` em conjunto com paginação: ```bash curl "https://api.mysebr.com.br/nfemyse-v3/rest/nfse?dataInicial=2026-05-01&page=1&page_size=50" \ -H "Authorization: Token $TOKEN" ``` Salve a data da última sincronização e use-a como `dataInicial` na próxima. Veja o recipe [Sincronizar clientes e produtos](/api/v3/recipes/sincronizar/). --- # Políticas operacionais Fonte: https://faznota.com.br/api/v3/politicas-operacionais/ Mecanismos de proteção, níveis progressivos de medida e comunicação prévia de mudanças. A plataforma FazNota aplica continuamente mecanismos de proteção operacional para preservar a estabilidade do serviço e proteger a infraestrutura compartilhada entre integradores. Esta página descreve essas políticas em termos práticos para integradores. ## Proteção operacional A plataforma realiza: - **Monitoramento contínuo** do consumo por credencial. - **Controle de throughput** automático para preservar a disponibilidade. - **Detecção de padrões anômalos** que possam indicar abuso ou erro de integração. - **Limitação de uso** quando aplicável. Esses mecanismos atuam **independentemente** da contratação comercial — protegem todos os integradores ao garantir capacidade compartilhada. ## Níveis progressivos de medida Comportamentos identificados como anormais podem resultar em medidas progressivas: ### Nível 1 — Restrição temporária de throughput Redução automática da taxa de processamento das requisições da credencial envolvida. Manifesta-se como: - Respostas `HTTP 429 Too Many Requests`. - Header `X-RateLimit-Remaining` próximo de zero. - Retomada automática após a janela informada em `X-RateLimit-Reset`. **Ação do integrador:** reduzir o ritmo de envio, aguardar o reset, revisar lógica que possa estar gerando consumo excessivo (loops, polling agressivo, retries sem backoff). ### Nível 2 — Suspensão temporária Suspensão da integração ou da credencial por um período determinado. Manifesta-se como: - Respostas `HTTP 401` ou `HTTP 403` em todas as requisições. - Comunicação do suporte indicando o período de suspensão. **Ação do integrador:** entrar em contato com o suporte. Não tentar reenviar requisições durante o período de suspensão — apenas prolonga o problema. ### Nível 3 — Bloqueio preventivo Revogação da credencial e bloqueio da integração até análise. Manifesta-se como: - Token revogado permanentemente. - Suporte acionará a empresa para entender o cenário. **Ação do integrador:** colaborar com a investigação do suporte. Após validação, um novo token pode ser emitido. ## Comunicação prévia Alterações relevantes que possam impactar integrações ativas serão comunicadas previamente, permitindo adaptação. Tipos de alteração que recebem aviso prévio: - Mudanças de limites operacionais com impacto perceptível. - Alterações estruturais em endpoints. - Depreciação de comportamentos legados. - Mudanças em formato de payload (com janela de retrocompatibilidade). A comunicação será feita via: - E-mail aos contatos cadastrados na empresa. - Avisos no Painel FazNota. - Atualização desta documentação com seção de [Changelog](/api/v3/changelog/). ## Boas práticas alinhadas com as políticas Para evitar qualquer restrição: | Prática | Por quê | |---|---| | Sempre enviar `numero-origem` | Protege contra duplicidade em retries; reduz consumo | | Polling com intervalo ≥ 2s e backoff | Evita pressionar a plataforma | | Cache local de listagens | Reduz consumo desnecessário | | Concorrência limitada por token | Distribui carga ao longo do tempo | | Retry só em erros transitórios | Não amplifica falhas | | Monitorar `X-RateLimit-Remaining` | Auto-regulação | | Sincronização incremental | Não percorre histórico completo a toda hora | ## O que não fazer | ❌ Anti-padrão | Por que é problemático | |---|---| | `setInterval` com poll de 1 segundo | Gera 60× mais requisições que o necessário | | Retries em loop sem backoff em erro 4xx | Amplifica falhas, ativa proteções | | Listagens sem paginação em volume grande | Consome janelas inteiras em poucas chamadas | | Reprocessamento agressivo de toda a base | Use sincronização incremental | | Múltiplas integrações concorrentes no mesmo token | Comparilhamento indevido; use tokens separados se possível | ## Em caso de suspeita de bloqueio 1. Verifique o status do health check: `GET /health` (não requer token). 2. Se a aplicação responde mas suas chamadas autenticadas falham com 401/403/429: - Confira `X-RateLimit-Remaining` na resposta (mesmo com 429 vem). - Verifique no Painel FazNota se o Token ainda está ativo. 3. Acione o suporte com: - Token (ou pelo menos os primeiros 6 caracteres + os últimos 6 para identificação). - Range horário do problema. - Pelo menos um `X-Request-Id` recente de uma requisição falha. ## Contato - 📧 [suporte@myse.com.br](mailto:suporte@myse.com.br) - 🌐 [myse.com.br](https://myse.com.br) --- # Postman / Insomnia Collection Fonte: https://faznota.com.br/api/v3/postman/ Importe a coleção oficial da API do FazNota no Postman ou Insomnia em 30 segundos. A coleção oficial cobre os endpoints principais da API do FazNota, com requests prontas e variáveis configuráveis (`base`, `token`, `recibo`, `chave_nfe`). ## Download - **Postman Collection v2.1:** [`myse-nfe.postman_collection.json`](/api/postman/myse-nfe.postman_collection.json) A mesma coleção funciona no **Insomnia** (importa o formato Postman v2.1 nativamente). ## Importar no Postman 1. Abra o Postman. 2. Clique em **File → Import** (ou Ctrl/Cmd + O). 3. Arraste o arquivo `myse-nfe.postman_collection.json` ou cole a URL acima. 4. Configure as variáveis da coleção (clique na coleção → aba **Variables**): | Variável | Valor sugerido | |---|---| | `base` | `https://api.mysebr.com.br/nfemyse-v3/rest` | | `token` | Seu Token (do Painel FazNota) | | `recibo` | (vazio — preencha após emitir) | | `chave_nfe` | (vazio — preencha quando tiver uma NFe) | 5. Clique em **Save**. ## Importar no Insomnia 1. Abra o Insomnia. 2. **Application → Preferences → Data → Import Data → From File**. 3. Selecione o `myse-nfe.postman_collection.json`. 4. Configure o **Environment** com as variáveis acima. ## Estrutura da coleção ``` API do FazNota — Nota Fiscal (v3) ├── Status │ └── GET /health ├── NFe │ ├── GET /nfe (listar com paginação) │ ├── GET /nfe/nota/:recibo │ ├── GET /nfe/nota/:recibo/completa │ ├── POST /nfe/emissao │ ├── PUT /nfe/:id (alterar) │ ├── POST /nfe/correcao │ └── POST /nfe/cancelamento ├── NFCe │ ├── GET /nfce (listar com paginação) │ ├── GET /nfce/cupom/:recibo │ ├── POST /nfce/emissao │ └── POST /nfce/cancelamento ├── NFSe │ ├── GET /nfse (com dataInicial) │ ├── POST /nfse/emissao │ └── POST /nfse/cancelamento ├── Fornecedor │ ├── GET /nffornecedor │ └── POST /nffornecedor/reconhecimento ├── Clientes │ ├── GET /clientes │ ├── GET /clientes/:doc │ ├── POST /clientes │ ├── PUT /clientes/:doc │ └── DELETE /clientes/:doc └── Produtos ├── GET /produtos └── POST /produtos ``` ## Fluxo recomendado para teste 1. **Verifique a saúde da API** Execute `Status → GET /health`. Deve retornar `200 OK` com `{"status":"ok",...}`. 2. **Cadastre um produto** (se não tiver) `Produtos → POST /produtos` — ajuste o body conforme necessário. 3. **Emita uma NFCe** (mais simples para teste) `NFCe → POST /nfce/emissao`. Copie o `data.recibo` da resposta para a variável `recibo`. 4. **Consulte o cupom** `NFCe → GET /nfce/cupom/:recibo`. Vai usar automaticamente a variável `recibo`. Aguarde alguns segundos entre tentativas até receber `status: "004"` (emitida). 5. **Pegue a chave** Da resposta do passo 4, copie `data.sefaz.chave` para a variável `chave_nfe`. 6. **Teste cancelamento** (se quiser) `NFCe → POST /nfce/cancelamento`. Body já está preparado com `{{chave_nfe}}`. ## Autenticação A coleção já vem com **API Key auth** configurada no nível da coleção, adicionando automaticamente o header: ```http Authorization: Token {{token}} ``` Em todos os requests, exceto `GET /health` (que sobrescreve para `noauth`). ## Geração automática A coleção é mantida sincronizada com o [OpenAPI](/api/v3/referencia/) oficial. Se você preferir gerar sua própria coleção a partir do OpenAPI: File → Import → URL: `https://faznota.com.br/api/openapi/nota-fiscal.yaml` Postman gera automaticamente a coleção a partir do OpenAPI. ```bash npm install -g openapi-to-postmanv2 openapi2postmanv2 -s nota-fiscal.yaml -o minha-colecao.json -p ``` Application → Import → From URL → `https://faznota.com.br/api/openapi/nota-fiscal.yaml` ## Próximos passos - [Quickstart](/api/v3/quickstart/) — siga o tutorial passo a passo emitindo sua primeira NFCe. - [Referência completa](/api/v3/referencia/) — explore todos os endpoints com "Try it" embutido. - [Código de Exemplo](/api/v3/exemplos/) — passe do Postman para integração de produção. --- # Quickstart — Sua primeira NFCe em 5 minutos Fonte: https://faznota.com.br/api/v3/quickstart/ Tutorial passo a passo para emitir uma Nota Fiscal de Consumidor Eletrônica (NFCe) usando a API do FazNota. Este guia mostra **de ponta a ponta** o fluxo de emissão de uma NFCe usando a API do FazNota. Você vai: 1. Obter o seu Token de integração 2. Cadastrar um produto e um cliente (opcional, se já existirem) 3. Emitir uma NFCe 4. Consultar o resultado pelo recibo ## Pré-requisitos - Acesso ao **Painel FazNota** com permissão de Integração - Empresa com **contrato ativo** e configurações fiscais já preenchidas - Um cliente HTTP (cURL, Postman, Insomnia ou linguagem de sua preferência) ## Passo a passo 1. **Obtenha seu Token de integração** No Painel FazNota, acesse **Meus Dados > aba Integração**. Copie o Token gerado. ```bash # Exemplo de Token (sempre use o seu, gerado no painel) export TOKEN="abc123def456ghi789jkl012mno345pq" ``` 2. **Teste a autenticação consultando a saúde da API** Antes de tudo, verifique se a API está respondendo: ```bash curl -i https://api.mysebr.com.br/nfemyse-v3/rest/health ``` Você deve ver `HTTP/1.1 200 OK` e o body: ```json { "status": "ok", "database": "ok", "timestamp": "2026-05-13T14:32:01Z" } ``` 3. **Cadastre um produto (opcional)** Pule este passo se já tiver produtos cadastrados. ```bash curl -X POST https://api.mysebr.com.br/nfemyse-v3/rest/produtos \ -H "Authorization: Token $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "referencia": "SKU-QUICKSTART-001", "nome": "Produto Teste Quickstart", "valor": "10.00", "medida": "UN", "ncm": "61051000", "origem": "0", "tipo-produto": "P" }' ``` Resposta esperada: ```json { "status": "001", "descricao": "Processamento do produto realizado com sucesso.", "data": { "mensagem": "Produto cadastrado com sucesso." }, "meta": { "request_id": "req_01HX...", "timestamp": "2026-05-13T14:33:00Z" } } ``` 4. **Cadastre um cliente (opcional)** Pule se já tiver clientes ou se a NFCe for sem identificação. ```bash curl -X POST https://api.mysebr.com.br/nfemyse-v3/rest/clientes \ -H "Authorization: Token $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "tipo": "F", "nome": "João da Silva", "cpf": "12345678901", "endereco": { "cep": "01310100", "rua": "Avenida Paulista", "numero": "1000", "bairro": "Bela Vista", "cidade": "São Paulo", "estado": "SP" } }' ``` 5. **Emita a NFCe** Note que a emissão é **assíncrona** — você recebe um `recibo` para consulta. ```bash curl -X POST https://api.mysebr.com.br/nfemyse-v3/rest/nfce/emissao \ -H "Authorization: Token $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "serie": "65", "cfop": "5102", "numero-origem": "QUICKSTART-001", "tp_pagamento": "dinheiro", "cliente": "12345678901", "itens": [ { "produto": "SKU-QUICKSTART-001", "quantidade": "1", "valor-unitario": "10.00" } ] }' ``` Resposta com o recibo: ```json { "status": "001", "descricao": "Cupom registrado com sucesso.", "data": { "recibo": "rec_abc123" }, "meta": { "request_id": "req_01HX...", "timestamp": "..." } } ``` Guarde o `recibo` para o próximo passo. 6. **Consulte o resultado pelo recibo** A NFCe leva alguns segundos para ser processada pela SEFAZ. Consulte periodicamente (intervalo mínimo recomendado: **2 segundos**) até receber um status terminal (`004` = emitida, `010` = cancelada, `900` = rejeitada). ```bash curl https://api.mysebr.com.br/nfemyse-v3/rest/nfce/cupom/rec_abc123 \ -H "Authorization: Token $TOKEN" ``` Resposta quando emitida: ```json { "status": "004", "descricao": "Cupom emitido com sucesso.", "data": { "id": "...", "numero": "1", "serie": "65", "sefaz": { "chave": "35260512345678000199650010000000011000000010", "url-danfe": "https://...", "url-xml": "https://..." } } } ``` 🎉 **Pronto!** Sua primeira NFCe foi emitida. ## Próximos passos - Aprofunde no [Fluxo assíncrono](/api/v3/conceitos/fluxo-assincrono/) para entender o polling com backoff. - Veja os [Códigos de status](/api/v3/padroes/codigos-status/) para tratar todas as respostas. - Conheça as [Boas práticas](/api/v3/boas-praticas/) para integração estável e segura. - Explore a [Referência da API](/api/v3/referencia/) interativa. ## Solução de problemas | Sintoma | Causa provável | O que fazer | |---|---|---| | HTTP 401 | Token ausente ou inválido | Verifique o header `Authorization: Token ` | | `status: "999"` no body | Erro de validação ou interno | Leia `data.erro` para mensagens controladas; cite `meta.request_id` no suporte para erros internos | | `status: "050"` | `numero-origem` duplicado | Use um valor único por emissão; isso protege contra duplicidade | | Resposta demora muito | Polling sem backoff | Use intervalo mínimo de 2 segundos entre consultas | --- # Baixar XMLs por período Fonte: https://faznota.com.br/api/v3/recipes/baixar-xml-periodo/ Solicite o ZIP com os XMLs de NFe ou NFCe de um período, consulte o protocolo e baixe o arquivo do link temporário. O download de XMLs em lote é **assíncrono**: você solicita, recebe um **protocolo**, consulta esse protocolo até o arquivo ficar pronto e então baixa o ZIP por um link temporário. O arquivo também é enviado por e-mail. Esse desenho existe porque montar o ZIP de um período grande leva minutos — uma requisição síncrona ficaria pendurada até estourar timeout. ## Fluxo 1. **Solicitar** ```bash curl -X POST $BASE/xml/solicitacao \ -H "Authorization: Token $TOKEN" -H "Content-Type: application/json" \ -d '{ "tipo": "NFe", "data-inicial": "2026-04-01", "data-final": "2026-04-30", "email": "fiscal@suaempresa.com.br" }' ``` Resposta: ```json { "status": "001", "descricao": "Solicitação de XML registrada com sucesso.", "data": { "protocolo": "665547" } } ``` 2. **Consultar o protocolo** Use intervalo mínimo de 2 segundos entre consultas. ```bash curl $BASE/xml/solicitacao/665547 -H "Authorization: Token $TOKEN" ``` Enquanto processa: ```json { "status": "002", "descricao": "A solicitação de XML está sendo processada.", "data": { "protocolo": "665547", "situacao": "processando", "data-solicitacao": "2026-08-06T11:35:46" } } ``` 3. **Baixar quando ficar pronto** ```json { "status": "005", "descricao": "Arquivo de XML disponível para download.", "data": { "protocolo": "665547", "situacao": "disponivel", "url-download": "https://mysetemp.s3-sa-east-1.amazonaws.com/xml/...zip?...", "descricao": "Arquivo de XMLs das notas solicitados do período 01/04/2026 a 30/04/2026.", "expira-em": "2026-08-13", "data-processamento": "2026-08-06T11:36:03" } } ``` Baixe direto da `url-download` — ela **não** exige o header de autenticação. ## Campos da solicitação | Campo | Obrigatório | Descrição | |---|---|---| | `tipo` | Sim | `"NFe"` ou `"NFCe"` | | `data-inicial` | Sim | `AAAA-MM-DD`. Não pode ser futura | | `data-final` | Sim | `AAAA-MM-DD`. Não pode ser anterior à inicial | | `email` | Não | Destino do aviso. Omitido, usa o e-mail cadastrado da empresa | ## Situações possíveis | `status` | `situacao` | Significado | |---|---|---| | `001` | — | Solicitação registrada. Guarde o `protocolo` | | `050` | — | Já existia uma solicitação **igual** em andamento. O `protocolo` devolvido é o dela — não é erro | | `002` | `processando` | Ainda na fila ou gerando. Consulte de novo | | `005` | `disponivel` | Pronto. Use `url-download` | | `006` | `sem-notas` | Terminou e **não havia nota** no período. Estado final — reconsultar não muda | | `007` | `falha` | A geração falhou. Estado final — faça uma **nova** solicitação | | `500` | — | Protocolo não existe (ou não pertence à sua empresa) | ## Detalhes que evitam dor de cabeça **O link expira em 7 dias.** O campo `expira-em` traz a data. Depois disso a URL passa a responder erro — baixe e guarde o arquivo. **A URL só aceita `GET`.** Uma requisição `HEAD` para "testar se o link está vivo" devolve **403**: a assinatura do link é calculada sobre o método HTTP. Para verificar disponibilidade, use `GET` com `Range: bytes=0-0`. **Solicitação repetida devolve o mesmo protocolo.** Se você repetir a mesma solicitação (mesmo tipo, mesmo período, mesmo e-mail) dentro de 10 minutos, a API devolve o protocolo da solicitação anterior com status `050`, em vez de gerar outro arquivo. **O e-mail é obrigatório na prática.** O arquivo é entregue por e-mail além do link. Se você omitir `email` e a sua empresa não tiver e-mail cadastrado válido, a solicitação é **recusada na hora** com mensagem explicando — melhor do que aceitar e nunca entregar. ## Erros comuns | Mensagem (`data.erro`) | Causa | |---|---| | `Campo xml@tipo inválido. Valores aceitos: "NFe" ou "NFCe".` | Tipo diferente desses dois (NFSe não é suportada neste endpoint) | | `Campo xml@data-inicial inválido. Use o formato AAAA-MM-DD.` | Formato fora do padrão | | `O período solicitado excede o limite de 92 dias.` | Divida em solicitações menores | | `Você já possui 3 solicitações de XML em processamento.` | Aguarde alguma concluir | | `Informe o campo email: o e-mail cadastrado da empresa está ausente ou inválido.` | Envie `email` no payload | ## Próximos passos - [Resumo diário de emissões](/api/v3/recipes/resumo-diario/) - [Consultar uma nota específica](/api/v3/recipes/emitir-nfe/) --- # Cancelar uma NFe Fonte: https://faznota.com.br/api/v3/recipes/cancelar-nfe/ Como cancelar uma NFe já autorizada pela SEFAZ. O cancelamento de NFe é regulamentado pela SEFAZ: precisa ser feito **dentro do prazo legal** (geralmente 24 horas após a autorização) e exige uma justificativa de 15 a 255 caracteres. ## Pré-requisitos - A NFe deve estar **autorizada** (`status: "004"`). - Você deve ter a **chave de acesso** (44 caracteres) da NFe. - O cancelamento deve estar dentro do prazo SEFAZ. ## Fluxo 1. **Enviar o cancelamento** ```bash curl -X POST $BASE/nfe/cancelamento \ -H "Authorization: Token $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "chave": "35260512345678000199550010000000011000000010", "motivo": "Cancelamento solicitado pelo cliente — pedido refeito." }' ``` Resposta: ```json { "status": "001", "descricao": "Evento registrado com sucesso.", "data": { "recibo": "rec_cancel_001" } } ``` 2. **Consultar o resultado** Polling com intervalo mínimo de 2 segundos: ```bash curl $BASE/nfe/cancelamento/rec_cancel_001 \ -H "Authorization: Token $TOKEN" ``` Quando aprovado pela SEFAZ: ```json { "status": "010", "descricao": "A nota está cancelada.", "data": { ... } } ``` 3. **(Opcional) Confirmar via consulta da nota original** ```bash curl $BASE/nfe/nota/{recibo_emissao_original}/completa \ -H "Authorization: Token $TOKEN" ``` A nota agora retorna `status: "010"`. ## Regras do `motivo` | Regra | Valor | |---|---| | Tamanho mínimo | 15 caracteres | | Tamanho máximo | 255 caracteres | | Quebras de linha | São substituídas por espaço | ### Exemplos de motivos válidos - ✅ `"Cancelamento solicitado pelo cliente — pedido 12345 refeito."` - ✅ `"Erro de valor no item 2; nova nota emitida com correção."` - ✅ `"Duplicidade detectada após retry; nota original mantida."` ### Motivos a evitar - ❌ `"Cancelar"` (curto demais — viola limite mínimo de 15 chars) - ❌ `"Teste"` (curto e suspeito) - ❌ `"Erro"` (sem contexto) ## Erros comuns | Mensagem | Causa | |---|---| | `Campo nfe@chave deve ter 44 caracteres.` | Chave incorreta ou incompleta | | `Campo nfe@motivo deve ter entre 15 e 255 caracteres.` | Texto fora do tamanho permitido | | `status: "900"` (rejeição SEFAZ) | Prazo de cancelamento expirado; ou nota não pertence à sua empresa | ## Fora do prazo SEFAZ? Se o prazo expirou (varia por estado, geralmente 24h): - A SEFAZ rejeitará o cancelamento (`status: "900"`). - Você precisará fazer uma **nota de devolução** (NFe `finalidade: "D"` referenciando a original). - Consulte seu contador para o procedimento correto na sua UF. ## E para NFCe e NFSe? | Documento | Endpoint | Notas | |---|---|---| | NFe | `POST /nfe/cancelamento` | Prazo SEFAZ ~24h | | NFCe | `POST /nfce/cancelamento` | Prazo geralmente menor (alguns minutos) | | NFSe | `POST /nfse/cancelamento` | Regras municipais variam | O formato do body é o mesmo (`chave` + `motivo`) para os três. --- # Lançar carta de correção (CC-e) Fonte: https://faznota.com.br/api/v3/recipes/carta-correcao/ Como corrigir erros não-críticos em uma NFe já autorizada usando CC-e. A Carta de Correção Eletrônica (CC-e) permite corrigir **erros não-críticos** em uma NFe já autorizada, sem cancelar e re-emitir. Use para erros de descrição, observação, endereço, etc. ## Pré-requisitos - NFe autorizada (`status: "004"`). - Chave de acesso da NFe (44 caracteres). - Texto da correção entre 15 e 255 caracteres. ## Fluxo 1. **Enviar a CC-e** ```bash curl -X POST $BASE/nfe/correcao \ -H "Authorization: Token $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "chave": "35260512345678000199550010000000011000000010", "motivo": "Correção da descrição do item 3 — modelo correto: XYZ-2." }' ``` Resposta: ```json { "status": "001", "descricao": "Evento registrado com sucesso.", "data": { "recibo": "rec_correcao_001" } } ``` 2. **Consultar o resultado** Polling com intervalo mínimo de 2 segundos: ```bash curl $BASE/nfe/correcao/rec_correcao_001 \ -H "Authorization: Token $TOKEN" ``` Quando aceita pela SEFAZ: ```json { "status": "004", "descricao": "Evento emitido com sucesso.", "data": { ... } } ``` ## Limites do `motivo` (texto da correção) | Regra | Valor | |---|---| | Tamanho mínimo | 15 caracteres | | Tamanho máximo | 255 caracteres | | Quebras de linha | Substituídas por espaço | ## Exemplos de motivos válidos - ✅ `"Correção da descrição do item 3 — modelo correto: XYZ-2."` - ✅ `"Correção do endereço de entrega: rua correta é Av. das Flores, 1000."` - ✅ `"Acréscimo de informação complementar: pedido referente à OC 12345."` - ✅ `"Correção do nome fantasia do destinatário."` ## Múltiplas CC-e A SEFAZ permite **até 20 CC-e** por NFe. Cada nova correção **substitui** a anterior (é cumulativa) — o texto deve sempre representar o estado final correto, não apenas o que mudou desde a última. ## Diferença entre cancelamento e CC-e | Cenário | Usar | |---|---| | Valor errado, item errado, destinatário errado | Cancelamento + nova nota | | Descrição imprecisa, observação esquecida, endereço incorreto | CC-e | | Erro de digitação na razão social | CC-e | | Mudança de CFOP / CST | Cancelamento + nova nota | ## Erros comuns | Mensagem | Causa | |---|---| | `Campo nfe@chave deve ter 44 caracteres.` | Chave incorreta | | `Campo nfe@motivo deve ter entre 15 e 255 caracteres.` | Texto curto ou longo demais | | `status: "900"` (rejeição SEFAZ) | Tentou corrigir campo vedado, NFe não pertence à empresa, ou prazo excedido | ## Próximos passos - [Cancelar uma NFe](/api/v3/recipes/cancelar-nfe/) — quando CC-e não resolve. - [Códigos de status](/api/v3/padroes/codigos-status/) — para tratar todas as respostas. --- # Emitir uma NFCe Fonte: https://faznota.com.br/api/v3/recipes/emitir-nfce/ Como emitir uma Nota Fiscal de Consumidor Eletrônica (modelo 65) para varejo presencial. A NFCe é o documento fiscal usado em operações de varejo presencial (substitui o cupom fiscal). Não exige transporte, é mais simples que a NFe, e geralmente é autorizada em segundos. ## Fluxo 1. **Emitir a NFCe** ```bash curl -X POST $BASE/nfce/emissao \ -H "Authorization: Token $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "serie": "65", "cfop": "5102", "numero-origem": "VENDA-2026-00100", "tp_pagamento": "credito", "porcentagem-desconto": "5", "cliente": "12345678901", "itens": [ { "produto": "SKU-001", "quantidade": "1", "valor-unitario": "59.90" } ] }' ``` 2. **Consultar pelo recibo** ```bash curl $BASE/nfce/cupom/{recibo} \ -H "Authorization: Token $TOKEN" ``` 3. **Pegar DANFE NFCe e XML** Quando `status: "004"`, `data.sefaz` traz `url-danfe` e `url-xml`. ## Tipos de pagamento | Valor `tp_pagamento` | Significa | |---|---| | `dinheiro` | Pagamento em espécie | | `credito` (ou `crédito`) | Cartão de crédito | | `debito` (ou `débito`) | Cartão de débito | | `outros` | PIX, vale-refeição, transferência, etc. | A API também aceita o **código numérico** SEFAZ diretamente (mais avançado). Para casos comuns, use as strings acima. ## Variações comuns ### Sem identificar o cliente (consumidor final) ```json { "serie": "65", "cfop": "5102", "tp_pagamento": "dinheiro", "itens": [ ... ] } ``` Pode omitir o campo `cliente` para emissões de balcão sem identificação. ### Com taxa de serviço (restaurantes) ```json { "serie": "65", "cfop": "5102", "tp_pagamento": "credito", "porcentagem-taxa-servico": "10", "itens": [ ... ] } ``` ### Com taxa de entrega (delivery) ```json { "serie": "65", "cfop": "5102", "tp_pagamento": "credito", "valor-taxa-entrega": "8.50", "itens": [ ... ] } ``` ## Alterar antes de emitir Você pode editar uma NFCe que ainda **não foi submetida** para emissão: ```bash curl -X PUT $BASE/nfce/{id} \ -H "Authorization: Token $TOKEN" \ -H "Content-Type: application/json" \ -d '{ ... mesmo formato do emissao ... }' ``` NFCes já em emissão ou com timer ativo (emissão iminente) **não podem** ser editadas. ## Cancelamento NFCe tem prazo de cancelamento **muito menor** que NFe (geralmente alguns minutos após autorização — varia por estado). Veja [Cancelar uma NFe](/api/v3/recipes/cancelar-nfe/) para a sintaxe (mesma estrutura, com endpoint `POST /nfce/cancelamento`). ## Erros comuns | Mensagem | Causa | |---|---| | `Campo nfce@serie inválido.` | Série não numérica ou > 999 | | `tp_pagamento` ignorado | Use valor exato em minúsculas: `dinheiro`, `credito`, `debito`, `outros` | | `status: "900"` com `cst` | CST do produto incompatível com NFCe — revise configurações fiscais | | `status: "900"` com `sefaz.codigo` `217`/`105` | **Não é erro** — SEFAZ lenta/instável, ainda processando. Continue consultando o mesmo recibo, não reemita. Veja [SEFAZ lenta ou instável](/api/v3/conceitos/instabilidade-sefaz/) | ## Diferenças NFCe vs NFe | Aspecto | NFe (55) | NFCe (65) | |---|---|---| | Uso | B2B e B2C com entrega | Varejo presencial | | Transporte | Detalhado | Não exige | | Faturas | Suporta | Não suporta | | Notas referenciadas | Sim | Não | | DANFE | A4 completo | DANFE NFCe (cupom estilo PDV) | | Prazo de cancelamento | ~24h | ~minutos | | Identificação do cliente | Sempre obrigatória | Opcional (consumidor final) | --- # Emitir uma NFe (Nota Fiscal Eletrônica) Fonte: https://faznota.com.br/api/v3/recipes/emitir-nfe/ Guia completo para emitir uma NFe modelo 55 com transporte, itens, faturas e notas referenciadas. A NFe é o documento fiscal padrão para operações B2B e B2C com entrega. Este recipe cobre o fluxo completo desde o cadastro até o XML autorizado. ## Pré-requisitos - Token de integração obtido ([ver Autenticação](/api/v3/autenticacao/)) - Configurações fiscais cadastradas na empresa (CFOP, CST, alíquotas) - Cliente (CPF/CNPJ) e produtos cadastrados ou enviados inline ## Fluxo 1. **(Opcional) Cadastrar produtos e cliente** Se ainda não estão na base: ```bash # Produto curl -X POST $BASE/produtos \ -H "Authorization: Token $TOKEN" -H "Content-Type: application/json" \ -d '{ "referencia": "SKU-001", "nome": "Camiseta Polo", "valor": "89.90", "medida": "UN", "ncm": "61051000", "origem": "0", "cfop-preferencial": "5102" }' # Cliente PJ curl -X POST $BASE/clientes \ -H "Authorization: Token $TOKEN" -H "Content-Type: application/json" \ -d '{ "tipo": "J", "razao-social": "Cliente Exemplo Ltda", "cnpj": "12345678000199", "tipo-inscricao-estadual": "1", "inscricao-estadual": "123456789", "endereco": { "cep": "01310100", "rua": "Av. Paulista", "numero": "1000", "bairro": "Bela Vista", "cidade": "São Paulo", "estado": "SP" } }' ``` 2. **Emitir a NFe** ```bash curl -X POST $BASE/nfe/emissao \ -H "Authorization: Token $TOKEN" -H "Content-Type: application/json" \ -d '{ "serie": "1", "cfop": "5102", "numero-origem": "PED-2026-00042", "tipo": "S", "finalidade": "N", "valor-despesas": "0.00", "cliente": "12345678000199", "transporte": { "modalidade": "9" }, "itens": [ { "produto": "SKU-001", "quantidade": "2", "valor-unitario": "89.90" } ] }' ``` Resposta: ```json { "status": "001", "descricao": "Nota registrada com sucesso.", "data": { "recibo": "rec_abc123" }, "meta": { "request_id": "req_...", "timestamp": "..." } } ``` 3. **Aguardar processamento (polling)** Use intervalo mínimo de 2 segundos. Veja [Fluxo assíncrono](/api/v3/conceitos/fluxo-assincrono/). ```bash curl $BASE/nfe/nota/rec_abc123 \ -H "Authorization: Token $TOKEN" ``` Status terminal: - `004` — NFe emitida ✅ - `010` — Cancelada - `900` — Rejeitada SEFAZ (veja `data.sefaz.mensagem`) — **exceto se `data.sefaz.codigo` for `217`/`105`: aí é SEFAZ lenta/instável, não é definitivo. Continue consultando, não reemita. Veja [SEFAZ lenta ou instável](/api/v3/conceitos/instabilidade-sefaz/) 4. **Pegar o DANFE e XML** Quando `status: "004"`: ```json { "status": "004", "data": { "sefaz": { "chave": "3526...", "protocolo": "...", "url-danfe": "https://...", "url-xml": "https://..." } } } ``` Baixe e armazene esses arquivos (XML é o documento fiscal, DANFE é a representação visual). ## Variações comuns ### Com transporte (modalidade ≠ 9) ```json { "serie": "1", "cfop": "5102", "cliente": "12345678000199", "transporte": { "modalidade": "0", "transportadora": "98765432000111", "placa-veiculo": "ABC-1D23", "valor-frete": "150.00", "valor-seguro": "0.00", "peso-bruto": "5.5", "peso-liquido": "5.0", "quantidade-volume": "1", "especie-volume": "Caixa", "marca-volume": "Embalagem padrão" }, "itens": [ ... ] } ``` ### Com faturas (vendas a prazo) ```json { ..., "faturas": [ { "valor": "299.50", "data": "2026-06-15" }, { "valor": "299.50", "data": "2026-07-15" } ] } ``` ### Com notas referenciadas (devolução / complementar) ```json { ..., "finalidade": "D", "notas-referenciadas": [ "35260512345678000199550010000000011000000010" ] } ``` ### Com guia de trânsito agropecuário (rejeições 311 / 836) Exclusivo de **NFe**. Use quando a operação exige **Guia de Trânsito** (produtos agropecuários animais, vegetais ou florestais) — o que evita as rejeições SEFAZ **311** (Guia de Trânsito Vegetal) e **836** (Guia de Trânsito Animal). O preenchimento é **manual**: você informa a guia quando sabe que aquela operação (pela combinação de NCM, CFOP e UF) a exige — a API **não** infere isso sozinha. O objeto `guia` é **opcional**; omita-o quando não houver guia. ```json { ..., "guia": { "tipo": "6", "numero": "123456789", "uf": "SP" } } ``` | Campo | Obrigatório | Descrição | |---|---|---| | `tipo` | Não (opcional) | Tipo da guia (enum): `1`-GTA, `2`-TTA, `3`-DTA, `4`-ATV, `5`-PTV, `6`-GTV, `7`-Guia Florestal | | `numero` | Se `tipo` informado | Número da guia — **apenas dígitos** | | `uf` | Se `tipo` informado | Sigla do estado da guia (ex.: `SP`, `RS`, `MG`) | ### Com IBS / CBS (reforma tributária) A v3 já contempla os tributos **IBS** e **CBS**, dentro da configuração fiscal do produto (bloco `cbs-ibs`, ao lado de `ipi`/`pis-cofins`/`icms`). O bloco é **opcional** — se omitido, a API preenche valores padrão. Veja a página dedicada [IBS / CBS (Reforma Tributária)](/api/v3/conceitos/ibs-cbs/) para a estrutura completa, os campos e os defaults. ## Limites de validação importantes | Campo | Regra | |---|---| | `serie` | Numérico entre 1 e 999 (no máx. 3 dígitos) | | `chave` (em referenciadas) | Exatamente 44 caracteres | | `observacao` | Até 2.000 caracteres | | `marca-volume`, `especie-volume` | Até 60 caracteres | | `placa-veiculo` | Regex `AAA-9999` ou Mercosul `AAA-9A99` | | `itens` | Pelo menos 1 item | | `guia.tipo` | Se informado: enum `1`–`7`; torna `guia.numero` (dígitos) e `guia.uf` (sigla) obrigatórios | ## Erros comuns | Mensagem (`data.erro`) | Causa | Correção | |---|---|---| | `Campo nfe@cliente documento não pertence a um cliente cadastrado.` | CPF/CNPJ desconhecido | Cadastre o cliente antes ou envie objeto inline | | `Campo nfe@cfop inválido.` | CFOP não numérico | Use 4 dígitos numéricos (ex.: `5102`) | | `Campo nfe@transporte@placa-veiculo inválido.` | Placa não bate regex | Use formato `AAA-9999` ou Mercosul `AAA-9A99` | | `Campo nfe@itens não pode ser vazio.` | Array vazio | Envie pelo menos um item | ## Próximos passos - [Cancelar uma NFe](/api/v3/recipes/cancelar-nfe/) - [Lançar carta de correção](/api/v3/recipes/carta-correcao/) - [Exemplos completos em Python/PHP/Java](/api/v3/exemplos/) --- # Emitir uma NFSe (Nota Fiscal de Serviço) Fonte: https://faznota.com.br/api/v3/recipes/emitir-nfse/ Como emitir uma Nota Fiscal de Serviço Eletrônica municipal. A NFSe é emitida pelo **município** (não pela SEFAZ estadual) e cada cidade tem regras um pouco diferentes. A API do FazNota abstrai essas diferenças. ## Pré-requisitos - Empresa com **inscrição municipal** ativa. - Município com integração suportada pela FazNota (verificar com o suporte). - Cliente e serviços (produtos `tipo: "S"`) cadastrados. ## Fluxo 1. **Emitir a NFSe** ```bash curl -X POST $BASE/nfse/emissao \ -H "Authorization: Token $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "numero-origem": "OS-2026-00088", "observacao": "Serviço de consultoria realizado em maio/2026.", "informacoes-complementares": "ISS retido pelo tomador conforme art. 6º da LC 116/2003. Contrato 4471/2026.", "iss-retido": "1", "nome-contato": "João Silva", "telefone-contato": "11987654321", "data-fim": "2026-05-31", "cliente": "12345678000199", "itens": [ { "produto": "SRV-CONSULT", "quantidade": "10", "valor-unitario": "250.00" } ] }' ``` 2. **Consultar pelo recibo** ```bash curl $BASE/nfse/nota/{recibo} \ -H "Authorization: Token $TOKEN" ``` ## Discriminação, informações complementares e ISS retido Três campos opcionais do corpo da emissão que costumam ser confundidos: | Campo | Onde aparece | Regras | |---|---|---| | `observacao` | **Discriminação do serviço** no DANFSe/XML | Texto livre, até 2.000 caracteres | | `informacoes-complementares` | Seção **"Informações Complementares"** do DANFSe/XML | Texto livre, até 2.000 caracteres. Seção **diferente** da discriminação — os dois podem ter conteúdos distintos na mesma nota. Alguns municípios truncam antes (ex.: 255 ou 1.000 caracteres), o que é tratado automaticamente | | `iss-retido` | Indicador de retenção da nota | `"1"` = ISS retido pelo tomador (substituição tributária), `"2"` = não retido. Padrão `"2"` quando omitido | ## Cadastro de produto de serviço ```bash curl -X POST $BASE/produtos \ -H "Authorization: Token $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "referencia": "SRV-CONSULT", "nome": "Consultoria técnica (hora)", "valor": "250.00", "medida": "H", "tipo-produto": "S", "codigo-servico": "1.04" }' ``` `codigo-servico` segue a lista municipal (varia por cidade — geralmente a lista da Lei Complementar 116/2003 com adaptações locais). ## Filtrar listagem por data ```bash curl "$BASE/nfse?dataInicial=2026-05-01&page=1&page_size=20" \ -H "Authorization: Token $TOKEN" ``` ## Cancelamento ```bash curl -X POST $BASE/nfse/cancelamento \ -H "Authorization: Token $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "chave": "chave_nfse_aqui", "motivo": "Cancelamento solicitado pelo tomador — serviço não realizado." }' ``` ## Particularidades por município Cada município tem variações. Os pontos mais comuns: | Aspecto | Comportamento | |---|---| | Numeração | Alguns municípios numeram, outros usam a chave municipal | | Cancelamento | Prazo varia (geralmente até virada do mês) | | Item de serviço | Lista padrão LC 116/2003 + adaptações locais | | Retenções (INSS, ISS) | Calculadas conforme cadastro fiscal do prestador | Verifique com o suporte FazNota se o seu município está homologado e quais campos adicionais são necessários. ## Erros comuns | Mensagem | Causa | |---|---| | `Campo nfse@cliente documento não pertence a um cliente cadastrado.` | Cliente desconhecido | | `Campo nfse@itens não pode ser vazio.` | Array de itens vazio | | `status: "900"` (rejeição municipal) | Inscrição municipal incorreta, código de serviço inválido para o município, ou retenção mal calculada | --- # Reconhecer nota de fornecedor (MD-e) Fonte: https://faznota.com.br/api/v3/recipes/reconhecer-fornecedor/ Como manifestar a destinação de uma NFe recebida (ciência, confirmação, desconhecimento ou operação não realizada). A **Manifestação do Destinatário (MD-e)** é o evento em que uma empresa declara formalmente o que aconteceu com uma NFe emitida contra ela. É exigido pela SEFAZ em diversas situações (operações com produtos perigosos, exportação, varejo acima de determinado valor, etc.). ## Códigos disponíveis | Código | Significado | Quando usar | |---|---|---| | `210200` | **Confirmação da operação** | A operação foi recebida e os produtos estão na minha empresa. **Estado terminal**, irreversível. | | `210210` | **Ciência da operação** | Tomei conhecimento da NFe, mas ainda vou verificar. Estado provisório (15 dias). | | `210220` | **Desconhecimento da operação** | Não conheço esta operação (NFe foi emitida contra mim por engano ou fraude). | | `210240` | **Operação não realizada** | A operação estava marcada mas não aconteceu (mercadoria não chegou, foi recusada). | ## Fluxo 1. **Listar notas de fornecedor recebidas** ```bash curl $BASE/nffornecedor \ -H "Authorization: Token $TOKEN" ``` Identifique a NFe que deseja manifestar e pegue a `chave`. 2. **Enviar o reconhecimento** ```bash curl -X POST $BASE/nffornecedor/reconhecimento \ -H "Authorization: Token $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "chave": "35260512345678000199550010000000011000000010", "codigo-reconhecimento": "210200" }' ``` Resposta: ```json { "status": "001", "descricao": "Reconhecimento de nota do fornecedor registrado com sucesso.", "data": { "recibo": "rec_abc123" } } ``` 3. **Consultar o resultado** ```bash curl $BASE/nffornecedor/{recibo} \ -H "Authorization: Token $TOKEN" ``` ## Fluxo recomendado ```mermaid flowchart LR A[NFe recebida do fornecedor] --> B{Verificação} B -->|Mercadoria conferida e recebida| C[210200 Confirmação] B -->|Ainda vou verificar| D[210210 Ciência] D --> E{Confirma?} E -->|Sim| C E -->|Não recebeu| F[210240 Operação não realizada] B -->|Não reconheço| G[210220 Desconhecimento] ``` ## Prazos A SEFAZ define prazos máximos para manifestação. Não cumprir pode gerar: - Bloqueio do recebimento de outras NFes. - Restrições fiscais à empresa. Verifique com seu contador os prazos aplicáveis ao seu segmento e UF. ## Automatização Para empresas com alto volume, considere: 1. **Polling diário** de `GET /nffornecedor?page=1&page_size=100` para detectar NFes novas. 2. **Conferência automática** contra pedidos de compra/notas internas. 3. **210200** automático quando bate; **210210** quando precisa de revisão humana. ## Erros comuns | Mensagem | Causa | |---|---| | `Campo nffornecedor@chave deve ter 44 caracteres.` | Chave incorreta | | `Campo codigo-reconhecimento inválido.` | Valor fora dos 4 permitidos | | `status: "900"` (rejeição SEFAZ) | NFe não está no portal MD-e (talvez já manifestada, ou ainda não enviada pelo emitente) | --- # Resumo diário de emissões Fonte: https://faznota.com.br/api/v3/recipes/resumo-diario/ Consulte totais por dia de NFe e NFCe emitidas, canceladas, denegadas e de falhas de emissão, para conciliação. Devolve, dia a dia, quantos documentos a empresa emitiu, cancelou, teve denegado e quantas emissões falharam. Serve para fechar a conta do período: *"mandei X, saíram Y, Z falharam"*. ## Requisição ```bash curl "$BASE/resumo?data-inicial=2026-08-01&data-final=2026-08-06" \ -H "Authorization: Token $TOKEN" ``` | Parâmetro | Obrigatório | Descrição | |---|---|---| | `data-inicial` | Sim | `AAAA-MM-DD` | | `data-final` | Sim | `AAAA-MM-DD`. Período máximo de **92 dias** | ## Resposta ```json { "status": "005", "descricao": "Resumo gerado com sucesso.", "data": { "periodo": { "data-inicial": "2026-08-01", "data-final": "2026-08-06" }, "dias": [ { "data": "2026-08-05", "nfe": { "emitidas": 24, "canceladas": 5, "denegadas": 0 }, "nfce": { "emitidas": 65, "canceladas": 2 }, "falhas": { "nfe": 1, "nfce": 78, "nfse": 0 } } ], "totais": { "nfe": { "emitidas": 24, "canceladas": 5, "denegadas": 0 }, "nfce": { "emitidas": 65, "canceladas": 2 }, "falhas": 79 }, "observacao": "As notas canceladas também estão contadas em emitidas — não some os dois campos." } } ``` ## Como ler os números | Campo | O que é | |---|---| | `nfe.emitidas` / `nfce.emitidas` | Autorizadas pela SEFAZ na data | | `nfe.canceladas` / `nfce.canceladas` | Canceladas — **subconjunto** das emitidas | | `nfe.denegadas` | Denegadas pela SEFAZ (exclusivo de NFe) | | `falhas.*` | Emissões que **não viraram nota**: rejeitadas ou com erro de processamento | As falhas são contadas pela **data da solicitação**, não pela data de emissão — uma emissão que falhou nunca chega a ter data de emissão. Dias sem nenhum movimento não aparecem na lista. ## Erros | Mensagem | Causa | |---|---| | `Os parâmetros data-inicial e data-final são obrigatórios no formato AAAA-MM-DD.` | Faltou parâmetro | | `Datas inválidas. Use o formato AAAA-MM-DD.` | Formato fora do padrão | | `O período solicitado excede o limite de 92 dias.` | Divida a consulta | ## Próximos passos - [Baixar XMLs por período](/api/v3/recipes/baixar-xml-periodo/) --- # Sincronizar clientes e produtos Fonte: https://faznota.com.br/api/v3/recipes/sincronizar/ Como manter clientes, produtos e notas sincronizados entre seu sistema e o FazNota usando paginação eficiente. Quando você tem um sistema próprio (ERP, e-commerce) integrado ao FazNota, é comum precisar manter os cadastros sincronizados. Esta página mostra padrões eficientes para isso. ## Quando sincronizar | Cenário | Recomendação | |---|---| | Cadastro de cliente novo no seu sistema | Push imediato para o FazNota (POST) | | Alteração de cliente | Push (PUT) sob demanda | | Pull periódico (espelhar FazNota → seu sistema) | A cada 1h ou diário, paginado | | Inicial (primeira sincronização) | Paginação completa, off-peak | ## Push sob demanda (sistema → FazNota) A forma mais simples: cada operação no seu sistema dispara uma chamada ao FazNota. ```typescript // Pseudo-código async function onClienteSalvo(cliente) { if (cliente.isNew) { await mApi('POST', '/clientes', toMyseFormat(cliente)); } else { await mApi('PUT', `/clientes/${cliente.cpfCnpj}`, toMyseFormat(cliente)); } } ``` Vantagens: simples, baixa latência. Desvantagem: se a chamada falhar, fica fora de sincronia até retry. ## Pull com paginação completa (FazNota → sistema) Para espelhar todos os clientes/produtos do FazNota no seu sistema: ```javascript async function sincronizarTodosClientes(token) { const todos = []; let pagina = 1; const tamanho = 100; while (true) { const url = `${BASE}/clientes?page=${pagina}&page_size=${tamanho}`; const res = await fetch(url, { headers: { Authorization: `Token ${token}` }, }); const json = await res.json(); if (json.status !== '005' || !json.data) break; if (json.data.length === 0) break; todos.push(...json.data); if (json.data.length < tamanho) break; pagina++; // Respeitando rate limit await new Promise((r) => setTimeout(r, 200)); } return todos; } ``` ## Pull incremental (apenas o que mudou) Hoje o FazNota oferece o filtro `dataInicial` em **NFSe**. Para outros recursos (clientes, produtos, NFe, NFCe), use o `id` como marcador: ```javascript async function sincronizarNovosClientes(token, ultimoIdSincronizado) { // Pega a primeira página (mais recentes vêm primeiro) const res = await fetch(`${BASE}/clientes?page=1&page_size=100`, { headers: { Authorization: `Token ${token}` }, }); const json = await res.json(); if (json.status !== '005') return []; // Para nos clientes que já vimos const novos = []; for (const c of json.data) { if (c.id <= ultimoIdSincronizado) break; novos.push(c); } return novos; } ``` ## Sincronização de NFSes (com `dataInicial`) ```javascript async function sincronizarNFSes(token, dataInicial) { const todos = []; let pagina = 1; const tamanho = 50; while (true) { const url = `${BASE}/nfse?dataInicial=${dataInicial}&page=${pagina}&page_size=${tamanho}`; const res = await fetch(url, { headers: { Authorization: `Token ${token}` }, }); const json = await res.json(); if (!json.data || json.data.length === 0) break; todos.push(...json.data); if (json.data.length < tamanho) break; pagina++; await new Promise((r) => setTimeout(r, 200)); } return todos; } // Uso: const ontem = '2026-05-12'; const nfses = await sincronizarNFSes(token, ontem); ``` ## Boas práticas 1. **Use cache local.** Não consulte o FazNota a cada operação interna se você já tem os dados em cache. Use TTL razoável (1h para clientes, 15 min para notas em emissão). 2. **Backoff exponencial em retries.** Se uma sincronização falhar, espere 1s, 2s, 4s antes de tentar novamente. 3. **Limite a concorrência.** Não faça 100 chamadas simultâneas — use no máximo 5–10 em paralelo. 4. **Monitore rate limit.** Olhe `X-RateLimit-Remaining` em cada resposta. Se ficar baixo, reduza o ritmo. 5. **Idempotência em escrita.** Use `numero-origem` único em emissões, e em POST de clientes/produtos use upsert (POST seguido de PUT se falhar por duplicidade). ## Anti-padrões | ❌ Evite | ✅ Prefira | |---|---| | `setInterval` consultando a cada 1 segundo | Polling com intervalo de 30s+ ou eventos manuais | | `for (i=1; i<99999; i++) fetch(...)` em paralelo | Loop sequencial com `await` e pausa entre páginas | | Consultar lista completa toda vez | Cache local + sincronização incremental | | Esquecer rate limit headers | Monitorar `X-RateLimit-Remaining` |