API Reference - Celebrae v1
Documentación técnica de la API pública de Celebrae para integraciones server-to-server.
Base URL: https://www.celebrae.com/api/v1
Colección Postman: Descarga celebrae-api-postman.json e impórtala en Postman. Configura las variables base_url (debe incluir /api/v1), client_id y secret en la colección.
Índice
Autenticación
Todas las peticiones a /api/v1/* requieren autenticación server-to-server.
Formato
Authorization: Bearer <client_id>:<secret>
client_id: Identificador del API client (ej.ce_a1b2c3d4...)secret: Secreto compartido (solo se muestra una vez al crear el client)
Ejemplo
curl -H "Authorization: Bearer ce_abc123:tu_secreto_aqui" \
https://api.celebrae.com/api/v1/shops
Obtención de credenciales
Si tienes un local en Celebrae, crea tus credenciales desde el panel de seller: Mi local → API y webhooks → Credenciales. El client_id y el secret se muestran una sola vez al crear.
# Crear client de prueba (solo desarrollo/admin)
npx tsx scripts/seed-api-client.ts <shop_id>
Scopes
Cada API client tiene scopes que definen qué puede hacer:
| Scope | Permisos |
|---|---|
shops:read | Listar y ver detalle de shops |
shops:write | (Reservado) |
experiences:read | Listar y ver experiencias y extras |
experiences:write | (Reservado) |
bookings:read | Ver reservas |
bookings:write | Crear reservas |
webhooks:read | Ver estado de deliveries y listar endpoints |
webhooks:write | Crear, actualizar y eliminar endpoints de webhook |
Multi-tenant
Un API client puede tener acceso a varios shops (negocios). La asociación se gestiona en api_client_shops.
Errores
La API usa RFC 7807 Problem Details.
Formato
{
"type": "https://api.celebrae.com/errors/404",
"title": "Not Found",
"status": 404,
"detail": "Resource not found",
"instance": "/api/v1/shops/xxx",
"code": "NOT_FOUND"
}
Códigos HTTP
| Status | Significado |
|---|---|
| 200 | OK |
| 201 | Created |
| 400 | Bad Request - Validación fallida |
| 401 | Unauthorized - Credenciales inválidas o faltantes |
| 403 | Forbidden - Sin permiso (scope o tenant) |
| 404 | Not Found - Recurso no existe o no tienes acceso |
| 409 | Conflict - Idempotency key ya usada con otro body |
| 429 | Too Many Requests - Rate limit excedido |
| 500 | Internal Server Error |
Rate limit y headers
Planes homologados: free (no consume por API), nitro, pro, enterprise. El plan del API client determina los límites.
X-RateLimit-Limit: Límite por minuto (ej. 100 para pro).X-RateLimit-Remaining: Peticiones restantes en la ventana actual.X-RateLimit-Reset: Timestamp Unix (segundos) de cuándo se resetea el contador.x-request-id: ID de correlación (incluye en soporte si reportas un error).
Códigos de error comunes
| code | Descripción |
|---|---|
INSUFFICIENT_SCOPE | Falta el scope requerido |
IDEMPOTENCY_KEY_REQUIRED | POST /bookings requiere header Idempotency-Key |
IDEMPOTENCY_CONFLICT | Misma key con body distinto |
VALIDATION_ERROR | Body inválido |
NOT_FOUND | Recurso no encontrado |
Shops
GET /shops
Lista los shops a los que tiene acceso el API client.
Scope: shops:read
Query params:
| Param | Tipo | Default | Descripción |
|---|---|---|---|
| limit | number | 20 | Máx 100 |
| offset | number | 0 | Paginación |
Respuesta 200:
{
"shops": [
{
"id": "uuid",
"name": "Mi Local",
"slug": "mi-local",
"short_description": "...",
"address": "...",
"country": "ES",
"currency": "EUR",
"email": "contacto@local.com",
"phone": "+34...",
"website": "https://...",
"created_at": "2024-01-15T10:00:00Z"
}
],
"total": 1
}
GET /shops/:id
Detalle de un shop.
Scope: shops:read
Respuesta 200:
{
"id": "uuid",
"name": "Mi Local",
"slug": "mi-local",
"description": "...",
"short_description": "...",
"address": "...",
"address_line_2": "...",
"country": "ES",
"currency": "EUR",
"email": "...",
"phone": "...",
"website": "...",
"created_at": "2024-01-15T10:00:00Z"
}
Experiencias
GET /shops/:id/experiences
Lista experiencias (kits) del shop.
Scope: experiences:read
Query params:
| Param | Tipo | Default | Descripción |
|---|---|---|---|
| limit | number | 20 | Máx 100 |
| offset | number | 0 | Paginación |
| status | string | - | active, paused, draft, archived |
Respuesta 200:
{
"experiences": [
{
"id": "uuid",
"title": "Tour guiado",
"slug": "tour-guiado",
"short_description": "...",
"base_price": 25.00,
"currency": "EUR",
"duration_minutes": 120,
"status": "active",
"created_at": "2024-01-15T10:00:00Z"
}
]
}
GET /experiences/:id
Detalle de una experiencia.
Scope: experiences:read
Query params:
| Param | Valor | Descripción |
|---|---|---|
| include | extras | Incluir lista de extras |
Respuesta 200:
{
"id": "uuid",
"title": "Tour guiado",
"slug": "tour-guiado",
"description": "...",
"short_description": "...",
"base_price": 25.00,
"currency": "EUR",
"duration_minutes": 120,
"base_capacity": 4,
"max_capacity": 10,
"status": "active",
"shop_id": "uuid",
"created_at": "2024-01-15T10:00:00Z",
"extras": [
{
"id": "uuid",
"name": "Fotos",
"price": 5.00,
"description": "..."
}
]
}
GET /experiences/:id/extras
Lista extras de la experiencia.
Scope: experiences:read
Query params:
| Param | Tipo | Default |
|---|---|---|
| limit | number | 50 |
| offset | number | 0 |
Respuesta 200:
{
"extras": [
{
"id": "uuid",
"name": "Fotos",
"description": "...",
"price": 5.00,
"category": "Servicios",
"max_quantity": 10
}
]
}
Bookings
GET /bookings
Lista reservas de los shops del partner.
Scope: bookings:read
Query params:
| Param | Tipo | Default | Descripción |
|---|---|---|---|
| shop_id | UUID | - | Filtrar por shop (debe estar en tus shops) |
| status | string | - | pending, confirmed, completed, cancelled |
| date_from | string | - | Fecha desde (YYYY-MM-DD) |
| date_to | string | - | Fecha hasta (YYYY-MM-DD) |
| limit | number | 20 | Máx 100 |
| offset | number | 0 | Paginación |
Respuesta 200:
{
"bookings": [
{
"id": "uuid",
"order_id": "uuid",
"kit_id": "uuid",
"booking_date": "2024-06-15",
"booking_time": "10:00:00",
"number_of_people": 2,
"total_price": 60.00,
"status": "pending",
"created_at": "2024-01-15T10:00:00Z"
}
],
"total": 42
}
GET /bookings/:id
Detalle de una reserva.
Scope: bookings:read
Respuesta 200:
{
"id": "uuid",
"order_id": "uuid",
"kit_id": "uuid",
"booking_date": "2024-06-15",
"booking_time": "10:00:00",
"number_of_people": 2,
"price_per_person": 25.00,
"extras_total": 10.00,
"total_price": 60.00,
"status": "pending",
"selected_extras": [...],
"special_requests": "...",
"created_at": "2024-01-15T10:00:00Z",
"order": { ... },
"kit": { ... }
}
POST /bookings
Crea una reserva. Requiere header Idempotency-Key.
Scope: bookings:write
Headers:
| Header | Requerido | Descripción |
|---|---|---|
| Idempotency-Key | Sí | UUID o string único para evitar duplicados |
Body:
{
"kit_id": "uuid",
"shop_id": "uuid",
"booking_date": "2024-06-15",
"booking_time": "10:00",
"number_of_people": 2,
"buyer_name": "Juan Pérez",
"buyer_email": "juan@ejemplo.com",
"buyer_phone": "+34600000000",
"selected_extras": [
{ "extra_id": "uuid", "quantity": 1 }
],
"special_requests": "Sin gluten"
}
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
| kit_id | UUID | Sí | ID de la experiencia |
| shop_id | UUID | Sí | ID del shop (debe estar en tus shops) |
| booking_date | string | Sí | YYYY-MM-DD |
| booking_time | string | Sí | HH:mm o HH:mm:ss |
| number_of_people | number | Sí | 1-100 |
| buyer_name | string | Sí | Máx 200 |
| buyer_email | string | Sí | Email válido |
| buyer_phone | string | No | Máx 50 |
| selected_extras | array | No | [{ extra_id, quantity }] |
| special_requests | string | No | Máx 1000 |
Nota: Las reservas creadas vía API tienen payment_source: 'external'. El pago no se gestiona en Celebrae.
Restricciones de reservas con payment_source: 'external':
- No pasan por el checkout de Celebrae; el pago se gestiona fuera de la plataforma.
- El
ordertienepayment_status: 'pending'(o'external'); no se genera flujo de pago CELEBRAE. - QR y validación: consulta la documentación del producto para reglas específicas de generación de QR y validación en punto de venta.
- Notificaciones: se dispara el webhook
booking.createdpara sincronización; las notificaciones por email pueden variar según configuración. - Integraciones externas (Turitop, Octo, etc.): pueden tener restricciones adicionales; contacta a soporte si necesitas integración con proveedores externos.
Respuesta 201:
{
"booking": {
"id": "uuid",
"order_id": "uuid",
"kit_id": "uuid",
"booking_date": "2024-06-15",
"booking_time": "10:00:00",
"number_of_people": 2,
"total_price": 60.00,
"status": "pending",
"created_at": "2024-01-15T10:00:00Z"
}
}
Idempotencia: Si repites la misma petición con la misma Idempotency-Key y el mismo body, recibirás la misma respuesta sin crear duplicados. Si el body es distinto, recibirás 409 Conflict.
Webhooks
GET /webhooks/deliveries
Lista intentos de envío de webhooks salientes.
Scope: webhooks:read
Query params:
| Param | Descripción |
|---|---|
| event_id | Filtrar por evento |
| endpoint_id | Filtrar por endpoint |
| status | pending, delivered, failed |
| limit | 20 (default), máx 100 |
| offset | 0 |
Respuesta 200:
{
"deliveries": [
{
"id": "uuid",
"event_id": "uuid",
"endpoint_id": "uuid",
"attempt_number": 1,
"status": "delivered",
"response_status": 200,
"error_message": null,
"created_at": "2024-01-15T10:00:00Z"
}
],
"total": 1
}
GET /webhooks/deliveries/:id
Detalle de un intento de envío.
Scope: webhooks:read
POST /webhooks/deliveries/:id/retry
Reintenta manualmente un envío fallido. Solo administradores (sesión Celebrae, no API client).
Webhooks salientes (consumir)
Cuando ocurren eventos (ej. booking.created), Celebrae envía un POST a la URL configurada.
Headers
| Header | Descripción |
|---|---|
| X-Celebrae-Signature | HMAC-SHA256 de timestamp + "." + body |
| X-Celebrae-Timestamp | Unix timestamp (segundos) |
| X-Celebrae-Event-Id | ID del evento |
| X-Celebrae-Idempotency-Key | (Opcional) Para deduplicar en tu lado |
| Content-Type | application/json |
Verificación de firma
const crypto = require('crypto');
const payload = `${timestamp}.${rawBody}`;
const expected = crypto.createHmac('sha256', secret).update(payload).digest('hex');
const valid = crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
Eventos
| event_type | Cuándo |
|---|---|
| booking.created | Se crea una reserva |
| booking.updated | Se actualiza una reserva (ej. confirmación) |
| booking.completed | Se marca una reserva como completada |
| booking.cancelled | Se cancela una reserva |
| * | Todos los eventos |
Ejemplo de payload (booking.created)
{
"booking_id": "uuid",
"order_id": "uuid",
"kit_id": "uuid",
"shop_id": "uuid",
"booking_date": "2024-06-15",
"booking_time": "10:00",
"number_of_people": 2,
"status": "pending",
"payment_source": "external"
}
Configuración de endpoints
Puedes registrar tus endpoints de webhook vía API (scope webhooks:write):
- POST /webhooks/endpoints — Crear endpoint. Body:
{ shop_id, url, events?: ["booking.created", "booking.updated", "booking.completed", "booking.cancelled", "*"] }. Devuelvesigning_secret(guárdalo, no se muestra de nuevo). - GET /webhooks/endpoints — Listar endpoints. Query:
shop_id(opcional). - PATCH /webhooks/endpoints/:id — Actualizar URL, eventos o status (
active,paused,disabled). - DELETE /webhooks/endpoints/:id — Eliminar endpoint.
También puedes gestionarlos desde el panel seller: Mi local → API y webhooks → Webhooks.
Resumen de endpoints
| Método | Ruta | Scope |
|---|---|---|
| GET | /shops | shops:read |
| GET | /shops/:id | shops:read |
| GET | /shops/:id/experiences | experiences:read |
| GET | /experiences/:id | experiences:read |
| GET | /experiences/:id/extras | experiences:read |
| GET | /bookings | bookings:read |
| GET | /bookings/:id | bookings:read |
| POST | /bookings | bookings:write |
| GET | /webhooks/deliveries | webhooks:read |
| GET | /webhooks/deliveries/:id | webhooks:read |