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

# Autenticação

> JWT e API Keys: como autenticar suas requisições na engineAPI.

A engineAPI suporta dois métodos de autenticação. Como cada credencial chega até a chamada:

```mermaid theme={null}
flowchart LR
    subgraph JWT["JWT Bearer Token · server-side, expira em 12h"]
        direction TB
        A1("POST /auth/login<br/>email + password") --> A2("access_token (JWT)")
        A2 --> A3("Authorization: Bearer &lt;token&gt;<br/>em cada requisição")
        A3 --> A4("engineAPI")
    end
    subgraph KEY["API Key · automações, sem expiração"]
        direction TB
        B1("Dashboard → API Keys<br/>gerar nova chave") --> B2("ek_live_... / ek_test_...")
        B2 --> B3("x-api-key: ek_live_...<br/>em cada requisição")
        B3 --> B4("engineAPI")
    end

    classDef destaque fill:#2D7AF6,stroke:#0F2A5E,color:#fff
    classDef nucleo fill:#1E56B1,stroke:#0F2A5E,color:#fff
    classDef profundo fill:#0F2A5E,stroke:#0F2A5E,color:#fff
    class A1,B1 destaque
    class A2,A3,B2,B3 nucleo
    class A4,B4 profundo
```

<CardGroup cols={2}>
  <Card title="API Key" icon="lock">
    Para automações, SDKs e integrações server-to-server: o caso comum de software house.
    Gerada no cadastro ou no Dashboard, nunca expira (até ser revogada).
  </Card>

  <Card title="JWT (Bearer Token)" icon="key">
    Para o dashboard e sessões de usuário. Obtenha via `POST /v1/auth/login` e use no header `Authorization`.
  </Card>
</CardGroup>

<Info>
  **Acesso antecipado (`POST /v1/auth/register`)** grava um pedido de acesso e responde
  `202` (não cria conta na hora). Nosso time revisa e, se liberar, você recebe um convite
  por e-mail para criar a senha; a conta (partner + usuário dono + plano Dev, R\$0) nasce
  no aceite do convite. Ver [Quickstart](/quickstart) para o passo a passo.
  `POST /v1/auth/register-partner` continua existindo para provisionamento manual por um
  parceiro com papel SUPERADMIN.
</Info>

***

## Método 1: JWT (Bearer Token)

Use quando seu backend/dashboard faz login com e-mail e senha.

### Obtendo o token

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl -X POST https://api.engineapi.com.br/v1/auth/login \
      -H "Content-Type: application/json" \
      -d '{
        "email": "dev@minhaempresa.com",
        "password": "suaSenha"
      }'
    ```
  </Tab>

  <Tab title="Node.js">
    ```typescript theme={null}
    const response = await fetch('https://api.engineapi.com.br/v1/auth/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        email: 'dev@minhaempresa.com',
        password: 'suaSenha',
      }),
    });

    const { data } = await response.json();
    // data.access_token, data.user.{id,email,name,partnerId,role}
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import httpx

    resp = httpx.post('https://api.engineapi.com.br/v1/auth/login', json={
        'email': 'dev@minhaempresa.com',
        'password': 'suaSenha',
    })

    token = resp.json()['data']['access_token']
    ```
  </Tab>
</Tabs>

```json Resposta (201) theme={null}
{
  "data": {
    "access_token": "eyJhbGciOiJIUzI1NiIs...",
    "user": {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "email": "dev@minhaempresa.com",
      "name": "Admin",
      "partnerId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "role": "ADMIN"
    }
  },
  "meta": {
    "requestId": "req_abc123",
    "timestamp": "2026-07-06T12:00:00.000Z"
  }
}
```

<Warning>
  A resposta é **`201 Created`**, não `200 OK` (versões anteriores desta doc afirmavam
  `200`). Trate qualquer resposta `2xx` como sucesso, em vez de checar um código exato.
</Warning>

<Info>
  Toda resposta de sucesso vem envelopada em `{ data, meta }`: `meta.requestId`/`meta.timestamp`
  são preenchidos automaticamente pelo interceptor global. Isso vale para **todas** as respostas
  desta documentação, não só o login.
</Info>

### Usando o token

```bash theme={null}
curl -X GET https://api.engineapi.com.br/v1/companies \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
```

### Expiração e renovação

| Campo                       | Valor                           |
| --------------------------- | ------------------------------- |
| **Duração do access token** | 12 horas                        |
| **Formato**                 | JWT assinado com HS256          |
| **Header**                  | `Authorization: Bearer <token>` |
| **Refresh token**           | 30 dias, rotacionado a cada uso |

O login devolve `access_token` **e** `refresh_token`. Quando o access token
expira, troque-o por um par novo:

```bash theme={null}
curl -X POST https://api.engineapi.com.br/v1/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{"refreshToken": "SEU_REFRESH_TOKEN"}'
```

A rotação é obrigatória: **o refresh token usado morre na mesma chamada**.
Guarde sempre o `refresh_token` novo que veio na resposta: reapresentar o
antigo devolve `401`.

Para encerrar a sessão:

```bash theme={null}
# encerra só esta sessão
curl -X POST https://api.engineapi.com.br/v1/auth/logout \
  -H "Authorization: Bearer SEU_TOKEN"

