---
title: "Configuração do cliente do SDK Python da NFE.io"
description: "Todas as opções do NfeClient e do AsyncNfeClient — as duas chaves de API, variáveis de ambiente, timeouts, retry, TLS, hosts por família de API, opções por chamada e logs."
source_url: https://nfe.io/docs/desenvolvedores/bibliotecas/python/configuracao/
product: bibliotecas
last_updated: 2026-10-14
tags: ["python", "referencia", "autenticacao"]
integration: ["sdk"]
---

# Configuração

O cliente é configurado só no construtor. Não existe configuração global: cada `NfeClient` (ou
`AsyncNfeClient`) guarda a sua, imutável, e é seguro compartilhar uma instância entre threads.

## As duas chaves

| Parâmetro | Variável de ambiente | Família de API | Host |
|---|---|---|---|
| `api_key` | `NFE_API_KEY` | NFS-e | `https://api.nfe.io` |
| `api_key` | `NFE_API_KEY` | empresas, certificados, webhooks | `https://api.nfse.io` |
| `data_api_key` | `NFE_DATA_API_KEY` | consulta de CNPJ | `https://legalentity.api.nfe.io` |
| `data_api_key` | `NFE_DATA_API_KEY` | consulta de CPF | `https://naturalperson.api.nfe.io` |
| `data_api_key` | `NFE_DATA_API_KEY` | consulta de CEP | `https://address.api.nfe.io` |

```python
import os

from nfeio import NfeClient

# Explícito: o argumento vence a variável de ambiente.
client = NfeClient(
    api_key=os.environ["NFE_API_KEY"],
    data_api_key=os.environ["NFE_DATA_API_KEY"],
)

# Ou lendo NFE_API_KEY e NFE_DATA_API_KEY do ambiente:
client = NfeClient()
```

- **Não há fallback entre as chaves.** Os hosts de consulta recusam a chave principal com `403`, e
  o SDK nunca tenta uma no lugar da outra. Se você tem uma chave só, passe o mesmo valor nos dois
  parâmetros.
- **Chave ausente não impede criar o cliente.** O erro aparece na primeira chamada que precisa
  dela: `ConfigurationError` dizendo qual parâmetro ou variável configurar. Assim um cliente que só
  emite NFS-e não precisa de `data_api_key`.
- Uma chave explícita vazia (`api_key=""`) é recusada na hora, com `ConfigurationError`.
- A chave vai no cabeçalho `Authorization` e nunca aparece em `repr`, logs ou exceções: o `repr`
  do cliente mostra só os 4 últimos caracteres (`NfeClient(api_key=SecretStr('****abcd'), ...)`).

:::note Produção e teste
Não existe host nem chave de homologação. O ambiente (produção ou teste) é uma configuração da
empresa na NFE.io, e a nota volta com o campo `environment` preenchido.
:::

## Todas as opções

| Opção | Padrão | O que faz |
|---|---|---|
| `api_key` | `NFE_API_KEY` | Chave de NFS-e, empresas, certificados e webhooks. |
| `data_api_key` | `NFE_DATA_API_KEY` | Chave das consultas de CNPJ, CPF e CEP. |
| `timeout` | `Timeout(connect=10, read=60, total=120)` | Prazos por tentativa, em segundos. Precisam ser finitos. |
| `max_retries` | `3` | Novas tentativas de requisições seguras (0 a 10). |
| `retry_base_delay` / `retry_max_delay` | `1.0` / `30.0` | Backoff exponencial (dobra a cada tentativa, com variação de ±30%). |
| `max_retry_after` | `60.0` | Maior `Retry-After` respeitado. Acima disso, o erro sobe sem espera. |
| `max_response_bytes` | 10 MiB | Respostas maiores são recusadas com `ResponseTooLargeError`. |
| `app_info` | `None` | `(nome, versão)` acrescentado ao `User-Agent`. |
| `ssl_context` | `None` | `ssl.SSLContext` próprio, que precisa verificar certificado e hostname. |
| `ca_bundle` | `None` | Caminho de um PEM com CAs extras (proxy corporativo, por exemplo). |
| `base_urls` | hosts oficiais | Troca o host de uma família de API (só `https`). |
| `transport` | `HttpClientTransport` | Transporte HTTP próprio (veja abaixo). |

```python
from nfeio import NfeClient, Timeout

client = NfeClient(
    timeout=Timeout(connect=10, read=60, total=120),  # os padrões
    max_retries=3,
    app_info=("meu-erp", "2.4.0"),                    # vai no User-Agent
)
```

## Timeouts

`Timeout(connect, read, total)` vale para **cada tentativa**:

- `connect`: DNS, TCP e handshake TLS;
- `read`: cada leitura do socket (cabeçalhos e cada bloco do corpo);
- `total`: a tentativa inteira, da conexão ao último byte.

