Skip to main content

Purchase orders

A purchase order is one commercial transaction on a trip, placed on one channel at one purchase moment. Each holds order lines with four status dimensions.

OperationEndpoint
Find purchase orders by quoteGET /order-orchestration/purchase-orders
Read a purchase orderGET /order-orchestration/purchase-orders/{poId}
Apply an order changePOST /order-orchestration/purchase-orders/{poId}/changes
Change a purchase order's commercial statusPOST /order-orchestration/purchase-orders/{poId}/transition
Hand frozen conditions over as a purchase orderPOST /order-orchestration/trips/{tripId}/handover
Create a purchase order on a tripPOST /order-orchestration/trips/{tripId}/purchase-orders

Find purchase orders by quote​

GET/order-orchestration/purchase-orders

Parameters

NameInRequiredNotes
quoteRefqueryyesThe quote reference the orders were converted from.

Response 200

Errors
StatusCodeMeaning
400invalid_requestThe request failed validation. The message names the first failing field; oms-ops and group surfaces also return every issue in details.
401unauthenticatedThe bearer token is missing, invalid, expired or minted for another purpose.
403scope_forbiddenThe token is scoped to an agency or an access scope; this surface requires an unscoped tenant token.
404not_enabledOrder orchestration is not enabled for the tenant.
500internalUnexpected failure. Retry with the same Idempotency-Key.

Read a purchase order​

GET/order-orchestration/purchase-orders/{poId}

Parameters

NameInRequiredNotes
poIdpathyes

Response 200

