Quickstart
This page walks a booking from a connected user to a confirmed PNR. Assumes you hold a valid access_token — see Authentication for the exchange.
The flow is provider-agnostic across Amadeus, Duffel, Dohop, and Darwin. The partner runs the provider search and submits the offer struct to Movmo.
The flow
Section titled “The flow”Recap of the auth exchange (covered in full on the Authentication page): your backend POSTs the authorization code to /v1/oauth/token and receives an access_token. The booking flow below assumes you hold one.
- Provider search — your code, not Movmo’s.
POST /v1/flights/providers/{provider}/offers— submit the provider offer struct, get back anoffer_id.POST /v1/users/{userid}/passengers— save the passenger if they do not already exist.POST .../offers/{offer_id}/passengers— map the saved passenger to the offer’s provider slot.POST /v1/flights/providers/{provider}/bookings— book with anidempotency_key.GET /v1/users/{userid}/bookings/{booking_id}— read the confirmed booking.
Samples use all-zero UUIDs for clarity. Replace placeholders with your own values.
REST variant
Section titled “REST variant”The helper below checks res.ok (which fetch does not) and unwraps Movmo’s {message, status_code, details} envelope:
const BASE = "https://e2e.api.movmo.io";const userId = "00000000-0000-0000-0000-000000000001";const headers = { "x-api-key": process.env.MOVMO_API_KEY, Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json",};
async function callMovmo(url, init) { const res = await fetch(url, init); const text = await res.text(); let body = null; try { body = text ? JSON.parse(text) : null; } catch { throw new Error(`Movmo non-JSON ${res.status}: ${text.slice(0, 200)}`); } if (!res.ok) { throw Object.assign(new Error(body?.message ?? `HTTP ${res.status}`), { status: res.status, body, }); } return body;}
// 3. Submit provider offer (API-key-only — no user JWT required)const offer = await callMovmo(`${BASE}/v1/flights/providers/duffel/offers`, { method: "POST", headers: { "x-api-key": process.env.MOVMO_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify(providerOfferStruct),});
// 4. Save the passengerconst passenger = await callMovmo(`${BASE}/v1/users/${userId}/passengers`, { method: "POST", headers, body: JSON.stringify({ first_name: "Jane", last_name: "Traveler", date_of_birth: "1990-04-15", gender: "female", email: "jane@example.com", phone_number: "+15550142", nationality: "US", }),});
// 5. Map saved passenger to offer slot — body is a top-level array.await callMovmo( `${BASE}/v1/flights/providers/duffel/offers/${offer.offer_id}/passengers`, { method: "POST", headers, body: JSON.stringify([ { user_passenger_id: passenger.id, provider_passenger_id: offer.passengers[0].provider_passenger_id, }, ]), },);
// 6. Read payment method + populated offer at booking time. Do NOT persist// customer_id; it is server-internal and will be re-pointed under Spreedly.const methods = await callMovmo(`${BASE}/v1/users/${userId}/payment-methods`, { headers,});const card = methods.find((m) => m.isDefault) ?? methods[0];const populated = await callMovmo( `${BASE}/v1/flights/providers/duffel/offers/${offer.offer_id}`, { headers: { "x-api-key": process.env.MOVMO_API_KEY } },);
// 7. Book. Replay this exact request with the same idempotency_key on 5xx.const result = await callMovmo(`${BASE}/v1/flights/providers/duffel/bookings`, { method: "POST", headers, body: JSON.stringify({ offer_id: offer.offer_id, passengers: populated.passengers, contact_info: { first_name: "Jane", last_name: "Traveler", email: "jane@example.com", phone_number: "+15550142", }, payment_method_id: card.id, customer_id: card.customerId, currency: populated.total_currency ?? "USD", user_id: userId, idempotency_key: crypto.randomUUID(), }),});
// 8. Read confirmationconst confirmed = await callMovmo( `${BASE}/v1/users/${userId}/bookings/${result.booking.id}`, { headers },);MCP variant
Section titled “MCP variant”Same flow as discrete tool calls. The Bearer token at transport time identifies the user.
{ "tool": "get_user_profile" }
{ "tool": "search_flights", "input": { "origin": "MCO", "destination": "JFK", "departure_date": "2026-06-15", "adults": 1 } }
{ "tool": "create_offer", "input": { "session_id": "darwin-session-0000", "trip_ref": "darwin-session-0000:0", "fare_class_code": "Y", "adults": 1 } }
{ "tool": "get_offer_details", "input": { "provider": "darwin", "offer_id": "00000000-0000-0000-0000-0000000000aa" } }
{ "tool": "get_passengers_profile", "input": { "user_id": "00000000-0000-0000-0000-000000000001" } }
{ "tool": "update_passenger", "input": { "provider": "darwin", "offer_id": "00000000-0000-0000-0000-0000000000aa", "passenger_id": "00000000-0000-0000-0000-0000000000bb", "passenger_json": "{\"first_name\":\"Jane\",\"last_name\":\"Traveler\",\"date_of_birth\":\"1990-04-15\",\"gender\":\"female\",\"email\":\"jane@example.com\",\"phone_number\":\"+15550142\"}" } }
{ "tool": "get_payment_methods", "input": { "user_id": "00000000-0000-0000-0000-000000000001" } }
{ "tool": "create_booking", "input": { "provider": "darwin", "offer_id": "00000000-0000-0000-0000-0000000000aa", "payment_method_id": "00000000-0000-0000-0000-000000000010" } }
{ "tool": "get_booking", "input": { "provider": "darwin", "booking_id": "00000000-0000-0000-0000-0000000000cc" } }For non-Darwin providers, submit the offer via REST POST .../offers, then continue with get_offer_details and the rest.