Pular para o conteúdo

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"
}
}
CampoTipoDescrição
statusstringCódigo interno de status (001999). Veja tabela.
descricaostringDescrição humana do status (em português).
dataobject | array | omittedCarga útil. Tipo depende do endpoint.
metaobjectMetadados sempre presentes para rastreabilidade.
meta.request_idstringIdêntico ao header X-Request-Id.
meta.timestampstring (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árioHTTPBody
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 inapto401{ 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álido405{ 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.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:

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

{
"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."
}
}
HTTPQuando ocorre
401Token ausente ou inválido.
403Contrato inapto para realizar transações.
429Limite de consumo atingido (proteção operacional).

Headers de resposta

Toda resposta inclui:

HeaderSempre presente?Descrição
X-Request-Id✅ SempreIdêntico a meta.request_id. Cite no suporte.
X-RateLimit-LimitAutenticadasLimite total na janela.
X-RateLimit-RemainingAutenticadasRestante na janela.
X-RateLimit-ResetAutenticadasUnix timestamp do reset.
Content-Type✅ Sempreapplication/json; charset=UTF-8
Access-Control-*✅ SempreCORS 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;
}