GET/order-orchestration/purchase-orders/PO-75EB654AFFACRead a purchase order
Request
curl -X GET "$BASE/order-orchestration/purchase-orders/PO-75EB654AFFAC" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Request-Id: req-0020"
Response
HTTP/1.1 200
X-Request-Id: req-0020
{
"poId": "PO-75EB654AFFAC",
"type": "standard",
"lines": [
{
"lineId": "L-490ECF37928C",
"status": {
"billing": "not_billed",
"payment": "not_due",
"commercial": "booked",
"fulfilment": "confirmed"
},
"offerId": "ALL-INCLUSIVE-2027",
"currency": "EUR",
"quantity": 1,
"ownership": {
"ruleId": "anchor-reservation",
"process": "create"
},
"amountMinor": 238000,
"description": "7-night Western Mediterranean, balcony cabin, 2 guests",
"explanation": [
{
"flag": "public",
"mode": "select",
"basis": "passenger",
"input": 0,
"layer": "base",
"reason": {
"key": "layer.base",
"params": {}
},
"ruleId": "base-balcony",
"source": {
"kind": "store",
"version": 14,
"timestamp": "2026-10-30T00:00:00Z"
},
"contribution": 264400,
"runningTotal": 264400
},
{
"flag": "public",
"mode": "adjust",
"basis": "booking",
"input": 264400,
"layer": "promotion",
"reason": {
"key": "layer.promotion",
"params": {}
},
"ruleId": "EARLY-BOOKING-10",
"source": {
"kind": "store",
"version": 14,
"timestamp": "2026-10-30T00:00:00Z"
},
"contribution": -26400,
"runningTotal": 238000
}
],
"externalRef": "RSV-1306C259-000001",
"productCode": "MED7-2027-04-12",
"productType": "cruise",
"passengerIds": [
"PAX-01",
"PAX-02"
],
"promotionIds": [
"EARLY-BOOKING-10"
],
"quoteLineRef": "q-line-1",
"sourceSystem": "reservation",
"sellingConditions": {
"refundable": true,
"depositPercent": 20
}
},
{
"lineId": "L-9F197F515BFD",
"status": {
"billing": "not_billed",
"payment": "not_due",
"commercial": "booked",
"fulfilment": "requested"
},
"offerId": "ALL-INCLUSIVE-2027",
"currency": "EUR",
"quantity": 2,
"ownership": {
"ruleId": "beverage-phase1",
"process": "create"
},
"amountMinor": 30800,
"description": "Classic drinks package, 7 nights",
"explanation": [],
"productCode": "DRINKS-CLASSIC-7N",
"productType": "beverage",
"passengerIds": [
"PAX-01",
"PAX-02"
],
"promotionIds": [],
"quoteLineRef": "q-line-2",
"sourceSystem": "onboard"
}
],
"payer": {
"ref": "CUST-20419",
"kind": "customer",
"displayName": "Giulia Rossi"
},
"terms": {},
"booker": {
"ref": "CUST-20419",
"kind": "customer",
"displayName": "Giulia Rossi"
},
"market": "IT",
"moment": "booking",
"origin": {
"channel": "web",
"actorRef": "CUST-20419"
},
"status": {
"billing": "not_billed",
"payment": "not_due",
"commercial": "booked",
"fulfilment": "requested"
},
"tripId": "TRP-0D3CE7B16625",
"channel": "web",
"version": 4,
"currency": "EUR",
"sequence": 1,
"createdAt": "2026-11-02T09:06:51.000Z",
"revisions": [],
"updatedAt": "2026-11-02T09:07:28.000Z",
"totalMinor": 268800,
"attribution": {},
"fullPayment": false,
"finalisation": {
"at": "2026-11-02T09:06:51.000Z",
"channel": "web"
},
"quotationRef": "QT-ED24E0954262",
"partiesLocked": true,
"frozenConditions": {
"document": {
"lines": [
{
"lineRef": "q-line-1",
"offerId": "ALL-INCLUSIVE-2027",
"quantity": 1,
"amountMinor": 238000,
"description": "7-night Western Mediterranean, balcony cabin, 2 guests",
"explanation": [
{
"flag": "public",
"mode": "select",
"basis": "passenger",
"input": 0,
"layer": "base",
"reason": {
"key": "layer.base",
"params": {}
},
"ruleId": "base-balcony",
"source": {
"kind": "store",
"version": 14,
"timestamp": "2026-10-30T00:00:00Z"
},
"contribution": 264400,
"runningTotal": 264400
},
{
"flag": "public",
"mode": "adjust",
"basis": "booking",
"input": 264400,
"layer": "promotion",
"reason": {
"key": "layer.promotion",
"params": {}
},
"ruleId": "EARLY-BOOKING-10",
"source": {
"kind": "store",
"version": 14,
"timestamp": "2026-10-30T00:00:00Z"
},
"contribution": -26400,
"runningTotal": 238000
}
],
"productCode": "MED7-2027-04-12",
"productType": "cruise",
"passengerIds": [
"PAX-01",
"PAX-02"
],
"promotionIds": [
"EARLY-BOOKING-10"
],
"sellingConditions": {
"refundable": true,
"depositPercent": 20
}
},
{
"lineRef": "q-line-2",
"offerId": "ALL-INCLUSIVE-2027",
"quantity": 2,
"amountMinor": 30800,
"description": "Classic drinks package, 7 nights",
"explanation": [],
"productCode": "DRINKS-CLASSIC-7N",
"productType": "beverage",
"passengerIds": [
"PAX-01",
"PAX-02"
],
"promotionIds": []
}
],
"market": "IT",
"channel": "web",
"currency": "EUR",
"frozenAt": "2026-11-02T09:04:00Z",
"quoteRef": "Q-2027-58213",
"promotions": [
{
"version": 3,
"promotionId": "EARLY-BOOKING-10",
"postBookingPolicy": "keep"
}
],
"totalMinor": 268800,
"customerRef": "CUST-20419",
"quoteVersion": 2,
"storeVersion": 14,
"configVersion": 9
},
"frozenAt": "2026-11-02T09:04:00Z",
"quoteRef": "Q-2027-58213",
"quoteVersion": 2,
"storeVersion": 14,
"configVersion": 9
}
}
Errors
StatusCodeMeaning
400invalid_requestThe request failed validation. The message names the first failing field; oms-ops and group surfaces also return every issue in details.
401unauthenticatedThe bearer token is missing, invalid, expired or minted for another purpose.
403scope_forbiddenThe token is scoped to an agency or an access scope; this surface requires an unscoped tenant token.
404not_foundThe trip, purchase order, quotation or configuration item does not exist.
404not_enabledOrder orchestration is not enabled for the tenant.
500internalUnexpected failure. Retry with the same Idempotency-Key.

