API pública
API REST (versión v1) para la web propia del negocio, una recepcionista con IA, Zapier, Make y otras integraciones. Las respuestas son JSON con el campo ok; en caso de error, también error. La descripción completa de todas las rutas y campos está en OpenAPI 3.1.
1. Autenticación con clave
Cada solicitud debe incluir la cabecera Authorization: Bearer rk_live_…. La clave se crea en el panel → Claves de API. La clave completa se muestra solo una vez — guárdela en variables de entorno o en un gestor de contraseñas; nosotros solo almacenamos su huella. Cada clave pertenece a un negocio, por eso el negocio no se indica en las solicitudes.
curl "https://app.terminuj.com/api/v1/services" \
-H "Authorization: Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX"2. Permisos de la clave
Al crear la clave elige sus permisos — concédale solo los que la integración realmente necesita.
| Permiso | Rutas (/api/v1/…) |
|---|---|
booking:read | GET /bookings, GET /bookings/{id}, GET /time-off |
booking:write | POST /bookings, PATCH /bookings/{id} (cancelar, cambiar de hora, confirmar), POST /time-off, DELETE /time-off/{id} |
customer:read | GET /customers |
service:read | GET /services, GET /availability |
webhook:manage | GET/POST /webhooks, GET/DELETE /webhooks/{id}, GET /webhooks/{id}/deliveries |
3. Usos más habituales
Horarios libres
Para cada día devuelve si está abierto y las horas libres tras descontar reservas, días libres y pausas del servicio; como pronto, dentro de 2 h. from = día con el formato AAAA-MM-DD (por defecto, hoy), days 1–62 (por defecto, 28), opcionalmente staff = un profesional concreto. La zona horaria es Europe/Bratislava.
curl "https://app.terminuj.com/api/v1/availability?service=<id-sluzby>&from=2026-06-15&days=7" \
-H "Authorization: Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX"Crear una reserva
startAt en UTC con Z al final (12:00Z = 14:00 en verano en Bratislava). El correo del cliente es obligatorio; sin él, solo con "channel": "telefon" (cita tomada por teléfono — basta con el nombre y el teléfono). Las citas del formulario público de su web envíelas con "channel": "web"; si el negocio exige el consentimiento para empezar el servicio antes de que termine el plazo de desistimiento, añada "serviceStartConsent": true según la casilla que haya marcado el cliente (si no, 422 consent_required). Opcionalmente preferredStaffId, customer.notes y locale (sk | en | cs | hu | de | es — idioma de los mensajes de la API y de los demás mensajes al cliente sobre esta reserva; por defecto, el idioma del negocio). Si se vuelve a enviar el mismo servicio y la misma hora con el mismo correo en menos de 10 minutos, se devuelve la misma reserva (doble clic, corte de red).
En las reservas hechas por API, Terminuj no envía el correo de confirmación al cliente — la confirmación la envía su integración. El propietario recibe un aviso según los ajustes del negocio. La reserva cuenta para el total mensual del plan.
curl -X POST "https://app.terminuj.com/api/v1/bookings" \
-H "Authorization: Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
-H "Content-Type: application/json" \
-d '{
"serviceId": "<id-sluzby>",
"startAt": "2026-06-15T12:00:00Z",
"customer": {
"fullName": "Jana Nováková",
"email": "jana@example.com",
"phone": "+421900123456"
}
}'Cancelar, cambiar de hora o confirmar una reserva
Una sola ruta, según el campo action: {"action": "cancel", "reason": "…"}, {"action": "reschedule", "startAt": "…Z"} o {"action": "confirm"} (solo una cita pendiente de señal). Al cancelar, el cliente recibe un correo; al cambiar de hora y al confirmar, si el negocio tiene activados los correos de confirmación.
curl -X PATCH "https://app.terminuj.com/api/v1/bookings/<id-rezervacie>" \
-H "Authorization: Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
-H "Content-Type: application/json" \
-d '{ "action": "cancel", "reason": "Zákazník neprišiel" }'Buscar clientes
search busca en el nombre, el correo y el teléfono.
curl "https://app.terminuj.com/api/v1/customers?search=jana&limit=10" \
-H "Authorization: Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX"Las demás rutas (lista de reservas con el filtro from/to/status/createdFrom, servicios, bloqueo de tiempo con /time-off, webhooks) se describen en OpenAPI.
4. Paginación
Las listas admiten los parámetros ?limit=25&cursor=<id>. Por defecto 25, como máximo 100 por página (reservas, clientes, entregas de webhooks); servicios por defecto 50, como máximo 200. La respuesta contiene { pagination: { nextCursor, hasMore, limit } }.
# Primera página
curl "https://app.terminuj.com/api/v1/bookings?limit=25" -H "Authorization: Bearer rk_live_…"
# Página siguiente (nextCursor de la respuesta anterior)
curl "https://app.terminuj.com/api/v1/bookings?limit=25&cursor=<nextCursor>" -H "Authorization: Bearer rk_live_…"5. Límite de solicitudes
El límite se cuenta por clave en una ventana deslizante de 1 minuto:
- Lectura (booking:read, customer:read, service:read): 600 solicitudes por minuto
- Escritura (booking:write, webhook:manage): 60 solicitudes por minuto
Al superarlo, se recibe 429 rate_limit_exceeded con la cabecera Retry-After (dentro de cuántos segundos volver a intentarlo) y el campo retryAfterSeconds. Las cabeceras X-RateLimit-Limit, X-RateLimit-Remaining y X-RateLimit-Reset llegan con el rechazo.
6. Códigos de error
| HTTP | error | Significado |
|---|---|---|
| 401 | missing_or_invalid_authorization | Falta la cabecera Authorization o la clave no tiene el formato rk_live_ + 24 caracteres |
| 401 | invalid_api_key | No existe esa clave |
| 401 | api_key_revoked | La clave fue revocada en el panel |
| 401 | api_key_expired | La clave ha caducado (expiresAt) |
| 402 | trial_ended | El periodo de prueba del negocio ha terminado — 14 días desde el registro (resetAt = fin de la prueba); nuevas reservas solo tras elegir un plan |
| 402 | monthly_limit_exceeded | El negocio ha agotado el número mensual de reservas de su plan de socio (used, limit, resetAt) |
| 403 | insufficient_scope | La clave no tiene el permiso necesario (required, have) |
| 403 | manual_requires_app | «manual: true» solo lo puede enviar una persona con sesión iniciada en la app Terminuj, no una clave de API |
| 404 | not_found | La reserva, el día libre o el webhook no existe en este negocio (también service_not_found) |
| 409 | slot_conflict | La hora se ha ocupado mientras tanto; al crear la reserva llegan también suggestedAlternatives |
| 409 | already_cancelled | El estado de la reserva no permite el cambio (también already_completed, cannot_cancel, not_reschedulable, already_confirmed, cannot_confirm) |
| 422 | invalid_body | El cuerpo de la solicitud no coincide con el esquema — detalles en detail |
| 422 | consent_required | El negocio exige el consentimiento para empezar el servicio antes de que termine el plazo de desistimiento y falta serviceStartConsent: true |
| 422 | start_in_past | El inicio está en el pasado (también outside_working_hours = fuera del horario de apertura) |
| 429 | rate_limit_exceeded | Se ha superado el límite de solicitudes de la clave — véase la sección 5 |
| 429 | daily_cap_reached | El negocio ha alcanzado el tope diario de reservas públicas (últimas 24 h) — escribir con una clave cuenta igual que el formulario |
7. Webhooks
Si desea que Terminuj le avise por sí mismo de una reserva nueva, cancelada o cambiada de hora, consulte /docs/webhooks — firma HMAC SHA-256, reintentos tras 1 min, 5 min, 30 min y 2 h, ejemplos de código.
Los webhooks también se pueden gestionar por API: GET/POST /api/v1/webhooks, GET/DELETE /api/v1/webhooks/{id} y GET /api/v1/webhooks/{id}/deliveries (registro de entregas).
8. Soporte
Error o solicitud de una nueva función: podpora@terminuj.com.