Crear una reserva
POST /api/v1/bookings
El corazón del producto. Aquí no se reescribe ninguna regla de negocio: la disponibilidad, el margen, el horario de apertura, la cuadrícula de tarifas, la moneda, la zona horaria y el límite del plan los aplica el mismo servicio al que llama la aplicación.
La fecha y la hora de inicio son locales de la organización, no instantes: envía
date: "2026-09-12" y startMinute: 540 para las 09:00 locales. Lee settings.timezone en
/api/v1/me si necesitas saber de qué hora local se trata.
Autorización
| Rol en la organización | Este endpoint |
|---|---|
owner | Permitido |
admin | Permitido |
member | Permitido |
El producto da a los miembros el derecho de crear reservas — a menudo son quienes tienen al cliente al teléfono. Rechazarlos aquí haría la API más estricta que la pantalla.
Cuerpo de la petición
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
propertyId | uuid | sí | Una villa activa de la organización |
customerId | uuid | sí | Un cliente de la organización |
date | cadena | sí | Día local YYYY-MM-DD |
startMinute | entero | sí | Minutos desde la medianoche local, p. ej. 540 para las 09:00 |
durationMinutes | entero | sí | Debe tener precio en esta villa |
status | enum | sí | HOLD o CONFIRMED |
priceAmount | entero | no | Precio negociado; por defecto, el importe de la cuadrícula |
depositPercent | entero | no | De 1 a 100; null significa que se debe el precio completo |
Ejemplo
curl -X POST -H "x-api-key: $VILLASLOT_API_KEY" -H "content-type: application/json" \
-d '{"propertyId":"1f0a…","customerId":"8d3e…","date":"2026-09-12","startMinute":540,"durationMinutes":120,"status":"HOLD"}' \
"https://villaslot.app/api/v1/bookings"Respuesta
201 Created— la reserva. UnHOLDllevaholdExpiresAt; unCONFIRMED, no.
Qué rechaza
-
409 Conflict— la reserva no se ha creado, ydetail.reasondice por qué:reasonSignificado OVERLAPLa franja, o su margen, está ocupada CLOSEDLa villa está cerrada ese día OUTSIDE_OPENING_HOURSLa franja cae fuera del horario de apertura INVALID_INTERVALEl fin es anterior al inicio DURATION_NOT_PRICEDEsa duración no tiene tarifa en esta villa CURRENCY_NOT_CONFIGUREDLa organización no ha elegido moneda PROPERTY_NOT_BOOKABLEVilla desconocida, archivada o de otra organización CUSTOMER_NOT_FOUNDCliente desconocido o de otra organización -
409 Conflict— se ha alcanzado el límite de reservas del plan. -
422 Unprocessable Entity— un campo mal formado;detailslos enumera. -
401 Unauthorized— clave ausente o no válida, o clave cuyo portador ha dejado la organización. -
404 Not Found— identificador desconocido, o identificador que pertenece a otra organización. Los dos son deliberadamente indistinguibles. -
429 Too Many Requests— más de 120 peticiones en un minuto para esta clave.
Receta para agentes
Pregunta primero la disponibilidad y luego crea — un POST rechazado cuesta una ida y vuelta y no te dice más
que lo que ya decían las franjas libres:
const {data} = await get(
`/api/v1/properties/${propertyId}/availability?date=2026-09-12&durationMinutes=120`
)
if (data.slots.length === 0) return 'no slot that day'
await post('/api/v1/bookings', {
propertyId,
customerId,
date: '2026-09-12',
startMinute: 540,
durationMinutes: 120,
status: 'HOLD',
})Notas
- Forzar un conflicto no está disponible por la API. Forzar una reserva en una franja ocupada es una decisión humana deliberada, tomada en pantalla después de ver con qué choca.
- Este endpoint todavía no cubre las estancias por noches.
Disponibilidad de Airbnb
Las escrituras correctas pueden devolver warnings genéricos con el código AIRBNB_CONFLICT o AIRBNB_SYNC_UNAVAILABLE. Una política de bloqueo rechaza la operación con el motivo de conflicto correspondiente; forceConflict no puede saltársela. Cuando la última sincronización correcta tiene entre 65 minutos y 3 horas, los conflictos se siguen comprobando contra la última instantánea y se devuelve la advertencia AIRBNB_SYNC_UNAVAILABLE; más allá de 3 horas, los datos bloqueantes provocan un rechazo temporal. No se devuelve ninguna URL de feed, UID ni dato del viajero.
Franja de check-out / check-in
Cuando la organización protege la franja de check-out / check-in (turnoverGuardEnabled), una sesión que empieza
antes de la hora de check-in de la villa, termina después de su hora de check-out y dura menos que
turnoverGuardMinimumMinutes se crea igualmente por la API, con el aviso
{"code": "TURNOVER_WINDOW", "minimumMinutes": 240}. Mover una reserva (POST /api/v1/bookings/{id}/reschedule)
también lo devuelve en los warnings de la reserva. Solo las páginas públicas rechazan esa franja.
Si, el día local de la sesión, una reserva de Airbnb o una estancia por noches activa de la misma villa sale o
llega, la sesión que invade la franja queda afectada sea cual sea su duración: se crea con el aviso
{"code": "TURNOVER_ADJACENT_STAY"}, en lugar de TURNOVER_WINDOW.
Un pago real se conserva cuando se impide la confirmación automática. La reserva mantiene su estado no confirmado y airbnbResolutionRequiredAt; el enlace del cliente indica que hace falta la intervención del equipo. Volver a procesar el pago nunca borra este marcador. Lo resuelve una confirmación explícita tras una nueva comprobación de disponibilidad, o una cancelación explícita. No se emite ningún reembolso automático.