Estrutura padrão de resposta
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
{ "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. |
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?
- 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 — semtry/catchobrigatório para erros de negócio. - 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.
- Idempotência clara.
status: "050"(duplicidade) é uma resposta válida — não é erro; retornar o recibo original é o comportamento correto. - 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
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.statusconst 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:
{ "status": "001", "descricao": "Nota registrada com sucesso.", "data": { "recibo": "rec_abc123def456" }, "meta": { ... }}Use o recibo em GET /<recurso>/<recibo> para consultar o resultado.
Padrão para consultas em lista
{ "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)
{ "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 <campo> <regra>". - 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_idao 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:
{ "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 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:
{ "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
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;}