{"openapi":"3.1.0","info":{"version":"1.0.0","title":"Ultra Network API","description":"The headless Ultra Network API. Stable v1 contract covering trips, suppliers, bookings, and supply-side data. The same spec powers the MCP server and the official `ultra` CLI.\n\nSee https://ultranetwork.co/developers for guides.","contact":{"name":"Ultra Support","url":"https://ultranetwork.co/contact"},"license":{"name":"Proprietary","url":"https://ultranetwork.co/legal/api-terms"}},"servers":[{"url":"https://ultranetwork.co/api/v1","description":"Ultra Network"}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","bearerFormat":"Ultra API Key (ulk_…)","description":"Pass your Ultra API key as `Authorization: Bearer ulk_…`. Keys are minted by org admins from /workspace/settings/api-keys. Treat them like passwords — never embed in client code."}},"schemas":{"Money":{"type":"object","properties":{"amount":{"type":"integer","description":"Minor units (cents). 10000 = USD 100.00.","example":10000},"currency":{"type":"string","minLength":3,"maxLength":3,"description":"ISO 4217 currency code.","example":"USD"}},"required":["amount","currency"]},"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["unauthenticated","forbidden","not_found","method_not_allowed","validation_failed","rate_limited","conflict","internal_error","upstream_unavailable","feature_disabled"]},"message":{"type":"string","example":"No API key supplied. Pass it as `Authorization: Bearer …`."},"request_id":{"type":"string","description":"Same value as the X-Request-Id response header.","example":"req_01HKZE6Y8N4P7Q"},"details":{"type":"object","additionalProperties":{},"description":"Optional structured context (e.g. Zod issues for validation_failed)."}},"required":["code","message","request_id"]}},"required":["error"]},"PageMeta":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Cursor for the next page. `null` when no more results."},"has_more":{"type":"boolean"},"limit":{"type":"integer"}},"required":["next_cursor","has_more","limit"]},"Trip":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"550e8400-e29b-41d4-a716-446655440000"},"organization_id":{"type":"string","format":"uuid","example":"550e8400-e29b-41d4-a716-446655440000"},"title":{"type":["string","null"],"example":"Cartagena anniversary, June"},"stage":{"type":"string","enum":["enquiry_received","qualifying","draft_plan","proposal_sent","negotiating","booked","follow_up","in_progress","completed","lost","archived"]},"priority":{"type":"string","enum":["low","medium","high","urgent"]},"source":{"type":"string","enum":["whatsapp","sms","email","web_form","widget","manual","phone"]},"destination":{"type":["string","null"],"example":"Colombia"},"origin_city":{"type":["string","null"],"example":"New York"},"start_date":{"type":["string","null"],"format":"date","pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-06-12"},"end_date":{"type":["string","null"],"format":"date","pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-06-12"},"party_size":{"type":"integer","minimum":1,"example":2},"budget":{"allOf":[{"$ref":"#/components/schemas/Money"},{"type":["object","null"],"description":"Indicative budget. May be advisor-internal — clients without view access see `null`."}]},"client":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid","example":"550e8400-e29b-41d4-a716-446655440000"},"name":{"type":"string"}},"required":["id","name"],"description":"Lead traveller. PII (email, phone) is intentionally omitted from v1."},"created_at":{"type":"string","format":"date-time","example":"2026-06-12T14:30:00Z"},"updated_at":{"type":"string","format":"date-time","example":"2026-06-12T14:30:00Z"},"booked_at":{"type":["string","null"],"format":"date-time","example":"2026-06-12T14:30:00Z"},"completed_at":{"type":["string","null"],"format":"date-time","example":"2026-06-12T14:30:00Z"}},"required":["id","organization_id","title","stage","priority","source","destination","origin_city","start_date","end_date","party_size","budget","client","created_at","updated_at","booked_at","completed_at"]},"CreateTripInput":{"type":"object","properties":{"client_id":{"type":"string","format":"uuid","description":"Lead traveller — must already exist and belong to your org.","example":"550e8400-e29b-41d4-a716-446655440000"},"title":{"type":"string","minLength":1,"maxLength":200,"example":"Cartagena anniversary, June"},"destination":{"type":"string","minLength":1,"maxLength":200,"example":"Colombia"},"origin_city":{"type":"string","minLength":1,"maxLength":200},"start_date":{"type":"string","format":"date","pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-06-12"},"end_date":{"type":"string","format":"date","pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-06-12"},"party_size":{"type":"integer","minimum":1,"maximum":100,"default":1},"priority":{"type":"string","enum":["low","medium","high","urgent"],"default":"medium"},"source":{"type":"string","enum":["whatsapp","sms","email","web_form","widget","manual","phone"],"default":"manual"},"budget":{"allOf":[{"$ref":"#/components/schemas/Money"},{"description":"Optional indicative budget. Defaults to `null`."}]},"special_requests":{"type":"array","items":{"type":"string","minLength":1,"maxLength":500},"maxItems":50,"default":[]},"internal_notes":{"type":"string","maxLength":5000,"description":"Free-form notes attached to the trip. Stored but not surfaced in any public read."}},"required":["client_id"]},"SupplierContact":{"type":"object","properties":{"name":{"type":["string","null"]},"email":{"type":["string","null"],"format":"email"},"phone":{"type":["string","null"]},"website":{"type":["string","null"],"format":"uri"}},"required":["name","email","phone","website"],"description":"The supplier's published contact info. Surfaced only when the supplier is fully onboarded. DMC-private contacts are never exposed via v1."},"Supplier":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"550e8400-e29b-41d4-a716-446655440000"},"organization_id":{"type":"string","format":"uuid","example":"550e8400-e29b-41d4-a716-446655440000"},"name":{"type":"string","example":"Hummingbird Travel"},"category":{"type":"string","enum":["hotel","restaurant","transport_car","transport_jet","transport_helicopter","flight","activity","cruise","tour","dmc","wholesale","platform","channel_manager","hub_marketplace","insurance","visa","other"]},"onboarding_status":{"type":"string","enum":["pending_application","application_approved","profile_setup","documents_uploaded","agreement_pending","agreement_signed","active","suspended"]},"country":{"type":["string","null"],"minLength":2,"maxLength":2,"description":"ISO 3166-1 alpha-2.","example":"CO"},"address":{"type":["string","null"]},"logo_url":{"type":["string","null"],"format":"uri"},"cover_image_url":{"type":["string","null"],"format":"uri"},"is_active":{"type":"boolean"},"is_preferred":{"type":"boolean","description":"Hint that this supplier is on the Ultra preferred-partner list. Not a quality score — a curation flag."},"contact":{"allOf":[{"$ref":"#/components/schemas/SupplierContact"},{"type":["object","null"],"description":"Null when the supplier is not fully onboarded (privacy gate)."}]},"created_at":{"type":"string","format":"date-time","example":"2026-06-12T14:30:00Z"},"updated_at":{"type":"string","format":"date-time","example":"2026-06-12T14:30:00Z"}},"required":["id","organization_id","name","category","onboarding_status","country","address","logo_url","cover_image_url","is_active","is_preferred","contact","created_at","updated_at"]},"Booking":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"550e8400-e29b-41d4-a716-446655440000"},"trip_id":{"type":"string","format":"uuid","example":"550e8400-e29b-41d4-a716-446655440000"},"supplier_source":{"type":"string","description":"The supplier adapter that owns this booking (e.g. `hbt`, `drivado`, `manual`).","example":"hbt"},"supplier_booking_id":{"type":["string","null"],"description":"The supplier's own reference for this booking (PNR / confirmation code)."},"venue_name":{"type":"string"},"venue_type":{"type":["string","null"],"example":"hotel"},"destination":{"type":["string","null"]},"check_in":{"type":["string","null"],"format":"date","pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-06-12"},"check_out":{"type":["string","null"],"format":"date","pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-06-12"},"guest_count":{"type":["integer","null"]},"total":{"$ref":"#/components/schemas/Money"},"status":{"type":"string","enum":["provisional","confirmed","active","completed","expired","failed","cancelled","pending_supplier_confirmation","rejected","modified"]},"payment_status":{"type":"string","enum":["pending","pending_confirmation","authorized","captured","partially_refunded","refunded","failed","cancelled","disputed"]},"voucher_url":{"type":["string","null"],"format":"uri","description":"Direct link to the supplier-issued voucher / itinerary PDF, when available."},"created_at":{"type":"string","format":"date-time","example":"2026-06-12T14:30:00Z"},"confirmed_at":{"type":["string","null"],"format":"date-time","example":"2026-06-12T14:30:00Z"},"cancelled_at":{"type":["string","null"],"format":"date-time","example":"2026-06-12T14:30:00Z"},"completed_at":{"type":["string","null"],"format":"date-time","example":"2026-06-12T14:30:00Z"},"updated_at":{"type":"string","format":"date-time","example":"2026-06-12T14:30:00Z"}},"required":["id","trip_id","supplier_source","supplier_booking_id","venue_name","venue_type","destination","check_in","check_out","guest_count","total","status","payment_status","voucher_url","created_at","confirmed_at","cancelled_at","completed_at","updated_at"]},"CreateBookingInput":{"type":"object","properties":{"trip_id":{"type":"string","format":"uuid","description":"The trip this booking belongs to. Must belong to your org.","example":"550e8400-e29b-41d4-a716-446655440000"},"supplier_source":{"type":"string","enum":["manual","hbt","drivado","nuitee","ratestellar","saltours","mews","apaleo","tue","limohawk","oracle_ohip"],"default":"manual","description":"Booking mode. `manual` records an out-of-band booking; the other values (`hbt`, `drivado`, `nuitee`, `ratestellar`, `saltours`, `mews`, `apaleo`, `tue`, `limohawk`, `oracle_ohip`) dispatch a live booking through the registered adapter and require a `plan_item_id` whose `venueSource` matches. Other in-platform adapters (`momorooms`, `shiji`) are not yet v1-exposed — see DURABLE_SUPPLIER_BOOKING_PATTERN.md.","example":"manual"},"plan_item_id":{"type":"string","minLength":1,"maxLength":200,"description":"For `manual`: optional link back to the trip item this booking confirms (`item_…` id from `POST /trips/{id}/items`). For any adapter source: REQUIRED — the server reads the item to recover the durable supplier context needed to re-quote and book."},"supplier_booking_id":{"type":"string","minLength":1,"maxLength":100,"description":"For `manual`: the supplier's own reference (PNR / confirmation code). Ignored for adapter sources — the adapter returns its own confirmation number."},"venue_name":{"type":"string","minLength":1,"maxLength":200,"description":"Display name of the booked venue. Required for `manual`; derived from the plan item for adapter sources."},"venue_type":{"type":"string","minLength":1,"maxLength":50},"destination":{"type":"string","minLength":1,"maxLength":200},"check_in":{"type":"string","format":"date","pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-06-12"},"check_out":{"type":"string","format":"date","pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-06-12"},"guest_count":{"type":"integer","minimum":1,"maximum":100},"total":{"allOf":[{"$ref":"#/components/schemas/Money"},{"description":"Total price + currency. Required for `manual`; derived from the supplier quote for adapter sources."}]},"voucher_url":{"type":"string","format":"uri"},"guest":{"type":"object","properties":{"first_name":{"type":"string","minLength":1,"maxLength":80},"last_name":{"type":"string","minLength":1,"maxLength":80},"email":{"type":"string","format":"email","description":"Lead guest email. Required by `drivado` (chauffeur — driver contact). Used by every supplier for confirmation correspondence when present."},"phone":{"type":"string","minLength":5,"maxLength":30,"description":"Lead guest phone number, ideally with country dial-code prefix (e.g. `+447700900123`). Required by `drivado` and `limohawk` (transport adapters need a contactable rider). Used by every supplier for confirmation correspondence when present."},"nationality":{"type":"string","minLength":2,"maxLength":3,"description":"ISO 3166-1 alpha-2 or alpha-3 country code. Required by some adapters (e.g. HBT) for accurate re-quote pricing."}},"required":["first_name","last_name"],"description":"Lead guest. Required for any adapter-dispatched source (`hbt`, `drivado`, `nuitee`, `ratestellar`, `saltours`, `mews`, `apaleo`, `tue`, `limohawk`, `oracle_ohip`); ignored for `manual`. Note: `limohawk` additionally requires `guest.phone` since Limohawk's booking API requires the lead-guest phone number."}},"required":["trip_id"]},"UpdateBookingInput":{"type":"object","properties":{"status":{"type":"string","enum":["provisional","confirmed","active","completed","expired","failed","cancelled","pending_supplier_confirmation","rejected","modified"],"description":"New lifecycle status. Must be a legal transition from the current status — see the operation description."},"payment_status":{"type":"string","enum":["pending","pending_confirmation","authorized","captured","partially_refunded","refunded","failed","cancelled","disputed"],"description":"New payment status. Allowed while the booking is in any non-terminal status."},"supplier_booking_id":{"type":["string","null"],"minLength":1,"maxLength":100,"description":"Update the supplier's reference (PNR / confirmation code). `null` clears it."},"voucher_url":{"type":["string","null"],"format":"uri","description":"Update the link to the supplier-issued voucher / itinerary PDF. `null` clears it."},"cancellation_reason":{"type":"string","minLength":1,"maxLength":500,"description":"Optional reason for cancellation. Forwarded to the supplier when status transitions to `cancelled` for adapter-sourced bookings. Ignored for `manual` bookings."}}},"TripItem":{"type":"object","properties":{"id":{"type":"string","description":"Stable item id — assigned server-side on create.","example":"item_550e8400e29b41d4a716446655440000"},"type":{"type":"string","enum":["accommodation","transport","activity","dining","other"]},"title":{"type":"string"},"day_index":{"type":"integer","minimum":0,"description":"0-indexed day within the trip. Day 0 = arrival day."},"location":{"type":["string","null"]},"start_time":{"type":["string","null"],"pattern":"^\\d{2}:\\d{2}$","description":"24-hour HH:MM. Null when not scheduled.","example":"14:30"},"end_time":{"type":["string","null"],"pattern":"^\\d{2}:\\d{2}$"},"check_in":{"type":["string","null"],"format":"date","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Only present for accommodation items.","example":"2026-06-12"},"check_out":{"type":["string","null"],"format":"date","pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-06-12"},"notes":{"type":["string","null"]},"price":{"allOf":[{"$ref":"#/components/schemas/Money"},{"type":["object","null"]}]},"supplier_source":{"type":["string","null"],"description":"When this item came from a supplier adapter (e.g. `hbt`), the adapter slug. Null for manual items."}},"required":["id","type","title","day_index","location","start_time","end_time","check_in","check_out","notes","price","supplier_source"]},"CreateTripItemInput":{"type":"object","properties":{"type":{"type":"string","enum":["accommodation","transport","activity","dining","other"]},"title":{"type":"string","minLength":1,"maxLength":200},"day_index":{"type":"integer","minimum":0,"maximum":365},"location":{"type":"string","minLength":1,"maxLength":200},"start_time":{"type":"string","pattern":"^\\d{2}:\\d{2}$"},"end_time":{"type":"string","pattern":"^\\d{2}:\\d{2}$"},"check_in":{"type":"string","format":"date","pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-06-12"},"check_out":{"type":"string","format":"date","pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-06-12"},"notes":{"type":"string","maxLength":2000},"price":{"$ref":"#/components/schemas/Money"},"supplier_source":{"type":"string","minLength":1,"maxLength":50,"description":"Optional hint that this item originated from a named supplier (e.g. `hbt`, `drivado`). v1 does not auto-book — pair with `POST /api/v1/bookings` to confirm."}},"required":["type","title","day_index"]},"UpdateTripItemInput":{"type":"object","properties":{"type":{"type":"string","enum":["accommodation","transport","activity","dining","other"]},"title":{"type":"string","minLength":1,"maxLength":200},"day_index":{"type":"integer","minimum":0,"maximum":365},"location":{"type":["string","null"],"minLength":1,"maxLength":200},"start_time":{"type":["string","null"],"pattern":"^\\d{2}:\\d{2}$"},"end_time":{"type":["string","null"],"pattern":"^\\d{2}:\\d{2}$"},"check_in":{"type":["string","null"],"format":"date","pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-06-12"},"check_out":{"type":["string","null"],"format":"date","pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-06-12"},"notes":{"type":["string","null"],"maxLength":2000},"price":{"allOf":[{"$ref":"#/components/schemas/Money"},{"type":["object","null"]}]},"supplier_source":{"type":["string","null"],"minLength":1,"maxLength":50}}},"GeoPoint":{"type":"object","properties":{"lat":{"type":"number","example":10.4236},"lon":{"type":"number","example":-75.5478}},"required":["lat","lon"],"description":"WGS84 latitude/longitude."},"Venue":{"type":"object","properties":{"id":{"type":"string","description":"Stable venue-graph id.","example":"venue_8400e29b41d4"},"name":{"type":"string","example":"Casa San Agustín"},"category":{"type":"string","enum":["accommodation","restaurant","attraction","activity","transport","shopping","nightlife","nature","religious","pets","children","other"]},"subcategories":{"type":["array","null"],"items":{"type":"string"},"description":"Source-supplied sub-classifications, when available."},"coordinates":{"allOf":[{"$ref":"#/components/schemas/GeoPoint"},{"type":["object","null"]}]},"distance_meters":{"type":["number","null"],"description":"Distance from the search point in metres, when a geo search was performed."},"rating":{"type":["number","null"],"minimum":0,"maximum":5,"description":"Normalized 0–5 rating, when available."},"price_level":{"type":["string","null"],"description":"Coarse price band, when available (e.g. budget, moderate, expensive, luxury)."},"website_url":{"type":["string","null"],"format":"uri"},"thumbnail_url":{"type":["string","null"],"format":"uri"},"highlights":{"type":["array","null"],"items":{"type":"string"}},"amenities":{"type":["array","null"],"items":{"type":"string"}},"bookable":{"type":["boolean","null"],"description":"Whether the venue can be booked through an Ultra supplier adapter."}},"required":["id","name","category","subcategories","coordinates","distance_meters","rating","price_level","website_url","thumbnail_url","highlights","amenities","bookable"]},"InventoryItem":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"550e8400-e29b-41d4-a716-446655440000"},"supplier_id":{"type":"string","format":"uuid","example":"550e8400-e29b-41d4-a716-446655440000"},"name":{"type":"string","example":"Garden Suite"},"option_type":{"type":"string","enum":["room","suite","villa","penthouse","table","private_room","tasting_menu","vehicle","aircraft","experience","tour","excursion","cabin","stateroom","package","service","transfer"]},"status":{"type":"string","enum":["draft","active","paused"]},"internal_code":{"type":["string","null"],"description":"The supplier's own SKU / reference."},"description":{"type":"string"},"max_occupancy":{"type":["integer","null"]},"max_children":{"type":["integer","null"]},"booking_lead_time_days":{"type":["integer","null"]},"cancellation_policy":{"type":["string","null"]},"venue_ids":{"type":"array","items":{"type":"string","format":"uuid","example":"550e8400-e29b-41d4-a716-446655440000"},"description":"Venue UUIDs this option is offered at."},"created_at":{"type":"string","format":"date-time","example":"2026-06-12T14:30:00Z"},"updated_at":{"type":"string","format":"date-time","example":"2026-06-12T14:30:00Z"}},"required":["id","supplier_id","name","option_type","status","internal_code","description","max_occupancy","max_children","booking_lead_time_days","cancellation_policy","venue_ids","created_at","updated_at"]}},"parameters":{}},"paths":{"/trips":{"get":{"operationId":"listTrips","tags":["Trips"],"summary":"List trips","description":"Returns trips visible to the calling API key, sorted by `updated_at` descending. Use `updated_since` + `cursor` for incremental sync.","security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string","description":"Opaque cursor from the previous page's `page.next_cursor`.","example":"eyJ0IjoiMjAyNi0wNS0xNVQxNDozMDowMFoiLCJpIjoiNTUwZTg0MDAifQ=="},"required":false,"name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":25,"description":"Items per page. Default 25, max 100.","example":25},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string","enum":["enquiry_received","qualifying","draft_plan","proposal_sent","negotiating","booked","follow_up","in_progress","completed","lost","archived"],"description":"Filter to a single workflow stage."},"required":false,"name":"stage","in":"query"},{"schema":{"type":"string","format":"uuid","description":"Filter to a single organization. Required when the API key spans multiple orgs.","example":"550e8400-e29b-41d4-a716-446655440000"},"required":false,"name":"organization_id","in":"query"},{"schema":{"type":"string","format":"date-time","description":"Return trips with `updated_at >=` this timestamp. For incremental sync.","example":"2026-06-12T14:30:00Z"},"required":false,"name":"updated_since","in":"query"}],"responses":{"200":{"description":"A page of trips.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Trip"}},"page":{"$ref":"#/components/schemas/PageMeta"}},"required":["data","page"]}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createTrip","tags":["Trips"],"summary":"Create a trip","description":"Creates a new trip for an existing client. `organization_id` is inferred from your API key — you do not pass it. Defaults: `stage=enquiry_received`, `priority=medium`, `source=manual`, `party_size=1`. Either `title` or `destination` is required.","security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTripInput"}}}},"responses":{"201":{"description":"Trip created.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Trip"}},"required":["data"]}}}},"400":{"description":"Invalid input — body failed validation or referenced client does not exist in your org.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"API key lacks `trips:write` scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/trips/{id}":{"get":{"operationId":"getTrip","tags":["Trips"],"summary":"Get a trip by ID","security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"550e8400-e29b-41d4-a716-446655440000"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"The requested trip.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Trip"}},"required":["data"]}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Trip not found or not visible to this API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/suppliers":{"get":{"operationId":"listSuppliers","tags":["Suppliers"],"summary":"List suppliers","description":"Returns suppliers visible to the calling API key, sorted by `updated_at` descending. Use `category` + `country` to narrow to a marketplace slice; combine with `cursor` for incremental sync.","security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string","description":"Opaque cursor from the previous page's `page.next_cursor`.","example":"eyJ0IjoiMjAyNi0wNS0xNVQxNDozMDowMFoiLCJpIjoiNTUwZTg0MDAifQ=="},"required":false,"name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":25,"description":"Items per page. Default 25, max 100.","example":25},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string","enum":["hotel","restaurant","transport_car","transport_jet","transport_helicopter","flight","activity","cruise","tour","dmc","wholesale","platform","channel_manager","hub_marketplace","insurance","visa","other"],"description":"Filter to a single category. Combine with `country` for region+type lookups."},"required":false,"name":"category","in":"query"},{"schema":{"type":"string","minLength":2,"maxLength":2,"description":"Filter by ISO 3166-1 alpha-2 country code (uppercased).","example":"CO"},"required":false,"name":"country","in":"query"},{"schema":{"type":"string","format":"uuid","description":"Filter to a single organization. Required when the API key spans multiple orgs.","example":"550e8400-e29b-41d4-a716-446655440000"},"required":false,"name":"organization_id","in":"query"},{"schema":{"type":["boolean","null"],"default":true,"description":"When `true` (default), only suppliers with `is_active = true`. The agreement gate (supplier has a fully-signed onboarding agreement) is applied UNCONDITIONALLY — `active_only=false` only relaxes the `is_active` switch, never the agreement gate."},"required":false,"name":"active_only","in":"query"},{"schema":{"type":"string","format":"date-time","description":"Return suppliers with `updated_at >=` this timestamp. For incremental sync.","example":"2026-06-12T14:30:00Z"},"required":false,"name":"updated_since","in":"query"}],"responses":{"200":{"description":"A page of suppliers.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Supplier"}},"page":{"$ref":"#/components/schemas/PageMeta"}},"required":["data","page"]}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/suppliers/{id}":{"get":{"operationId":"getSupplier","tags":["Suppliers"],"summary":"Get a supplier by ID","description":"Returns the full supplier record. Contact info is included only when the supplier is fully onboarded — privacy gate is enforced server-side, not at the schema level.","security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"550e8400-e29b-41d4-a716-446655440000"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"The requested supplier.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Supplier"}},"required":["data"]}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Supplier not found or not visible to this API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/bookings":{"get":{"operationId":"listBookings","tags":["Bookings"],"summary":"List bookings","description":"Returns bookings on trips owned by the calling API key's organization, sorted by `updated_at` descending. Use `trip_id` to fetch all line-items of one trip; combine with `cursor` for incremental sync.","security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string","description":"Opaque cursor from the previous page's `page.next_cursor`.","example":"eyJ0IjoiMjAyNi0wNS0xNVQxNDozMDowMFoiLCJpIjoiNTUwZTg0MDAifQ=="},"required":false,"name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":25,"description":"Items per page. Default 25, max 100.","example":25},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string","format":"uuid","description":"Filter to bookings on a single trip. Useful for fetching all line-items of a planned itinerary.","example":"550e8400-e29b-41d4-a716-446655440000"},"required":false,"name":"trip_id","in":"query"},{"schema":{"type":"string","enum":["provisional","confirmed","active","completed","expired","failed","cancelled","pending_supplier_confirmation","rejected","modified"],"description":"Filter to a single booking status."},"required":false,"name":"status","in":"query"},{"schema":{"type":"string","format":"uuid","description":"Filter to a single organization. Required when the API key spans multiple orgs.","example":"550e8400-e29b-41d4-a716-446655440000"},"required":false,"name":"organization_id","in":"query"},{"schema":{"type":"string","format":"date-time","description":"Return bookings with `updated_at >=` this timestamp. For incremental sync.","example":"2026-06-12T14:30:00Z"},"required":false,"name":"updated_since","in":"query"}],"responses":{"200":{"description":"A page of bookings.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Booking"}},"page":{"$ref":"#/components/schemas/PageMeta"}},"required":["data","page"]}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createBooking","tags":["Bookings"],"summary":"Create a booking (manual record or adapter dispatch)","description":"Records a booking on an existing trip in one of two modes:\n\n- **Manual** (`supplier_source: \"manual\"`) — caller records a booking they secured out-of-band. Caller supplies the full record. Server defaults: `status=provisional`, `payment_status=pending`.\n\n- **Adapter dispatch** (`supplier_source: \"hbt\"`, `\"drivado\"`, `\"nuitee\"`, `\"ratestellar\"`, `\"saltours\"`, `\"mews\"`, or `\"apaleo\"`) — server dispatches a live booking through the registered adapter. Caller MUST supply `plan_item_id` for a trip item whose `venueSource` matches the requested source. The server reads the item's durable supplier context (venue id, dates, party size, optional `bookingContext` blob), re-quotes via the adapter's availability check (each adapter declares `requiresAvailabilityCheck() → true` so a fresh rate id is fetched before booking), books, and persists through the standard pipeline. Status and payment_status reflect the supplier's response.\n\nNot yet v1-exposed: `tue`, `limohawk`, `oracle_ohip`, `momorooms`, `shiji` — each has a specific structural blocker documented in `DURABLE_SUPPLIER_BOOKING_PATTERN.md § 1`.","security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBookingInput"}}}},"responses":{"201":{"description":"Booking created (manual record persisted, or adapter dispatch succeeded and persisted).","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Booking"}},"required":["data"]}}}},"400":{"description":"Invalid input — body failed validation, `trip_id` is not in your org, the named `plan_item_id` does not exist on the trip, or the item is missing the durable supplier context required for adapter dispatch.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"API key lacks `bookings:write` scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Adapter dispatch only — the supplier API rejected the booking (no availability, quote expired, supplier-side error). The `details` object includes `supplier_source`, `supplier_status`, and `supplier_error`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/bookings/{id}":{"get":{"operationId":"getBooking","tags":["Bookings"],"summary":"Get a booking by ID","description":"Returns a single booking. 404 when the booking does not exist OR its trip is not visible to this API key — anti-enumeration rule applies.","security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"550e8400-e29b-41d4-a716-446655440000"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"The requested booking.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Booking"}},"required":["data"]}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Booking not found or not visible to this API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updateBooking","tags":["Bookings"],"summary":"Update a booking (status, payment, supplier id, voucher) — and cancel adapter bookings","description":"Partial update. All body fields are optional; send only what you want to change. The server validates the lifecycle transition matrix on the merged state.\n\n**Status transitions allowed (v1, conservative):**\n- `provisional` → `confirmed | cancelled | failed`\n- `pending_supplier_confirmation` → `confirmed | cancelled | failed | rejected`\n- `confirmed` → `active | cancelled | modified`\n- `active` → `completed | cancelled`\n- `modified` → `confirmed | cancelled`\n- Terminal (`completed | cancelled | expired | failed | rejected`) → no transitions allowed.\n\n**Cancellation dispatch**: setting `status: \"cancelled\"` on a booking whose `supplier_source` is one of the adapter sources (`hbt`, `drivado`, `nuitee`, `ratestellar`, `saltours`, `mews`, `apaleo`, `tue`, `limohawk`, `oracle_ohip`) dispatches the cancellation to the supplier via `bookingOrchestrator.cancelBooking`. Pass `cancellation_reason` to forward a reason. Manual bookings just flip the DB row.","security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"550e8400-e29b-41d4-a716-446655440000"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateBookingInput"}}}},"responses":{"200":{"description":"Updated booking.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Booking"}},"required":["data"]}}}},"400":{"description":"Invalid input — empty body, illegal status transition, or terminal-status row.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"API key lacks `bookings:write` scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Booking not found OR not visible to this API key (anti-enumeration).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Booking was modified concurrently. Re-fetch and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Cancellation dispatch only — the supplier API rejected the cancellation. `details` includes `supplier_source`, `supplier_status`, `supplier_error`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/trips/{id}/items":{"get":{"operationId":"listTripItems","tags":["Trips"],"summary":"List items on a trip","description":"Returns the trip's planned items (accommodation, transport, activities, etc.) in `day_index` order. Items live inside `plan_data.items` on the trip row.","security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"550e8400-e29b-41d4-a716-446655440000"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"The trip's items.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/TripItem"}}},"required":["data"]}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Trip not found or not visible to this API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"appendTripItem","tags":["Trips"],"summary":"Append an item to a trip","description":"Appends a single planned item to the trip's `plan_data.items`. Append-only — PATCH/DELETE land in a follow-up. Returns the created item including its server-assigned `id`. On concurrent write conflict (3 retries exhausted) returns 409 — caller should re-fetch and retry.","security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"550e8400-e29b-41d4-a716-446655440000"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTripItemInput"}}}},"responses":{"201":{"description":"Item appended.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/TripItem"}},"required":["data"]}}}},"400":{"description":"Invalid input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"API key lacks `trips:write` scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Trip not found or not visible to this API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Concurrent write conflict — re-fetch the trip and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/trips/{id}/items/{itemId}":{"patch":{"operationId":"updateTripItem","tags":["Trips"],"summary":"Update an item on a trip","description":"Partial update — only fields present in the body are touched. Pass `null` for a nullable field to clear it. Same optimistic-concurrency posture as POST: 3 retries, 409 on exhaust.","security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Trip ID.","example":"550e8400-e29b-41d4-a716-446655440000"},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","pattern":"^item_[a-f0-9]{32}$","description":"Item ID (as returned by POST /trips/{id}/items)."},"required":true,"name":"itemId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTripItemInput"}}}},"responses":{"200":{"description":"Updated item.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/TripItem"}},"required":["data"]}}}},"400":{"description":"Invalid input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"API key lacks `trips:write` scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Trip or item not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Concurrent write conflict — re-fetch and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"deleteTripItem","tags":["Trips"],"summary":"Remove an item from a trip","description":"Removes the item from `plan_data.items`. Idempotent: a second delete on the same itemId returns 404 (anti-enumeration applies — same response as truly-missing).","security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Trip ID.","example":"550e8400-e29b-41d4-a716-446655440000"},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","pattern":"^item_[a-f0-9]{32}$","description":"Item ID (as returned by POST /trips/{id}/items)."},"required":true,"name":"itemId","in":"path"}],"responses":{"204":{"description":"Item deleted (no body)."},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"API key lacks `trips:write` scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Trip or item not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Concurrent write conflict — re-fetch and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/venues":{"get":{"operationId":"searchVenues","tags":["Venues"],"summary":"Search the venue knowledge graph","description":"Searches Ultra’s classified, deduplicated venue knowledge graph — the supply-intelligence layer across every supplier adapter. Filter by `category` and/or a geo radius (`lat` + `lon` + optional `radius_m`). Returns up to `limit` venues. This is a search resource: `page.next_cursor` is reserved for future cursor pagination and is currently always null.","security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string","enum":["accommodation","restaurant","attraction","activity","transport","shopping","nightlife","nature","religious","pets","children","other"],"description":"Filter to a single venue category."},"required":false,"name":"category","in":"query"},{"schema":{"type":["number","null"],"description":"Latitude of the search centre. Provide with `lon` for a radius search."},"required":false,"name":"lat","in":"query"},{"schema":{"type":["number","null"],"description":"Longitude of the search centre. Provide with `lat` for a radius search."},"required":false,"name":"lon","in":"query"},{"schema":{"type":"integer","exclusiveMinimum":0,"description":"Radius in metres for a geo search. Defaults to 50000 (50km) when `lat`/`lon` are given."},"required":false,"name":"radius_m","in":"query"},{"schema":{"type":"string","format":"uuid","description":"Bias the graph to a single organization’s preferred supply, when set.","example":"550e8400-e29b-41d4-a716-446655440000"},"required":false,"name":"organization_id","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":20,"description":"Maximum venues to return (1–100, default 20)."},"required":false,"name":"limit","in":"query"}],"responses":{"200":{"description":"A page of venues from the knowledge graph.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Venue"}},"page":{"$ref":"#/components/schemas/PageMeta"}},"required":["data","page"]}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/inventory":{"post":{"operationId":"createInventoryItem","tags":["Inventory"],"summary":"Create an inventory item","description":"Pushes a supplier inventory item (\"option\") — the same data the /inventory extranet creates by hand. The supplier is resolved from the API key's organization; the key must hold the `inventory:write` scope and belong to an organization with an active supplier. Pricing and availability are separate and not set here.","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200,"example":"Garden Suite"},"option_type":{"type":"string","enum":["room","suite","villa","penthouse","table","private_room","tasting_menu","vehicle","aircraft","experience","tour","excursion","cabin","stateroom","package","service","transfer"]},"description":{"type":"string","maxLength":5000,"description":"Defaults to empty. Required (non-empty) for the item to be projected into the venue knowledge graph."},"internal_code":{"type":"string","minLength":1,"maxLength":100},"status":{"type":"string","enum":["draft","active","paused"],"description":"Defaults to 'draft'. Set 'active' to publish."},"max_occupancy":{"type":"integer","exclusiveMinimum":0,"maximum":1000},"max_children":{"type":["integer","null"],"minimum":0,"maximum":1000},"booking_lead_time_days":{"type":["integer","null"],"minimum":0,"maximum":3650},"cancellation_policy":{"type":"string","maxLength":2000},"venue_ids":{"type":"array","items":{"type":"string","format":"uuid","example":"550e8400-e29b-41d4-a716-446655440000"},"maxItems":50,"description":"Venue UUIDs (from GET /venues) this option is offered at."}},"required":["name","option_type"]}}}},"responses":{"201":{"description":"The created inventory item.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/InventoryItem"}},"required":["data"]}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Key lacks `inventory:write`, or its organization has no active supplier.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"webhooks":{}}