# Autenticação, erros e limites

## Chave de API

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

```http
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):

```bash
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:

```json
{ "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](09-erros.md).

| 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
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](10-producao.md#tentativas-e-erros-de-rede).

## `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:

```bash
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:

```json
{ "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](05-emitir-nfe.md#campos-aceitos-e-ignorados).
- **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](13-novidades.md).
