Autenticação, erros e limites

Chave de API

Toda chamada (menos GET /openapi.json) leva a chave da conta em um destes formatos:

Authorization: Bearer nfe_SUA_CHAVE_AQUI

ou HTTP Basic, com a chave como usuário e a senha vazia (o mesmo formato do Focus NFe, para quem já tem o cliente HTTP configurado assim):

curl -u "nfe_SUA_CHAVE_AQUI:" https://srv1839254.hstgr.cloud/api/v2/empresas

Envie também Accept: application/json e, nos POST/PUT/PATCH/DELETE com corpo, Content-Type: application/json.

  • A chave é entregue uma única vez, na criação. A plataforma guarda só um hash dela: se você perder, peça uma nova e revogue a antiga.
  • Uma chave pode ter validade. Chave revogada, vencida ou inexistente responde sempre o mesmo 401, para não ensinar nada a quem está adivinhando.
  • Conta desativada responde 403 conta_inativa.
  • Rotação sem parada: peça uma chave nova, coloque-a no seu sistema e só então revogue a antiga. Várias chaves valem ao mesmo tempo.
  • Trate a chave como senha: não a coloque em código-fonte, front-end ou repositório.

Formato dos erros

Qualquer erro tem o mesmo corpo:

{ "codigo": "requisicao_invalida", "mensagem": "O campo nome destinatario é obrigatório.", "erros": { "nome_destinatario": ["O campo nome destinatario é obrigatório."] } }
  • codigo: estável. Programe contra ele, não contra a mensagem.
  • mensagem: para uma pessoa ler (em português; pode mudar de redação).
  • erros (só na validação): todos os problemas de uma vez, por campo, com o caminho (items.0.codigo_ncm, formas_pagamento.1.valor_pagamento).

Veja todos os códigos e o que fazer em cada um em Erros.

HTTP Quando
200 sucesso; ou reenvio de uma nota que já existe com o mesmo conteúdo, em estado final
201 criado (empresa, webhook, produto novo)
202 aceito; o resultado depende da SEFAZ e vem depois
204 removido
401 chave ausente, inválida, revogada ou vencida
403 conta desativada
404 não existe (ou não é da sua conta)
409 conflito: a ref já pertence a uma nota com outro conteúdo
422 a requisição é inválida ou o cálculo fiscal recusou
429 limite de requisições por minuto estourado
5xx falha da plataforma: tente de novo (com a mesma ref, é seguro)

Limites por minuto

Cada chave tem o seu balde: uma chave que dispara chamadas demais não gasta a cota das outras.

Balde Padrão O que conta
geral 300 por minuto toda chamada autenticada
emissão 120 por minuto POST /nfe
sensível 10 por minuto POST /empresas e PUT /empresas/{cnpj}/certificado

Os padrões podem ser ajustados por conta. Além disso há um limite por IP, antes de a chave ser conferida (1200 por minuto), contra quem fica adivinhando chaves.

Toda resposta autenticada traz X-RateLimit-Limit e X-RateLimit-Remaining. Ao estourar:

HTTP/1.1 429 Too Many Requests
Retry-After: 51
X-RateLimit-Limit: 300

{ "codigo": "limite_de_requisicoes", "mensagem": "Muitas requisições. Tente de novo em 51 segundo(s)." }

Espere Retry-After segundos e repita. Não faça laço apertado de tentativas: veja o backoff recomendado.

X-Request-Id e o log de requisições

Toda resposta autenticada traz X-Request-Id. Guarde-o nos seus logs e cite-o ao pedir suporte: ele identifica a chamada exata do nosso lado.

GET /requisicoes mostra o log da sua conta (todas as chaves), das mais recentes para as mais antigas:

curl -s "https://srv1839254.hstgr.cloud/api/v2/requisicoes?status=4xx&metodo=POST&per_page=20" -H "Authorization: Bearer $CHAVE"

Filtros: status (código exato como 404, ou a classe 4xx/5xx), metodo, caminho (contém), desde (data/hora ISO 8601; codifique o + do fuso como %2B), per_page (até 100). Cada item tem id (o mesmo X-Request-Id), metodo, caminho, status, duracao_ms, chave (prefixo da chave usada), ip, user_agent e criada_em.

O log guarda só metadados: nunca o corpo, a query string nem os cabeçalhos (neles há certificado, senha e dados do destinatário). Fica por 30 dias.

Paginação

Listas grandes (/produtos, /webhooks/{id}/entregas, /requisicoes) são paginadas: ?per_page=25 (máximo 100) e ?page=2. A resposta traz:

{ "data": [ ... ], "links": { "first": "...?page=1", "last": "...?page=14", "prev": null, "next": "...?page=2" },
  "meta": { "current_page": 1, "last_page": 14, "per_page": 2, "total": 27 } }

Siga links.next até ele ser null. As listas curtas (/empresas, /webhooks, /nfe/inutilizacoes) vêm inteiras em data, sem paginação.

Convenções de dados

  • Valores monetários: reais, como número JSON com ponto decimal (100.00, 0.3333 no preço unitário). Totais aceitam até 2 casas; preço unitário até 10; quantidade até 4. Nas respostas, valor_total é um número em reais.
  • Datas e horas: ISO 8601 com fuso (2026-10-04T10:00:00-03:00).
  • Documentos: CPF, CNPJ, CEP e telefone aceitam máscara (529.982.247-25): a API remove. O CNPJ pode ser alfanumérico (formato vigente desde julho de 2026).
  • Códigos com zero à esquerda (codigo_ncm, forma_pagamento 01, CST 00) podem ser enviados como texto ou como número: 4011000 vira 04011000.
  • Campos desconhecidos são recusados com 422 e o caminho do campo. Digitação errada nunca vira dado ignorado em silêncio. As exceções (campos aceitos e ignorados de propósito) estão em Emitir NF-e.
  • Identificadores: a empresa é identificada pelo CNPJ na URL e pelo id (uuid) nas respostas; o produto pelo SKU; a nota pela ref. Os ids numéricos internos não aparecem.

Compatibilidade

A versão está na URL (/api/v2). Dentro da v2 só adicionamos: campos novos em respostas, endpoints novos, valores novos de enumerações (status, evento...). Seu cliente deve ignorar campos que não conhece e tratar um status desconhecido como "não final". Mudanças que quebram contrato só entram em uma versão nova, com aviso prévio. Veja Novidades.