{"openapi":"3.0.3","info":{"title":"SERVER FLIGHT API","version":"2.1.0","description":"Aggregator API gộp chuyến bay + đặt chỗ + xuất vé từ 3 NCC (BGT/HNH/HPL), phục vụ đại lý & website đối tác.\n\n## Quick start\n1. Xin `X-API-Key` từ DLV Tech (env var `API_KEY_DEFAULT` / `AGENCY_KEY_<name>`).\n2. `POST /api/v1/flights/search` → lấy `data.fares[].fareSelectionId` của 1 option.\n3. `POST /api/v1/flights/bookings` với `fareSelectionId + passengers + contact` → nhận `data.bookingRef`.\n4. `POST /api/v1/flights/bookings/{ref}/issue` → xuất vé.\n5. Mọi op sau đều qua `bookingRef` hoặc `ticketNo` — aggregator tự dispatch về NCC sở hữu.\n\n## Quan trọng\n- **Raw envelope**: mọi response dạng `{ ok, traceId, supplier, operation, data, error? }`. Parse `data` khi `ok=true`.\n- **Cache**: search có SWR cache (`fresh<180s`, `stale<900s`). Stale vẫn dùng được — server refresh background.\n- **totalPrice**: unified = `(fareAdt+taxAdt)*adt + (fareChd+taxChd)*chd + (fareInf+taxInf)*inf`. Raw supplier total giữ trong `totalPriceSupplier`.\n- **SSE /search-stream** emit per-NCC chunk `{type:\"supplier\", ...}` khi ready, kết thúc `{type:\"envelope\"}` + `[DONE]`.\n- **Multi-city**: pass `flights[]` array. **Round-trip**: pass `returnDate`. Cache key bao gồm filter.\n- **Codeshare**: filter `airlines:[BL]` match cả `leg.operatingCarrier=BL` (VN-sell/BL-operate).\n- **Supplier limits**: GDS quốc tế qua `airdata`; `tripota` hỗ trợ VN/VJ/QH/9G/1A; `skeeper` hiện chỉ VU (VN/QH tạm ẩn — HPL data thiếu kg baggage). Mở rộng qua env `AIRDATA_SOURCES`, `TRIPOTA_SYSTEMS`, `SKEEPER_AIRLINES`.\n\n## Vòng đời sau vé (canonical modules)\nCác nghiệp vụ sau-vé dùng module chuẩn hoá thay cho passthrough adapter trực tiếp. Mỗi module theo cùng khuôn: **đọc state NCC trước (retrieve/eligibility) → báo giá (quote/options) → thực thi idempotent → đối soát (retrieve)**.\n- **Refund** `/flights/refunds/*` — retrieve / quote / submit / status.\n- **Exchange** `/flights/exchanges/*` — eligibility / search-options / quote / execute / {ref}/retrieve.\n- **Upgrade** `/flights/upgrades/*` — retrieve / options / reprice / confirm / {ref}/retrieve-after / capability.\n- **Split** `/flights/splits/*` — eligibility / (execute) / {ref}/retrieve.\nĐặc điểm chung: **deny-default** (chỉ live khi bật env `FLIGHT_<MODULE>_ENABLED` + `..._LIVE_EXECUTE_ENABLED` + supplier nằm trong `..._SUPPORTED_SUPPLIERS`), **idempotent** (gửi `X-Idempotency-Key`), eligibility-gate TRƯỚC khi gọi NCC, và **timeout/5xx → state `unknown`** (KHÔNG bao giờ auto-retry — tra lại qua endpoint `retrieve`/`status`).\n\n## Idempotency\nMọi op mutate-tiền (book/issue/void/refund/exchange/upgrade/split) nhận header `X-Idempotency-Key`. Gửi lại cùng key sẽ **replay** kết quả cũ thay vì gọi NCC lần nữa (header `x-idempotent-replay: true`). Bắt buộc để IPN/retry không double-book/issue.\n\n## Error model\nEnvelope `ok=false` kèm `error.code`:\n- `VALIDATION` — schema sai (Zod reject) → 400\n- `BOOKING_NOT_FOUND` / `TICKET_NOT_FOUND` → 404\n- `FARE_EXPIRED` — `fareSelectionId` quá 60 phút → 404\n- `SUPPLIER_REJECTED` — NCC upstream trả lỗi (giữ raw trong `supplierMessage`) → 409\n- `NOT_SUPPORTED_BY_OWNING_SUPPLIER` → 501\n- `NO_SUPPLIER` — không NCC nào có capability → 501\n\n## Test UI\n- Swagger UI: [`/api/v1/docs-ui`](/api/v1/docs-ui) — try-it-out interactive\n- Raw spec JSON: [`/api/v1/openapi.json`](/api/v1/openapi.json) — import Postman/Insomnia\n- Search playground: [`/`](/)","contact":{"name":"DLV Tech","email":"dat.le@blueboltsoftware.com"},"license":{"name":"Internal"}},"servers":[{"url":"/api/v1","description":"Current host (relative)"}],"security":[{"ApiKeyAuth":[]}],"tags":[{"name":"Search","description":"Tìm kiếm chuyến bay (fan-out 3 NCC + merge + filter)"},{"name":"Pricing","description":"Báo giá lại, fare rules, min-fare calendar"},{"name":"PreBookAncillary","description":"Seat-map / hành lý / dịch vụ theo fareSelectionId (chưa đặt)"},{"name":"Booking","description":"Hold / retrieve / hold-status / events / cancel / issue (gồm RT 2-PNR parent)"},{"name":"BookingAncillary","description":"Seat / hành lý / dịch vụ trên PNR đã đặt"},{"name":"Modify","description":"Thêm-đổi-hủy hành trình, thêm em bé, sửa pax, tách PNR"},{"name":"PNROps","description":"Remarks / OSI / SK / passport / membership"},{"name":"Tickets","description":"Void + (legacy) refund/exchange passthrough theo bookingRef hoặc ticketNo. Dùng module Refund/Exchange chuẩn hoá bên dưới cho luồng production."},{"name":"Refund","description":"Module hoàn vé chuẩn (retrieve → quote → submit → status) — eligibility-gated + idempotent"},{"name":"Exchange","description":"Module đổi vé/reissue chuẩn (eligibility → search-options → quote → execute → retrieve)"},{"name":"Upgrade","description":"Module nâng hạng (retrieve → options → reprice → confirm → retrieve-after)"},{"name":"Split","description":"Tách hành khách ra PNR riêng (eligibility → execute → retrieve)"},{"name":"Comms","description":"Email / queue / face / print / update contact"},{"name":"FlightInfo","description":"Tình trạng chuyến bay"},{"name":"Ops","description":"Tiện ích đại lý: credit balance, distributor khả dụng"},{"name":"Meta","description":"Health / OpenAPI / suppliers"}],"paths":{"/health":{"get":{"tags":["Meta"],"summary":"Health check (gateway + aggregator)","security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"service":"api-gateway","status":"ok","aggregator":{"service":"flight-aggregator","status":"ok","traceId":"abc"}}}}}}}},"/openapi.json":{"get":{"tags":["Meta"],"summary":"Spec này (JSON)","security":[],"responses":{"200":{"description":"OpenAPI 3.0 document"}}}},"/docs-ui":{"get":{"tags":["Meta"],"summary":"Swagger UI (try-it-out)","security":[],"responses":{"200":{"description":"HTML page","content":{"text/html":{}}}}}},"/docs":{"get":{"tags":["Meta"],"summary":"Legacy docs JSON (deprecated — dùng /openapi.json)","security":[],"deprecated":true,"responses":{"200":{"description":"Legacy format"}}}},"/suppliers":{"get":{"tags":["Meta"],"summary":"Danh sách NCC + capability matrix","responses":{"200":{"description":"Suppliers & their supported operations","content":{"application/json":{"example":{"ok":true,"suppliers":[{"code":"airdata","ops":["search","book","issue"]},{"code":"tripota","ops":["search","book","issue","void","refund"]},{"code":"skeeper","ops":["search","book","issue"]}],"matrix":{}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/flights/search":{"post":{"tags":["Search"],"summary":"Tìm chuyến bay (REST fan-out 3 NCC)","description":"Fan-out BGT/HNH/HPL song song, merge + sort theo `totalPrice`. Filter `airlines/preferCabin/promoCode` apply post-aggregation, cache key bao gồm filter. Cold miss 5-40s; hit cache < 50ms.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchRequest"},"example":{"startPoint":"SGN","endPoint":"HAN","departDate":"2026-12-20","returnDate":"2026-12-25","adt":2,"chd":1,"inf":1,"airlines":["VN","VJ","QH","VU","BL"],"preferCabin":"Y","promoCode":""}}}},"responses":{"200":{"description":"Envelope chứa `data.fares[]`, `data.combos[]` (RT only, GDS-bundled), `data.suppliers{}`, `data.totals{}` (incl. `combosBuilt`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchEnvelope"},"example":{"ok":true,"traceId":"abc123","supplier":"aggregator","operation":"search","capability":{"supported":true,"notes":null},"latencyMs":35327,"data":{"fares":[{"fareSelectionId":"fs_AqeDpYVEriJd36u5qM_19","supplier":"airdata","supplierSource":"VN1A","airline":"VN","currency":"VND","totalPrice":11165500,"totalPriceSupplier":3741000,"fareAdt":2937000,"taxAdt":804000,"fareChd":2644000,"taxChd":721500,"fareInf":294000,"taxInf":24000,"legs":[{"airline":"VN","operatingCarrier":"BL","flightNumber":"VN6002","startPoint":"SGN","endPoint":"HAN","departTime":"05:00","arriveTime":"07:10","duration":130,"aircraft":"321","cabin":"ECONOMY FLEX","fareBasis":"BVNF","bookingClass":"B","seatsAvailable":9,"baggage":"1 PCs x 23kg"}],"fareConditions":{"cabinName":"ECONOMY FLEX","fareBasis":"BVNF","bookingClass":"B","groupClass":"EF","refundable":true,"baggage":{"checked":{"included":true,"pieces":1,"weight":23,"unit":"KG","text":"1 PCs x 23kg"},"carryOn":{"included":true,"pieces":1,"text":"(1 piece + 1 accessory) not exceed 10kg"}},"penalties":null},"cabinCanonical":"ECONOMY","cabinDisplay":"Phổ thông","supportedOps":["book","hold","issue","retrieveBooking","faceBooking","modifyCancelFlights","seatAdd","baggageAdd","ancillaryAdd"],"unsupportedOps":["void","refund","exchange","addRemark","addOsi","addSk","addDocument","addMembership","split","modifyAddFlights","modifyPaxInfo","modifyAddInfant","sendEmail","queuePlace","printTicket","checkinOnline","computeEmission","ancillaryDelete","baggageDelete","seatDelete","cancelBooking"]}],"fareGroups":[{"groupId":"fg_3a9c1f0b8e2d4a67","grouped":true,"flightKey":"VN|VN6002|2026-12-20|05:00|SGN|HAN","tripDirection":"oneway","airline":"VN","supplier":"tripota","supplierSource":"VN","cabinCanonical":"ECONOMY","cabinDisplay":"Phổ thông","fareFamily":"Economy Lite","fareFamilyCode":"ECONOMY_LITE","currency":"VND","baggage":{"checked":{"included":true,"pieces":1,"weight":23,"unit":"KG","text":"1 PCs x 23kg"},"carryOn":{"included":true,"pieces":1,"text":"(1 piece + 1 accessory) not exceed 10kg"}},"conditions":{"refundable":true,"changeable":null,"penalties":null},"representativeFareId":"fs_1","priceFrom":1480000,"priceTo":1500000,"fareCount":2,"alternativeCount":1,"members":[{"fareSelectionId":"fs_1","totalPrice":1480000,"currency":"VND","fareBasis":"TLOWVN","bookingClass":"T","supplier":"tripota","supplierSource":"VN","airline":"VN","holdSupported":true,"requiresManualIssue":false},{"fareSelectionId":"fs_2","totalPrice":1500000,"currency":"VND","fareBasis":"NLOWVN","bookingClass":"N","supplier":"tripota","supplierSource":"VN","airline":"VN","holdSupported":true,"requiresManualIssue":false}],"reason":{"basis":"fare-family","matched":["flight","cabin","fareFamily","baggage"],"fareFamily":"Economy Lite","fareFamilyCode":"ECONOMY_LITE","cabinCanonical":"ECONOMY","baggageText":{"checked":"1 PCs x 23kg","carryOn":"(1 piece + 1 accessory) not exceed 10kg"},"penaltiesKnown":false}}],"groupedFlights":[{"flightKey":"VN|VN6002|2026-12-20|SGN|HAN","airline":"VN","legs":[{"airline":"VN","operatingCarrier":"BL","flightNumber":"VN6002","startPoint":"SGN","endPoint":"HAN","departTime":"2026-12-20T05:00:00Z","arriveTime":"2026-12-20T07:10:00Z","duration":130,"aircraft":"321"}],"startPoint":"SGN","endPoint":"HAN","departTime":"2026-12-20T05:00:00Z","arriveTime":"2026-12-20T07:10:00Z","tripDirection":"oneway","minPrice":1480000,"maxPrice":5500000,"currency":"VND","supplierCount":2,"suppliers":["airdata","tripota"],"fareCount":3,"cabinOptions":[{"cabinCanonical":"ECONOMY","cabinDisplay":"Phổ thông","minPrice":1480000,"maxPrice":1500000,"suppliers":["airdata","tripota"],"fareCount":2,"fareSelectionIds":["fs_1","fs_2"]},{"cabinCanonical":"BUSINESS","cabinDisplay":"Thương gia","minPrice":5500000,"maxPrice":5500000,"suppliers":["airdata"],"fareCount":1,"fareSelectionIds":["fs_3"]}],"fares":[{"fareSelectionId":"fs_AqeDpYVEriJd36u5qM_19","supplier":"airdata","supplierSource":"VN1A","airline":"VN","currency":"VND","totalPrice":11165500,"totalPriceSupplier":3741000,"fareAdt":2937000,"taxAdt":804000,"fareChd":2644000,"taxChd":721500,"fareInf":294000,"taxInf":24000,"legs":[{"airline":"VN","operatingCarrier":"BL","flightNumber":"VN6002","startPoint":"SGN","endPoint":"HAN","departTime":"05:00","arriveTime":"07:10","duration":130,"aircraft":"321","cabin":"ECONOMY FLEX","fareBasis":"BVNF","bookingClass":"B","seatsAvailable":9,"baggage":"1 PCs x 23kg"}],"fareConditions":{"cabinName":"ECONOMY FLEX","fareBasis":"BVNF","bookingClass":"B","groupClass":"EF","refundable":true,"baggage":{"checked":{"included":true,"pieces":1,"weight":23,"unit":"KG","text":"1 PCs x 23kg"},"carryOn":{"included":true,"pieces":1,"text":"(1 piece + 1 accessory) not exceed 10kg"}},"penalties":null},"cabinCanonical":"ECONOMY","cabinDisplay":"Phổ thông","supportedOps":["book","hold","issue","retrieveBooking","faceBooking","modifyCancelFlights","seatAdd","baggageAdd","ancillaryAdd"],"unsupportedOps":["void","refund","exchange","addRemark","addOsi","addSk","addDocument","addMembership","split","modifyAddFlights","modifyPaxInfo","modifyAddInfant","sendEmail","queuePlace","printTicket","checkinOnline","computeEmission","ancillaryDelete","baggageDelete","seatDelete","cancelBooking"]}]}],"suppliers":{"airdata":{"supplier":"airdata","name":"airdata","count":148,"traceId":"gO930n37ozubnyUVtJWe","latencyMs":30658,"fromCache":null,"fares":[{"fareSelectionId":"fs_AqeDpYVEriJd36u5qM_19","supplier":"airdata","supplierSource":"VN1A","airline":"VN","currency":"VND","totalPrice":11165500,"totalPriceSupplier":3741000,"fareAdt":2937000,"taxAdt":804000,"fareChd":2644000,"taxChd":721500,"fareInf":294000,"taxInf":24000,"legs":[{"airline":"VN","operatingCarrier":"BL","flightNumber":"VN6002","startPoint":"SGN","endPoint":"HAN","departTime":"05:00","arriveTime":"07:10","duration":130,"aircraft":"321","cabin":"ECONOMY FLEX","fareBasis":"BVNF","bookingClass":"B","seatsAvailable":9,"baggage":"1 PCs x 23kg"}],"fareConditions":{"cabinName":"ECONOMY FLEX","fareBasis":"BVNF","bookingClass":"B","groupClass":"EF","refundable":true,"baggage":{"checked":{"included":true,"pieces":1,"weight":23,"unit":"KG","text":"1 PCs x 23kg"},"carryOn":{"included":true,"pieces":1,"text":"(1 piece + 1 accessory) not exceed 10kg"}},"penalties":null},"cabinCanonical":"ECONOMY","cabinDisplay":"Phổ thông","supportedOps":["book","hold","issue","retrieveBooking","faceBooking","modifyCancelFlights","seatAdd","baggageAdd","ancillaryAdd"],"unsupportedOps":["void","refund","exchange","addRemark","addOsi","addSk","addDocument","addMembership","split","modifyAddFlights","modifyPaxInfo","modifyAddInfant","sendEmail","queuePlace","printTicket","checkinOnline","computeEmission","ancillaryDelete","baggageDelete","seatDelete","cancelBooking"]}]},"tripota":{"supplier":"tripota","name":"tripota","count":79,"traceId":"gO930n37ozubnyUVtJWe","latencyMs":30658,"fromCache":null,"fares":[{"fareSelectionId":"fs_AqeDpYVEriJd36u5qM_19","supplier":"airdata","supplierSource":"VN1A","airline":"VN","currency":"VND","totalPrice":11165500,"totalPriceSupplier":3741000,"fareAdt":2937000,"taxAdt":804000,"fareChd":2644000,"taxChd":721500,"fareInf":294000,"taxInf":24000,"legs":[{"airline":"VN","operatingCarrier":"BL","flightNumber":"VN6002","startPoint":"SGN","endPoint":"HAN","departTime":"05:00","arriveTime":"07:10","duration":130,"aircraft":"321","cabin":"ECONOMY FLEX","fareBasis":"BVNF","bookingClass":"B","seatsAvailable":9,"baggage":"1 PCs x 23kg"}],"fareConditions":{"cabinName":"ECONOMY FLEX","fareBasis":"BVNF","bookingClass":"B","groupClass":"EF","refundable":true,"baggage":{"checked":{"included":true,"pieces":1,"weight":23,"unit":"KG","text":"1 PCs x 23kg"},"carryOn":{"included":true,"pieces":1,"text":"(1 piece + 1 accessory) not exceed 10kg"}},"penalties":null},"cabinCanonical":"ECONOMY","cabinDisplay":"Phổ thông","supportedOps":["book","hold","issue","retrieveBooking","faceBooking","modifyCancelFlights","seatAdd","baggageAdd","ancillaryAdd"],"unsupportedOps":["void","refund","exchange","addRemark","addOsi","addSk","addDocument","addMembership","split","modifyAddFlights","modifyPaxInfo","modifyAddInfant","sendEmail","queuePlace","printTicket","checkinOnline","computeEmission","ancillaryDelete","baggageDelete","seatDelete","cancelBooking"]}]},"skeeper":{"supplier":"skeeper","name":"skeeper","count":79,"traceId":"gO930n37ozubnyUVtJWe","latencyMs":30658,"fromCache":null,"fares":[{"fareSelectionId":"fs_AqeDpYVEriJd36u5qM_19","supplier":"airdata","supplierSource":"VN1A","airline":"VN","currency":"VND","totalPrice":11165500,"totalPriceSupplier":3741000,"fareAdt":2937000,"taxAdt":804000,"fareChd":2644000,"taxChd":721500,"fareInf":294000,"taxInf":24000,"legs":[{"airline":"VN","operatingCarrier":"BL","flightNumber":"VN6002","startPoint":"SGN","endPoint":"HAN","departTime":"05:00","arriveTime":"07:10","duration":130,"aircraft":"321","cabin":"ECONOMY FLEX","fareBasis":"BVNF","bookingClass":"B","seatsAvailable":9,"baggage":"1 PCs x 23kg"}],"fareConditions":{"cabinName":"ECONOMY FLEX","fareBasis":"BVNF","bookingClass":"B","groupClass":"EF","refundable":true,"baggage":{"checked":{"included":true,"pieces":1,"weight":23,"unit":"KG","text":"1 PCs x 23kg"},"carryOn":{"included":true,"pieces":1,"text":"(1 piece + 1 accessory) not exceed 10kg"}},"penalties":null},"cabinCanonical":"ECONOMY","cabinDisplay":"Phổ thông","supportedOps":["book","hold","issue","retrieveBooking","faceBooking","modifyCancelFlights","seatAdd","baggageAdd","ancillaryAdd"],"unsupportedOps":["void","refund","exchange","addRemark","addOsi","addSk","addDocument","addMembership","split","modifyAddFlights","modifyPaxInfo","modifyAddInfant","sendEmail","queuePlace","printTicket","checkinOnline","computeEmission","ancillaryDelete","baggageDelete","seatDelete","cancelBooking"]}]}},"totals":{"totalFares":306,"totalFlights":87,"totalFareGroups":290,"totalSuppliers":3,"skippedSuppliers":[],"degraded":false}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"502":{"$ref":"#/components/responses/BadGateway"}}}},"/flights/search-stream":{"post":{"tags":["Search"],"summary":"Tìm chuyến bay (SSE stream per-NCC)","description":"Server-Sent Events. Sequence:\n\n```\ndata: {\"type\":\"supplier\",\"supplier\":\"skeeper\",\"count\":38,\"fares\":[...]}\n\ndata: {\"type\":\"supplier\",\"supplier\":\"airdata\",\"count\":154,...}\n\ndata: {\"type\":\"supplier\",\"supplier\":\"tripota\",\"count\":79,...}\n\ndata: {\"type\":\"envelope\",\"envelope\":{...full merged...}}\n\ndata: [DONE]\n\n```\nClient có thể render tab NCC trước khi NCC chậm nhất xong. Cache hit thì emit toàn bộ ngay.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchRequest"},"example":{"startPoint":"SGN","endPoint":"HAN","departDate":"2026-12-20","returnDate":"2026-12-25","adt":2,"chd":1,"inf":1,"airlines":["VN","VJ","QH","VU","BL"],"preferCabin":"Y","promoCode":""}}}},"responses":{"200":{"description":"SSE stream","content":{"text/event-stream":{}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/flights/min-fare":{"post":{"tags":["Pricing"],"summary":"Min-fare calendar by month (v1: skeeper only)","description":"Returns the cheapest fare per day for a given month. v1 supports skeeper (HPL) only. airdata is excluded by default (BGT no-spam rule). Response is a date-keyed map.\n\nReturns HTTP 501 when no supplier supports the capability (e.g. skeeper disabled via env).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MinFareRequest"},"example":{"startPoint":"SGN","endPoint":"HAN","month":"2026-06","adt":1,"chd":0,"inf":0}}}},"responses":{"200":{"description":"Date-keyed min-fare map","content":{"application/json":{"example":{"ok":true,"supplier":"aggregator","operation":"searchMinFareByMonth","data":{"2026-06-03":{"price":1690000,"currency":"VND","supplier":"skeeper","airline":"VJ"},"2026-06-04":{"price":1750000,"currency":"VND","supplier":"skeeper","airline":"VJ"}},"meta":{"suppliers":["skeeper"],"respondingSuppliers":["skeeper"],"domestic":true,"cached":false,"ttlSec":1800}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"501":{"description":"No supplier supports min-fare for this route/config","content":{"application/json":{"example":{"ok":false,"error":{"code":"NO_SUPPLIER","category":"capability","retryable":false,"message":"No supplier supports min-fare for this trip"}}}}}}}},"/flights/price-quote":{"post":{"tags":["Pricing"],"summary":"Reprice theo fareSelectionId","description":"Recalc giá trước khi book. NCC có thể trả giá mới (different_amount != 0 nếu tăng/giảm).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PriceQuoteRequest"},"example":{"fareSelectionId":"fs_xxx","passengerCounts":{"adt":1,"chd":0,"inf":0}}}}},"responses":{"200":{"description":"Quote kết quả","content":{"application/json":{"example":{"ok":true,"supplier":"tripota","operation":"priceQuote","data":{"totalPrice":3741000,"currency":"VND","fareAdt":2937000,"taxAdt":804000,"priceAdt":3741000,"status":true}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"description":"fareSelectionId expired","content":{"application/json":{"example":{"ok":false,"error":{"code":"FARE_EXPIRED","message":"fareSelectionId fs_xxx not found or expired"}}}}},"409":{"$ref":"#/components/responses/Conflict"}}}},"/flights/fare-rules":{"post":{"tags":["Pricing"],"summary":"Show fare rules (điều kiện vé)","description":"Raw fare rule text theo fareSelectionId. ATPCO GDS rules với airdata/tripota (text tiếng Anh); catalog tĩnh tiếng Việt với skeeper.\n\nĐể xem điều kiện **đã được chuẩn hoá** (pieces, weight, unit, refundable), hãy đọc trực tiếp field `fareConditions` trên mỗi fare trong response `/flights/search`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FareRuleRequest"},"example":{"fareSelectionId":"fs_xxx","isMiniRule":false}}}},"responses":{"200":{"description":"Fare rules raw","content":{"application/json":{"example":{"ok":true,"supplier":"airdata","operation":"fareRule","data":{"rules":"..."}}}}},"409":{"$ref":"#/components/responses/Conflict"}}}},"/flights/fare-rules-catalog":{"get":{"tags":["Pricing"],"summary":"Catalog điều kiện theo group-class (seat/meal/change/refund…)","description":"Trả về catalog static (không cần fareSelectionId) từ BGT `/all-fare-rules` — enum FREE/FEE/NONE cho 9 dimensions (seat, meal, checkinPriority, loungeService, changeName, changeFlights, refund, cancel). Chỉ 6 hãng LCC: VJ/AK/TR/JQ/6E/5J. FE có thể merge với `fareConditions.groupClass` để hiển thị đầy đủ.","parameters":[{"name":"supplier","in":"query","required":false,"schema":{"type":"string","enum":["airdata","tripota","skeeper"]},"description":"Lọc 1 supplier. Mặc định fan-out tất cả (chỉ airdata thực sự có data)."}],"responses":{"200":{"description":"Catalog","content":{"application/json":{"example":{"ok":true,"traceId":"abc123","operation":"allFareRules","latencyMs":170,"data":{"sources":[{"supplier":"airdata","source":"VJ","configs":[{"groupClass":"Eco","cabinName":"Eco","seat":"FEE","meal":"FEE","checkinPriority":"NONE","loungeService":"NONE","changeName":"NONE","changeFlights":"FEE","refund":"FEE","cancel":"FREE"},{"groupClass":"Deluxe","cabinName":"Deluxe","seat":"FREE","meal":"FEE","checkinPriority":"NONE","loungeService":"NONE","changeName":"NONE","changeFlights":"FEE","refund":"FEE","cancel":"FREE"},{"groupClass":"SkyBoss","cabinName":"SkyBoss","seat":"FREE","meal":"FREE","checkinPriority":"FREE","loungeService":"FREE","changeName":"FEE","changeFlights":"FEE","refund":"FEE","cancel":"FREE"},{"groupClass":"Business","cabinName":"Business","seat":"FREE","meal":"FREE","checkinPriority":"FREE","loungeService":"FREE","changeName":"FEE","changeFlights":"FEE","refund":"FEE","cancel":"FREE"}]}],"totalSources":6,"totalConfigs":18},"suppliers":{"airdata":{"ok":true,"data":{"totalSources":6}},"tripota":{"ok":false,"error":{"code":"NOT_SUPPORTED_BY_OWNING_SUPPLIER"}},"skeeper":{"ok":false,"error":{"code":"NOT_SUPPORTED_BY_OWNING_SUPPLIER"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/flights/seat-map":{"post":{"tags":["PreBookAncillary"],"summary":"Sơ đồ ghế (trước book)","description":"Trả `data.addable[]` — danh sách ghế bán được đã phẳng hoá: mỗi item là `{ seatValue, passengerType, label, price, currency, route, cabinClass, available, addable }`. **`seatValue` là token mã hoá (lấy từ `prices[].value`), KHÔNG phải nhãn ghế (\"20A\").** Client POST nguyên item về `/seats` (kèm `passengerIndex` hoặc `passengerId`). `totalRows` đếm số hàng ghế trên tất cả khoang.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FareRefRequest"},"example":{"fareSelectionId":"fs_xxx"}}}},"responses":{"200":{"description":"Seat map","content":{"application/json":{"example":{"ok":true,"supplier":"tripota","operation":"seatMap","data":{"addable":[{"seatValue":"35c94bbd…|Ymkby8ax…","passengerType":"ADT","label":"20A","price":152000,"currency":"VND","route":"HANSGN","cabinClass":"Y","available":true,"addable":true}],"totalRows":30,"flightSeats":[{"aircraft":"321","flightNumber":"VN6002","cabins":[{"firstRow":"1","lastRow":"37","cabinClass":"ECONOMY","rows":[{"rowNumber":"20","seats":[{"text":"20A","available":true,"prices":[{"passengerType":"ADT","value":"35c94bbd…|Ymkby8ax…","price":152000}]}]}]}]}]}}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/flights/baggages":{"post":{"tags":["PreBookAncillary"],"summary":"List hành lý mua thêm","description":"Trả `data.items` (shape gốc của NCC) **và** `data.addable[]` — bản phẳng đã chuẩn hoá để POST thẳng lại. Mỗi phần tử `addable` = `{ passengerId, baggageValue (TOKEN), label, weightKg, price, currency, code, route, addable }`. **`weightKg`** = số kg đã parse sẵn từ tên gói để Web hiển thị \"15 kg\" mà KHÔNG tự parse free-text: tên GDS dán cả LB+KG (PG `\"UPTO33LB15KG\"` → **15**, đã ưu tiên KG bỏ LB); gói không mang kg trong nguồn (TG `XBAG \"BAGGAGE CHARGE\"`) → **`weightKg: null`** (phụ phí phẳng, hiển thị nhãn + giá, KHÔNG bịa số). Khi mua thêm: lấy `baggageValue` + `passengerId` từ `addable[]` đưa vào `POST .../baggage` — KHÔNG gửi nhãn \"15KG\". (skeeper: options theo từng option, `passengerId=null`, client tự gán pax.) Response luôn kèm hợp đồng canonical never-block: `baggage_status` (`available`|`unavailable`), `action_required` (`null`|`CONTINUE_WITHOUT_BAGGAGE`), `capability`, và `baggage[]` (mirror của `data.addable`). Pre-book GDS timeout/session chết → vẫn HTTP 200 `ok:true` + `baggage_status:\"unavailable\"` + `CONTINUE_WITHOUT_BAGGAGE` (không bao giờ treo/chặn luồng book).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FareRefRequest"},"example":{"fareSelectionId":"fs_xxx"}}}},"responses":{"200":{"description":"Baggage catalog","content":{"application/json":{"example":{"ok":true,"baggage_status":"available","action_required":null,"capability":{"supplier":"tripota","supported":true},"data":{"items":[{"code":"PBAG","name":"UPTO33LB15KG BAGGAGE","route":"BKKHKT","prices":[{"passengerId":"2","price":1096000,"value":"b80767b4|pLU…"}]},{"code":"XBAG","name":"BAGGAGE CHARGE","route":"SGNBKK","prices":[{"passengerId":"2","price":212000,"value":"23ab57db|pLU…"}]}],"addable":[{"passengerId":"2","baggageValue":"b80767b4|pLU…","label":"UPTO33LB15KG BAGGAGE","weightKg":15,"price":1096000,"currency":"VND","code":"PBAG","route":"BKKHKT","addable":true},{"passengerId":"2","baggageValue":"23ab57db|pLU…","label":"BAGGAGE CHARGE","weightKg":null,"price":212000,"currency":"VND","code":"XBAG","route":"SGNBKK","addable":true}]},"baggage":[{"passengerId":"2","baggageValue":"b80767b4|pLU…","label":"UPTO33LB15KG BAGGAGE","weightKg":15,"price":1096000,"currency":"VND","code":"PBAG","route":"BKKHKT","addable":true},{"passengerId":"2","baggageValue":"23ab57db|pLU…","label":"BAGGAGE CHARGE","weightKg":null,"price":212000,"currency":"VND","code":"XBAG","route":"SGNBKK","addable":true}]}}}}}}},"/flights/ancillaries":{"post":{"tags":["PreBookAncillary"],"summary":"List dịch vụ mua kèm","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FareRefRequest"},"example":{"fareSelectionId":"fs_xxx"}}}},"responses":{"200":{"description":"Ancillary catalog"}}}},"/flights/bookings":{"post":{"tags":["Booking"],"summary":"Đặt giữ chỗ (hold) hoặc Hold+Issue","description":"`mode=draft` → chỉ hold; `mode=confirm` → hold + issue ngay (chung 1 call).\n\nServer trả `data.bookingRef` (prefix `bk_`) lưu 90 ngày; sau issue lưu 365 ngày.\n\n**Multi-fare (khứ hồi khác hãng)**: truyền `fareSelectionIds: [outboundId, inboundId]` — server book từng fare tuần tự, trả `data.bookings[]` thay vì `data.bookingRef`. Mỗi phần tử có `{bookingRef, supplierPnr, airline, supplier, state}`. Nếu fare thứ 2 thất bại, response lỗi kèm `data.bookedSoFar[]` để client biết PNR nào đã tạo (cần cancel thủ công).\n\n**Pax fields**: `type ∈ ADULT|CHILD|INFANT`. `contact.email` phải là email hợp lệ (zod). `contact.phone` số điện thoại VN 10 chữ số.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookRequest"},"examples":{"single":{"summary":"Single fare (1 airline)","value":{"fareSelectionId":"fs_AqeDpYVEriJd36u5qM_19","mode":"draft","passengers":[{"type":"ADULT","title":"MR","firstName":"NGUYEN","lastName":"VANA","dateOfBirth":"1990-01-01","gender":"M","nationality":"VN"}],"contact":{"email":"agent@example.com","phone":"0987654321","name":"NGUYEN VAN A"}}},"withServices":{"summary":"Kèm ghế + hành lý mua thêm (tripota/HNH)","value":{"fareSelectionId":"fs_AqeDpYVEriJd36u5qM_19","mode":"draft","passengers":[{"type":"ADULT","title":"MR","firstName":"NGUYEN","lastName":"VANA","dateOfBirth":"1990-01-01","gender":"M","nationality":"VN"}],"contact":{"email":"agent@example.com","phone":"0987654321","name":"NGUYEN VAN A"},"seats":[{"passengerIndex":0,"seatValue":"35c94bbd…|Ymkby8ax…","seatCode":"16A","segmentIndex":0}],"baggages":[{"passengerIndex":0,"baggageValue":"b80767b4|pLU…","quantity":1}]}},"international":{"summary":"Quốc tế hãng APIS — passport + dateOfBirth BẮT BUỘC","value":{"fareSelectionId":"fs_QR_SGN_DOH_id","mode":"confirm","passengers":[{"type":"ADULT","title":"MR","firstName":"NGUYEN","lastName":"VANA","dateOfBirth":"1990-01-01","gender":"M","nationality":"VN","document":{"type":"P","number":"B1234567","nationality":"VN","issueCountry":"VN","expireDate":"2030-12-31"}}],"contact":{"email":"agent@example.com","phone":"0987654321","name":"NGUYEN VAN A"}}},"multi":{"summary":"Multi-fare (cross-airline RT — 2 PNRs)","value":{"fareSelectionId":"fs_VJ_outbound_id","fareSelectionIds":["fs_VJ_outbound_id","fs_QH_inbound_id"],"mode":"draft","passengers":[{"type":"ADULT","title":"MR","firstName":"NGUYEN","lastName":"VANA","dateOfBirth":"1990-01-01","gender":"M","nationality":"VN"}],"contact":{"email":"agent@example.com","phone":"0987654321","name":"NGUYEN VAN A"}}}}}}},"responses":{"200":{"description":"Hold thành công","content":{"application/json":{"examples":{"single":{"summary":"Single booking → data.bookingRef","value":{"ok":true,"traceId":"xyz789","supplier":"tripota","operation":"book","capability":{"supported":true,"notes":null},"latencyMs":5200,"data":{"supplier":"tripota","supplierPnr":"FJA99U","gdsCode":null,"airline":"VN","source":"VN","state":"draft","bookingStatus":"HOLD","timeHold":"2026-12-20T10:00:00Z","totalAmount":2499000,"currency":"VND","passengers":[{"paxId":"12190534","firstName":"NGUYEN","lastName":"VANA","dateOfBirth":"1990-01-01","type":"ADULT"}],"itineraries":[{"airline":"VN","startPoint":"SGN","endPoint":"HAN","departTime":"2026-12-20T05:00:00Z","arriveTime":"2026-12-20T07:10:00Z","flightNumber":"VN6002","bookingClass":"B","status":"HK"}],"tickets":[],"bookingRef":"bk_XmwEWM5kTFAVS4yBb0uWK"}}},"multi":{"summary":"Multi booking → data.bookings[]","value":{"ok":true,"traceId":"xyz789","supplier":"airdata+tripota","operation":"book","data":{"bookings":[{"bookingRef":"bk_111","supplierPnr":"ABC123","airline":"VJ","supplier":"airdata","state":"draft"},{"bookingRef":"bk_222","supplierPnr":"DEF456","airline":"QH","supplier":"tripota","state":"draft"}]}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"description":"fareSelectionId expired","content":{"application/json":{"example":{"ok":false,"error":{"code":"FARE_EXPIRED","message":"..."}}}}},"409":{"$ref":"#/components/responses/Conflict"}}}},"/flights/bookings/{ref}":{"parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"},"description":"bookingRef `bk_xxx` từ `POST /bookings`"}],"get":{"tags":["Booking"],"summary":"Mở PNR (retrieve) + itinerary + pax","responses":{"200":{"description":"Booking record — gồm `data.void_capability` (NCC có void được không) và `data.tickets[].ticket_status/coupon_status` cho preflight void","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookEnvelope"},"example":{"ok":true,"traceId":"xyz789","supplier":"tripota","operation":"book","capability":{"supported":true,"notes":null},"latencyMs":5200,"data":{"supplier":"tripota","supplierPnr":"FJA99U","gdsCode":null,"airline":"VN","source":"VN","state":"issued","bookingStatus":"HOLD","timeHold":"2026-12-20T10:00:00Z","totalAmount":2499000,"currency":"VND","passengers":[{"paxId":"12190534","firstName":"NGUYEN","lastName":"VANA","dateOfBirth":"1990-01-01","type":"ADULT"}],"itineraries":[{"airline":"VN","startPoint":"SGN","endPoint":"HAN","departTime":"2026-12-20T05:00:00Z","arriveTime":"2026-12-20T07:10:00Z","flightNumber":"VN6002","bookingClass":"B","status":"HK"}],"tickets":["7382315673190"],"bookingRef":"bk_XmwEWM5kTFAVS4yBb0uWK","void_capability":{"supplier":"tripota","void_supported":true,"ticket_level_void_supported":true,"booking_level_void_supported":true,"void_requires_booking_ref":true,"void_requires_ticket_number":false,"void_idempotency_supported":true,"void_sandbox_available":false}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"tags":["Booking"],"summary":"Hủy PNR (cancelBooking)","responses":{"200":{"description":"Cancelled"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/flights/bookings/{ref}/issue":{"parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"},"description":""}],"post":{"tags":["Booking"],"summary":"Xuất vé (issue)","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IssueRequest"},"example":{"sendEmail":true,"tourCode":""}}}},"responses":{"200":{"description":"Ticket issued","content":{"application/json":{"example":{"ok":true,"supplier":"tripota","operation":"issue","data":{"tickets":["7382315673190"],"bookingStatus":"ACTIVE","totalAmount":2499000}}}}},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/flights/bookings/retrieve":{"post":{"tags":["Booking"],"summary":"Retrieve PNR theo supplier + mã PNR (booking tạo ngoài hệ thống)","description":"Mở 1 PNR khi chỉ biết `supplier + pnr` (vd. booking tạo ngoài Server Flight, hoặc cần void/issue mà không có `bookingRef`). Server tự mint/tái dùng `bookingRef` cho cặp supplier+pnr. Một số NCC bắt buộc thêm `airline` và/hoặc `lastName`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RetrieveByPnrRequest"},"example":{"supplier":"tripota","pnr":"FJA99U","airline":"VN","lastName":"NGUYEN"}}}},"responses":{"200":{"description":"Booking record (giống GET /bookings/{ref})","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookEnvelope"}}}},"400":{"description":"UNKNOWN_SUPPLIER — supplier sai hoặc không hỗ trợ retrieve","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"502":{"$ref":"#/components/responses/BadGateway"}}}},"/flights/bookings/{ref}/hold-status":{"parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"},"description":""}],"get":{"tags":["Booking"],"summary":"Trạng thái giữ chỗ (hold còn sống / TTL còn lại)","description":"Re-check fresh từ NCC khi có thể; fallback local store nếu NCC timeout (`supplierConfirmed=false`). Dùng trước issue để chắc PNR chưa hết hạn. Luôn 200.","responses":{"200":{"description":"Hold status","content":{"application/json":{"example":{"bookingRef":"bk_xxx","supplier":"tripota","stillHeld":true,"ttlSeconds":3600,"expiresAt":"2026-12-20T10:00:00Z","state":"draft","supplierConfirmed":true,"retrievedAt":"2026-12-20T09:00:00Z","booking_state":"held","ticket_state":"not_started","can_cancel":true,"can_issue":true}}}}}}},"/flights/bookings/{ref}/events":{"parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"},"description":""},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50},"description":"Số event tối đa trả về"}],"get":{"tags":["Booking"],"summary":"Lịch sử audit event của booking","description":"Timeline state-transition (book/issue/void/refund/cancel…) để ops trace. Event có thể sống lâu hơn booking record.","responses":{"200":{"description":"Events","content":{"application/json":{"example":{"ok":true,"operation":"bookingEvents","data":{"bookingRef":"bk_xxx","events":[{"id":"ev_1","type":"BOOK_DRAFT_CREATED","previousState":null,"newState":"draft","source":"book","supplier":"tripota","createdAt":1734688800000,"payload":{}},{"id":"ev_2","type":"TICKET_ISSUED","previousState":"draft","newState":"issued","source":"issue","supplier":"tripota","createdAt":1734692400000,"payload":{}}]}}}}}}}},"/flights/bookings/parent/{parentRef}":{"parameters":[{"name":"parentRef","in":"path","required":true,"schema":{"type":"string"},"description":"Parent ref của booking khứ hồi 2-PNR (cross-airline)"}],"get":{"tags":["Booking"],"summary":"Mở booking khứ hồi 2-PNR theo parentRef","description":"Cross-airline RT tạo 2 PNR con (outbound + return) gắn 1 `parentRef`. Endpoint trả cả 2 leg + trạng thái tổng hợp (`overall_supplier_state`).","responses":{"200":{"description":"Legs + overall state","content":{"application/json":{"example":{"ok":true,"operation":"retrieveParent","data":{"parent_booking_ref":"par_xxx","legs":[{"leg":"outbound","booking_ref":"bk_111","supplier":"airdata","supplier_pnr":"ABC123","state":"issued","booking_state":"issued","ticket_state":"issued","expires_at":null},{"leg":"return","booking_ref":"bk_222","supplier":"tripota","supplier_pnr":"DEF456","state":"issued","booking_state":"issued","ticket_state":"issued","expires_at":null}],"overall_supplier_state":"issued"}}}}},"404":{"description":"PARENT_NOT_FOUND","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/flights/bookings/parent/{parentRef}/issue":{"parameters":[{"name":"parentRef","in":"path","required":true,"schema":{"type":"string"},"description":""}],"post":{"tags":["Booking"],"summary":"Xuất vé cả booking khứ hồi 2-PNR (atomic-ish)","description":"Orchestrate issue cho RT 2-PNR: ticketing leg pay-now (rủi ro, không hold) TRƯỚC; chỉ khi xong mới issue leg đã hold. Nếu leg pay-now fail → dừng, giữ nguyên leg hold (`PAYNOW_LEG_FAILED`, 409). Forward `X-Idempotency-Key`.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IssueRequest"},"example":{"sendEmail":true}}}},"responses":{"200":{"description":"Tất cả leg issued","content":{"application/json":{"example":{"ok":true,"operation":"issue","data":{"parent_booking_ref":"par_xxx","results":[{"bookingRef":"bk_111","legType":"outbound","supplier":"airdata","ok":true,"state":"issued"},{"bookingRef":"bk_222","legType":"return","supplier":"tripota","ok":true,"state":"issued"}],"overall_supplier_state":"issued"}}}}},"207":{"description":"Mixed — một số leg fail"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"PAYNOW_LEG_FAILED — leg pay-now lỗi, leg hold được giữ lại","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/flights/bookings/{ref}/baggage":{"parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"},"description":""}],"get":{"tags":["BookingAncillary"],"summary":"List baggage mua thêm cho PNR (post-issue)","description":"Giống `/flights/baggages` nhưng cho PNR đã có: trả `data.items` + `data.addable[]` (kèm **`weightKg`** đã parse — xem `/flights/baggages` về cách đọc kg cho GDS & XBAG=null). Dùng `addable[].baggageValue` (TOKEN) + `passengerId` cho `POST .../baggage`.","responses":{"200":{"description":"Items + addable[]","content":{"application/json":{"example":{"ok":true,"operation":"baggageList","data":{"items":[{"code":"XWBG","name":"20Kg","route":"SGNDAD","prices":[{"passengerId":"2","price":260000,"value":"b8…|TVEG…"}]}],"addable":[{"passengerId":"2","baggageValue":"b8…|TVEG…","label":"20Kg","weightKg":20,"price":260000,"currency":"VND","code":"XWBG","route":"SGNDAD","addable":true}]}}}}}}},"post":{"tags":["BookingAncillary"],"summary":"Mua thêm hành lý","description":"Gửi `baggageValue` = TOKEN lấy từ `addable[].baggageValue` của GET `.../baggage` (KHÔNG phải nhãn \"15KG\" — nhãn literal gây HNH 21001 \"Index outside bounds\"). `passengerId`/`passengerIndex` chỉ định pax (skeeper bắt buộc client tự gán vì list không kèm passengerId).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddBaggageRequest"},"example":{"baggages":[{"passengerId":"2","baggageValue":"b8…|TVEG…","quantity":1}]}}}},"responses":{"200":{"description":"OK"},"409":{"$ref":"#/components/responses/Conflict"}}},"delete":{"tags":["BookingAncillary"],"summary":"Xoá baggage đã thêm","requestBody":{"content":{"application/json":{"example":{"baggages":[{"baggageValue":"15KG_APBG"}]}}}},"responses":{"200":{"description":"OK"}}}},"/flights/bookings/{ref}/seats":{"parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"},"description":""}],"get":{"tags":["BookingAncillary"],"summary":"Seat map cho PNR","responses":{"200":{"description":"Map"}}},"post":{"tags":["BookingAncillary"],"summary":"Mua thêm chỗ ngồi","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddSeatRequest"},"example":{"seats":[{"passengerIndex":0,"seatValue":"12A"}]}}}},"responses":{"200":{"description":"OK"}}},"delete":{"tags":["BookingAncillary"],"summary":"Xoá seat","responses":{"200":{"description":"OK"}}}},"/flights/bookings/{ref}/ancillary":{"parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"},"description":""}],"get":{"tags":["BookingAncillary"],"summary":"List services mua thêm","responses":{"200":{"description":"Items"}}},"post":{"tags":["BookingAncillary"],"summary":"Mua thêm dịch vụ","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddAncillaryRequest"},"example":{"services":[{"passengerIndex":0,"ancillaryValue":"UPGD_FROM-B_TO-W","quantity":1}]}}}},"responses":{"200":{"description":"OK"}}},"delete":{"tags":["BookingAncillary"],"summary":"Xoá service","responses":{"200":{"description":"OK"}}}},"/flights/bookings/{ref}/segments":{"parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"},"description":""}],"post":{"tags":["Modify"],"summary":"Thêm hành trình (modifyAddFlights)","requestBody":{"required":true,"content":{"application/json":{"example":{"flightAddNews":["<flight_value_opaque_key>"]}}}},"responses":{"200":{"description":"OK"}}},"delete":{"tags":["Modify"],"summary":"Hủy hành trình (cancelAll hoặc từng segment)","requestBody":{"required":true,"content":{"application/json":{"example":{"cancelAll":true}}}},"responses":{"200":{"description":"OK"}}},"patch":{"tags":["Modify"],"summary":"Đổi hành trình atomic (cancel cũ + add mới)","requestBody":{"required":true,"content":{"application/json":{"example":{"flightCancels":["<fv1>"],"flightAddNews":["<fv2>"]}}}},"responses":{"200":{"description":"OK"}}}},"/flights/bookings/{ref}/passengers":{"parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"},"description":""}],"patch":{"tags":["Modify"],"summary":"Sửa thông tin pax / đổi tên (name-change)","requestBody":{"required":true,"content":{"application/json":{"example":{"passengers":[{"passengerId":"12190534","firstName":"NEW_NAME","lastName":"SURNAME","dateOfBirth":"1990-01-01","type":"ADULT"}]}}}},"responses":{"200":{"description":"OK"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/flights/bookings/{ref}/infant":{"parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"},"description":""}],"post":{"tags":["Modify"],"summary":"Thêm em bé (INF)","requestBody":{"required":true,"content":{"application/json":{"example":{"infants":[{"givenName":"BABY","surname":"USER","dateOfBirth":"2025-06-01","parentId":"12190534"}]}}}},"responses":{"200":{"description":"OK"}}}},"/flights/bookings/{ref}/split":{"parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"},"description":""}],"post":{"tags":["Modify"],"summary":"Tách hành khách ra PNR riêng","requestBody":{"required":true,"content":{"application/json":{"example":{"splitPassengers":[{"type":"ADT","givenName":"TEST","surName":"USER","passengerId":"12190534"}]}}}},"responses":{"200":{"description":"OK"}}}},"/flights/bookings/{ref}/remarks":{"parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"},"description":""}],"post":{"tags":["PNROps"],"summary":"Add remarks (note nội bộ)","requestBody":{"required":true,"content":{"application/json":{"example":{"remarks":[{"remark":"TEST REMARK","public":false}]}}}},"responses":{"200":{"description":"OK"}}}},"/flights/bookings/{ref}/osi":{"parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"},"description":""}],"post":{"tags":["PNROps"],"summary":"Add OSI (airline info)","requestBody":{"required":true,"content":{"application/json":{"example":{"items":[{"osi":"VU PSGR HAS PEANUT ALLERGY","passengerId":"12190534"}]}}}},"responses":{"200":{"description":"OK"}}}},"/flights/bookings/{ref}/sk":{"parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"},"description":""}],"post":{"tags":["PNROps"],"summary":"Add SK (special keyword)","requestBody":{"required":true,"content":{"application/json":{"example":{"items":[{"sk":"TEST SK","type":"info","passengerId":"12190534"}]}}}},"responses":{"200":{"description":"OK"}}}},"/flights/bookings/{ref}/documents":{"parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"},"description":""}],"post":{"tags":["PNROps"],"summary":"Add Passport / Visa","requestBody":{"required":true,"content":{"application/json":{"example":{"passengers":[{"passengerId":"12190534","documents":[{"type":"P","number":"B12345678","issueCountry":"VN","national":"VN","expiryDate":"2030-12-31","gender":"M","dateOfBirth":"1990-01-01","surName":"USER","givenName":"NGUYEN"}]}]}}}},"responses":{"200":{"description":"OK"}}}},"/flights/bookings/{ref}/fftv":{"parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"},"description":""}],"post":{"tags":["PNROps"],"summary":"Add Membership (FFTV Lotus Miles)","requestBody":{"required":true,"content":{"application/json":{"example":{"passengers":[{"passengerId":"12190534","memberships":[{"airline":"VN","membershipId":"VN1234567"}]}]}}}},"responses":{"200":{"description":"OK"}}}},"/flights/bookings/{ref}/void":{"parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"},"description":""},{"name":"X-Idempotency-Key","in":"header","required":false,"schema":{"type":"string"},"description":"Khóa idempotency `void:{bookingRef}:{ticketNumber}` — retry cùng key sẽ replay, không gọi lại NCC."}],"post":{"tags":["Tickets"],"summary":"Void PNR (trước deadline void)","requestBody":{"content":{"application/json":{"example":{"voidAll":true}}}},"responses":{"200":{"description":"OK — `data.void` chứa quyết định chuẩn hóa","content":{"application/json":{"example":{"ok":true,"data":{"void":{"ok":true,"decision":"success","ticket_number":"7382315673190","booking_ref":"bk_xxx","supplier":"tripota","airline":"VN","void_ref":null,"error_code":null,"error_message":null,"trace_id":"t","raw_ref":"raw:t","idempotency_key":"void:bk_xxx:7382315673190"}}}}}},"409":{"$ref":"#/components/responses/Conflict"}}}},"/flights/bookings/{ref}/refund":{"parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"},"description":""}],"post":{"tags":["Tickets"],"summary":"Hoàn vé theo PNR (legacy passthrough)","description":"Passthrough trực tiếp tới adapter.refund (KHÔNG eligibility-gate, KHÔNG idempotency). Production nên dùng module chuẩn `POST /flights/refunds/submit`.","requestBody":{"content":{"application/json":{"example":{"refundAll":true,"mode":"full","reason":"customer"}}}},"responses":{"200":{"description":"OK"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/flights/bookings/{ref}/exchange":{"parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"},"description":""}],"post":{"tags":["Tickets"],"summary":"Đổi vé theo PNR (legacy passthrough)","description":"Passthrough trực tiếp tới adapter.exchange. Production nên dùng module chuẩn `/flights/exchanges/*` (eligibility → quote → execute → retrieve).","requestBody":{"required":true,"content":{"application/json":{"example":{"mode":"search"}}}},"responses":{"200":{"description":"OK"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/flights/tickets/{ticketNo}/void":{"parameters":[{"name":"ticketNo","in":"path","required":true,"schema":{"type":"string"},"description":"Ticket number VD `7382315673190`"},{"name":"X-Idempotency-Key","in":"header","required":false,"schema":{"type":"string"},"description":"Khóa idempotency `void:{bookingRef}:{ticketNumber}` — retry cùng key sẽ replay, không gọi lại NCC."}],"post":{"tags":["Tickets"],"summary":"Void theo ticket number","responses":{"200":{"description":"OK — `data.void.decision` ∈ success|already_voided|rejected|failed|timeout|unknown"},"404":{"description":"Ticket not registered"},"501":{"description":"NCC không hỗ trợ void (data.void.decision=rejected)"}}}},"/flights/tickets/{ticketNo}/refund":{"parameters":[{"name":"ticketNo","in":"path","required":true,"schema":{"type":"string"},"description":""}],"post":{"tags":["Tickets"],"summary":"Hoàn theo ticket number","responses":{"200":{"description":"OK"}}}},"/flights/tickets/{ticketNo}/exchange":{"parameters":[{"name":"ticketNo","in":"path","required":true,"schema":{"type":"string"},"description":""}],"post":{"tags":["Tickets"],"summary":"Đổi theo ticket number","requestBody":{"required":true,"content":{"application/json":{"example":{"mode":"search"}}}},"responses":{"200":{"description":"OK"}}}},"/flights/bookings/{ref}/email":{"parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"},"description":""}],"post":{"tags":["Comms"],"summary":"Gửi email xác nhận / e-ticket","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendEmailRequest"},"example":{"email":"pax@example.com","language":"vi"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"ok":true,"data":{"raw":{"trackingId":"abc","status":true}}}}}}}}},"/flights/bookings/{ref}/queue":{"parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"},"description":""}],"post":{"tags":["Comms"],"summary":"Queue place (đẩy PNR vào queue OPS)","requestBody":{"content":{"application/json":{"example":{"queueNumber":50,"validatingCarrier":"VN"}}}},"responses":{"200":{"description":"OK"}}}},"/flights/bookings/{ref}/face":{"parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"},"description":""}],"get":{"tags":["Comms"],"summary":"Face PNR (plain text)","responses":{"200":{"description":"Plain text PNR","content":{"application/json":{"example":{"ok":true,"data":{"raw":{"system":"VU","plainText":"WXEUM6\n1. USER/TEST MRS (ADT 01JAN90)\n2. VU 672 A 20SEP SGNDAD HK1 0800 0925"}}}}}}}}},"/flights/bookings/{ref}/print":{"parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"},"description":""}],"get":{"tags":["Comms"],"summary":"In vé (sau xuất)","responses":{"200":{"description":"Ticket image / PDF ref"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/flights/bookings/{ref}/contact":{"parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"},"description":""}],"patch":{"tags":["Comms"],"summary":"Update email / phone / fullName","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateContactRequest"},"example":{"email":"new@example.com","phone":"0987654321","fullName":"NGUYEN VAN A"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"ok":true,"operation":"updateContact","data":{"raw":{"status":true,"trackingId":"xyz"}}}}}},"501":{"$ref":"#/components/responses/NotSupported"}}}},"/flights/flight-status":{"post":{"tags":["FlightInfo"],"summary":"Tình trạng chuyến bay (flight status)","description":"Hỗ trợ 2 mode:\n- `{bookingRef}` → tra status cho mọi segment trong booking.\n- `{system, flightValue}` hoặc `{system, flightValues[]}` → stateless ad-hoc lookup (cần opaque key từ prior search).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlightStatusRequest"},"examples":{"byBookingRef":{"value":{"bookingRef":"bk_xxx"}},"byFlightValue":{"value":{"system":"VN","flightValue":"0VN6002BSGNHAN20261220"}}}}}},"responses":{"200":{"description":"Flight info"},"404":{"$ref":"#/components/responses/NotFound"},"501":{"$ref":"#/components/responses/NotSupported"}}}},"/flights/refunds/retrieve":{"post":{"tags":["Refund"],"summary":"Đọc trạng thái NCC + eligibility hoàn vé (read-only)","description":"Bước 1 luồng hoàn vé chuẩn. Retrieve-before: đọc state NCC, không mutate. Trả `data.refund_retrieve` + `data.capability`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["bookingRef"],"properties":{"bookingRef":{"type":"string"},"pnr":{"type":"string"},"supplier":{"type":"string"},"airline":{"type":"string"},"tickets":{"type":"array","items":{"type":"string"}}}},"example":{"bookingRef":"bk_xxx"}}}},"responses":{"200":{"description":"Refund state + eligibility","content":{"application/json":{"example":{"ok":true,"operation":"refundRetrieve","supplier":"tripota","data":{"refund_retrieve":{"ok":true,"booking_ref":"bk_xxx","supplier":"tripota","airline":"VN","pnr":"FJA99U","booking_state":"issued","ticket_state":"issued","coupon_status":"open","refund_state":null,"eligible":true,"eligibility_reason":null,"next_action":"refund","retrieve_ok":true,"error_code":null},"capability":{"refund_supported":true,"quote_supported":false,"full_booking_supported":true,"ticket_refund_supported":true,"status_check_supported":true}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/flights/refunds/quote":{"post":{"tags":["Refund"],"summary":"Báo phí + ước tính tiền hoàn (nếu NCC hỗ trợ; else manual_required)","description":"Bước 2. Hiện không NCC nào có quote API → `quote_supported=false, manual_required=true` (vẫn submit được, NV tính phí tay). Có thể trả `eligible=false` + `eligibility_reason` (TICKET_VOIDED/ALREADY_REFUNDED/…).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["bookingRef"],"properties":{"bookingRef":{"type":"string"},"refundType":{"type":"string","enum":["full_booking","ticket","passenger","segment"],"default":"ticket"},"tickets":{"type":"array","items":{"type":"string"}},"passengers":{"type":"array","items":{"type":"string"}},"segments":{"type":"array","items":{"type":"string"}},"reasonCode":{"type":"string"}}},"example":{"bookingRef":"bk_xxx","refundType":"ticket","tickets":["7382315673190"],"reasonCode":"SCHEDULE_CHANGE"}}}},"responses":{"200":{"description":"Quote hoặc manual_required","content":{"application/json":{"example":{"ok":true,"operation":"refundQuote","data":{"refund_quote":{"booking_ref":"bk_xxx","supplier":"tripota","refund_type":"ticket","ticket_numbers":["7382315673190"],"eligible":true,"quote_supported":false,"fee":null,"estimated_refund_amount":null,"currency":null,"manual_required":true}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/flights/refunds/submit":{"post":{"tags":["Refund"],"summary":"Gửi yêu cầu hoàn (idempotent, eligibility-gated)","description":"Bước 3. Eligibility gate TRƯỚC khi gọi NCC (voided/refunded/pending KHÔNG gọi NCC). Idempotent qua `idempotencyKey`/`X-Idempotency-Key`. Timeout/5xx → `refund_state=unknown` (KHÔNG auto-retry; tra qua /status). airdata/skeeper → `manual_required` (chưa hỗ trợ refund API).","parameters":[{"name":"X-Idempotency-Key","in":"header","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["bookingRef","reasonCode"],"properties":{"bookingRef":{"type":"string"},"refundReference":{"type":"string","description":"DLV ref; server sinh rf_<id> nếu bỏ trống"},"idempotencyKey":{"type":"string"},"refundType":{"type":"string","enum":["full_booking","ticket","passenger","segment"],"default":"ticket"},"tickets":{"type":"array","items":{"type":"string"}},"passengers":{"type":"array","items":{"type":"string"}},"segments":{"type":"array","items":{"type":"string"}},"reasonCode":{"type":"string"},"reasonNote":{"type":"string"},"isEMD":{"type":"boolean"}}},"example":{"bookingRef":"bk_xxx","refundType":"ticket","tickets":["7382315673190"],"reasonCode":"SCHEDULE_CHANGE"}}}},"responses":{"200":{"description":"Refund result (refund_state ∈ submitted|pending|approved|refunded|rejected|failed|unknown|manual_required)","content":{"application/json":{"example":{"ok":true,"operation":"refundSubmit","data":{"refund":{"ok":true,"refund_state":"refunded","refund_reference":"rf_xxx","supplier_refund_ref":"RF9","refund_type":"ticket","ticket_numbers":["7382315673190"],"booking_ref":"bk_xxx","supplier":"tripota","manual_required":false,"idempotency_key":"idem-xxx"}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/flights/refunds/status":{"post":{"tags":["Refund"],"summary":"Tra trạng thái hoàn đã submit","description":"Resolve `supplier_refund_state` (refunded/pending/rejected/… hoặc `not_received` khi NCC chưa có record). Cần ≥1 trong `refundReference`/`supplierRefundRef`/`idempotencyKey`/`bookingRef`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"bookingRef":{"type":"string"},"refundReference":{"type":"string"},"supplierRefundRef":{"type":"string"},"idempotencyKey":{"type":"string"}}},"example":{"refundReference":"rf_xxx"}}}},"responses":{"200":{"description":"Refund status","content":{"application/json":{"example":{"ok":true,"operation":"refundStatus","data":{"refund_status":{"ok":true,"supplier_refund_state":"refunded","refund_reference":"rf_xxx","supplier_refund_ref":"RF9","booking_ref":"bk_xxx","supplier":"tripota"}}}}}}}}},"/flights/exchanges/eligibility":{"post":{"tags":["Exchange"],"summary":"Kiểm tra điều kiện đổi vé (read-only, retrieve-before)","description":"Bước 1. Gate `FLIGHT_EXCHANGE_ENABLED` (mặc định off). Đọc state NCC, không mutate. `reasonCode` set khi ineligible (ALREADY_EXCHANGED/EXCHANGE_NOT_SUPPORTED/…).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["bookingRef"],"properties":{"bookingRef":{"type":"string"},"pnr":{"type":"string"},"supplier":{"type":"string"},"airline":{"type":"string"},"scope":{"$ref":"#/components/schemas/ChangeScope"}}},"example":{"bookingRef":"bk_xxx","scope":{"type":"ticket","ticketNumbers":["7382315673190"]}}}}},"responses":{"200":{"description":"Eligibility","content":{"application/json":{"example":{"ok":true,"operation":"exchangeEligibility","data":{"exchange_eligibility":{"eligible":true,"bookingState":"issued","ticketState":"issued","scope_type":"ticket","reasonCode":null,"warnings":[],"booking_ref":"bk_xxx","supplier":"tripota","retrieve_ok":true}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/flights/exchanges/search-options":{"post":{"tags":["Exchange"],"summary":"Reshop — tìm hành trình đổi (read-only)","description":"Bước 2. Trả `options[]` từ NCC. `manual_required=true` nếu flag off (EXCHANGE_DISABLED) hoặc NCC không có reshop API (EXCHANGE_NOT_SUPPORTED).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["bookingRef"],"properties":{"bookingRef":{"type":"string"},"scope":{"$ref":"#/components/schemas/ChangeScope"},"newItineraryRequest":{"$ref":"#/components/schemas/NewItineraryRequest"}}},"example":{"bookingRef":"bk_xxx","newItineraryRequest":{"origin":"SGN","destination":"HAN","departureDate":"2026-12-28"}}}}},"responses":{"200":{"description":"Options","content":{"application/json":{"example":{"ok":true,"operation":"exchangeSearchOptions","data":{"exchange_search":{"booking_ref":"bk_xxx","supplier":"tripota","scope_type":"ticket","options":[],"search_supported":true,"manual_required":false,"reasonCode":null}}}}}}}}},"/flights/exchanges/quote":{"post":{"tags":["Exchange"],"summary":"Reprice — phí đổi + chênh lệch giá (read-only)","description":"Bước 3. Lưu quote + token NCC để execute. `validUntil` mặc định `FLIGHT_EXCHANGE_QUOTE_TTL_MINUTES` (15). Không bao giờ tự tính phí.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["bookingRef"],"properties":{"bookingRef":{"type":"string"},"scope":{"$ref":"#/components/schemas/ChangeScope"},"newItineraryRequest":{"$ref":"#/components/schemas/NewItineraryRequest"},"flightCancels":{"type":"array","items":{},"description":"Opaque token NCC"},"flightExchanges":{"type":"array","items":{},"description":"Opaque token NCC"}}},"example":{"bookingRef":"bk_xxx","flightExchanges":["<opaque-token>"]}}}},"responses":{"200":{"description":"Quote","content":{"application/json":{"example":{"ok":true,"operation":"exchangeQuote","data":{"exchange_quote":{"quoteRef":"xq_xxx","bookingRef":"bk_xxx","supplier":"tripota","currency":"VND","priceBreakdown":{"airlineChangeFee":600000,"fareDifference":200000,"taxDifference":50000,"supplierFee":0,"totalDueToSupplier":850000},"quote_supported":true,"manual_required":false,"eligible":true,"validUntil":"2026-12-20T09:15:00Z"}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/flights/exchanges/execute":{"post":{"tags":["Exchange"],"summary":"Xác nhận đổi vé (idempotent, gated, reprice-before-confirm)","description":"Bước 4. CHỈ live khi `FLIGHT_EXCHANGE_ENABLED` + `FLIGHT_EXCHANGE_LIVE_EXECUTE_ENABLED` + supplier ∈ `FLIGHT_EXCHANGE_SUPPORTED_SUPPLIERS` (deny-default → `rejected` + `manual_required`). Cần `quoteRef` còn hạn (execute KHÔNG tự quote). Reprice lại trước confirm (mặc định on). Timeout → `unknown`.","parameters":[{"name":"X-Idempotency-Key","in":"header","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["bookingRef","quoteRef"],"properties":{"bookingRef":{"type":"string"},"quoteRef":{"type":"string"},"exchangeReference":{"type":"string","description":"server sinh xc_<id> nếu bỏ trống"},"newFareSelectionId":{"type":"string"},"scope":{"$ref":"#/components/schemas/ChangeScope"},"idempotencyKey":{"type":"string"}}},"example":{"bookingRef":"bk_xxx","quoteRef":"xq_xxx"}}}},"responses":{"200":{"description":"Exchange result (exchange_state ∈ exchanged|rejected|already_exchanged|price_changed|quote_expired|no_seat|failed|unknown)","content":{"application/json":{"example":{"ok":true,"operation":"exchangeExecute","data":{"exchange":{"exchange_state":"exchanged","reason_code":null,"manual_required":false,"supplier_exchange_ref":"XC123"}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/flights/exchanges/{exchangeRef}/retrieve":{"parameters":[{"name":"exchangeRef","in":"path","required":true,"schema":{"type":"string"},"description":""},{"name":"bookingRef","in":"query","required":false,"schema":{"type":"string"}}],"get":{"tags":["Exchange"],"summary":"Đối soát sau đổi vé (resolve unknown)","description":"Sau execute=unknown: xác nhận `exchanged` / `not_exchanged` (`can_retry=true` CHỈ khi NCC xác nhận chưa đổi) / `unknown`. Luôn 200.","responses":{"200":{"description":"Retrieve-after","content":{"application/json":{"example":{"ok":true,"operation":"exchangeRetrieve","data":{"exchange_retrieve":{"exchange_state":"exchanged","can_retry":false,"booking_ref":"bk_xxx","supplier":"tripota"}}}}}}}}},"/flights/upgrades/capability":{"get":{"tags":["Upgrade"],"summary":"Capability nâng hạng của 1 NCC","parameters":[{"name":"supplier","in":"query","required":true,"schema":{"type":"string","enum":["airdata","tripota","skeeper"]}},{"name":"airline","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Capability","content":{"application/json":{"example":{"ok":true,"operation":"upgradeCapability","data":{"supported":false,"mode":"manual_only","supports_quote":false,"supports_idempotency":false,"reason":"Upgrade chưa bật cho NCC này"}}}}},"400":{"description":"SUPPLIER_REQUIRED"}}}},"/flights/upgrades/retrieve":{"post":{"tags":["Upgrade"],"summary":"Đọc state vé trước khi nâng hạng","description":"Cần ≥1 trong `bookingRef`/`booking_id`/`pnr`/`ticket_numbers`. Gate `FLIGHT_UPGRADE_ENABLED`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"bookingRef":{"type":"string"},"booking_id":{"type":"string"},"pnr":{"type":"string"},"supplier":{"type":"string"},"airline":{"type":"string"},"ticket_numbers":{"type":"array","items":{"type":"string"}}}},"example":{"bookingRef":"bk_xxx"}}}},"responses":{"200":{"description":"State","content":{"application/json":{"example":{"ok":true,"operation":"upgradeRetrieve","data":{"upgrade_retrieve":{"status":"OK","state":"ISSUED","pnr":"FJA99U","tickets":[{"ticket_number":"7382315673190","state":"ISSUED","cabin":"Y","booking_class":"B","coupon_status":"OPEN"}],"segments":[{"segment_id":"s1","from":"SGN","to":"HAN","flown":false}]}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/flights/upgrades/options":{"post":{"tags":["Upgrade"],"summary":"Tìm + báo giá option nâng hạng","description":"Trả `quote_id` (UPQ_*) + `options[]`. `status` ∈ HAS_OPTIONS|MANUAL_ONLY|NO_OPTION. TTL theo `FLIGHT_UPGRADE_QUOTE_TTL_MINUTES` (15).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["bookingRef"],"properties":{"bookingRef":{"type":"string"},"scope":{"$ref":"#/components/schemas/ChangeScope"},"desired_cabin":{"type":"string"}}},"example":{"bookingRef":"bk_xxx","desired_cabin":"C"}}}},"responses":{"200":{"description":"Options","content":{"application/json":{"example":{"ok":true,"operation":"upgradeOptions","data":{"upgrade_options":{"status":"HAS_OPTIONS","quote_id":"UPQ_xxx","expires_at":"2026-12-20T09:15:00Z","options":[{"option_id":"opt_1","operation_type":"DIRECT_UPGRADE","from_cabin":"Y","to_cabin":"C","fare_diff":1500000,"supplier_fee":0,"total_supplier_collect":1500000,"currency":"VND","rules":{"refundable":false,"changeable":true}}]}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/flights/upgrades/reprice":{"post":{"tags":["Upgrade"],"summary":"Re-validate giá option (read-only)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["quote_id","option_id"],"properties":{"bookingRef":{"type":"string"},"quote_id":{"type":"string"},"option_id":{"type":"string"}}},"example":{"bookingRef":"bk_xxx","quote_id":"UPQ_xxx","option_id":"opt_1"}}}},"responses":{"200":{"description":"Reprice — status ∈ PRICE_STILL_VALID|PRICE_CHANGED|NO_SEAT|REPRICE_FAILED|QUOTE_EXPIRED","content":{"application/json":{"example":{"ok":true,"operation":"upgradeReprice","data":{"upgrade_reprice":{"status":"PRICE_STILL_VALID","quote_id":"UPQ_xxx","option_id":"opt_1","total_supplier_collect":1500000,"currency":"VND","expires_at":"2026-12-20T09:20:00Z"}}}}}}}}},"/flights/upgrades/confirm":{"post":{"tags":["Upgrade"],"summary":"Xác nhận nâng hạng (idempotent, gated)","description":"CHỈ live khi `FLIGHT_UPGRADE_ENABLED` + `FLIGHT_UPGRADE_LIVE_EXECUTE_ENABLED` + supplier ∈ `FLIGHT_UPGRADE_SUPPORTED_SUPPLIERS` (deny-default). Timeout → `unknown` → dùng /retrieve-after. Bắt buộc `idempotency_key`.","parameters":[{"name":"X-Idempotency-Key","in":"header","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["bookingRef","quote_id","option_id","idempotency_key"],"properties":{"bookingRef":{"type":"string"},"quote_id":{"type":"string"},"option_id":{"type":"string"},"scope":{"$ref":"#/components/schemas/ChangeScope"},"upgradeReference":{"type":"string"},"idempotency_key":{"type":"string"}}},"example":{"bookingRef":"bk_xxx","quote_id":"UPQ_xxx","option_id":"opt_1","idempotency_key":"up-1"}}}},"responses":{"200":{"description":"Upgrade result (status ∈ success|already_upgraded|manual_required|live_disabled|quote_expired|price_changed|no_seat|reissued|failed|unknown)","content":{"application/json":{"example":{"ok":true,"operation":"upgradeConfirm","data":{"upgrade":{"ok":true,"status":"success","upgrade_state":"success","pnr":"FJA99U","new_tickets":["7382315673999"],"new_cabin":"C","new_booking_class":"D","amount_charged":1500000,"supplier_upgrade_ref":"UP1","can_retry":false,"recovered":false}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/flights/upgrades/{upgradeRef}/retrieve-after":{"parameters":[{"name":"upgradeRef","in":"path","required":true,"schema":{"type":"string"},"description":""},{"name":"bookingRef","in":"query","required":false,"schema":{"type":"string"}}],"get":{"tags":["Upgrade"],"summary":"Đối soát sau confirm (resolve unknown)","responses":{"200":{"description":"Retrieve-after — status ∈ success|not_upgraded|unknown|manual_processing|not_found","content":{"application/json":{"example":{"ok":true,"operation":"upgradeRetrieveAfter","data":{"upgrade_retrieve_after":{"status":"success","upgrade_state":"success","pnr":"FJA99U","new_tickets":["7382315673999"],"new_cabin":"C","can_retry":false,"recovered":true}}}}}}}}},"/flights/splits/eligibility":{"post":{"tags":["Split"],"summary":"Kiểm tra điều kiện tách PNR (read-only)","description":"Gate `FLIGHT_SPLIT_ENABLED`. Trả `splitStage` (pre_issue/post_issue) + `warnings`. Bị chặn nếu có op nguy hiểm đang pending (issue/refund/exchange/baggage/upgrade).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["bookingRef"],"properties":{"bookingRef":{"type":"string"},"pnr":{"type":"string"},"supplier":{"type":"string"},"airline":{"type":"string"},"selectedPassengers":{"$ref":"#/components/schemas/SelectedPassengers"}}},"example":{"bookingRef":"bk_xxx","selectedPassengers":[{"passengerId":"12190534"}]}}}},"responses":{"200":{"description":"Eligibility","content":{"application/json":{"example":{"ok":true,"operation":"splitEligibility","data":{"split_eligibility":{"eligible":true,"bookingState":"issued","splitStage":"post_issue","pnr":"FJA99U","passengerCount":3,"warnings":[],"reasonCode":null,"booking_ref":"bk_xxx","supplier":"tripota","retrieve_ok":true}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/flights/splits":{"post":{"tags":["Split"],"summary":"Tách hành khách ra PNR mới (idempotent, gated, locked)","description":"CHỈ live khi `FLIGHT_SPLIT_ENABLED` + `FLIGHT_SPLIT_LIVE_EXECUTE_ENABLED` + supplier ∈ `FLIGHT_SPLIT_SUPPORTED_SUPPLIERS` (deny-default). Lock per-booking (409 `SPLIT_IN_PROGRESS`). Timeout → `unknown` → dùng /retrieve.","parameters":[{"name":"X-Idempotency-Key","in":"header","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["bookingRef","selectedPassengers"],"properties":{"bookingRef":{"type":"string"},"selectedPassengers":{"$ref":"#/components/schemas/SelectedPassengers"},"splitReference":{"type":"string"},"reason":{"type":"string"},"note":{"type":"string"},"idempotencyKey":{"type":"string"}}},"example":{"bookingRef":"bk_xxx","selectedPassengers":[{"passengerId":"12190534"}],"reason":"customer request"}}}},"responses":{"200":{"description":"Split result (status ∈ success|already_split|rejected|failed|manual_processing|unknown)","content":{"application/json":{"example":{"ok":true,"operation":"splitExecute","data":{"split":{"ok":true,"status":"success","splitReference":"sp_xxx","splitStage":"post_issue","newPnr":"XYZ789","newBookingRef":"bk_new","selectedPassengers":[{"passengerId":"12190534"}],"can_retry":false,"recovered":false}}}}}},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"SPLIT_IN_PROGRESS — đang có split khác chạy trên booking này","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/flights/splits/{splitRef}/retrieve":{"parameters":[{"name":"splitRef","in":"path","required":true,"schema":{"type":"string"},"description":""},{"name":"bookingRef","in":"query","required":false,"schema":{"type":"string"}}],"get":{"tags":["Split"],"summary":"Đối soát sau tách (resolve unknown)","responses":{"200":{"description":"Retrieve-after — status ∈ success|not_split|unknown|not_found","content":{"application/json":{"example":{"ok":true,"operation":"splitRetrieve","data":{"split_retrieve":{"status":"success","splitReference":"sp_xxx","newPnr":"XYZ789","newBookingRef":"bk_new","can_retry":false,"recovered":false}}}}}}}}},"/flights/check-credit":{"get":{"tags":["Ops"],"summary":"Kiểm tra credit balance đại lý","description":"Chỉ `airdata` hỗ trợ. `amount` optional để check đủ tiền (`sufficientCredit`). NCC khác → 501.","parameters":[{"name":"supplier","in":"query","required":true,"schema":{"type":"string","enum":["airdata","tripota","skeeper"]}},{"name":"amount","in":"query","required":false,"schema":{"type":"number"}}],"responses":{"200":{"description":"Credit","content":{"application/json":{"example":{"ok":true,"operation":"checkCredit","supplier":"airdata","data":{"creditBalance":50000000,"currency":"VND","sufficientCredit":true}}}}},"400":{"description":"MISSING_SUPPLIER"},"501":{"$ref":"#/components/responses/NotSupported"}}}},"/flights/available-distributor":{"get":{"tags":["Ops"],"summary":"Danh sách distributor/source khả dụng","description":"Chỉ `airdata` hỗ trợ. Shape `raw` tuỳ NCC — đọc defensive. NCC khác → 501.","parameters":[{"name":"supplier","in":"query","required":true,"schema":{"type":"string","enum":["airdata","tripota","skeeper"]}}],"responses":{"200":{"description":"Distributors","content":{"application/json":{"example":{"ok":true,"operation":"availableDistributors","supplier":"airdata","data":{"items":[{"id":"VN1A","name":"Vietnam Airlines 1A","source":"VN1A","status":"active"}]}}}}},"400":{"description":"MISSING_SUPPLIER"},"501":{"$ref":"#/components/responses/NotSupported"}}}}},"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"API key được cấp cho mỗi đại lý (env `API_KEY_DEFAULT` hoặc `AGENCY_KEY_<name>`). Liên hệ DLV Tech."}},"responses":{"BadRequest":{"description":"Validation lỗi — thiếu/sai field. Response có `error.details[]`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"},"example":{"ok":false,"error":{"code":"VALIDATION","message":"Invalid request body","category":"validation","retryable":false,"details":[{"path":"passengers.0.type","message":"Invalid option: expected ADULT|CHILD|INFANT","code":"invalid_value"}]}}}}},"Unauthorized":{"description":"Thiếu API key. Gắn `X-API-Key: <key>`.","content":{"application/json":{"example":{"error":"Missing API key. Use X-API-Key header."}}}},"Forbidden":{"description":"API key sai hoặc bị revoke.","content":{"application/json":{"example":{"error":"Invalid API key."}}}},"NotFound":{"description":"Booking/ticket không tồn tại hoặc đã expire (TTL 90d booked, 365d issued).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"},"example":{"ok":false,"error":{"code":"BOOKING_NOT_FOUND","message":"Booking \"bk_xxx\" not found or expired","category":"logical","retryable":false}}}}},"Conflict":{"description":"Supplier từ chối hợp lệ (vd. System is not supported, wrong state, quota).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"},"example":{"ok":false,"error":{"code":"SUPPLIER_REJECTED","message":"tripota rejected request: HTTP 400","supplierCode":"00038","supplierMessage":"System is not supported","category":"logical","retryable":false}}}}},"BadGateway":{"description":"Aggregator unreachable.","content":{"application/json":{"example":{"error":"Aggregator unavailable","details":"AbortError: signal timed out"}}}},"NotSupported":{"description":"Supplier không implement operation này.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"},"example":{"ok":false,"error":{"code":"NOT_SUPPORTED_BY_OWNING_SUPPLIER","message":"Operation \"exchange\" not supported by supplier \"skeeper\"","category":"capability","retryable":false}}}}}},"schemas":{"IataCode":{"type":"string","minLength":3,"maxLength":3,"pattern":"^[A-Z]{3}$","example":"SGN","description":"Mã sân bay IATA 3 ký tự"},"IsoDate":{"type":"string","format":"date","example":"2026-12-20","description":"Ngày YYYY-MM-DD"},"PaxType":{"type":"string","enum":["ADULT","CHILD","INFANT","ADT","CHD","INF"]},"Currency":{"type":"string","example":"VND"},"SearchRequest":{"type":"object","required":["startPoint","endPoint","departDate"],"description":"Một chiều (OW), khứ hồi (thêm `returnDate`), đa chặng (dùng `flights[]` thay `startPoint/endPoint/departDate`).","properties":{"type":{"type":"string","enum":["OW","RT","MC"],"description":"Optional hint. Server tự infer từ returnDate/flights[]"},"startPoint":{"$ref":"#/components/schemas/IataCode"},"endPoint":{"$ref":"#/components/schemas/IataCode"},"departDate":{"$ref":"#/components/schemas/IsoDate"},"returnDate":{"allOf":[{"$ref":"#/components/schemas/IsoDate"}],"nullable":true,"description":"Nếu có → round-trip"},"flights":{"type":"array","description":"Multi-city: mỗi item là 1 chặng. Nếu dùng thì `startPoint/endPoint/departDate` top-level bỏ qua. Item hỗ trợ thêm `returnDate?` và `leg?` (int ≥ 0).","items":{"type":"object","required":["startPoint","endPoint","departDate"],"properties":{"startPoint":{"$ref":"#/components/schemas/IataCode"},"endPoint":{"$ref":"#/components/schemas/IataCode"},"departDate":{"$ref":"#/components/schemas/IsoDate"},"returnDate":{"$ref":"#/components/schemas/IsoDate"},"leg":{"type":"integer","minimum":0}}}},"adt":{"type":"integer","minimum":1,"maximum":9,"default":1},"chd":{"type":"integer","minimum":0,"maximum":9,"default":0,"description":"2-12 tuổi"},"inf":{"type":"integer","minimum":0,"maximum":9,"default":0,"description":"< 2 tuổi"},"airlines":{"type":"array","items":{"type":"string"},"example":["VN","VJ","QH","VU","BL"],"description":"Filter post-aggregation. BL match cả operating-carrier codeshare."},"airline":{"type":"string","example":"VN","description":"DEPRECATED-on-arrival alias of airlines[0]. Kept for DLV PHP compatibility. Prefer airlines:[...] for new integrations. If both are sent, airlines wins."},"preferCabin":{"type":"string","enum":["Y","F","C","W","M"],"description":"Y=Economy, C=Business, F=First, W=Premium Economy, M=Mixed"},"promoCode":{"type":"string","description":"Pass-through cho NCC (BGT/HNH/HPL đều nhận)"},"requiredCapabilities":{"type":"array","items":{"type":"string"},"description":"Skip NCC không có capability này. VD `[\"book\",\"issue\"]`"}}},"MinFareRequest":{"type":"object","required":["startPoint","endPoint","month"],"properties":{"startPoint":{"$ref":"#/components/schemas/IataCode"},"endPoint":{"$ref":"#/components/schemas/IataCode"},"month":{"type":"string","pattern":"^\\d{4}-(0[1-9]|1[0-2])$","example":"2026-06","description":"Month in YYYY-MM format (required). One-way only in v1."},"adt":{"type":"integer","minimum":1,"default":1,"description":"Adult count"},"chd":{"type":"integer","minimum":0,"default":0,"description":"Child count"},"inf":{"type":"integer","minimum":0,"default":0,"description":"Infant count"},"airline":{"type":"string","example":"VN","description":"DEPRECATED-on-arrival alias of airlines[0]. Kept for DLV PHP compatibility. Prefer airlines:[...] for new integrations. If both are sent, airlines wins."},"airlines":{"type":"array","items":{"type":"string"},"example":["VN"],"description":"Carrier filter. If omitted, returns cheapest across all carriers."}}},"FareRefRequest":{"type":"object","description":"Dùng `fareSelectionId` (trước book) hoặc `bookingRef` (sau hold).","properties":{"fareSelectionId":{"type":"string"},"bookingRef":{"type":"string"}}},"PriceQuoteRequest":{"type":"object","required":["fareSelectionId"],"properties":{"fareSelectionId":{"type":"string"},"passengerCounts":{"type":"object","properties":{"adt":{"type":"integer"},"chd":{"type":"integer"},"inf":{"type":"integer"}}}}},"FareRuleRequest":{"type":"object","required":["fareSelectionId"],"properties":{"fareSelectionId":{"type":"string"},"bookingRef":{"type":"string"},"isMiniRule":{"type":"boolean"}}},"RetrieveByPnrRequest":{"type":"object","required":["supplier","pnr"],"description":"Mở PNR theo định danh NCC (cho booking tạo ngoài hệ thống).","properties":{"supplier":{"type":"string","description":"Internal (tripota/airdata/skeeper) hoặc mã NCC (HNH/BGT/HPL)"},"pnr":{"type":"string","example":"FJA99U"},"airline":{"type":"string","example":"VN","description":"Bắt buộc với một số NCC"},"lastName":{"type":"string","description":"Họ pax — bắt buộc với một số NCC"},"source":{"type":"string","description":"Mã hệ thống GDS; default = airline nếu bỏ trống"}}},"ChangeScope":{"type":"object","description":"Phạm vi áp dụng cho refund/exchange/upgrade: cả booking, theo vé, hoặc theo pax+segment.","properties":{"type":{"type":"string","enum":["booking","ticket","passenger_segment"],"default":"ticket"},"ticketNumbers":{"type":"array","items":{"type":"string"}},"passengers":{"type":"array","items":{"type":"string"}},"segments":{"type":"array","items":{"type":"string"}}}},"NewItineraryRequest":{"type":"object","description":"Hành trình mới mong muốn cho reshop/quote đổi vé. Hoặc truyền `newFlights[]` là opaque token NCC.","properties":{"origin":{"$ref":"#/components/schemas/IataCode"},"destination":{"$ref":"#/components/schemas/IataCode"},"departureDate":{"$ref":"#/components/schemas/IsoDate"},"returnDate":{"$ref":"#/components/schemas/IsoDate"},"flightNumber":{"type":"string"},"cabinClass":{"type":"string"},"newFlights":{"type":"array","items":{}}}},"SelectedPassengers":{"type":"array","minItems":1,"description":"Pax cần tách. Mỗi item cần ≥1 trong `passengerId`/`paxRef`/`paxId`/`id`.","items":{"type":"object","properties":{"passengerId":{"type":"string"},"paxRef":{"type":"string"},"paxId":{"type":"string"},"id":{"type":"string"},"type":{"type":"string"}}}},"Passenger":{"type":"object","required":["type","firstName","lastName"],"properties":{"paxId":{"type":"string","description":"Canonical ID server trả sau book; pass lại cho update/split/infant"},"passengerId":{"type":"string","description":"Alias of `paxId` — cả hai đều được chấp nhận"},"type":{"$ref":"#/components/schemas/PaxType"},"title":{"type":"string","example":"MR","enum":["MR","MRS","MS","MSTR","MISS"]},"firstName":{"type":"string","example":"NGUYEN","description":"Chỉ chữ + space, không số/dấu gạch ngang (HPL/VNA reject)"},"lastName":{"type":"string","example":"VAN A"},"dateOfBirth":{"type":"string","format":"date","example":"1990-01-01","description":"YYYY-MM-DD. **BẮT BUỘC cho chuyến quốc tế hãng APIS** — server ghép DOB vào DOCS gửi GDS; thiếu sẽ bị từ chối xuất vé \"require passport\"."},"gender":{"type":"string","example":"M","enum":["M","F"]},"nationality":{"type":"string","example":"VN","description":"Quốc tịch alpha-2 (VN). **Bắt buộc cho chuyến quốc tế** — vào DOCS APIS (server đổi alpha-3 cho GDS)."},"parentPaxId":{"type":"string","description":"Với INF: paxId của người lớn đi kèm"},"phone":{"type":"string"},"email":{"type":"string","format":"email"},"wheelchair":{"type":"boolean","default":false,"description":"Bắt buộc = false nếu truyền qua HPL (zod default)"},"vegetarian":{"type":"boolean","default":false},"membership":{"description":"String (VD \"VN1234567\") hoặc object `{ airline, number }`","oneOf":[{"type":"string","example":"VN1234567"},{"type":"object","required":["airline","number"],"properties":{"airline":{"type":"string","example":"VN"},"number":{"type":"string","example":"VN1234567"}}}]},"memberFly":{"type":"object","required":["airline","number"],"properties":{"airline":{"type":"string"},"number":{"type":"string"}},"description":"Legacy name for `membership` (object form)"},"document":{"type":"object","nullable":true,"properties":{"type":{"type":"string","example":"P","enum":["P","PP","PASSPORT","ID","NI"],"description":"P/PP=hộ chiếu, ID/NI=CCCD. Mặc định coi là hộ chiếu nếu bỏ trống."},"number":{"type":"string","example":"B12345678"},"issueCountry":{"type":"string","example":"VN","description":"Nước cấp (alpha-2). Server tự đổi sang alpha-3 cho GDS."},"issueDate":{"type":"string","format":"date"},"expireDate":{"type":"string","format":"date","description":"Ngày hết hạn hộ chiếu — BẮT BUỘC cho hãng quốc tế APIS."},"nationality":{"type":"string","example":"VN","description":"Quốc tịch (alpha-2). Server đổi alpha-3 cho GDS DOCS."}},"description":"Hộ chiếu/giấy tờ. **BẮT BUỘC cho chuyến quốc tế hãng APIS** (QR, CZ, AA, SQ, EK, CX, LX, WY, TG, MH, …): phải gửi đủ `type` + `number` + `nationality` + `issueCountry` + `expireDate`, VÀ pax phải có `dateOfBirth` (server ghép DOB vào DOCS gửi GDS). Thiếu bất kỳ field nào (đặc biệt **dateOfBirth**) → hãng từ chối XUẤT VÉ với \"require all passengers to enter a passport\" dù book thành công. Có thể add sau qua `POST /bookings/:ref/documents`."}}},"Contact":{"type":"object","required":["email","phone","name"],"properties":{"email":{"type":"string","format":"email","example":"agent@example.com"},"phone":{"type":"string","example":"0987654321","description":"Số VN 10 chữ số (bắt đầu 03/05/07/08/09). HPL validate strict."},"name":{"type":"string","example":"NGUYEN VAN A"},"address":{"type":"string"},"city":{"type":"string"},"country":{"type":"string"},"gender":{"type":"string","enum":["M","F"]},"language":{"type":"string","enum":["vi","en"],"default":"vi"}}},"BookRequest":{"type":"object","required":["fareSelectionId","passengers","contact"],"properties":{"fareSelectionId":{"type":"string","description":"Từ `data.fares[].fareSelectionId` của search response. TTL 60 phút."},"fareSelectionIds":{"type":"array","items":{"type":"string"},"minItems":2,"description":"Multi-fare: book 2 hãng khác nhau cho khứ hồi. Truyền [outboundId, inboundId] — server book tuần tự, trả `data.bookings[]` thay vì `data.bookingRef`. `fareSelectionId` phải = phần tử đầu tiên."},"mode":{"type":"string","enum":["draft","confirm"],"default":"confirm","description":"`draft` = hold (PayLater); `confirm` = book+issue (PayNow). LCC không hỗ trợ hold."},"passengers":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/Passenger"}},"contact":{"$ref":"#/components/schemas/Contact"},"promoCode":{"type":"string"},"tourCode":{"type":"string"},"accountCode":{"type":"string"},"remark":{"type":"string","description":"Internal note (HPL dùng khi gửi tới upstream)"},"ipRequest":{"type":"string","description":"IP của end-user (HPL audit); default \"127.0.0.1\" nếu omit"},"seats":{"type":"array","description":"Ghế mua kèm lúc book. Mỗi item = `{ passengerIndex, seatValue, segmentIndex?, seatCode? }`. `seatValue` = TOKEN mã hoá từ `data.addable[].seatValue` của `POST /flights/seat-map` (KHÔNG phải nhãn \"16A\").","items":{"type":"object","required":["passengerIndex","seatValue"],"properties":{"passengerIndex":{"type":"integer"},"seatValue":{"type":"string","description":"Token từ seat-map addable[].seatValue"},"segmentIndex":{"type":"integer"},"seatCode":{"type":"string","description":"Nhãn hiển thị (audit) — không phải token"},"price":{"type":"number"},"currency":{"type":"string"}}}},"selectedSeats":{"type":"array","description":"Alias UI của `seats[]` (server canonicalize về `seats`). Gửi MỘT trong hai; cả hai khác nội dung → 400 BOOK_SEATS_ALIAS_CONFLICT.","items":{"type":"object","required":["passengerIndex","seatValue"],"properties":{"passengerIndex":{"type":"integer"},"seatValue":{"type":"string"},"segmentIndex":{"type":"integer"},"seatCode":{"type":"string"}}}},"baggages":{"type":"array","description":"Hành lý mua kèm lúc book. Mỗi item = `{ passengerIndex, baggageValue, quantity? }`. `baggageValue` = TOKEN từ `data.addable[].baggageValue` của `POST /flights/baggages` (KHÔNG phải nhãn \"20kg\"). 1 token = 1 đơn vị; quantity>1 bị từ chối — dùng post-book add cho nhiều.","items":{"type":"object","required":["baggageValue"],"properties":{"passengerIndex":{"type":"integer"},"paxRef":{"type":"string","description":"Alias passengerIndex (vd \"ADT_1\")"},"baggageValue":{"type":"string"},"value":{"type":"string","description":"Alias baggageValue"},"segmentIndex":{"type":"integer"},"quantity":{"type":"integer","default":1},"code":{"type":"string"},"price":{"type":"number"},"currency":{"type":"string"}}}},"ancillaries":{"type":"array","description":"Dịch vụ mua kèm lúc book. Mỗi item = `{ passengerIndex, ancillaryValue, quantity? }`. `ancillaryValue` = TOKEN từ `POST /flights/ancillaries`. (Post-book endpoint tương ứng dùng field `services[]` — nhưng tại /book field canonical là `ancillaries[]`.)","items":{"type":"object","required":["passengerIndex","ancillaryValue"],"properties":{"passengerIndex":{"type":"integer"},"ancillaryValue":{"type":"string"},"type":{"type":"string"},"code":{"type":"string"},"quantity":{"type":"integer","default":1},"price":{"type":"number"},"currency":{"type":"string"}}}}}},"IssueRequest":{"type":"object","description":"Tuỳ supplier mà trường nào được dùng. Aggregator strip field không khớp schema — nên ok nếu client pass cả nhóm.","properties":{"comAmount":{"type":"number","description":"Tripota: hoa hồng đại lý"},"paymentPassword":{"type":"string","description":"Airdata: payment password (default lấy từ env AIRDATA_PAYMENT_PASSWORD)"},"totalAmount":{"type":"number","description":"Skeeper: tổng tiền đã verify. Nếu bỏ trống adapter tự retrieve từ PNR."},"tourCode":{"type":"string","description":"Skeeper/Tripota: tour code"},"sendEmail":{"type":"boolean","default":false,"description":"Skeeper: auto gửi email e-ticket sau issue"}}},"SendEmailRequest":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"},"language":{"type":"string","enum":["vi","en"],"default":"vi"},"paymentDisplayType":{"type":"string","enum":["S","D","F"],"default":"D"}}},"UpdateContactRequest":{"type":"object","description":"Ít nhất 1 field required","properties":{"fullName":{"type":"string"},"email":{"type":"string","format":"email"},"phone":{"type":"string"}}},"FlightStatusRequest":{"type":"object","description":"Ít nhất 1 trong `bookingRef` / `flightValue` / `flightValues[]`","properties":{"bookingRef":{"type":"string"},"system":{"type":"string"},"flightValue":{"type":"string"},"flightValues":{"type":"array","items":{"type":"string"}}}},"AddBaggageRequest":{"type":"object","required":["baggages"],"properties":{"baggages":{"type":"array","minItems":1,"items":{"type":"object","required":["baggageValue"],"properties":{"passengerIndex":{"type":"integer"},"passengerId":{"type":"string"},"baggageValue":{"type":"string","description":"Từ /baggages list response"},"quantity":{"type":"integer","default":1}}}}}},"AddSeatRequest":{"type":"object","required":["seats"],"properties":{"seats":{"type":"array","minItems":1,"items":{"type":"object","required":["seatValue"],"properties":{"passengerIndex":{"type":"integer"},"seatValue":{"type":"string","example":"12A"},"segmentId":{"type":"string"}}}}}},"AddAncillaryRequest":{"type":"object","required":["services"],"properties":{"airline":{"type":"string"},"services":{"type":"array","minItems":1,"items":{"type":"object","required":["ancillaryValue"],"properties":{"passengerIndex":{"type":"integer"},"ancillaryValue":{"type":"string"},"quantity":{"type":"integer","default":1},"type":{"type":"string"}}}}}},"FlightLeg":{"type":"object","properties":{"airline":{"type":"string","example":"VN"},"operatingCarrier":{"type":"string","nullable":true,"example":"BL","description":"Nếu khác marketing carrier (codeshare)"},"flightNumber":{"type":"string","example":"VN6002"},"startPoint":{"$ref":"#/components/schemas/IataCode"},"endPoint":{"$ref":"#/components/schemas/IataCode"},"departTime":{"type":"string","example":"05:00","description":"HH:mm giờ VN"},"arriveTime":{"type":"string","example":"07:10"},"duration":{"type":"integer","description":"Phút"},"aircraft":{"type":"string","example":"321"},"cabin":{"type":"string","example":"ECONOMY FLEX"},"fareBasis":{"type":"string","example":"BVNF"},"bookingClass":{"type":"string","example":"B"},"seatsAvailable":{"type":"integer","nullable":true},"baggage":{"type":"string","example":"1 PCs x 23kg"}}},"Fare":{"type":"object","properties":{"fareSelectionId":{"type":"string","example":"fs_AqeDpYVEriJd36u5qM_19"},"supplier":{"type":"string","enum":["airdata","tripota","skeeper"]},"supplierSource":{"type":"string","example":"VN1A","description":"System code upstream"},"airline":{"type":"string"},"currency":{"$ref":"#/components/schemas/Currency"},"totalPrice":{"type":"number","description":"UNIFIED: tính theo `(fareAdt+taxAdt)*adt + (fareChd+taxChd)*chd + (fareInf+taxInf)*inf`"},"totalPriceSupplier":{"type":"number","description":"Raw từ NCC, có thể khác convention (airdata=chỉ ADT, skeeper thiếu pax trong 1 số case)"},"fareAdt":{"type":"number"},"taxAdt":{"type":"number"},"fareChd":{"type":"number"},"taxChd":{"type":"number"},"fareInf":{"type":"number"},"taxInf":{"type":"number"},"legs":{"type":"array","items":{"$ref":"#/components/schemas/FlightLeg"}},"fareConditions":{"$ref":"#/components/schemas/FareConditions"},"cabinCanonical":{"$ref":"#/components/schemas/CabinCanonical"},"cabinDisplay":{"type":"string","nullable":true,"example":"Phổ thông","description":"Tên hiển thị tiếng Việt của cabinCanonical (Phổ thông / Phổ thông đặc biệt / Thương gia / Hạng nhất)"},"supportedOps":{"type":"array","items":{"type":"string"},"description":"Ops mà supplier này hỗ trợ cho fare này"},"unsupportedOps":{"type":"array","items":{"type":"string"}}}},"CabinCanonical":{"type":"string","nullable":true,"enum":["ECONOMY","PREMIUM_ECONOMY","BUSINESS","FIRST",null],"description":"Hạng ghế chuẩn hoá. Server tự dịch từ HNH single-letter (Y/M/W/C/F), HPL text (STARTER/ECONOMY/BUSINESS), BGT text (ECONOMY/PREMIUM_ECONOMY/BUSINESS/FIRST), và branded fare names (SkyBoss/Deluxe/...). null = không xác định được."},"CabinOption":{"type":"object","description":"Một mức hạng cho một chuyến bay đã group. Gộp tất cả fare cùng hạng từ mọi NCC.","properties":{"cabinCanonical":{"$ref":"#/components/schemas/CabinCanonical"},"cabinDisplay":{"type":"string","nullable":true,"example":"Phổ thông"},"minPrice":{"type":"number","description":"Giá thấp nhất trong nhóm này (đã unified theo pax counts)"},"maxPrice":{"type":"number"},"suppliers":{"type":"array","items":{"type":"string"},"description":"Danh sách NCC đang bán hạng này cho chuyến này"},"fareCount":{"type":"integer","description":"Số fare trong nhóm — có thể >suppliers nếu cùng NCC bán nhiều fareBasis"},"fareSelectionIds":{"type":"array","items":{"type":"string"},"description":"IDs để FE pass thẳng vào /price-quote hoặc /book sau khi user chọn"}}},"GroupedFlight":{"type":"object","description":"Một chuyến bay vật lý sau khi gộp các fare từ mọi NCC × mọi hạng. FE dùng `groupedFlights` thay cho `fares` để hiển thị 1 row/chuyến (như Mytour/Traveloka).","properties":{"flightKey":{"type":"string","example":"VN|VN6002|2026-12-20|SGN|HAN","description":"Composite key dùng để dedupe — không hiển thị cho user"},"airline":{"type":"string","example":"VN"},"legs":{"type":"array","items":{"type":"object","properties":{"airline":{"type":"string"},"operatingCarrier":{"type":"string"},"flightNumber":{"type":"string"},"startPoint":{"type":"string"},"endPoint":{"type":"string"},"departTime":{"type":"string"},"arriveTime":{"type":"string"},"duration":{"type":"integer"},"aircraft":{"type":"string"}}}},"startPoint":{"type":"string"},"endPoint":{"type":"string"},"departTime":{"type":"string"},"arriveTime":{"type":"string"},"tripDirection":{"type":"string","enum":["oneway","roundtrip","outbound","inbound","unknown"]},"minPrice":{"type":"number"},"maxPrice":{"type":"number"},"currency":{"$ref":"#/components/schemas/Currency"},"supplierCount":{"type":"integer"},"suppliers":{"type":"array","items":{"type":"string"}},"fareCount":{"type":"integer"},"cabinOptions":{"type":"array","items":{"$ref":"#/components/schemas/CabinOption"}},"fares":{"type":"array","items":{"$ref":"#/components/schemas/Fare"},"description":"Fares trong group. Có thể duplicate với top-level `fares[]` — dùng nếu cần raw access."}}},"Combo":{"type":"object","description":"GDS-bundled round-trip fare promoted to a combo object. ONLY fares with tripDirection=roundtrip (pre-bundled by GDS) appear here — NO Cartesian pairing of separate outbound+inbound legs. Controlled by ROUNDTRIP_COMBO_ENABLED env flag (default true).","required":["fareSelectionId","airline","supplier","totalPrice","currency","legs","bundled"],"properties":{"fareSelectionId":{"type":"string","example":"fs_AqeDpYVEriJd36u5qM_19","description":"Same as source fare — pass to /book directly"},"airline":{"type":"string","example":"VN"},"supplier":{"type":"string","enum":["airdata","tripota","skeeper"]},"totalPrice":{"type":"number","description":"Unified total price (same convention as Fare.totalPrice)"},"currency":{"$ref":"#/components/schemas/Currency"},"legs":{"type":"array","items":{"$ref":"#/components/schemas/FlightLeg"},"description":"All legs for this round-trip (both outbound and inbound)"},"cabinCanonical":{"allOf":[{"$ref":"#/components/schemas/CabinCanonical"}],"nullable":true},"cabinDisplay":{"type":"string","nullable":true,"example":"Phổ thông"},"bundled":{"type":"boolean","enum":[true],"description":"Always true — marks this as a GDS-bundled round-trip combo"}}},"BaggageInfo":{"type":"object","nullable":true,"description":"Hành lý ký gửi đã parse. null = supplier không trả data. included=false = không bao gồm.","properties":{"included":{"type":"boolean"},"pieces":{"type":"integer","nullable":true},"weight":{"type":"number","nullable":true,"description":"Kg — chỉ có ở checked baggage, không có ở carryOn"},"unit":{"type":"string","nullable":true,"enum":["KG","LB",null]},"text":{"type":"string","description":"Raw text gốc từ supplier"}}},"CarryOnInfo":{"type":"object","nullable":true,"description":"Hành lý xách tay. Chỉ lưu số kiện (pieces), không lưu kg — carry-on policy là số kiện, không phải trọng lượng.","properties":{"included":{"type":"boolean"},"pieces":{"type":"integer","nullable":true,"description":"1 hoặc 2 kiện. null nếu không xác định"},"text":{"type":"string","description":"Raw text gốc từ supplier"}}},"FareConditions":{"type":"object","nullable":true,"description":"Điều kiện hạng vé, normalized từ BGT fareOption / HNH segment / HPL segment. BGT là nguồn có fidelity cao nhất (weight+unit+pieces đầy đủ). Với AK/TR/5J có thêm field `penalties` (schema present từ BGT nhưng empty trong staging).","properties":{"cabinName":{"type":"string","example":"ECONOMY FLEX","description":"Tên hạng hiển thị. HPL thường đẹp nhất (\"ECONOMY LITE\", \"BUSINESS CLASSIC\"). BGT=cabin/groupClass, HNH=cabinCode."},"fareBasis":{"type":"string","example":"BVNF","description":"Lấy phần đầu nếu multi-leg pipe-delimited (\"K02H01 | Z01H00\" → \"K02H01\")"},"bookingClass":{"type":"string","example":"B"},"groupClass":{"type":"string","example":"EF","description":"Brand code (Eco/Deluxe/SkyBoss/EL/EC…). Stripped của pipe-notation"},"refundable":{"type":"boolean","nullable":true,"description":"true=hoàn được; false=không hoàn; null=supplier không trả"},"baggage":{"type":"object","properties":{"checked":{"$ref":"#/components/schemas/BaggageInfo"},"carryOn":{"$ref":"#/components/schemas/CarryOnInfo"}}},"penalties":{"type":"object","nullable":true,"description":"null = supplier không có penalty schema (VN/VJ/QH/VU). Object với values null = schema hiện diện nhưng chưa có data (AK/TR/5J trong staging). Object với values array = có data phí cụ thể.","properties":{"refund":{"nullable":true,"description":"Phí hoàn vé. null = schema có nhưng chưa populate"},"change":{"nullable":true,"description":"Phí đổi chuyến"},"cancel":{"nullable":true,"description":"Phí hủy vé"},"noShow":{"nullable":true,"description":"Phí no-show"}}}}},"FareRuleCatalogItem":{"type":"object","description":"Per group-class policy từ BGT /all-fare-rules (chỉ 6 LCC: VJ/AK/TR/JQ/6E/5J)","properties":{"supplier":{"type":"string","example":"airdata"},"source":{"type":"string","example":"VJ"},"configs":{"type":"array","items":{"type":"object","properties":{"groupClass":{"type":"string","example":"Eco"},"cabinName":{"type":"string","example":"Eco"},"seat":{"type":"string","enum":["FREE","FEE","NONE"]},"meal":{"type":"string","enum":["FREE","FEE","NONE"]},"checkinPriority":{"type":"string","enum":["FREE","FEE","NONE"]},"loungeService":{"type":"string","enum":["FREE","FEE","NONE"]},"changeName":{"type":"string","enum":["FREE","FEE","NONE"]},"changeFlights":{"type":"string","enum":["FREE","FEE","NONE"]},"refund":{"type":"string","enum":["FREE","FEE","NONE"]},"cancel":{"type":"string","enum":["FREE","FEE","NONE"]}}}}}},"SupplierResult":{"type":"object","properties":{"supplier":{"type":"string"},"name":{"type":"string"},"count":{"type":"integer","description":"Số fares sau filter"},"error":{"type":"object","nullable":true,"description":"Chi tiết lỗi nếu NCC fail"},"traceId":{"type":"string"},"latencyMs":{"type":"integer"},"fromCache":{"type":"string","nullable":true,"enum":["fresh","stale",null]},"fares":{"type":"array","items":{"$ref":"#/components/schemas/Fare"},"description":"Per-NCC fare list (duplicate of top-level sorted merged list)"}}},"SearchTotals":{"type":"object","properties":{"totalFares":{"type":"integer","description":"Tổng số fare flat (gồm duplicate cùng chuyến từ các NCC khác nhau)"},"totalFlights":{"type":"integer","description":"Số chuyến bay vật lý sau khi gộp — thường nhỏ hơn `totalFares` 3-4 lần"},"totalSuppliers":{"type":"integer"},"skippedSuppliers":{"type":"array","items":{"type":"string"}},"degraded":{"type":"boolean","description":"true khi flights=[] và có NCC lỗi"},"cached":{"oneOf":[{"type":"boolean"},{"type":"string","enum":["fresh","stale"]}],"description":"Cache state (thêm bởi route khi hit cache)"},"combosBuilt":{"type":"integer","description":"Number of GDS-bundled round-trip combos in data.combos[]. 0 for OW searches or when feature disabled."}}},"SearchData":{"type":"object","properties":{"fares":{"type":"array","items":{"$ref":"#/components/schemas/Fare"},"description":"Flat list — sort theo totalPrice ASC. Backward-compatible."},"combos":{"type":"array","items":{"$ref":"#/components/schemas/Combo"},"description":"GDS-bundled round-trip combos sorted cheapest-first. Present only for RT searches when ROUNDTRIP_COMBO_ENABLED=true. Cap=200 by default (ROUNDTRIP_COMBO_CAP env). Anti-Cartesian: only GDS-pre-bundled fares (tripDirection=roundtrip) appear here."},"groupedFlights":{"type":"array","items":{"$ref":"#/components/schemas/GroupedFlight"},"description":"Đã gộp theo chuyến bay vật lý + tách theo hạng. FE nên dùng view này để hiển thị 1 row/chuyến."},"suppliers":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/SupplierResult"}},"totals":{"$ref":"#/components/schemas/SearchTotals"}}},"SearchEnvelope":{"type":"object","description":"Envelope chuẩn: mọi response (success + error) dùng shape này. Check `ok` trước khi đọc `data` / `error`.","required":["ok","traceId","operation","latencyMs"],"properties":{"ok":{"type":"boolean","example":true,"description":"`true` khi operation thành công. Khi `false`, đọc `error`."},"traceId":{"type":"string","example":"abc123","description":"Unique ID để trace request cross-system. Dùng `/internal/raw/:traceId` (internal) xem raw upstream."},"supplier":{"type":"string","example":"tripota","enum":["aggregator","airdata","tripota","skeeper",null],"nullable":true,"description":"`aggregator` = orchestrator (search/min-fare), else = NCC owning the op."},"operation":{"type":"string","example":"book"},"capability":{"type":"object","description":"Supplier capability metadata.","properties":{"supported":{"type":"boolean"},"reason":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true},"alternatives":{"type":"array","items":{"type":"object"}}}},"latencyMs":{"type":"integer","example":5200},"data":{"$ref":"#/components/schemas/SearchData","nullable":true},"raw":{"type":"object","nullable":true,"description":"Debug info — upstream request/response log. Có thể strip ở prod.","properties":{"archiveKey":{"type":"string","example":"raw:abc123"},"inline":{"type":"array","nullable":true,"items":{"type":"object"}},"requests":{"type":"array","items":{"type":"object","properties":{"step":{"type":"string"},"url":{"type":"string"},"method":{"type":"string"},"reqBody":{},"resStatus":{"type":"integer"},"resBody":{},"ms":{"type":"integer"}}}}}},"error":{"nullable":true,"oneOf":[{"$ref":"#/components/schemas/ErrorObj"},{"type":"null"}],"description":"null khi `ok=true`. Object khi `ok=false`."},"meta":{"type":"object","nullable":true,"description":"Route-level metadata (vd cache state)."}}},"BookingRecord":{"type":"object","description":"Booking state returned by `/bookings` (has `bookingRef`) và `/bookings/:ref` (no `bookingRef`, dùng path param).","properties":{"supplier":{"type":"string","enum":["airdata","tripota","skeeper"]},"supplierPnr":{"type":"string","example":"FJA99U","description":"PNR từ NCC/airline backend"},"gdsCode":{"type":"string","nullable":true,"description":"GDS confirmation code (1G) nếu có"},"airline":{"type":"string","example":"VN"},"source":{"type":"string","example":"VN","description":"System code NCC dùng (VN1A / VN / 1G / JQ...)"},"state":{"type":"string","enum":["draft","booked","issued","cancelled","voided","refunded"],"description":"Aggregator-normalized state"},"bookingStatus":{"type":"string","example":"ACTIVE","description":"Upstream raw state (HOLD/ACTIVE/CANCEL/TICKETED)"},"bookingDate":{"type":"string","format":"date-time","nullable":true,"description":"Khi booking được create upstream"},"issueDate":{"type":"string","format":"date-time","nullable":true,"description":"Khi issue ticket"},"expiryDate":{"type":"string","format":"date-time","nullable":true,"description":"Hạn phải issue ticket hoặc PNR sẽ cancel tự động"},"timeHold":{"type":"string","nullable":true,"description":"Alias cũ của expiryDate (một số supplier dùng)"},"totalAmount":{"type":"number"},"currency":{"$ref":"#/components/schemas/Currency"},"passengers":{"type":"array","items":{"type":"object","properties":{"paxId":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"type":{"type":"string"},"dateOfBirth":{"type":"string"}}}},"itineraries":{"type":"array","items":{"type":"object","properties":{"airline":{"type":"string"},"flightNumber":{"type":"string"},"startPoint":{"type":"string"},"endPoint":{"type":"string"},"departTime":{"type":"string","description":"ISO datetime hoặc HH:mm tuỳ supplier"},"arriveTime":{"type":"string"},"bookingClass":{"type":"string"},"status":{"type":"string","example":"HK"}}}},"tickets":{"type":"array","items":{"type":"string"},"example":["7382315673190"]},"bookingRef":{"type":"string","example":"bk_xxx","description":"Server-generated, 90d TTL booked / 365d issued. CHỈ trả về ở response của `POST /bookings` (response của `GET /bookings/:ref` không lặp lại vì path đã có)."},"bookingImage":{"type":"string","nullable":true,"description":"Base64 PDF/e-ticket nếu supplier trả"},"raw":{"type":"object","nullable":true,"description":"Raw upstream response — dùng debug khi cần field không được normalize."}}},"BookEnvelope":{"type":"object","description":"Envelope chuẩn: mọi response (success + error) dùng shape này. Check `ok` trước khi đọc `data` / `error`.","required":["ok","traceId","operation","latencyMs"],"properties":{"ok":{"type":"boolean","example":true,"description":"`true` khi operation thành công. Khi `false`, đọc `error`."},"traceId":{"type":"string","example":"abc123","description":"Unique ID để trace request cross-system. Dùng `/internal/raw/:traceId` (internal) xem raw upstream."},"supplier":{"type":"string","example":"tripota","enum":["aggregator","airdata","tripota","skeeper",null],"nullable":true,"description":"`aggregator` = orchestrator (search/min-fare), else = NCC owning the op."},"operation":{"type":"string","example":"book"},"capability":{"type":"object","description":"Supplier capability metadata.","properties":{"supported":{"type":"boolean"},"reason":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true},"alternatives":{"type":"array","items":{"type":"object"}}}},"latencyMs":{"type":"integer","example":5200},"data":{"$ref":"#/components/schemas/BookingRecord","nullable":true},"raw":{"type":"object","nullable":true,"description":"Debug info — upstream request/response log. Có thể strip ở prod.","properties":{"archiveKey":{"type":"string","example":"raw:abc123"},"inline":{"type":"array","nullable":true,"items":{"type":"object"}},"requests":{"type":"array","items":{"type":"object","properties":{"step":{"type":"string"},"url":{"type":"string"},"method":{"type":"string"},"reqBody":{},"resStatus":{"type":"integer"},"resBody":{},"ms":{"type":"integer"}}}}}},"error":{"nullable":true,"oneOf":[{"$ref":"#/components/schemas/ErrorObj"},{"type":"null"}],"description":"null khi `ok=true`. Object khi `ok=false`."},"meta":{"type":"object","nullable":true,"description":"Route-level metadata (vd cache state)."}}},"ErrorCodeEnum":{"type":"string","enum":["VALIDATION","MISSING_REF","MISSING_TICKET","BOOKING_NOT_FOUND","PARENT_NOT_FOUND","TICKET_NOT_FOUND","FARE_EXPIRED","SUPPLIER_REJECTED","NOT_SUPPORTED_BY_OWNING_SUPPLIER","NO_SUPPLIER","NO_ADAPTER","ADAPTER_THREW","MISSING_PARAMS","MISSING_SUPPLIER","SUPPLIER_REQUIRED","UNKNOWN_SUPPLIER","BOOK_FAILED","ISSUE_FAILED","PAYNOW_LEG_FAILED","SPLIT_IN_PROGRESS"]},"ErrorObj":{"type":"object","properties":{"code":{"$ref":"#/components/schemas/ErrorCodeEnum"},"message":{"type":"string"},"category":{"type":"string","enum":["validation","logical","capability","transport","unknown"]},"retryable":{"type":"boolean"},"supplierCode":{"type":"string","nullable":true},"supplierMessage":{"type":"string","nullable":true},"details":{"type":"array","nullable":true,"items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"},"code":{"type":"string"}}}}}},"ErrorEnvelope":{"type":"object","description":"Envelope chuẩn: mọi response (success + error) dùng shape này. Check `ok` trước khi đọc `data` / `error`.","required":["ok","traceId","operation","latencyMs"],"properties":{"ok":{"type":"boolean","example":true,"description":"`true` khi operation thành công. Khi `false`, đọc `error`."},"traceId":{"type":"string","example":"abc123","description":"Unique ID để trace request cross-system. Dùng `/internal/raw/:traceId` (internal) xem raw upstream."},"supplier":{"type":"string","example":"tripota","enum":["aggregator","airdata","tripota","skeeper",null],"nullable":true,"description":"`aggregator` = orchestrator (search/min-fare), else = NCC owning the op."},"operation":{"type":"string","example":"book"},"capability":{"type":"object","description":"Supplier capability metadata.","properties":{"supported":{"type":"boolean"},"reason":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true},"alternatives":{"type":"array","items":{"type":"object"}}}},"latencyMs":{"type":"integer","example":5200},"data":{"type":"null","nullable":true},"raw":{"type":"object","nullable":true,"description":"Debug info — upstream request/response log. Có thể strip ở prod.","properties":{"archiveKey":{"type":"string","example":"raw:abc123"},"inline":{"type":"array","nullable":true,"items":{"type":"object"}},"requests":{"type":"array","items":{"type":"object","properties":{"step":{"type":"string"},"url":{"type":"string"},"method":{"type":"string"},"reqBody":{},"resStatus":{"type":"integer"},"resBody":{},"ms":{"type":"integer"}}}}}},"error":{"$ref":"#/components/schemas/ErrorObj"},"meta":{"type":"object","nullable":true,"description":"Route-level metadata (vd cache state)."}}}}}}