Metadata-Version: 2.4
Name: zavora
Version: 0.1.0
Summary: SDK de Python para la API pública de Zavora: pedidos, catálogo, inventario, citas y mensajes.
Author: Zavora
License-Expression: MIT
Project-URL: Homepage, https://zavora.ai
Project-URL: Documentation, https://zavora.ai/developers
Keywords: zavora,commerce,whatsapp,agents,ai,mcp
Classifier: Programming Language :: Python :: 3
Classifier: Intended Audience :: Developers
Classifier: Topic :: Office/Business
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: httpx>=0.24
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
Requires-Dist: respx>=0.20; extra == "dev"

# Zavora · SDK de Python

Tu negocio en Zavora desde Python: pedidos, catálogo, inventario, compradores,
citas y mensajes. Y las herramientas listas para dárselas a un agente.

```bash
pip install zavora
```

## Empezar

```python
from zavora import Zavora

z = Zavora()                      # lee ZAVORA_API_KEY del entorno
yo = z.me()                       # llamada 1: qué puede hacer esta llave
print(yo["tenant"]["name"], yo["scopes"])

for pedido in z.orders.iter_all(status="paid"):
    print(pedido["id"], pedido["total_amount"])
```

La llave se crea en el panel: **Integraciones → API y webhooks**, con los
permisos que quieras. `z.me()` te dice cuáles quedaron concedidos, así sabes
qué puedes hacer antes de intentarlo y chocar con un 403.

## Lo que hay

```python
z.products.list(limit=50)            z.products.get(id)
z.products.create(name="Panela", price=3500)
z.products.update(id, price=4000)

z.orders.list(status="paid")         z.orders.get(id)
z.orders.create(buyer_id=…, items=[{"product_id": …, "quantity": 2}])
z.orders.update(id, status="delivered")        # o dispatch_stage="en_camino"

z.buyers.list()                      z.buyers.get(id)
z.buyers.create(whatsapp_number="+57300…", name="Ana")

z.adjust_inventory(product_id, delta=-3, note="rotura")
z.availability("2026-09-20")
z.schedule(buyer_id=…, scheduled_at="2026-09-20T09:00")
z.send_message(buyer_id, "Tu pedido va en camino 🚚")
```

Cada método vive detrás de su scope. `.iter_all()` recorre todas las páginas
solo — el cursor no se toca a mano.

## Probar sin romper nada

Crea una llave de **prueba** en el panel (`zvk_test_…`) y úsala igual:

```python
z = Zavora("zvk_test_…")
z.me()["sandbox"]          # True
z.orders.create(...)        # responde 201… y no guarda nada
```

Valida todo contra la base de verdad —permisos, plan, topes, stock, aislamiento—
y no confirma ni un cambio. Tampoco le manda WhatsApp a nadie: `send_message`
responde `status: "simulated"`.

## Errores

Cada cosa que puede salir mal tiene su excepción, para que no haya que
comparar strings:

```python
from zavora import SinPermiso, Conflicto, TopeAlcanzado

try:
    z.send_message(buyer_id, "hola")
except SinPermiso:
    print("a esta llave le falta el scope write:messages")
except Conflicto as e:
    if e.code == "window_closed":
        print("pasaron más de 24 h desde su último mensaje")
except TopeAlcanzado as e:
    print("ir más despacio:", e.retry_after, "segundos")
```

Todas heredan de `ErrorZavora`.

## Reintentos

Se reintenta solo lo que no cambió nada: un error de red antes de que el
servidor contestara, un 5xx o un 429 (respetando su `Retry-After`), con
backoff exponencial y jitter.

**Una escritura sin `Idempotency-Key` nunca se reintenta**: si el servidor
alcanzó a aplicarla y la respuesta se perdió, repetirla crearía un segundo
pedido. `create()` manda la clave sola, así que esas sí se reintentan seguras.

## Agentes

```python
from zavora import Zavora
from zavora.agents import ZavoraTools

tools = ZavoraTools(Zavora())

respuesta = claude.messages.create(
    model="claude-opus-5",
    tools=tools.anthropic(),          # o tools.openai()
    messages=[{"role": "user", "content": "¿Cuánto vendimos y qué falta despachar?"}],
)

for bloque in respuesta.content:
    if bloque.type == "tool_use":
        resultado = tools.run(bloque.name, bloque.input)
```

Trece herramientas con descripciones que dicen **cuándo** usar cada una, no
solo qué hacen — que es lo que un modelo necesita para elegir bien.

**Las escrituras ensayan primero.** Con `confirm=False` (el defecto) la
herramienta devuelve lo que *haría* sin tocar nada:

```python
tools.run("zavora_create_order", {"buyer_id": b, "items": [...]})
# {"dry_run": True, "would_do": {...},
#  "next_step": "Enséñaselo a la persona y vuelve a llamar con confirm=true."}
```

El agente enseña el ensayo, la persona aprueba, y recién ahí va
`confirm=True`. `tools.writes()` lista cuáles cambian algo, por si quieres
pedir aprobación humana solo en esas.

`tools.run()` nunca lanza: un error de la API vuelve como `{"error": {...}}`
para que el agente lo lea y reaccione en vez de tumbar el bucle.

## Asíncrono

```python
from zavora import AsyncZavora

async with AsyncZavora() as z:
    async for pedido in z.orders.iter_all():
        ...
```

## Configuración

| Variable | Para qué |
|---|---|
| `ZAVORA_API_KEY` | La llave (`zvk_live_…`). |
| `ZAVORA_API_URL` | Apuntar a otro servidor. Por defecto, producción. |

También por argumento: `Zavora(api_key=…, base_url=…, timeout=20, max_retries=2)`.

## Referencia

La especificación OpenAPI está en `GET /api/v1/openapi.json`, y la guía en
[`docs/integraciones/API-PUBLICA-V1.md`](../docs/integraciones/API-PUBLICA-V1.md).