Apply an order change​

POST/order-orchestration/purchase-orders/{poId}/changes

Applies a repricing proposal to the order as a revision, either passed inline or by the id of a confirmed proposal from the repricing service. Modify requests go to the owning systems. Send If-Match to make the write conditional.

Parameters

NameInRequiredNotes
poIdpathyes

Headers: Idempotency-Key, If-Match, plus X-Request-Id.

Request body

FieldTypeRequiredNotes
Shape 1
proposalobjectyes
proposal.baselineQuoteRefstringyesmin 1 chars
proposal.changeTypestringyesmin 1 chars
proposal.linesarray of objectyesat least 1
proposal.lines[].actionenum: keep, recalculate, lose, add, removeyes
proposal.lines[].explanationarray of object
proposal.lines[].lineRefstringyesmin 1 chars
proposal.lines[].newAmountMinorone of several shapesyes
proposal.lines[].newLineobject
proposal.lines[].oldAmountMinorone of several shapesyes
proposal.lines[].promotionOutcomesarray of object
proposal.newTotalMinorintegeryes
proposal.oldTotalMinorintegeryes
proposal.proposalIdstringyesmin 1 chars
proposal.reasonstringmax 500 chars
actorRefstringyesmin 1 chars
Shape 2
repricingProposalIdstringyesmin 1 chars
actorRefstringyesmin 1 chars

Response 200

Errors
StatusCodeMeaning
400invalid_requestThe request failed validation. The message names the first failing field; oms-ops and group surfaces also return every issue in details.
401unauthenticatedThe bearer token is missing, invalid, expired or minted for another purpose.
403scope_forbiddenThe token is scoped to an agency or an access scope; this surface requires an unscoped tenant token.
404not_foundThe trip, purchase order, quotation or configuration item does not exist.
404repricing_not_enabledRepricing is not enabled for the tenant.
404not_enabledOrder orchestration is not enabled for the tenant.
409not_changeableThe purchase order is not booked or optioned.
409baseline_mismatchThe proposal baseline does not match the purchase order's frozen conditions.
409stale_proposalThe repricing proposal was computed against an older purchase order.
409idempotency_in_progressA request with this Idempotency-Key is still running. Retry after the Retry-After interval.
412version_mismatchThe If-Match version is not the current version.
422invalid_proposalThe order change proposal is invalid.
422unknown_lineA line named in the request is not on the order.
422total_mismatchThe proposal totals do not match its lines.
422idempotency_key_reusedThe Idempotency-Key was used for a different request.
500internalUnexpected failure. Retry with the same Idempotency-Key.

Change a purchase order's commercial status​

POST/order-orchestration/purchase-orders/{poId}/transition

Allowed transitions: quotation or option to booked or cancelled; booked to cancelled, frozen or closed; frozen to booked, cancelled or closed. Send If-Match with the version you read to make the write conditional.

Parameters

NameInRequiredNotes
poIdpathyes

Headers: Idempotency-Key, If-Match, plus X-Request-Id.

Request body

FieldTypeRequiredNotes
toenum: cancelled, frozen, closed, bookedyes
reasonstringyesmin 1 chars

Response 200

