engineAPIengineAPI
// referência

SDK Python

SDK Python oficial da engineAPI (EngineApiClient). Pacote engine-api-python, versão 1.0.0.

SDK Python

O SDK Python oficial está na versão 1.0.0 (Python 3.9+, dependências httpx+pydantic). Confirme a disponibilidade do pacote no PyPI antes de instalar. Se ainda não estiver publicado no seu ambiente, use a integração direta com httpx na seção final desta página.

bash
pip install engine-api-python

Configuração

python
from engineapi import EngineApiClient

client = EngineApiClient(
    base_url="https://api.engineapi.com.br",  # sem /v1, o client adiciona
    api_key="ek_live_SEU_API_KEY",
    timeout=30.0,  # opcional, padrão 30s
)
ParâmetroTipoObrigatórioDescrição
base_urlstrSimURL base da API (sem /v1)
api_keystrSim*API Key server-to-server (ek_live_/ek_test_)
tokenstrSim*JWT de sessão de dashboard
timeoutfloatNãoTimeout em segundos (padrão 30)

*Use api_key OU token. O client também suporta uso como context manager (with EngineApiClient(...) as client: ...), fechando a conexão HTTP automaticamente.


Módulos Disponíveis

python
client.nfe        # NFe: emissão, cancelamento, CC-e, download, status
client.companies  # Empresas: ver aviso abaixo, contrato desatualizado

O pacote também expõe client.dfe; esse módulo está fora do contrato público hoje. Não use em produção; será reativado quando a funcionalidade entrar na superfície pública.


NFe: Emitir

emitir() recebe um dict repassado verbatim ao corpo da requisição: use os nomes de campo reais do contrato: destinatario, items (não itens), pagamentos (lista, não pagamento singular), cnpjCpf/nome/ie no destinatário.

python
nfe = client.nfe.emitir({
    "naturezaOperacao": "VENDA DE MERCADORIA",
    "destinatario": {
        "cnpjCpf": "99888777000100",
        "nome": "Cliente Exemplo SA",
        "indicadorIE": 1,
        "endereco": {
            "logradouro": "Av Brasil", "numero": "500",
            "bairro": "Centro", "codigoMunicipio": "3550308",
            "municipio": "São Paulo", "uf": "SP", "cep": "01001000",
        },
    },
    "items": [{
        "codigo": "PROD001",
        "descricao": "Camiseta Azul M", "ncm": "61091000",
        "cfop": "5102", "unidade": "UN", "quantidade": 1,
        "valorUnitario": 89.90,
        "icms": {"origem": 0, "csosn": "400"},
    }],
    "pagamentos": [{"forma": "01", "valor": 89.90}],
})

print(f"NFe autorizada: {nfe['data']['accessKey']} ({nfe['data']['status']})")

issuerId é opcional no payload de NFe/NFCe: com um único emissor pode omitir; com dois ou mais, informe o issuerId (UUID) do CNPJ que deve emitir; sem ele a API responde 400.

Cancelar, CC-e e downloads

python
# Cancelar: campo justificativa, mínimo 15 caracteres
client.nfe.cancelar(
    "35260211222333000181550010000000011000000019",
    "Erro nos dados do destinatário informado na venda",
)

# Carta de Correção
client.nfe.carta_correcao(
    "35260211222333000181550010000000011000000019",
    "Corrijo o endereço do destinatário para Av Brasil, 500",
)

# Downloads
pdf_bytes = client.nfe.download_pdf("35260211...")
xml_bytes = client.nfe.download_xml("35260211...")

Uso assíncrono

O pacote é síncrono (baseado em httpx.Client). Para FastAPI/asyncio, rode as chamadas em thread separada (asyncio.to_thread) ou aguarde a variante async oficial:

python
import asyncio
from fastapi import FastAPI

app = FastAPI()

@app.post("/nfe")
async def emitir(dados: dict):
    return await asyncio.to_thread(client.nfe.emitir, dados)

Companies: Empresas Emissoras

O client companies desta versão do pacote chama rotas erradas. criar(), listar(), buscar() e upload_certificado() fazem requisições para /issuers/*, mas a API real expõe o módulo em /companies; /issuers não existe e retorna 404. O upload de certificado também está errado: o client manda {"certificado": ..., "senha": ...} em JSON, mas a rota real é multipart/form-data com o campo file. Até essa versão ser corrigida, use httpx diretamente para o módulo de empresas (exemplo abaixo).

python
import httpx

http = httpx.Client(base_url="https://api.engineapi.com.br/v1",
                     headers={"Authorization": f"Bearer {token}"})

# Cadastrar empresa
empresa = http.post("/companies", json={"cnpj": "11222333000181", "name": "Empresa Exemplo Ltda"})

# Upload de certificado: multipart, campo "file"
with open("certificado.pfx", "rb") as f:
    http.post(
        f"/companies/{issuer_id}/certificate",
        files={"file": ("certificado.pfx", f, "application/x-pkcs12")},
        data={"password": "senhaDoCertificado"},
    )

Login e API Keys

python
# Login: retorna {access_token, partner} segundo a docstring do client, mas o
# servidor real devolve {access_token, user: {id,email,name,partnerId,role}}. Trate
# `user`, não `partner`, ao ler o retorno.
login = client.login("usuario@partner.com", "senha")

# Regenerar API Key de produção (exige subscription ACTIVE)
client.regenerate_api_key()

# Info da key atual
client.api_key_info()

Tratamento de erros

python
import httpx

try:
    nfe = client.nfe.emitir(dados)
except httpx.HTTPStatusError as e:
    body = e.response.json()
    if e.response.status_code == 400:
        # Validação OU rejeição SEFAZ: ver body["error"]["erros"]
        print("Erro:", body["error"]["detail"])
    elif e.response.status_code == 422:
        print("Emissão assistida: campo não resolvido:", body["error"])
    elif e.response.status_code == 429:
        print("Rate limit. Aguarde e tente novamente")
    else:
        print(f"Erro {e.response.status_code}: {body}")

Não existem sefazCode/sefazMessage no corpo do erro: o formato real é RFC 7807: error.erros[] ({codigo, descricao}) para rejeição SEFAZ. Ver Erros e Rejeições.


Alternativa: integração direta com httpx

Se o pacote engine-api-python ainda não estiver disponível no seu ambiente, integre diretamente:

python
import httpx

class EngineAPI:
    def __init__(self, base_url: str, api_key: str):
        self.client = httpx.Client(
            base_url=f"{base_url.rstrip('/')}/v1",
            headers={"x-api-key": api_key},
            timeout=30.0,
        )

    def emitir_nfe(self, dados: dict) -> dict:
        response = self.client.post("/nfe", json=dados)
        response.raise_for_status()
        return response.json()

    def cancelar_nfe(self, id_ou_chave: str, justificativa: str) -> dict:
        response = self.client.post(
            f"/nfe/{id_ou_chave}/cancelar",
            json={"justificativa": justificativa},
        )
        response.raise_for_status()
        return response.json()

engine = EngineAPI("https://api.engineapi.com.br", "ek_live_SEU_API_KEY")
nfe = engine.emitir_nfe({ # payload real, ver /guides/emitir-nfe
})
print(f"NFe autorizada: {nfe['data']['accessKey']}")

Próximos passos