API de DevRoam
Conecta Roa con tu CRM, tu web o tus automatizaciones: consulta huecos libres, crea y cambia citas, y recibe un webhook cada vez que Roa reserva, atiende una llamada o toma un recado.
La API y los webhooks están incluidos en los planes Profesional y Avanzado. ¿Sin código? Mira la guía de Zapier y Make.
Inicio rápido
- En la app ve a Ajustes → API y pulsa Crear clave. Cópiala: empieza por
dr_live_y no volveremos a mostrarla. - Guárdala como variable de entorno
DEVROAM_API_KEY(nunca en el código del navegador). - Pide los huecos libres de un día:
curl "https://app.devroam.com/api/v1/availability?date=2026-11-03" \
-H "Authorization: Bearer $DEVROAM_API_KEY"const res = await fetch("https://app.devroam.com/api/v1/availability?date=2026-11-03", {
headers: { Authorization: `Bearer ${process.env.DEVROAM_API_KEY}` },
});
console.log(await res.json());import os, requests
r = requests.get("https://app.devroam.com/api/v1/availability?date=2026-11-03",
headers={"Authorization": f"Bearer {os.environ['DEVROAM_API_KEY']}"})
print(r.json())Respuesta: las horas libres de ese día en hora local del negocio, listas para mostrarlas o reservar una con POST /api/v1/appointments.
Autenticación
Envía la clave en cada petición con la cabecera Authorization: Bearer dr_live_…. Cada clave pertenece a un negocio; puedes crear varias (una por integración) y revocarlas cuando quieras desde Ajustes. Una clave revocada deja de funcionar al momento (401).
Fechas y horas
Todas las horas son hora local del negocio con el formato YYYY-MM-DD HH:MM (por ejemplo 2026-11-03 17:30). No envíes zona horaria ni conviertas a UTC: DevRoam aplica la zona del negocio y los cambios de horario de verano.
Endpoints
Base URL: https://app.devroam.com
| Método | Ruta | Descripción |
|---|---|---|
| GET | /api/v1/business | Datos del negocio (nombre, sector, zona horaria…). |
| GET | /api/v1/services | Servicios con nombre, precio, duración y margen. |
| GET | /api/v1/staff | Profesionales del equipo (id y nombre). Usa el id como staff_id. |
| GET | /api/v1/availability | Huecos libres de un día. Usa el mismo motor que Roa y la página de reservas: horario y turnos partidos, festivos, bloqueos, antelación, capacidad y citas existentes.date (YYYY-MM-DD, obligatorio) · staff_id · service · party_size · area |
| GET | /api/v1/appointments | Lista citas o reservas.from · to · status · limit |
| POST | /api/v1/appointments | Crea una cita. Devuelve 201 o 409 si el hueco ya no está libre.Cuerpo: customer_name*, start_local*, customer_phone, service, staff_id, notes, party_size, area, notify_customer (por defecto true) |
| GET | /api/v1/appointments/{id} | Una cita. |
| PATCH | /api/v1/appointments/{id} | Cambia hora, datos o notas. Mismos campos que al crear. |
| DELETE | /api/v1/appointments/{id} | Cancela la cita. |
| GET | /api/v1/calls | Últimas llamadas atendidas por Roa.limit |
| GET | /api/v1/calls/{id} | Una llamada con su transcripción. |
| GET | /api/v1/customers | Clientes conocidos (llamantes).q · limit |
| GET | /api/v1/messages | Recados tomados por Roa.limit |
Ejemplo: listar los servicios
curl "https://app.devroam.com/api/v1/services" \
-H "Authorization: Bearer $DEVROAM_API_KEY"const res = await fetch("https://app.devroam.com/api/v1/services", {
headers: { Authorization: `Bearer ${process.env.DEVROAM_API_KEY}` },
});
console.log(await res.json());import os, requests
r = requests.get("https://app.devroam.com/api/v1/services",
headers={"Authorization": f"Bearer {os.environ['DEVROAM_API_KEY']}"})
print(r.json())Crear una cita
Comprueba antes la disponibilidad o gestiona el 409: si el hueco se ha ocupado entretanto, DevRoam no crea la cita. Con notify_customer (por defecto true) el cliente recibe la confirmación como si hubiera reservado con Roa.
curl -X POST https://app.devroam.com/api/v1/appointments \
-H "Authorization: Bearer $DEVROAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"customer_name":"Ana López","customer_phone":"612345678","service":"Corte","start_local":"2026-11-03 17:30"}' const res = await fetch("https://app.devroam.com/api/v1/appointments", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.DEVROAM_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
customer_name: "Ana López",
customer_phone: "612345678",
service: "Corte",
start_local: "2026-11-03 17:30",
}),
});
if (res.status === 409) console.log("Ese hueco ya no está libre");
else console.log(await res.json());import os, requests
r = requests.post("https://app.devroam.com/api/v1/appointments",
headers={"Authorization": f"Bearer {os.environ['DEVROAM_API_KEY']}"},
json={"customer_name": "Ana López", "customer_phone": "612345678",
"service": "Corte", "start_local": "2026-11-03 17:30"})
print(r.status_code, r.json())Webhooks
En Ajustes → Webhooks añade una URL HTTPS y elige los eventos. DevRoam envía un POST JSON con id, type, created, business_id y data.
booking.created | Cita creada (por Roa, la página de reservas, el panel o la API) |
booking.changed | Cita cambiada |
booking.cancelled | Cita cancelada |
call.completed | Llamada terminada |
message.received | Recado nuevo |
test.ping | Botón «Enviar prueba» |
Verificar la firma
Cada envío lleva la cabecera DevRoam-Signature: t=<unix>,v1=<hex>, donde v1 es HMAC-SHA256 con tu secreto de webhook sobre t + "." + cuerpo. Verifícala con el cuerpo sin modificar:
# Cabecera recibida:
# DevRoam-Signature: t=1761231000,v1=5f2c…
# v1 = HMAC-SHA256(secreto, "<t>.<cuerpo sin modificar>") en hexadecimalimport crypto from "node:crypto";
function verify(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
return fresh && crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}import hmac, hashlib, time
def verify(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body,
hashlib.sha256).hexdigest()
fresh = abs(time.time() - int(parts["t"])) < 300
return fresh and hmac.compare_digest(parts["v1"], expected)Reintentos
Responde con un 2xx en menos de 8 segundos. Si no, DevRoam reintenta a los 1 min, 5 min, 30 min, 2 h y 6 h. Usa el id del evento para ignorar duplicados.
Errores y límites
400 | Petición no válida (falta un campo o el formato de fecha no es correcto) |
401 | Clave ausente, no válida o revocada |
403 | Tu plan no incluye API o la suscripción no está activa |
404 | No existe (o no es de tu negocio) |
409 | El hueco no está disponible |
429 | Más de 120 peticiones por minuto con la misma clave. Espera los segundos de la cabecera Retry-After |
Los errores devuelven JSON: {{"error":{{"status":401,"code":"invalid_api_key","message":"…","docs":"…"}}}}. Usa code en tu lógica; message es legible y llega en inglés si envías Accept-Language: en.
Especificación OpenAPI
Descarga la especificación OpenAPI 3.1 para generar un cliente o importarla en Postman o Insomnia: openapi.json.