POST/order-orchestration/purchase-orders/PO-8B18D9E89570/transitionA write conditional on a stale version
Request
curl -X POST "$BASE/order-orchestration/purchase-orders/PO-8B18D9E89570/transition" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Request-Id: req-0035" \
-H "If-Match: "3"" \
-H "Content-Type: application/json" \
-d '{
"to": "frozen",
"reason": "Hold for payment review"
}'
Response
HTTP/1.1 412
X-Request-Id: req-0035
{
"error": "version_mismatch",
"message": "purchase order PO-8B18D9E89570 is at version 4, not 3",
"code": "version_mismatch",
"requestId": "req-0035"
}
Errors
StatusCodeMeaning
400invalid_requestThe request failed validation. The message names the first failing field; oms-ops and group surfaces also return every issue in details.
400invalid_if_matchThe If-Match header is not a version number.
401unauthenticatedThe bearer token is missing, invalid, expired or minted for another purpose.
403scope_forbiddenThe token is scoped to an agency or an access scope; this surface requires an unscoped tenant token.
404not_foundThe trip, purchase order, quotation or configuration item does not exist.
404not_enabledOrder orchestration is not enabled for the tenant.
409invalid_transitionThe status transition is not allowed from the current status.
409idempotency_in_progressA request with this Idempotency-Key is still running. Retry after the Retry-After interval.
412version_mismatchThe If-Match version is not the current version.
422idempotency_key_reusedThe Idempotency-Key was used for a different request.
500internalUnexpected failure. Retry with the same Idempotency-Key.

Hand frozen conditions over as a purchase order​

POST/order-orchestration/trips/{tripId}/handover

Creates a booked purchase order on the trip directly from frozen conditions, without a tracked quotation. Idempotent per quote reference and version.

Parameters

NameInRequiredNotes
tripIdpathyes

Headers: Idempotency-Key, plus X-Request-Id.

Request body

FieldTypeRequiredNotes
frozenobjectyes
frozen.channelstringyesmin 1 chars
frozen.configVersionone of several shapes
frozen.currencystringyespattern ^[A-Z]{3}$
frozen.customerRefstringmax 200 chars
frozen.frozenAtstring (date-time)yes
frozen.linesarray of objectyesat least 1
frozen.lines[].amountMinorintegeryes
frozen.lines[].beneficiaryRefstringmax 200 chars
frozen.lines[].descriptionstringmax 500 chars
frozen.lines[].explanationarray of object
frozen.lines[].lineRefstringyesmin 1 chars
frozen.lines[].offerIdstringmax 200 chars
frozen.lines[].passengerIdsarray of string
frozen.lines[].productCodestringyesmin 1 chars
frozen.lines[].productTypestringyesmin 1 chars
frozen.lines[].promotionIdsarray of string
frozen.lines[].quantityinteger> 0; default 1
frozen.lines[].sellingConditionsobject
frozen.lines[].serviceDatestring (date-time)
frozen.marketstringmax 60 chars
frozen.promotionsarray of object
frozen.promotions[].postBookingPolicyenum: keep, recalculate, loseyes
frozen.promotions[].promotionIdstringyesmin 1 chars
frozen.promotions[].versioninteger≥ 0
frozen.quoteRefstringyesmin 1 chars
frozen.quoteVersionintegeryes≥ 0
frozen.storeVersionone of several shapes
frozen.totalMinorintegeryes
momentstringyesmin 1 chars
channelstring
bookerobject
booker.displayNamestringmax 200 chars
booker.kindenum: customer, agency, agent, companyyes
booker.refstringyes1–200 chars
payerobject
payer.displayNamestringmax 200 chars
payer.kindenum: customer, agency, agent, companyyes
payer.refstringyes1–200 chars
typestring
fullPaymentboolean
actorRefstring

Response 201

