dotly — SDK oficial de Python para Dotly
Conectá tu sistema con Dotly: contactos y embudo, conversaciones y mensajes de WhatsApp, plantillas, catálogo e inventario, webhooks firmados y Dot AI.
pip install dotly
Python 3.9 o superior. Única dependencia: httpx.
Primeros pasos
- En Dotly, entrá a API & SDK → Aplicaciones, creá una aplicación y una clave con los permisos que
necesitás. La clave (
dtly_live_...) se muestra una sola vez. - Guardala en una variable de entorno, nunca en el código:
export DOTLY_API_KEY="dtly_live_..."
from dotly import Dotly
client = Dotly() # lee DOTLY_API_KEY
contact = client.contacts.create(phone="595981123456", name="Juan Pérez", tags=["vip"])
client.contacts.update(contact["id"], stage="propuesta", deal_value=2_500_000)
for conversation in client.conversations.iter(status="open"):
print(conversation["contact"]["name"], conversation["unread_count"])
client.messages.send(to="595981123456", text="¡Hola Juan! Te escribimos de Acme.")
Cada clave actúa en nombre de un integrante de tu equipo: puede, como mucho, lo que esa persona puede en Dotly y ve solo lo que ella ve. Los cambios quedan en la auditoría a su nombre, marcados como hechos por la API.
Cliente asíncrono
import asyncio
from dotly import AsyncDotly
async def main():
async with AsyncDotly() as client:
page = await client.contacts.list(limit=50)
async for message in client.conversations.iter_messages(conversation_id):
print(message["text"])
asyncio.run(main())
Dotly y AsyncDotly tienen los mismos recursos y métodos.
Recursos
| Recurso | Métodos | Permiso de la clave |
|---|---|---|
client.contacts |
list, iter, create, get, update, delete, conversations |
contacts:read, contacts:write, contacts:delete |
client.tags |
list, create (con color) |
contacts:read, contacts:write |
client.conversations |
list, iter, get, update, set_labels, messages, iter_messages, send_message |
conversations:read, conversations:write, contacts:write, messages:send |
client.messages |
send, get, edit, delete (para todos), react |
messages:send, conversations:read |
client.groups |
preview_invite, join, import_all, reset |
conversations:write |
client.templates |
list, get, flow (menús y submenús), render |
templates:read |
client.members, client.teams, client.whatsapp_accounts |
list |
team:read |
client.scheduled_messages |
list, create, cancel |
conversations:read, messages:send |
client.automations |
list, runs |
automations:read |
client.stats |
get |
analytics:read |
client.account |
get |
account:read |
client.events |
list, iter |
events:read |
client.webhooks |
list, create, get, update, delete, test, rotate_secret |
webhooks:manage |
client.ai |
suggest_reply, summarize, rewrite, translate, usage |
ai:use |
client.integration (= client.catalog) |
ver abajo | contacts:write, catalog:read, catalog:write |
client.embed_sessions |
create, list, get, revoke |
embed:sessions |
Las listas devuelven una Page (data, has_more, next_cursor); iter(...) recorre todas las páginas.
Conector de integración (CRM, ERP, tienda)
Tu sistema manda sus datos identificados por su id (external_id). Repetir un upsert es seguro.
client.integration.contacts.upsert("crm-1042", {
"name": "Juan Pérez",
"phone": "595981123456",
"company": "Acme",
"source_updated_at": "2026-10-19T14:03:00Z", # descarta cambios más viejos que lleguen tarde
})
client.integration.items.upsert("SKU-1042", {"name": "Zapatilla Runner", "price": 450000, "track_stock": True, "stock": 25})
client.integration.inventory.set("SKU-1042", 24)
results = client.integration.contacts.batch([
{"external_id": "crm-1", "data": {"phone": "595981000001", "name": "Ana"}},
{"external_id": "crm-2", "data": {"phone": "595981000002", "name": "Luis"}},
])
failed = [r for r in results["data"] if "error" in r] # las demás se aplicaron igual
- Un contacto que ya escribió por WhatsApp se vincula por teléfono, sin duplicarlo.
- Lo que no enviás queda como está en Dotly (la etapa o las etiquetas que cambió tu equipo no se pisan).
- Sincronización completa del catálogo:
sync_runs.start(), upserts conrun_id=...ysync_runs.complete(run_id, missing="deactivate"). Nunca toca contactos.
Dotly dentro de tu sistema (embebido)
session = client.embed_sessions.create(origin="https://crm.tuempresa.com", modules=["inbox", "contacts"],
member_email="ana@tuempresa.com", theme={"accent": "#ff6600", "radius": 8})
# session["url"]: una sola vez y vence en 2 minutos. Mostrala en un <iframe> o con dotly-sdk/embed (navegador).
El dominio y los módulos se autorizan en Dotly (API & SDK). Dotly se abre con los permisos de esa persona recortados a los módulos pedidos, y solo se deja mostrar en ese dominio.
Webhooks
from dotly.webhooks import construct_event, WebhookVerificationError
event = construct_event(raw_body, request.headers.get("Dotly-Signature"), secret)
- Verificá siempre con el cuerpo crudo (bytes), antes de parsear el JSON.
- Se rechazan firmas inválidas y marcas de tiempo de más de 5 minutos (repeticiones).
- Las entregas son "al menos una vez": guardá
event["id"]y descartá los repetidos. - Ejemplos completos con Flask y FastAPI en
examples/.
Dot AI
suggestion = client.ai.suggest_reply(conversation_id)
print(suggestion["data"]["text"])
Dotly verifica todo en el servidor antes de gastar: suscripción, plan, créditos (aparta el costo máximo y
cobra el real) y los límites de la persona. Si algo falta, recibís una excepción específica
(InsufficientCreditsError, SubscriptionInactiveError, AILimitReachedError, AIUnavailableError...).
El SDK no calcula ni guarda saldos.
Errores y reintentos
Todas las excepciones heredan de dotly.DotlyError y traen status, code, message, details y request_id
(dáselo a soporte si necesitás ayuda).
El SDK reintenta solo lo que es seguro: lecturas y escrituras con Idempotency-Key. Cada escritura lleva una
clave única (la misma en sus reintentos), así un reintento nunca duplica un envío ni cobra dos veces Dot AI.
Reintenta errores de red, timeouts, 429 (respetando Retry-After) y 5xx. Podés pasar tu propia clave con
idempotency_key=... (por ejemplo, el id del pedido en tu sistema).
Configuración
Dotly(
api_key="dtly_live_...", # o DOTLY_API_KEY
timeout=30, # segundos
max_retries=2,
app_info="mi-crm/2.3", # se suma al User-Agent (diagnóstico)
)
La URL base es la de Dotly. Una URL propia tiene que ser https y, si no es de dotly.com.py, hay que permitirla
con allow_custom_base_url=True: así una configuración manipulada no envía tu clave a otro servidor.
Versiones
- El SDK sigue versionado semántico.
0.x: la API v1 está estable; el SDK todavía puede ajustar nombres de métodos en versiones menores (se avisa en el CHANGELOG). dotly.API_VERSIONdice para qué versión de la API está hecho ("1"). La API suma campos sin cambiar de versión: leé lo opcional con.get(...).
Licencia MIT.
Metadata
Release files for dotly 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| dotly-0.1.0.tar.gz | 29.0 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| dotly-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 61.5 kB
Release files / dotly-0.1.0.tar.gz
| Download URL | dotly-0.1.0.tar.gz |
|---|---|
| Size | 29.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c439224949f9d562eff994c8150d0e6ffacdd924401c88f452851603b361ca22
|
|
BLAKE2b-256 checksum How to use checksums |
8f9dde23af253b6d0ada87c8c87a4a234df5a1a946bb93657c23ca00013cb45d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logRelease files / dotly-0.1.0-py3-none-any.whl
| Download URL | dotly-0.1.0-py3-none-any.whl |
|---|---|
| Size | 32.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8ab5de4375b3b67ac20657dac8c4d5df0ce046b00a488bb207bb9e3ea130a590
|
|
BLAKE2b-256 checksum How to use checksums |
26014410ee399a88d0080e69a9dee05c392def8a9ae5a86fd5b689d198421c4d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logSecurity for dotly 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for dotly-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.