Metadata-Version: 2.4
Name: arplyx
Version: 0.5.0
Summary: SDK oficial de Arplyx: enviÃ¡ mensajes y broadcasts de WhatsApp desde Python.
Author-email: Arplyx <info@arplyx.com>
License: MIT
Project-URL: Homepage, https://docs.arplyx.com/api/
Project-URL: Documentation, https://docs.arplyx.com/api/
Keywords: arplyx,whatsapp,whatsapp-api,sdk,mensajeria,broadcasts
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: requests>=2.25

# arplyx (Python SDK)

SDK oficial de **Arplyx** para Python: enviá mensajes y broadcasts de WhatsApp y consultá estados con una API tipada.

## Instalación

```bash
pip install arplyx
```

Requiere Python 3.8+.

## Uso

```python
from arplyx import Arplyx, ArplyxError

client = Arplyx(api_key="ak_live_...")

# Iniciar la conversación con una plantilla aprobada (canal whatsapp_meta, el default)
msg = client.send_message(
    external_id="pedido-10045",
    to="+5491155551234",
    template={"name": "pedido_confirmado", "language": "es_AR", "body": ["Juan", "10045"]},
)
print(msg.message_id, msg.status)

# Texto libre: solo dentro de la ventana de 24 h que abre el contacto al escribirte
client.send_message(
    external_id="respuesta-10045",
    to="+5491155551234",
    text="Tu pedido fue confirmado.",
)

# Consultar estado (por message_id o por tu external_id)
status = client.get_message("pedido-10045")
print(status.status)  # pending | queued | sent | delivered | read | failed
```

### Plantillas (Meta Official)

```python
client.send_message(
    external_id="pedido-10045",
    to="+5491155551234",
    channel="whatsapp_meta",
    template={"name": "pedido_confirmado", "language": "es_AR", "body": ["Juan", "10045"]},
)
```

Si la plantilla usa parámetros nombrados, `body` es una lista de `{"name": ..., "text": ...}` (solo BODY; el header sigue posicional y los botones no se soportan: un `buttons` no vacío responde `400`).

### Catálogo de plantillas

Requiere una API key con `templateAccess` (`read` para listar, `manage` para crear) y que la cuenta tenga credencial de gestión cargada por el operador.

```python
page = client.list_templates(account_id, status="APPROVED")
for t in page.templates:
    print(t.name, t.language, t.status, t.parameter_format)

created = client.create_template(
    account_id,
    name="turno_recordatorio",
    language="es_AR",
    category="UTILITY",
    body="Hola {{nombre}}, te recordamos tu turno el {{fecha}}.",
    examples=[{"name": "nombre", "text": "Juan"}, {"name": "fecha", "text": "13/10"}],
)
print(created.template.status)  # PENDING: la aprueba Meta
```

### Diagnóstico de un envío

`get_message` devuelve, además de los estados, `provider_message_id` (wamid), `processing` (reintentos internos) y `error` con el `code` de Arplyx y el detalle saneado del proveedor (`provider.code`, `subcode`, `fbtrace_id`). Un 201 al enviar no prueba entrega: mirá `status`.

### Imágenes y documentos

El archivo se referencia por URL pública HTTPS (Arplyx no lo almacena). Imágenes JPEG/PNG hasta 5 MB; documentos PDF/Office/texto hasta 20 MB. `caption` y `filename` son opcionales. Como el texto libre, por Meta Official un adjunto solo se entrega dentro de la ventana de 24 h.

```python
client.send_message(
    external_id="pedido-10045-foto",
    to="+5491155551234",
    image={"url": "https://cdn.tuempresa.com/pedidos/10045/foto.jpg", "caption": "Tu pedido salió a reparto."},
)

client.send_message(
    external_id="factura-A-0001-00012345",
    to="+5491155551234",
    document={
        "url": "https://cdn.tuempresa.com/facturas/A-0001-00012345.pdf",
        "filename": "Factura A-0001-00012345.pdf",
        "caption": "Te adjunto la factura de este mes.",
    },
)
```

Si la URL no se puede descargar, el mensaje termina en `failed` con `last_error_code: MEDIA_UNAVAILABLE` (sin reintentos).

#### Adjuntar el archivo directamente (sin URL)

Pasá `path` (ruta local) o `file` (bytes o file-like) y el SDK lo sube a Arplyx (`POST /media`) antes de enviar. Arplyx guarda el archivo solo hasta que el mensaje termina de procesarse.

```python
client.send_message(
    external_id="factura-A-0001-00012345",
    to="+5491155551234",
    document={"path": "./factura.pdf", "filename": "Factura A-0001-00012345.pdf"},
)

# O en dos pasos, para reusar el media_id en varios envíos:
media = client.upload_media("./promo.jpg")
client.send_message(external_id="promo-1", to="+549...", image={"media_id": media.media_id})
```

### Broadcast a una lista (planes Basic/Pro)

Los broadcasts son texto libre: por Meta Official solo llegan a los contactos con la ventana de 24 h abierta (al resto les queda `failed` con `OUTSIDE_WINDOW`). Para iniciar conversaciones enviá una plantilla con `send_message`.

```python
client.send_broadcast(
    external_id="promo-junio-2026",
    list_id="TU_LIST_ID",
    text="Gracias por escribirnos: ya podés reservar tu turno desde la web.",
    channel="whatsapp_meta",
)
```

### Listar cuentas de WhatsApp

```python
for acc in client.list_whatsapp_accounts():
    print(acc.id, acc.type, acc.status)  # acc.id se usa como whatsapp_account_id
```

### ¿Este número ya me escribió? (opt-in)

Para confirmar el opt-in, pedile a la persona que te escriba primero y
verificalo antes de habilitarla como destino:

```python
sender = client.get_inbound_sender("+5493411234567")
if sender.has_written:
    print(sender.first_inbound_at, sender.inbound_count)

# Batch (máx 200 números por request)
senders = client.lookup_inbound_senders(["+5493411234567", "+5491155551234"])
```

El dato sobrevive a la retención de mensajes y está disponible en todos los
planes. "Nunca escribió" devuelve ``has_written=False`` (no lanza).

### Listar mensajes entrantes (plan Pro)

```python
page = client.list_inbound_messages(from_phone="+5493411234567")
for m in page.inbound_messages:
    print(m.received_at, m.text)
```

## Manejo de errores

```python
try:
    client.send_message(external_id="x", to="+549...", text="hola")
except ArplyxError as e:
    print(e.status)   # 400, 401, 409, 422, 429, 500...
    print(e.code)     # 'validation_error', 'conflict', 'quota_exceeded'...
    print(e.details)  # detalles por campo si es validation_error
```

## Idempotencia

Todos los envíos usan tu `external_id` como clave de idempotencia: reintentar con el mismo `external_id` y payload no duplica el envío. Es seguro reintentar ante timeouts.

## Configuración

| Parámetro | Requerido | Default |
|---|---|---|
| `api_key` | Sí | — |
| `base_url` | No | `https://api.arplyx.com` |
| `timeout` | No | `10.0` |
| `session` | No | `requests.Session()` nueva |

Docs de la API: https://docs.arplyx.com/api/ · OpenAPI: https://api.arplyx.com/openapi.yaml