Um número também é aceito: `timeout=90` significa "esta tentativa pode levar até 90 s"
(`read = total = 90`, `connect = min(10, 90)`). `None`, zero, negativo e infinito são recusados com
`ConfigurationError`: não existe requisição sem prazo.

:::note Limite da biblioteca padrão
A resolução de DNS não respeita o timeout de conexão.
:::

## Retry

O retry depende do método e de quando a falha aconteceu:

| Método | 429 | 408, 500, 502, 503, 504 | Falha antes de conectar | Falha depois de enviar |
|---|---|---|---|---|
| `GET`, `PUT`, `DELETE` | repete | repete | repete | repete |
| `POST` (emissão, upload, criação) | **não** | **não** | repete | **não** |

Um `POST` nunca é repetido depois que pode ter chegado à API, porque a API pode criar a nota e mesmo
assim responder `500` ou `504`. Esses erros chegam com `outcome_unknown=True`, e a decisão de tentar
de novo é sua, depois de reconciliar. A receita está em [Tratamento de erros](./errors.md).

`Retry-After` é respeitado até `max_retry_after` segundos. Sem ele, a espera é exponencial: 1 s, 2 s,
4 s… até 30 s, com variação aleatória de ±30%.

## Opções por chamada

Todo método aceita `options=RequestOptions(...)`. Os campos são opcionais, e `None` mantém o valor do
cliente.

```python
from nfeio import NfeClient, RequestOptions

client = NfeClient()
company_id = "ID_DA_EMPRESA"

page = client.service_invoices.list(
    company_id,
    options=RequestOptions(timeout=90, max_retries=0),
)
```

| Campo | O que faz |
|---|---|
| `api_key` | Usa outra chave só nesta chamada (por exemplo, a de outra conta). |
| `timeout` | `Timeout` ou segundos, como no cliente. |
| `max_retries` | Novas tentativas desta chamada (`0` desliga). Nunca torna um `POST` repetível. |
| `idempotency_key` | Enviado como `Idempotency-Key`. A API ignora esse cabeçalho hoje, então ele **não** muda a regra de retry. |
| `extra_headers` | Cabeçalhos adicionais. Não podem sobrescrever `Authorization`, `User-Agent` nem `Content-Type`. |

## TLS

TLS é sempre verificado, com versão mínima 1.2. Não existe opção para desligar a verificação: um
`ssl_context` com `verify_mode` diferente de `CERT_REQUIRED` ou com `check_hostname=False` é recusado.

Para confiar numa CA extra (um proxy corporativo que inspeciona TLS, por exemplo):

```python
from nfeio import NfeClient

client = NfeClient(ca_bundle="/etc/ssl/certs/ca-proxy.pem")
```

`ca_bundle` e `ssl_context` são excludentes, e nenhum dos dois vale com `transport=`: nesse caso,
configure TLS no próprio transporte.

## Hosts por família de API

`base_urls` troca o host de uma família, para apontar para um gateway interno, por exemplo. As
chaves aceitas são os valores de `ApiFamily`: `"service_invoices"`, `"account"` (empresas,
certificados e webhooks), `"legal_entity"`, `"natural_person"` e `"address"`.

```python
from nfeio import ApiFamily, NfeClient

client = NfeClient(base_urls={ApiFamily.SERVICE_INVOICES: "https://nfe-gateway.minhaempresa.com.br"})
```

Só `https`, sem usuário e senha na URL, sem query e sem fragmento.

## Transporte próprio

O transporte padrão usa só a biblioteca padrão (`http.client`). Para usar outra pilha HTTP, ou um
proxy, implemente o protocolo `nfeio.transport.Transport` (ou `AsyncTransport` no cliente
assíncrono) e passe em `transport=`. O transporte não pode seguir redirecionamentos e precisa levantar
`APIConnectionError` com a `FailurePhase` certa em falhas de rede, porque é ela que decide se um
`POST` pode ser repetido.

:::note Proxy
As variáveis `HTTPS_PROXY` e `HTTP_PROXY` são ignoradas na versão 0.1. Um parâmetro `proxy=` está
previsto para uma próxima versão.
:::

## Ciclo de vida

O cliente mantém um pool de conexões. Feche ao terminar, ou use como gerenciador de contexto:

```python
from nfeio import NfeClient

with NfeClient() as client:
    for company in client.companies.list().auto_paging_iter():
        print(company.id)
```

No cliente assíncrono, o equivalente é `async with AsyncNfeClient() as client:` ou
`await client.aclose()`.

## Logs

O logger `nfeio` registra requisições e novas tentativas em nível `DEBUG`, com método, host, caminho,
status, tentativa e `x-request-id`. Ele nunca registra cabeçalhos, corpo, query, chaves ou
documentos: ids e documentos no caminho aparecem como `*`.

```python
import logging

logging.basicConfig()
logging.getLogger("nfeio").setLevel(logging.DEBUG)
```
