Flujo completo de una reserva
Inicio rápido muestra el camino feliz en cinco llamadas.
Esta página muestra el flujo entero que una integración real tiene que manejar:
qué se llama en cada paso, qué hay que guardar de cada respuesta y a qué paso
se vuelve cuando algo falla.
La regla que ordena todo el flujo: nada queda apartado hasta que existe la
reserva. La disponibilidad y la cotización son ciertas en el instante en que
responden. Entre eso y POST /bookings la habitación se puede vender, y la
integración tiene que estar preparada para volver atrás.
El mapa
flowchart TD
A[GET /properties] --> B{api_rate_plans_configured?}
B -- false --> X[El hotel debe crear un plan tarifario en el canal api]
B -- true --> C[GET /units]
C --> D[GET /availability]
D --> E{available: true?}
E -- no --> D2[Otras fechas u otra unidad] --> D
E -- sí --> F[GET /rates]
F --> G[POST /quote]
G --> H{200?}
H -- "422 RATE_PLAN_* / OCCUPANCY_*" --> F
H -- sí --> I{¿Hace falta tiempo antes de comprometerse?}
I -- no --> J[POST /bookings]
I -- sí --> K[POST /bookings con hold_minutes]
J -- "409 UNIT_NOT_AVAILABLE" --> D
J -- 201 --> Z[Reserva pending: mostrar code]
K -- "409 UNIT_NOT_AVAILABLE" --> D
K -- "201 is_hold: true" --> L{¿El huésped se comprometió?}
L -- sí --> M[POST /bookings/id/confirm]
L -- no --> N[POST /bookings/id/cancel]
L -- nadie responde --> O[El barrido libera el hold solo]
M -- "409 BOOKING_HOLD_EXPIRED" --> D
M -- 200 --> Z2[Reserva confirmed]
Antes de empezar
export AXIS_KEY="axis_live_…"
export AXIS="https://api.sandbox.axispro.travel/api/external/v1"Los alcances que pide el flujo completo:
| Paso | Llamada | Alcance |
|---|---|---|
| 1 | GET /properties | properties:read |
| 2 | GET /units, GET /units/{unit} | units:read |
| 3 | GET /availability | availability:read |
| 4–5 | GET /rates, POST /quote | rates:read |
| 6–7 | POST /bookings, POST /bookings/{booking}/confirm | bookings:write |
| 7 | POST /bookings/{booking}/cancel | bookings:cancel |
| 8 | GET /bookings, GET /bookings/{booking} | bookings:read |
Una key sin el alcance recibe 403 APIKEY_SCOPE_MISSING, y el mensaje nombra el
que falta. Si la key cubre varias propiedades, toda llamada desde el paso 2
lleva X-Property: <id de la propiedad> — ver
Alcances y el header X-Property.
1. La propiedad
curl "$AXIS/properties" -H "X-API-Key: $AXIS_KEY"{
"data": [
{
"id": "01a06051-69f0-72f8-8ad6-b46960495eb2",
"name": "Hotel Casa Antigua",
"timezone": "America/Guatemala",
"default_currency": "USD",
"check_in_time": "15:00:00",
"check_out_time": "11:00:00",
"api_rate_plans_configured": true
}
]
}Guardar: id (para X-Property si hace falta), timezone, check_in_time
y check_out_time para mostrarle al huésped.
Si api_rate_plans_configured es false, detenerse aquí. El hotel no tiene
ningún plan tarifario en el canal api, y todo lo que sigue va a volver vacío —
correctamente. Sólo el hotel lo puede arreglar; ver
Planes tarifarios.
2. El catálogo
curl "$AXIS/units" -H "X-API-Key: $AXIS_KEY"{
"data": [
{
"id": "01a06051-69f2-724a-bfe0-57b5b5dab318",
"name": "Deluxe Double",
"is_room_type": false,
"room_kind": "room",
"max_adults": 4,
"max_children": 2,
"max_occupancy": 6,
"size": null,
"thumbnail_url": null
}
],
"meta": { "next_cursor": null, "per_page": 50 }
}Este paso no cambia con cada búsqueda: el catálogo se puede cachear y refrescar
cada tanto. GET /units/{unit} trae el detalle completo — descripción, camas,
amenidades, imágenes — para una página de habitación. La lista se pagina por
cursor; ver Paginación.
Guardar: id, y max_adults / max_children para no ofrecer una ocupación
que la unidad no admite.
3. La disponibilidad
curl -G "$AXIS/availability" -H "X-API-Key: $AXIS_KEY" \
-d start=2027-03-01 -d end=2027-03-04{
"data": [
{
"unit_id": "01a06051-69f2-724a-bfe0-57b5b5dab318",
"name": "Deluxe Double",
"available": true,
"remaining_capacity": 1
}
],
"meta": { "start_date": "2027-03-01", "end_date": "2027-03-04" }
}startes la primera noche yendel día de salida: esto son 3 noches.
La ventana máxima es de 365 días.- Vuelve una fila por unidad vendible. Una unidad sin plan en el canal
apino
aparece comoavailable: false— no aparece. remaining_capacityes1o0para una habitación física, y cuántas quedan
para un Room Type.
Guardar: los unit_id con available: true.
4. Los precios
curl -G "$AXIS/rates" -H "X-API-Key: $AXIS_KEY" \
-d from=2027-03-01 -d to=2027-03-03 \
-d "unit_ids[]=01a06051-69f2-724a-bfe0-57b5b5dab318"{
"data": [
{
"unit_id": "01a06051-69f2-724a-bfe0-57b5b5dab318",
"name": "Deluxe Double",
"rate_plans": [
{
"rate_plan_id": "01a06051-69f4-7072-b4da-9bb17cb00e73",
"name": "Integrations Flexible",
"days": [
{ "date": "2027-03-01", "price": "150.00", "currency": "USD", "min_stay": 1, "max_stay": null },
{ "date": "2027-03-02", "price": "150.00", "currency": "USD", "min_stay": 1, "max_stay": null },
{ "date": "2027-03-03", "price": "150.00", "currency": "USD", "min_stay": 1, "max_stay": null }
]
}
]
}
],
"meta": { "from": "2027-03-01", "to": "2027-03-03" }
}Las fechas no se escriben igual que en el paso 3.
toes la última
noche, incluida. Para la misma estadía del 1 al 4 de marzo,/availability
llevaend=2027-03-04y/ratesllevato=2027-03-03.
- Sólo aparecen planes del canal
api. Una variación ("No reembolsable") llega
como un plan más, con su propiorate_plan_idy su precio final — no hay
descuento que aplicar del lado de la integración. min_stayymax_stayson restricciones reales: una estadía que no las
cumple se rechaza en los pasos 5 y 6.
Guardar: el rate_plan_id que elija el huésped, de los listados bajo esa
unidad. Un plan de otra unidad responde RATE_PLAN_UNIT_NOT_LINKED.
5. La cotización
El cuerpo es exactamente el de la reserva, sin el huésped:
curl -X POST "$AXIS/quote" -H "X-API-Key: $AXIS_KEY" \
-H "Content-Type: application/json" \
-d '{
"unit_id": "01a06051-69f2-724a-bfe0-57b5b5dab318",
"rate_plan_id": "01a06051-69f4-7072-b4da-9bb17cb00e73",
"start_date": "2027-03-01",
"end_date": "2027-03-04",
"adults": 2
}'{
"data": {
"unit_id": "01a06051-69f2-724a-bfe0-57b5b5dab318",
"rate_plan_id": "01a06051-69f4-7072-b4da-9bb17cb00e73",
"start_date": "2027-03-01",
"end_date": "2027-03-04",
"nights": 3,
"quantity": 1,
"currency": "USD",
"subtotal": "450.00",
"total_price": "450.00",
"total_with_tax": "450.00",
"tax_amount": "0.00",
"nightly": [
{ "date": "2027-03-01", "amount": "150.00" },
{ "date": "2027-03-02", "amount": "150.00" },
{ "date": "2027-03-03", "amount": "150.00" }
],
"fees": [],
"available": true
}
}La cotización corre las mismas validaciones que la reserva — plan enganchado a
la unidad, estadía mínima y máxima, ocupación. Un 200 aquí es una estadía que
POST /bookings va a aceptar, mientras la habitación siga libre. available
es informativo y cierto sólo en este instante.
Mostrar: total_with_tax, que es lo que debe el huésped. El dinero llega
como string con dos decimales; parsearlo como float pierde centavos en silencio.
| Si responde | Qué pasó | Volver a |
|---|---|---|
404 UNIT_NOT_FOUND | La unidad dejó de ser vendible por la API | Paso 2 |
404 RATE_PLAN_NOT_FOUND | El plan no está activo en el canal api | Paso 4 |
422 RATE_PLAN_UNIT_NOT_LINKED | El plan no pertenece a esa unidad | Paso 4 |
422 RATE_PLAN_MIN_STAY / RATE_PLAN_MAX_STAY | La estadía no cumple el plan; trae min_stay o max_stay y nights | Paso 3, con otras fechas |
422 OCCUPANCY_OUT_OF_BOUNDS | Más huéspedes de los que admite la unidad | Paso 2 |
422 RATE_PLAN_OCCUPANCY_NOT_CONFIGURED | El plan no tiene precio para esa cantidad de adultos; trae requested_adults y configured_adults | Paso 4, otro plan |
6. La reserva
Aquí el flujo se abre en dos, y la pregunta que decide es: ¿pasa algo entre
que el huésped elige y que se compromete? Una tarjeta que se autoriza, una
segunda pantalla, una aprobación de la empresa.
- No → reserva directa (6a).
- Sí → reserva con hold (6b), y después el paso 7.
6a. Reserva directa
curl -X POST "$AXIS/bookings" -H "X-API-Key: $AXIS_KEY" \
-H "Idempotency-Key: carrito-8842" \
-H "Content-Type: application/json" \
-d '{
"unit_id": "01a06051-69f2-724a-bfe0-57b5b5dab318",
"rate_plan_id": "01a06051-69f4-7072-b4da-9bb17cb00e73",
"start_date": "2027-03-01",
"end_date": "2027-03-04",
"adults": 2,
"guest": {
"first_name": "Ada",
"last_name": "Lovelace",
"email": "[email protected]",
"phone": "+34600000000"
},
"notes": "Llegada tarde, cerca de las 23:00."
}'201 Created:
{
"data": {
"id": "01a06052-324e-72d6-8bbe-7f6fe7dd29f2",
"code": "P8963-000001",
"status": "pending",
"is_hold": false,
"hold_expires_at": null,
"start_date": "2027-03-01",
"end_date": "2027-03-04",
"nights": 3,
"adults": 2,
"children": 0,
"infants": 0,
"currency": "USD",
"total_price": "450.00",
"paid_amount": "0.00",
"balance_due": "450.00",
"unit": { "id": "01a06052-3219-71e2-9537-1b6232c6d145", "name": "Deluxe Double" },
"guest": {
"id": "01a06052-324c-7326-81b9-ea40f3f80f71",
"first_name": "Ada",
"last_name": "Lovelace",
"email": "[email protected]",
"phone": "+34600000000"
},
"created_at": "2026-09-02T04:13:14+00:00"
}
}Guardar: id (para confirmar, cancelar o releer) y code, que es la
referencia que usan el huésped y el hotel — hay que mostrársela al huésped.
Lo que conviene saber de esta respuesta:
Idempotency-Keyes obligatorio. Se deriva de algo estable del sistema
propio — un carrito, una orden — y se reusa en cada reintento del mismo
intento. Ver Idempotencia.- La reserva nace
pendingy sin pagar. Esta API no toma pagos. - El total que cuenta es el de la reserva, no el de la cotización.
- Leer
balance_due; no calculartotal_price − paid_amount. Ver
Reservas. guestsiempre crea un huésped nuevo, aunque el email ya exista. Para
reusar uno, mandarguest_iden su lugar. Uno de los dos es obligatorio.- Una habitación por llamada. Cinco habitaciones son cinco llamadas, cada
una con su propiaIdempotency-Key.
6b. Reserva con hold
El mismo cuerpo, más hold_minutes (de 1 a 30):
curl -X POST "$AXIS/bookings" -H "X-API-Key: $AXIS_KEY" \
-H "Idempotency-Key: carrito-8842" \
-H "Content-Type: application/json" \
-d '{
"unit_id": "01a06051-69f2-724a-bfe0-57b5b5dab318",
"rate_plan_id": "01a06051-69f4-7072-b4da-9bb17cb00e73",
"start_date": "2027-03-01",
"end_date": "2027-03-04",
"adults": 2,
"hold_minutes": 15,
"guest": { "first_name": "Ada", "last_name": "Lovelace", "email": "[email protected]" }
}'{
"data": {
"id": "01a06052-324e-72d6-8bbe-7f6fe7dd29f2",
"code": "P8963-000001",
"status": "pending",
"is_hold": true,
"hold_expires_at": "2027-02-10T14:15:00+00:00"
}
}La habitación queda fuera de la venta desde este momento, pero la reserva no
aparece en las listas del hotel hasta que se confirma. Un hold aparta la
habitación, no congela el precio. Ver Holds.
Si el paso 6 falla
| Si responde | Qué pasó | Qué hacer |
|---|---|---|
409 UNIT_NOT_AVAILABLE | La habitación se vendió después de cotizar. En un Room Type puede traer remaining y requested | Volver al paso 3 |
409 IDEMPOTENCY_IN_FLIGHT | Un reintento propio llegó antes de que terminara el primero | Esperar y reintentar con la misma key |
409 IDEMPOTENCY_KEY_REUSED | La misma key con otro cuerpo | Error de la integración: reserva nueva, key nueva |
422 IDEMPOTENCY_KEY_REQUIRED | Falta el header | Mandarlo |
422 RATE_PLAN_* · OCCUPANCY_OUT_OF_BOUNDS | Igual que en el paso 5 | Ver la tabla del paso 5 |
404 GUEST_NOT_FOUND | El guest_id no existe o fue borrado | Mandar un objeto guest |
429 KEY_BOOKING_QUOTA_EXCEEDED | La key llegó a su tope diario de reservas; trae limit, used y Retry-After | Esperar, o pedirle al hotel que lo suba |
429 KEY_HOLD_LIMIT_REACHED | Sólo con hold: la key ya tiene su máximo de holds vivos | Cerrar holds abiertos (paso 7) |
| Timeout, sin respuesta | No se sabe si la reserva se creó | Reintentar con la misma key y el mismo cuerpo |
Un timeout no es una falla. Con la misma Idempotency-Key y el mismo cuerpo,
si el primer intento creó la reserva vuelve ese mismo 201; si no, este la crea.
Un intento que falló con 4xx o 5xx libera la key, así que se puede corregir el
cuerpo y reintentar con ella.
7. Cerrar el hold
Sólo para el camino 6b. Hay tres salidas.
El huésped se comprometió → confirmar:
curl -X POST "$AXIS/bookings/01a06052-324e-72d6-8bbe-7f6fe7dd29f2/confirm" \
-H "X-API-Key: $AXIS_KEY"200, con la reserva en status: "confirmed", is_hold: false y
hold_expires_at: null.
El huésped abandonó → cancelar:
curl -X POST "$AXIS/bookings/01a06052-324e-72d6-8bbe-7f6fe7dd29f2/cancel" \
-H "X-API-Key: $AXIS_KEY"200, con la reserva en status: "cancelled". La habitación vuelve a la venta
de inmediato y el cupo de holds se libera. Requiere bookings:cancel.
Nadie hace nada → se libera solo. Un barrido cada cinco minutos cancela los
holds vencidos. Funciona, pero el cupo de holds de la key queda ocupado hasta
entonces; cerrar explícitamente es mejor.
Ninguna de las dos llamadas lleva Idempotency-Key: repetir un confirm o un
cancel devuelve el mismo 200.
| Si responde | Qué pasó | Qué hacer |
|---|---|---|
409 BOOKING_HOLD_EXPIRED | El reloj venció antes del confirm; la habitación pudo venderse | Volver al paso 3 |
422 BOOKING_NOT_CONFIRMABLE | La reserva ya no está pending | Releerla (paso 8) |
422 BOOKING_NOT_CANCELLABLE | La estadía ya empezó o terminó | Es decisión del hotel |
cancel también sirve para una reserva directa (6a) mientras siga pending o
confirmed.
8. Releer la reserva
curl "$AXIS/bookings/01a06052-324e-72d6-8bbe-7f6fe7dd29f2" -H "X-API-Key: $AXIS_KEY"Devuelve la misma forma que el paso 6. GET /bookings lista las reservas, con
filtros from, to y status, paginadas por cursor.
Una key sólo ve las reservas que ella misma creó. Una reserva de otra key
responde 404 BOOKING_NOT_FOUND, igual que una que no existe. Para ver todas las
de la propiedad hace falta además bookings:read:all, que el hotel otorga por
separado.
Resumen
GET /properties→ seguir sólo siapi_rate_plans_configuredestrue.GET /units→ catálogo, cacheable.GET /availability→ quedarse con losunit_iddisponibles.GET /rates→ elegir unrate_plan_idde esa unidad, respetando
min_stayymax_stay.POST /quote→ mostrartotal_with_tax.POST /bookingscon unaIdempotency-Keyestable → mostrarcode.
Conhold_minutessi hace falta tiempo antes de comprometerse.- Con hold:
confirmocancel, sin dejarlo vencer. - Ante
409 UNIT_NOT_AVAILABLEo409 BOOKING_HOLD_EXPIRED, volver al
paso 3. Ante un timeout, repetir el paso 6 con la misma key.
Relacionado
- Inicio rápido — el camino feliz, en cinco llamadas.
- Idempotencia — antes de poner en producción algo que
reintente. - Holds — el reloj, el barrido y el techo de holds vivos.
- Errores — todos los códigos, y los
422que no traen
error_code. - Límites de uso — el límite por minuto y los dos techos
de inventario.
Updated 5 days ago