# encerra todas as sessões da conta
curl -X POST "https://api.engineapi.com.br/v1/auth/logout?all=true" \
  -H "Authorization: Bearer SEU_TOKEN"
```

O logout **invalida o access token na hora**: ele não continua valendo até
expirar. Trocar a senha também derruba todas as sessões abertas.

<Warning>
  **Nunca exponha seu token** no frontend, em logs ou em repositórios públicos. Use variáveis de ambiente.
</Warning>

### Verificação em duas etapas (2FA)

Contas com acesso privilegiado (**superadmin da plataforma** e **dono de
parceiro**, `PartnerMember.role = OWNER`) são **obrigadas** a usar um
aplicativo autenticador (TOTP, RFC 6238). Para as demais é opcional, ligável em
**Configurações → Segurança**.

Quando a conta tem 2FA ativo, o `POST /v1/auth/login` responde **sem**
`access_token`:

```json theme={null}
{
  "mfa_required": true,
  "mfa_enrollment_required": false,
  "mfa_token": "eyJhbGciOiJIUzI1NiIs...",
  "expires_in": 300
}
```

Conclua com o código de 6 dígitos do autenticador:

```bash theme={null}
curl -X POST https://api.engineapi.com.br/v1/auth/2fa/verify \
  -H "Content-Type: application/json" \
  -d '{"mfaToken": "SEU_MFA_TOKEN", "totpCode": "123456"}'
```

Clientes que preferem uma chamada só podem mandar o código junto no login
(`totpCode` no corpo do `POST /v1/auth/login`).

| Campo da resposta               | O que significa                                                                                                               |
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `mfa_required: true`            | A conta tem 2FA ativo, falta o código                                                                                         |
| `mfa_enrollment_required: true` | O papel exige 2FA e a conta ainda não configurou. Use o `mfa_token` em `POST /v1/auth/2fa/setup` e `POST /v1/auth/2fa/enable` |
| `mfa_token`                     | Credencial de propósito único, válida por 5 minutos. **Não** vale como `Bearer` nas demais rotas                              |

Perdeu o celular? Envie um dos **códigos de recuperação** em `recoveryCode` no
lugar de `totpCode`. Cada código funciona **uma única vez**, e o titular
(mais o dono da plataforma) recebe um e-mail avisando que um deles foi usado.

### Bloqueio por tentativas de login

Tentativas falhas bloqueiam progressivamente, por **e-mail** e por **IP**:
5 falhas → 1 minuto, 10 → 15 minutos, 15 → 1 hora, 20 → 4 horas. Um balde sem
falha nova por 1 hora zera sozinho, e um login bem-sucedido limpa o balde do
e-mail.

Enquanto o bloqueio vale, **a senha certa também é recusada**. A resposta é
sempre a mesma (`401 Credenciais inválidas`) para senha errada, e-mail
inexistente, código 2FA errado e conta bloqueada: a API não confirma quais
e-mails existem.

***

## Método 2: API Key

Use em automações de longa duração, SDKs ou integrações server-to-server sem sessão de login.

### Gerando uma API Key

1. Acesse o **Dashboard** → [app.engineapi.com.br](https://app.engineapi.com.br)
2. Vá em **Configurações → API Keys**
3. Gere a key de teste (`ek_test_`), disponível sem assinatura paga, ou a key de produção
   (`ek_live_`), que exige uma **subscription ACTIVE** (403 sem plano pago)
4. Copie e guarde. Ela é exibida **uma única vez**

Via API (sessão JWT do dashboard):

```bash theme={null}
# Key de teste: qualquer partner, sem exigir assinatura
curl -X POST https://api.engineapi.com.br/v1/auth/api-keys/test/regenerate \
  -H "Authorization: Bearer SEU_TOKEN"

