Metadata-Version: 2.4
Name: foxnfe
Version: 1.3.2
Summary: SDK oficial FOX NF-e para Python — emissão NF-e, NFSe, cancelamento, consulta e MCP
Author-email: Central Fox Tecnologia <dev@centralfox.online>
License: MIT
Project-URL: Homepage, https://www.foxnfe.com.br
Project-URL: Documentation, https://docs.foxnfe.com.br
Keywords: nfe,nfse,nota-fiscal,fiscal,brasil
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: License :: OSI Approved :: MIT License
Classifier: Topic :: Office/Business :: Financial
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: requests>=2.28.0

# foxnfe (Python SDK)

[![PyPI](https://img.shields.io/pypi/v/foxnfe)](https://pypi.org/project/foxnfe/)

SDK oficial FOX NF-e para Python — emissão NF-e, NFSe, cancelamento, consulta e integração MCP.

## Requisitos

- Python 3.9+
- [requests](https://requests.readthedocs.io) `>=2.28`

## Instalação

```bash
pip install foxnfe
# ou
poetry add foxnfe
# ou
uv add foxnfe
```

## Quick Start

```python
from foxnfe import Client

client = Client(tenant_slug="minha-empresa")

# Autenticar
auth = client.login("email@empresa.com", "senha-segura")
print(f"Token: {auth.token}")

# Ou usar token existente
client = Client(tenant_slug="minha-empresa", token="seu-token-aqui")
# Ou via with_token (retorna nova instância)
authed = client.with_token("seu-token-aqui")
```

## NF-e

```python
from foxnfe import Client, NfeEmitRequest

client = Client(tenant_slug="minha-empresa", token="seu-token")

payload = NfeEmitRequest(
    ambiente=2,  # 2=homologação
    certificate_id=1,
    tomador={
        "cnpj": "12345678000190",
        "razao_social": "Empresa Tomadora Ltda",
        "endereco": {
            "logradouro": "Rua das Flores",
            "numero": "100",
            "municipio": "São Paulo",
            "uf": "SP",
            "cep": "01310100",
        },
    },
    itens=[{
        "codigo": "SRV001",
        "descricao": "Serviço de consultoria",
        "cfop": "5933",
        "quantidade": 1,
        "valor_unitario": 1000.00,
        "valor_total": 1000.00,
    }],
    pagamentos=[{"forma": "01", "valor": 1000.00}],
    total=1000.00,
)

result = client.nfe.emit(payload)
print(f"NF-e ID: {result['id']}")

# Aguardar autorização (polling automático)
nfe_autorizada = client.nfe.wait_for_authorization(result["id"])
print(f"Status: {nfe_autorizada.status}")  # authorized

# Baixar XML
xml_bytes = client.nfe.xml(result["id"])
with open("nfe.xml", "wb") as f:
    f.write(xml_bytes)

# Baixar DANFE PDF
pdf_bytes = client.nfe.pdf(result["id"])
with open("danfe.pdf", "wb") as f:
    f.write(pdf_bytes)

# Cancelar
client.nfe.cancel(result["id"], "Cancelamento solicitado pelo cliente")
```

## NFSe

```python
from foxnfe import Client, NfseEmitRequest

client = Client(tenant_slug="minha-empresa", token="seu-token")

payload = NfseEmitRequest(
    competencia="2026-09",                       # AAAA-MM
    cnpj_prestador="12345678000190",
    inscricao_municipal="123456",
    razao_social_prestador="Minha Empresa Ltda",  # opcional
    descricao_servico="Desenvolvimento de software sob encomenda",
    codigo_municipio_prestacao="3550308",        # IBGE 7 dígitos
    valor_servico=5000.00,
    # informe cnae OU o trio abaixo
    codigo_tributacao_nacional="01.03.01.00",    # formato dd.dd.dd.dd
    codigo_tributacao_municipal="0103",
    aliquota_iss=2.0,
    tomador={"cnpj": "98765432000110", "nome": "Cliente S.A."},  # cnpj OU cpf
    prestador={"endereco": {
        "logradouro": "Av. Paulista", "numero": "1000", "bairro": "Bela Vista",
        "codigo_municipio": "3550308", "uf": "SP", "cep": "01310100",
    }},
)

result = client.nfse.emit(payload)         # também aceita um dict equivalente
nfse = client.nfse.get(result["nfse_id"])  # 202 {nfse_id, status: pending, message}
print(f"Número NFSe: {nfse.numero_nfse}")

# Consultar por RPS ou chave
client.nfse.consult_by_numero("00000001")
client.nfse.consult_by_chave("SP3550308202605010000000000001")

# Cancelar (justificativa 15..255) / Substituir (motivo 15..255 + campos de emissão)
client.nfse.cancel(result["nfse_id"], "Erro nos dados do tomador")
client.nfse.substitute(result["nfse_id"], payload, "Correção dos dados do tomador")
```

## MCP (Model Context Protocol)

```python
from foxnfe import Client

client = Client(tenant_slug="minha-empresa", token="seu-token")

# Inicializar sessão MCP
info = client.mcp.initialize()
print(f"MCP Server: {info['result']['serverInfo']['name']}")

# Listar tools
tools = client.mcp.list_tools()
for tool in tools:
    print(f"{tool.name}: {tool.description}")

# Chamar uma tool
result = client.mcp.call_tool("emitir_nfe", {
    "ambiente": 2,
    "certificate_id": 1,
})

if result.get("result", {}).get("isError"):
    print("Tool error:", result["result"]["content"][0]["text"])
else:
    print("Tool result:", result["result"]["content"][0]["text"])
```

## Tratamento de Erros

```python
from foxnfe import Client
from foxnfe.exceptions import ApiException, AuthException, FoxNfeException

try:
    client.nfe.emit(payload)
except AuthException as e:
    # Token inválido ou expirado (401/403)
    print(f"Auth error: {e}")
except ApiException as e:
    # Erro da API (422, 500, etc.)
    print(f"API error {e.status_code}: {e}")
    print(f"Body: {e.response_body}")
except FoxNfeException as e:
    # Timeout, erro de conexão, etc.
    print(f"SDK error: {e}")
```

## Uso com context manager

```python
from foxnfe import Client

# Client usa requests.Session internamente (pode ser fechado manualmente)
client = Client(tenant_slug="minha-empresa", token="seu-token")
try:
    result = client.nfe.emit(payload)
finally:
    client._session.close()
```

## Configuração avançada

```python
client = Client(
    tenant_slug="minha-empresa",
    token="seu-token",
    base_url="https://foxnfe.centralfox.online/api/v1",  # host legado; padrão: https://www.foxnfe.com.br/api/v1
    timeout=60.0,
)
```

## Estrutura do pacote

```
foxnfe/
├── __init__.py      # Exports públicos
├── client.py        # Cliente HTTP principal
├── nfe.py           # Módulo NF-e
├── nfse.py          # Módulo NFSe
├── mcp.py           # Módulo MCP
├── types.py         # Dataclasses com tipagem
└── exceptions.py    # Classes de erro
```

## Links

- [Documentação API](https://docs.foxnfe.com.br)
- [Portal FOX NF-e](https://www.foxnfe.com.br)
- [Suporte](mailto:suporte@centralfox.online)

## 1.3.0 — eventos, rejeições, homologação, RTC, cobertura NFS-e e suporte

```python
ev = client.nfe_events
ev.ator_interessado(15, "11222333000181")                       # 110150
ev.insucesso_entrega(15, "2026-09-08T10:00:00-03:00", tp_motivo=1)  # 110192
ev.inutilizar(serie=1, numero_inicial=10, numero_final=12, justificativa="Numeração pulada por falha do ERP")
ev.contratos(); ev.registrar_evento(15, "econf", {...})           # eventos por contrato (conciliação financeira, RTC…)

client.nfe.rejeicao("539"); client.nfe.homologacao_run(65)         # rejeições explicadas / amostras simuladas
client.nfse.cobertura_municipio("2304400")                        # driver, operações e provas
client.rtc.verify_resolution("550e8400-e29b-41d4-a716-446655440000")
client.support.create_case("Webhook sem entrega desde ontem", priority="high")
```

Validação local (ids, dígitos, tamanhos, enums) antes do transporte; regra fiscal fica na API. `NfeResource.rejection` traz a rejeição classificada.


## 1.3.2 — correção do contrato NFS-e e URL base canônica

- `nfse.emit`: corpo alinhado ao `EmitNfseRequest` da API (`competencia`, `cnpj_prestador`, `inscricao_municipal`, `descricao_servico`, `codigo_municipio_prestacao`, `valor_servico`, `tomador`, `prestador.endereco` + opcionais). `ambiente`, `certificate_id` e `servico` saíram do tipo — não existem no contrato NFS-e. Campos nulos não são enviados.
- `nfse.cancel`: envia `justificativa` (15..255, validado antes do envio); `motivo` segue aceito como alias.
- `nfse.substitute`: envia `motivo` (15..255) + campos de emissão; `motivo_cancelamento` segue aceito como alias e não é enviado. Na substituição a API exige `codigo_tributacao_nacional`, `codigo_tributacao_municipal` e `aliquota_iss` (sem rota por `cnae`).
- Respostas de emit/cancel/substitute são `{nfse_id, message}` (202); a consulta usa `nfse_id`.
- URL base padrão: `https://www.foxnfe.com.br/api/v1` (canônico). O host legado `https://foxnfe.centralfox.online/api/v1` continua respondendo e pode ser passado explicitamente.


### Landing e Site IA — SEO 1.1.0 (11/09/2026)