Errors
StatusCodeMeaning
400invalid_requestThe request failed validation. The message names the first failing field; oms-ops and group surfaces also return every issue in details.
401unauthenticatedThe bearer token is missing, invalid, expired or minted for another purpose.
403scope_forbiddenThe token is scoped to an agency or an access scope; this surface requires an unscoped tenant token.
404not_foundThe trip, purchase order, quotation or configuration item does not exist.
404not_enabledOrder orchestration is not enabled for the tenant.
409trip_closedThe trip is closed.
409frozen_conditions_mismatchThe frozen money posted differs from the accepted quote (tenants with verify_frozen_conditions).
409idempotency_in_progressA request with this Idempotency-Key is still running. Retry after the Retry-After interval.
422unknown_momentThe purchase moment is not in the orchestration configuration.
422idempotency_key_reusedThe Idempotency-Key was used for a different request.
500internalUnexpected failure. Retry with the same Idempotency-Key.
503handover_source_unavailableThe quote store could not be reached to verify frozen conditions.

Create a purchase order on a trip​

POST/order-orchestration/trips/{tripId}/purchase-orders

Sells one or more lines into an existing trip on a given channel and purchase moment, for example an add-on bought in the mobile app after booking. Each line is routed to its owning system by the execution-ownership rules.

Parameters

NameInRequiredNotes
tripIdpathyes

Headers: Idempotency-Key, plus X-Request-Id.

Request body

FieldTypeRequiredNotes
actorRefstringmax 200 chars
attributionobject
attribution.agencyRefstringmax 200 chars
attribution.campaignRefstringmax 200 chars
bookerobject
booker.displayNamestringmax 200 chars
booker.kindenum: customer, agency, agent, companyyes
booker.refstringyes1–200 chars
channelstringyesmin 1 chars
commercialStatusenum: quotation, option, bookeddefault "booked"
currencystringyespattern ^[A-Z]{3}$
fullPaymentbooleandefault false
idempotencyKeystring1–200 chars
journeysOrderRefstring
linesarray of objectyesat least 1
lines[].amountMinorintegeryes
lines[].beneficiaryRefstringmax 200 chars
lines[].descriptionstringmax 500 chars
lines[].explanationarray of object
lines[].offerIdstringmax 200 chars
lines[].passengerIdsarray of string
lines[].productCodestringyesmin 1 chars
lines[].productTypestringyesmin 1 chars
lines[].promotionIdsarray of string
lines[].quantityinteger> 0; default 1
lines[].quoteLineRefstring
lines[].sellingConditionsobject
lines[].sellingConditions.balanceDueAtstring (date-time)
lines[].sellingConditions.cancellationPolicyIdstring
lines[].sellingConditions.depositPercentnumber≥ 0; ≤ 100
lines[].sellingConditions.refundableboolean
lines[].serviceDatestring (date-time)
marketstringmax 60 chars
momentstringyesmin 1 chars
payerobject
payer.displayNamestringmax 200 chars
payer.kindenum: customer, agency, agent, companyyes
payer.refstringyes1–200 chars
termsobject
terms.balanceDueAtstring (date-time)
terms.cancellationPolicyIdstring
terms.depositPercentnumber≥ 0; ≤ 100
terms.refundableboolean
touchpointstringmax 100 chars
typestring1–60 chars; default "standard"

Schema: apps/journeys/src/order-orchestration/contracts.ts (CreatePurchaseOrderInputSchema)

Response 201

