> ## Documentation Index
> Fetch the complete documentation index at: https://docs.enemhub.com.br/llms.txt
> Use this file to discover all available pages before exploring further.

# Autenticação

> API Keys, escopo por produto e o que cada erro de autenticação quer dizer.

Toda rota sob `/v1/enem` exige uma API Key. Mande a chave no header
`X-API-Key`:

```bash theme={null}
curl https://api.enemhub.com.br/v1/enem/questions \
  -H "X-API-Key: ehub_enem_SUA_CHAVE"
```

Também aceitamos `Authorization: Bearer`, para clientes HTTP que já têm o campo
pronto:

```bash theme={null}
curl https://api.enemhub.com.br/v1/enem/questions \
  -H "Authorization: Bearer ehub_enem_SUA_CHAVE"
```

As duas formas são equivalentes. `X-API-Key` tem prioridade se você mandar as
duas.

## O formato da chave

```
ehub_enem_aZ3fK9pQ...
                └── 43 caracteres aleatórios (base64url)
      └────────────── produto: `enem` neste caso
```

Só o hash SHA-256 é armazenado. A chave em claro existe uma única vez, na
resposta da criação — depois disso, nem nós conseguimos lê-la.

## Escopo: a chave pertence a um produto

Uma chave `ehub_enem_...` abre **apenas** as rotas `/v1/enem/*`. Apontá-la para
`/v1/vestibulares/*` devolve `403`, mesmo que a organização assine os dois
produtos:

```json theme={null}
{
  "error": "Forbidden",
  "message": "Esta API key (produto: enem) não tem acesso a este endpoint."
}
```

Isso é proposital. Se você usa os dois produtos, gere uma chave para cada.

## Onde guardar

<Warning>
  A chave é uma credencial de servidor. Ela dá acesso à cota que você paga e
  **não** deve ir para o front-end: qualquer pessoa lê o JavaScript da sua
  página. Chame a EnemHub do seu backend e sirva o resultado para o cliente.
</Warning>

Em variável de ambiente, não no código:

```bash .env theme={null}
ENEMHUB_API_KEY=ehub_enem_...
```

## Rotação e revogação

Em [API Keys](https://platform.enemhub.com.br/dashboard/keys) você cria, lista e
revoga chaves. Para rotacionar sem downtime: crie a nova, atualize a aplicação,
confirme que o tráfego migrou e só então revogue a antiga.

A revogação é imediata — a chave passa a devolver `401` na requisição seguinte.

O plano Starter permite até **2 chaves ativas** por organização, o que é
exatamente o suficiente para uma rotação.

## Erros de autenticação

| Status | Corpo                                                                                              | O que checar                                                                                                      |
| ------ | -------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `401`  | `{ "error": "Unauthorized", "message": "API key inválida, revogada ou expirada." }`                | O header chegou? A chave foi revogada ou expirou?                                                                 |
| `403`  | `{ "error": "Forbidden", "message": "Esta API key (produto: …) não tem acesso a este endpoint." }` | Chave de outro produto — veja o escopo acima.                                                                     |
| `403`  | `{ "error": "Forbidden", "message": "Nenhuma assinatura ativa para este produto." }`               | A assinatura venceu ou foi cancelada. Confira o [Faturamento](https://platform.enemhub.com.br/dashboard/billing). |

<Note>
  Tentativas de autenticação falhas são contadas por IP. Um volume alto de `401`
  seguidos passa a receber `429` por alguns minutos — é proteção contra força
  bruta, e some sozinho. Se apareceu na sua aplicação, quase sempre significa que
  ela está repetindo uma chave errada em loop.
</Note>

## Limites

Toda resposta de `/v1/*` traz dois headers:

| Header                  | Significado                                       |
| ----------------------- | ------------------------------------------------- |
| `X-RateLimit-Limit`     | Requisições por minuto permitidas pelo seu plano. |
| `X-RateLimit-Remaining` | Quantas ainda cabem no minuto corrente.           |

Ao estourar, a resposta é `429` com `Retry-After: 60`. São três limites
independentes:

* **Por minuto** — definido pelo plano.
* **Por dia** — 10.000 requisições, igual para todos os planos.
* **Por mês** — a cota do plano. Ao estourar, a mensagem pede upgrade.

<Tip>
  Uma requisição recusada com `429` **não** consome cota mensal. Estourar o
  limite por minuto não queima o seu volume contratado.
</Tip>

```js Retry com backoff theme={null}
async function comRetry(url, init, tentativas = 3) {
  for (let i = 0; i < tentativas; i++) {
    const res = await fetch(url, init);
    if (res.status !== 429) return res;

    const espera = Number(res.headers.get("Retry-After") ?? 60);
    await new Promise((r) => setTimeout(r, espera * 1000));
  }
  throw new Error("Rate limit persistente após 3 tentativas");
}
```