# Key de produção: exige subscription ACTIVE
curl -X POST https://api.engineapi.com.br/v1/auth/api-keys/regenerate \
  -H "Authorization: Bearer SEU_TOKEN"
```

### Usando a API Key

<Info>
  `/v1/companies` aceita `x-api-key` **ou** JWT (`ApiKeyGuard`, mesmo escopo de partner
  nos dois casos); este exemplo funciona de ponta a ponta.
</Info>

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl -X GET https://api.engineapi.com.br/v1/companies \
      -H "x-api-key: ek_live_xxxxxxxxxxxxxxxx"
    ```
  </Tab>

  <Tab title="Node.js / SDK">
    ```typescript theme={null}
    import { EngineApiClient } from '@engineapi/sdk';

    const client = new EngineApiClient({
      baseUrl: 'https://api.engineapi.com.br', // sem /v1, o SDK adiciona
      apiKey: process.env.ENGINE_API_KEY!,     // ek_live_... ou ek_test_...
    });

    const companies = await client.companies.listar();
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import httpx

    resp = httpx.get('https://api.engineapi.com.br/v1/companies',
        headers={'x-api-key': 'ek_live_xxxxxxxxxxxxxxxx'},
    )
    ```
  </Tab>
</Tabs>

| Propriedade   | Detalhe                                                                       |
| ------------- | ----------------------------------------------------------------------------- |
| **Header**    | `x-api-key: ek_live_...` (produção) ou `x-api-key: ek_test_...` (teste)       |
| **Prefixo**   | `ek_live_` (produção) / `ek_test_` (teste: homologação/sandbox, não faturado) |
| **Expiração** | Nunca. Revogue/regenere manualmente pelo Dashboard ou pela API                |

<Warning>
  A `ek_test_` só opera emissores em homologação (`ambienteFiscal=2`) ou marcados como
  sandbox. Usá-la contra um emissor de produção retorna **403**. Emissões com `ek_test_`
  não são faturadas.
</Warning>

***

## Multi-tenancy

Um único token (JWT) ou API Key gerencia **múltiplos CNPJs emissores** (`Issuer`) sob o mesmo Partner:

```
Partner (seu token / API key)
├── Empresa A (issuerId: "uuid-a")
├── Empresa B (issuerId: "uuid-b")
└── Empresa C (issuerId: "uuid-c")
```

<Info>
  **Seleção explícita de emissor:** NF-e, NFC-e e NFS-e aceitam **`issuerId`** (UUID) no
  corpo da emissão para escolher qual CNPJ emite a nota.

  * **NF-e / NFC-e**: `issuerId` é **opcional**:
    * se você tem **um único emissor**, pode omitir: a API usa o seu emissor (retrocompatível);
    * se você tem **dois ou mais emissores**, informe `issuerId`; sem ele a API responde
      **`400`** ("informe issuerId: sua conta tem N emissores") em vez de emitir pelo CNPJ errado;
    * um `issuerId` que não pertence ao partner autenticado responde **`404`** (isolamento entre tenants).
  * **NFS-e**: `issuerId` é **obrigatório** no corpo da requisição.

  ```json theme={null}
  // POST /v1/nfe  (ou /v1/nfce, /v1/nfse)
  {
    "issuerId": "uuid-a",   // opcional em NFe/NFCe; obrigatório em NFSe
    "destinatario": { "...": "..." },
    "items": [ "..." ]
  }
  ```
</Info>

O `issuerId` (UUID) é retornado ao criar uma empresa via `POST /v1/companies`.