POST/order-orchestration/trips/TRP-B2BD5A9771E4/purchase-ordersAdd shore excursions from the mobile app as a second purchase order
Request
curl -X POST "$BASE/order-orchestration/trips/TRP-B2BD5A9771E4/purchase-orders" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Request-Id: req-0006" \
-H "Idempotency-Key: 43563ae7-1d12-4b6d-a226-3a66f7d48c7f" \
-H "Content-Type: application/json" \
-d '{
"channel": "app",
"moment": "pre_trip",
"currency": "EUR",
"booker": {
"kind": "customer",
"ref": "CUST-20419",
"displayName": "Giulia Rossi"
},
"payer": {
"kind": "customer",
"ref": "CUST-20419",
"displayName": "Giulia Rossi"
},
"lines": [
{
"productType": "excursion",
"productCode": "EXC-NAP-POMPEII",
"description": "Pompeii and Vesuvius, Naples call",
"quantity": 2,
"amountMinor": 17800,
"passengerIds": [
"PAX-01",
"PAX-02"
],
"serviceDate": "2027-04-14T08:30:00Z"
},
{
"productType": "excursion",
"productCode": "EXC-BCN-SAGRADA",
"description": "Sagrada Familia skip-the-line, Barcelona call",
"quantity": 1,
"amountMinor": 6900,
"passengerIds": [
"PAX-02"
],
"serviceDate": "2027-04-17T09:00:00Z"
}
]
}'
Response
HTTP/1.1 201
X-Request-Id: req-0006
{
"poId": "PO-71B4D4926EDD",
"type": "standard",
"lines": [
{
"lineId": "L-FDAF4DA2CF1A",
"status": {
"billing": "not_billed",
"payment": "not_due",
"commercial": "booked",
"fulfilment": "requested"
},
"currency": "EUR",
"quantity": 2,
"ownership": {
"ruleId": "excursions",
"process": "create"
},
"amountMinor": 17800,
"description": "Pompeii and Vesuvius, Naples call",
"productCode": "EXC-NAP-POMPEII",
"productType": "excursion",
"serviceDate": "2027-04-14T08:30:00Z",
"passengerIds": [
"PAX-01",
"PAX-02"
],
"promotionIds": [],
"sourceSystem": "excursions"
},
{
"lineId": "L-93A9D925FCE1",
"status": {
"billing": "not_billed",
"payment": "not_due",
"commercial": "booked",
"fulfilment": "requested"
},
"currency": "EUR",
"quantity": 1,
"ownership": {
"ruleId": "excursions",
"process": "create"
},
"amountMinor": 6900,
"description": "Sagrada Familia skip-the-line, Barcelona call",
"productCode": "EXC-BCN-SAGRADA",
"productType": "excursion",
"serviceDate": "2027-04-17T09:00:00Z",
"passengerIds": [
"PAX-02"
],
"promotionIds": [],
"sourceSystem": "excursions"
}
],
"payer": {
"ref": "CUST-20419",
"kind": "customer",
"displayName": "Giulia Rossi"
},
"terms": {},
"booker": {
"ref": "CUST-20419",
"kind": "customer",
"displayName": "Giulia Rossi"
},
"moment": "pre_trip",
"origin": {
"channel": "app"
},
"status": {
"billing": "not_billed",
"payment": "not_due",
"commercial": "booked",
"fulfilment": "requested"
},
"tripId": "TRP-B2BD5A9771E4",
"channel": "app",
"version": 2,
"currency": "EUR",
"sequence": 2,
"createdAt": "2026-11-02T09:08:42.000Z",
"revisions": [],
"updatedAt": "2026-11-02T09:08:42.000Z",
"totalMinor": 24700,
"attribution": {},
"fullPayment": false,
"finalisation": {
"at": "2026-11-02T09:08:42.000Z",
"channel": "app"
},
"partiesLocked": true
}
Errors
StatusCodeMeaning
400invalid_requestThe request failed validation. The message names the first failing field; oms-ops and group surfaces also return every issue in details.
401unauthenticatedThe bearer token is missing, invalid, expired or minted for another purpose.
403scope_forbiddenThe token is scoped to an agency or an access scope; this surface requires an unscoped tenant token.
404not_foundThe trip, purchase order, quotation or configuration item does not exist.
404not_enabledOrder orchestration is not enabled for the tenant.
409trip_closedThe trip is closed.
409idempotency_in_progressA request with this Idempotency-Key is still running. Retry after the Retry-After interval.
422unknown_channelThe channel is not in the orchestration configuration.
422unknown_momentThe purchase moment is not in the orchestration configuration.
422unknown_marketThe market is not in the orchestration configuration.
422second_anchorThe trip already has an anchor product.
422not_available_on_channelThe product type may not be sold on this channel.
422idempotency_key_reusedThe Idempotency-Key was used for a different request.
500internalUnexpected failure. Retry with the same Idempotency-Key.