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 |
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:
ConfigurationErrordizendo qual parâmetro ou variável configurar. Assim um cliente que só emite NFS-e não precisa dedata_api_key. - Uma chave explícita vazia (
api_key="") é recusada na hora, comConfigurationError. - A chave vai no cabeçalho
Authorizatione nunca aparece emrepr, logs ou exceções: oreprdo cliente mostra só os 4 últimos caracteres (NfeClient(api_key=SecretStr('****abcd'), ...)).
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). |
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.
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.
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.
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):
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".
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.
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:
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 *.
import logging
logging.basicConfig()
logging.getLogger("nfeio").setLevel(logging.DEBUG)