> ## 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.

# SDK Python

> Client Python da engineAPI (EngineApiClient): NFe, empresas emissoras e autenticação. Uso direto via httpx hoje; pacote no PyPI a caminho.

<Info>
  O client Python (`EngineApiClient`) cobre NFe, empresas emissoras e autenticação. O
  pacote `engine-api-python` está a caminho do PyPI. Hoje, use a integração direta com
  `httpx` abaixo: mesmo contrato da API, zero dependência de publicação externa.
</Info>

## Integração direta com httpx

```python theme={null}
import httpx


class EngineApiClient:
    def __init__(
        self,
        base_url: str,
        api_key: str | None = None,
        token: str | None = None,
        timeout: float = 30.0,
    ):
        headers = {"x-api-key": api_key} if api_key else {"Authorization": f"Bearer {token}"}
        self.http = httpx.Client(
            base_url=f"{base_url.rstrip('/')}/v1",
            headers=headers,
            timeout=timeout,
        )

    def login(self, email: str, senha: str) -> dict:
        response = self.http.post("/auth/login", json={"email": email, "password": senha})
        response.raise_for_status()
        return response.json()
```

| Parâmetro  | Tipo  | Obrigatório | Descrição                                        |
| ---------- | ----- | ----------- | ------------------------------------------------ |
| `base_url` | str   | Sim         | URL base da API (sem `/v1`, o client adiciona)   |
| `api_key`  | str   | Sim\*       | API Key server-to-server (`ek_live_`/`ek_test_`) |
| `token`    | str   | Sim\*       | JWT de sessão de dashboard                       |
| `timeout`  | float | Não         | Timeout em segundos (padrão 30)                  |

\*Use `api_key` OU `token`.

***

## NFe: Emitir

`POST /nfe` 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 theme={null}
def emitir_nfe(client: EngineApiClient, dados: dict) -> dict:
    response = client.http.post("/nfe", json=dados)
    response.raise_for_status()
    return response.json()


nfe = emitir_nfe(client, {
    "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']})")
```

<Info>
  `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`.
</Info>

### Cancelar, CC-e e downloads

```python theme={null}
# Cancelar: campo justificativa, mínimo 15 caracteres
chave = "35260211222333000181550010000000011000000019"

client.http.post(
    f"/nfe/{chave}/cancelar",
    json={"justificativa": "Erro nos dados do destinatário informado na venda"},
).raise_for_status()

# Carta de Correção: campo correcao, mínimo 15 caracteres
client.http.post(
    f"/nfe/{chave}/carta-correcao",
    json={"correcao": "Corrijo o endereço do destinatário para Av Brasil, 500"},
).raise_for_status()

# Downloads
pdf_bytes = client.http.get(f"/nfe/pdf/{chave}").content
xml_bytes = client.http.get(f"/nfe/xml/{chave}").content
```

***

## Companies: Empresas Emissoras

```python theme={null}
empresa = client.http.post("/companies", json={
    "cnpj": "11222333000181",
    "name": "Empresa Exemplo Ltda",
    "crt": 1,
}).json()

with open("certificado.pfx", "rb") as f:
    client.http.post(
        f"/companies/{empresa['data']['id']}/certificate",
        files={"file": ("certificado.pfx", f, "application/x-pkcs12")},
        data={"password": "senhaDoCertificado"},
    ).raise_for_status()
```

***

## Login

```python theme={null}
login = client.login("usuario@partner.com", "senha")
print(login["access_token"])
```

***

## Tratamento de erros

```python theme={null}
import httpx

try:
    nfe = emitir_nfe(client, 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}")
```

<Warning>
  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](/guides/errors).
</Warning>

***

## Próximos passos

<CardGroup cols={2}>
  <Card title="SDK TypeScript" icon="js" href="/sdks/typescript">
    SDK oficial com tipagem completa
  </Card>

  <Card title="API Reference" icon="book" href="/api-reference/overview">
    Documentação completa de todos os endpoints
  </Card>
</CardGroup>
