Disruption and mass change
Record a disruption, plan a mass change (line update or re-accommodation with price protection), commit it with its commit token, retry failures and roll back.
| Operation | Endpoint |
|---|---|
| List disruptions | GET /oms-ops/disruptions |
| Record a disruption | POST /oms-ops/disruptions |
| Read a disruption | GET /oms-ops/disruptions/{disruptionId} |
| Read the impact of a disruption | GET /oms-ops/disruptions/{disruptionId}/impact |
| List a disruption's mass changes | GET /oms-ops/disruptions/{disruptionId}/mass-changes |
| Plan a mass change | POST /oms-ops/disruptions/{disruptionId}/mass-changes |
| Resolve, supersede or reopen a disruption | POST /oms-ops/disruptions/{disruptionId}/status |
| List mass changes | GET /oms-ops/mass-changes |
| Read a mass change | GET /oms-ops/mass-changes/{changeId} |
| Commit a planned mass change | POST /oms-ops/mass-changes/{changeId}/commit |
| Dry-run the pending items | POST /oms-ops/mass-changes/{changeId}/dry-run |
| List a mass change's items | GET /oms-ops/mass-changes/{changeId}/items |
| Record a guest's choice of the alternative or a refund | POST /oms-ops/mass-changes/{changeId}/items/{tripId}/guest-choice |
| Retry failed items | POST /oms-ops/mass-changes/{changeId}/retry |
| Roll back a mass change | POST /oms-ops/mass-changes/{changeId}/rollback |
List disruptions
/oms-ops/disruptionsParameters
| Name | In | Required | Notes |
|---|---|---|---|
status | query | open | |
limit | query | Default 50, maximum 200.; integer | |
cursor | query | The nextCursor of the previous page. |
Response 200
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | The request failed validation. The message names the first failing field; oms-ops and group surfaces also return every issue in details. |
| 400 | invalid_cursor | The pagination cursor is malformed. |
| 401 | unauthenticated | The bearer token is missing, invalid, expired or minted for another purpose. |
| 403 | scope_forbidden | The token is scoped to an agency or an access scope; this surface requires an unscoped tenant token. |
| 404 | order_operations_not_enabled | The order operations policy section is not configured for the tenant. |
| 404 | oms_ops_not_enabled | Order operations are not enabled for the tenant. |
| 500 | internal | Unexpected failure. Retry with the same Idempotency-Key. |
Record a disruption
/oms-ops/disruptionsHeaders: Idempotency-Key, plus X-Request-Id.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
actor | object | yes | |
actor.channel | string | max 60 chars | |
actor.id | string | yes | 1–200 chars |
actor.name | string | max 200 chars | |
actor.role | string | yes | 1–100 chars |
description | string | yes | 1–2000 chars |
disruptionId | string | 1–200 chars | |
kind | enum: itinerary_change, service_unavailable, departure_cancelled, transport_change, other | yes | |
occurredAt | string (date-time) | ||
reasonCode | string | yes | 1–60 chars |
scope | object | yes | |
scope.departureRefs | array of string | ||
scope.lineStatuses | array of string | ||
scope.productKinds | array of string | ||
scope.productRefs | array of string | ||
scope.tripIds | array of string | ||
sourceSystem | string | max 100 chars |
Schema: apps/journeys/src/oms-ops/contracts.ts (RecordDisruptionSchema)
Response 201
/oms-ops/disruptionsRecord a cancelled departurecurl -X POST "$BASE/oms-ops/disruptions" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Request-Id: req-0041" \
-H "Content-Type: application/json" \
-d '{
"kind": "departure_cancelled",
"reasonCode": "ship_technical",
"description": "Departure of 12 April 2027 cancelled; guests are offered the 19 April departure.",
"scope": {
"departureRefs": [
"MED7-2027-04-12"
],
"productKinds": [
"cruise"
]
},
"sourceSystem": "reservation",
"actor": {
"id": "ops-0007",
"name": "Paolo Ricci",
"role": "ops_manager",
"channel": "back_office"
}
}'
HTTP/1.1 201
X-Request-Id: req-0041
{
"disruptionId": "dis_6aa95d68-a42d-4d47-8170-95390ba977c3",
"tenantId": "meridian",
"kind": "departure_cancelled",
"origin": "disruption",
"reasonCode": "ship_technical",
"description": "Departure of 12 April 2027 cancelled; guests are offered the 19 April departure.",
"scope": {
"departureRefs": [
"MED7-2027-04-12"
],
"productRefs": [],
"productKinds": [
"cruise"
],
"tripIds": [],
"lineStatuses": []
},
"occurredAt": "2026-11-02T09:30:17.000Z",
"sourceSystem": "reservation",
"status": "open",
"actor": {
"id": "ops-0007",
"name": "Paolo Ricci",
"role": "ops_manager",
"channel": "back_office"
},
"createdAt": "2026-11-02T09:30:17.000Z",
"updatedAt": "2026-11-02T09:30:17.000Z"
}
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | The request failed validation. The message names the first failing field; oms-ops and group surfaces also return every issue in details. |
| 401 | unauthenticated | The bearer token is missing, invalid, expired or minted for another purpose. |
| 403 | scope_forbidden | The token is scoped to an agency or an access scope; this surface requires an unscoped tenant token. |
| 404 | oms_ops_not_enabled | Order operations are not enabled for the tenant. |
| 409 | idempotency_in_progress | A request with this Idempotency-Key is still running. Retry after the Retry-After interval. |
| 422 | idempotency_key_reused | The Idempotency-Key was used for a different request. |
| 500 | internal | Unexpected failure. Retry with the same Idempotency-Key. |
Read a disruption
/oms-ops/disruptions/{disruptionId}Parameters
| Name | In | Required | Notes |
|---|---|---|---|
disruptionId | path | yes |
Response 200
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | The request failed validation. The message names the first failing field; oms-ops and group surfaces also return every issue in details. |
| 401 | unauthenticated | The bearer token is missing, invalid, expired or minted for another purpose. |
| 403 | scope_forbidden | The token is scoped to an agency or an access scope; this surface requires an unscoped tenant token. |
| 404 | disruption_not_found | The disruption does not exist. |
| 404 | oms_ops_not_enabled | Order operations are not enabled for the tenant. |
| 500 | internal | Unexpected failure. Retry with the same Idempotency-Key. |
Read the impact of a disruption
/oms-ops/disruptions/{disruptionId}/impactParameters
| Name | In | Required | Notes |
|---|---|---|---|
disruptionId | path | yes |
Response 200
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | The request failed validation. The message names the first failing field; oms-ops and group surfaces also return every issue in details. |
| 401 | unauthenticated | The bearer token is missing, invalid, expired or minted for another purpose. |
| 403 | scope_forbidden | The token is scoped to an agency or an access scope; this surface requires an unscoped tenant token. |
| 404 | order_operations_not_enabled | The order operations policy section is not configured for the tenant. |
| 404 | oms_ops_not_enabled | Order operations are not enabled for the tenant. |
| 500 | internal | Unexpected failure. Retry with the same Idempotency-Key. |
List a disruption's mass changes
/oms-ops/disruptions/{disruptionId}/mass-changesParameters
| Name | In | Required | Notes |
|---|---|---|---|
disruptionId | path | yes |
Response 200
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | The request failed validation. The message names the first failing field; oms-ops and group surfaces also return every issue in details. |
| 401 | unauthenticated | The bearer token is missing, invalid, expired or minted for another purpose. |
| 403 | scope_forbidden | The token is scoped to an agency or an access scope; this surface requires an unscoped tenant token. |
| 404 | oms_ops_not_enabled | Order operations are not enabled for the tenant. |
| 500 | internal | Unexpected failure. Retry with the same Idempotency-Key. |
Plan a mass change
/oms-ops/disruptions/{disruptionId}/mass-changesReturns the planned change with a preview of every affected trip and a commitToken. Nothing is applied until the change is committed with that token.
Parameters
| Name | In | Required | Notes |
|---|---|---|---|
disruptionId | path | yes |
Headers: Idempotency-Key, plus X-Request-Id.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
actor | object | yes | |
actor.channel | string | max 60 chars | |
actor.id | string | yes | 1–200 chars |
actor.name | string | max 200 chars | |
actor.role | string | yes | 1–100 chars |
changeId | string | 1–200 chars | |
reason | string | max 2000 chars | |
spec | one of several shapes | yes |
Schema: apps/journeys/src/oms-ops/contracts.ts (PlanMassChangeSchema)
Response 201
/oms-ops/disruptions/dis_6aa95d68-a42d-4d47-8170-95390ba977c3/mass-changesPlan the re-accommodation with same-price protectioncurl -X POST "$BASE/oms-ops/disruptions/dis_6aa95d68-a42d-4d47-8170-95390ba977c3/mass-changes" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Request-Id: req-0042" \
-H "Content-Type: application/json" \
-d '{
"spec": {
"kind": "re_accommodation",
"targetDepartureRef": "MED7-2027-04-19",
"categoryMapping": {},
"protection": {
"ruleId": "same-price"
},
"targetPrices": {
"BALCONY-B2": {
"amountMinor": 252000
}
}
},
"actor": {
"id": "ops-0007",
"name": "Paolo Ricci",
"role": "ops_manager",
"channel": "back_office"
},
"reason": "Move affected guests to the next departure"
}'
HTTP/1.1 201
X-Request-Id: req-0042
{
"change": {
"changeId": "mc_510291cb-9863-4349-b300-eaaf0d44f0d6",
"tenantId": "meridian",
"disruptionId": "dis_6aa95d68-a42d-4d47-8170-95390ba977c3",
"kind": "re_accommodation",
"status": "planned",
"spec": {
"kind": "re_accommodation",
"targetDepartureRef": "MED7-2027-04-19",
"categoryMapping": {},
"protection": {
"ruleId": "same-price"
},
"targetPrices": {
"BALCONY-B2": {
"amountMinor": 252000,
"perTraveller": false
}
}
},
"rollbackOf": null,
"rolledBackBy": null,
"commitToken": "3f1d37d6df1a7b076339f1f93da107f4",
"population": {
"trips": 3,
"lines": 3
},
"counts": {
"total": 3,
"pending": 3,
"applied": 0,
"failed": 0,
"needs_person": 0,
"skipped": 0,
"rolled_back": 0,
"rollback_conflict": 0
},
"actor": {
"id": "ops-0007",
"name": "Paolo Ricci",
"role": "ops_manager",
"channel": "back_office"
},
"reason": "Move affected guests to the next departure",
"createdAt": "2026-11-02T09:30:54.000Z",
"updatedAt": "2026-11-02T09:30:54.000Z",
"startedAt": null,
"finishedAt": null,
"throughputPerSecond": null
},
"preview": [
{
"changeId": "mc_510291cb-9863-4349-b300-eaaf0d44f0d6",
"tripId": "TRP-F0A893B4CB27",
"status": "pending",
"attempts": 0,
"error": null,
"plan": {
"lines": [
{
"lineId": "L-4364586D534F",
"expectedVersion": 3
}
]
},
"before": null,
"after": null,
"outcome": null,
"updatedAt": "2026-11-02T09:30:54.000Z"
},
{
"changeId": "mc_510291cb-9863-4349-b300-eaaf0d44f0d6",
"tripId": "TRP-976512C541CF",
"status": "pending",
"attempts": 0,
"error": null,
"plan": {
"lines": [
{
"lineId": "L-A349D3520C1B",
"expectedVersion": 3
}
]
},
"before": null,
"after": null,
"outcome": null,
"updatedAt": "2026-11-02T09:30:54.000Z"
},
{
"changeId": "mc_510291cb-9863-4349-b300-eaaf0d44f0d6",
"tripId": "TRP-8232B7FF83C4",
"status": "pending",
"attempts": 0,
"error": null,
"plan": {
"lines": [
{
"lineId": "L-0A54E44D0375",
"expectedVersion": 4
}
]
},
"before": null,
"after": null,
"outcome": null,
"updatedAt": "2026-11-02T09:30:54.000Z"
}
]
}
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | The request failed validation. The message names the first failing field; oms-ops and group surfaces also return every issue in details. |
| 401 | unauthenticated | The bearer token is missing, invalid, expired or minted for another purpose. |
| 403 | scope_forbidden | The token is scoped to an agency or an access scope; this surface requires an unscoped tenant token. |
| 404 | oms_ops_not_enabled | Order operations are not enabled for the tenant. |
| 409 | idempotency_in_progress | A request with this Idempotency-Key is still running. Retry after the Retry-After interval. |
| 422 | idempotency_key_reused | The Idempotency-Key was used for a different request. |
| 500 | internal | Unexpected failure. Retry with the same Idempotency-Key. |
Resolve, supersede or reopen a disruption
/oms-ops/disruptions/{disruptionId}/statusParameters
| Name | In | Required | Notes |
|---|---|---|---|
disruptionId | path | yes |
Headers: Idempotency-Key, plus X-Request-Id.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
actor | object | yes | |
actor.channel | string | max 60 chars | |
actor.id | string | yes | 1–200 chars |
actor.name | string | max 200 chars | |
actor.role | string | yes | 1–100 chars |
reason | string | yes | 1–2000 chars |
status | enum: open, resolved, superseded | yes | |
supersededBy | string | 1–200 chars |
Schema: apps/journeys/src/oms-ops/contracts.ts (DisruptionStatusChangeSchema)
Response 200
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | The request failed validation. The message names the first failing field; oms-ops and group surfaces also return every issue in details. |
| 401 | unauthenticated | The bearer token is missing, invalid, expired or minted for another purpose. |
| 403 | scope_forbidden | The token is scoped to an agency or an access scope; this surface requires an unscoped tenant token. |
| 404 | oms_ops_not_enabled | Order operations are not enabled for the tenant. |
| 409 | mass_change_running | A mass change for this disruption is still running. |
| 409 | idempotency_in_progress | A request with this Idempotency-Key is still running. Retry after the Retry-After interval. |
| 422 | idempotency_key_reused | The Idempotency-Key was used for a different request. |
| 500 | internal | Unexpected failure. Retry with the same Idempotency-Key. |
List mass changes
/oms-ops/mass-changesParameters
| Name | In | Required | Notes |
|---|---|---|---|
status | query | ||
disruptionId | query | ||
limit | query | Default 50, maximum 200.; integer | |
offset | query | integer |
Response 200
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | The request failed validation. The message names the first failing field; oms-ops and group surfaces also return every issue in details. |
| 401 | unauthenticated | The bearer token is missing, invalid, expired or minted for another purpose. |
| 403 | scope_forbidden | The token is scoped to an agency or an access scope; this surface requires an unscoped tenant token. |
| 404 | order_operations_not_enabled | The order operations policy section is not configured for the tenant. |
| 404 | oms_ops_not_enabled | Order operations are not enabled for the tenant. |
| 500 | internal | Unexpected failure. Retry with the same Idempotency-Key. |
Read a mass change
/oms-ops/mass-changes/{changeId}Parameters
| Name | In | Required | Notes |
|---|---|---|---|
changeId | path | yes |
Response 200
/oms-ops/mass-changes/mc_510291cb-9863-4349-b300-eaaf0d44f0d6Read the mass changecurl -X GET "$BASE/oms-ops/mass-changes/mc_510291cb-9863-4349-b300-eaaf0d44f0d6" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Request-Id: req-0044"
HTTP/1.1 200
X-Request-Id: req-0044
{
"kind": "re_accommodation",
"spec": {
"kind": "re_accommodation",
"protection": {
"ruleId": "same-price"
},
"targetPrices": {
"BALCONY-B2": {
"amountMinor": 252000,
"perTraveller": false
}
},
"categoryMapping": {},
"targetDepartureRef": "MED7-2027-04-19"
},
"actor": {
"id": "ops-0007",
"name": "Paolo Ricci",
"role": "ops_manager",
"channel": "back_office"
},
"counts": {
"total": 3,
"pending": 0,
"applied": 3,
"failed": 0,
"needs_person": 0,
"skipped": 0,
"rolled_back": 0,
"rollback_conflict": 0
},
"reason": "Move affected guests to the next departure",
"status": "completed",
"changeId": "mc_510291cb-9863-4349-b300-eaaf0d44f0d6",
"tenantId": "meridian",
"createdAt": "2026-11-02T09:30:54.000Z",
"startedAt": "2026-11-02T09:31:31.000Z",
"updatedAt": "2026-11-02T09:30:54.000Z",
"finishedAt": "2026-11-02T09:31:31.000Z",
"population": {
"lines": 3,
"trips": 3
},
"rollbackOf": null,
"commitToken": "3f1d37d6df1a7b076339f1f93da107f4",
"disruptionId": "dis_6aa95d68-a42d-4d47-8170-95390ba977c3",
"rolledBackBy": null,
"throughputPerSecond": 3000,
"contact": {
"requested": 8,
"trips": 3
}
}
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | The request failed validation. The message names the first failing field; oms-ops and group surfaces also return every issue in details. |
| 401 | unauthenticated | The bearer token is missing, invalid, expired or minted for another purpose. |
| 403 | scope_forbidden | The token is scoped to an agency or an access scope; this surface requires an unscoped tenant token. |
| 404 | oms_ops_not_enabled | Order operations are not enabled for the tenant. |
| 500 | internal | Unexpected failure. Retry with the same Idempotency-Key. |
Commit a planned mass change
/oms-ops/mass-changes/{changeId}/commitParameters
| Name | In | Required | Notes |
|---|---|---|---|
changeId | path | yes |
Headers: Idempotency-Key, plus X-Request-Id.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
commitToken | string | yes | |
actor | object | yes | |
actor.channel | string | max 60 chars | |
actor.id | string | yes | 1–200 chars |
actor.name | string | max 200 chars | |
actor.role | string | yes | 1–100 chars |
Response 200
/oms-ops/mass-changes/mc_510291cb-9863-4349-b300-eaaf0d44f0d6/commitCommit the planned change with its commit tokencurl -X POST "$BASE/oms-ops/mass-changes/mc_510291cb-9863-4349-b300-eaaf0d44f0d6/commit" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Request-Id: req-0043" \
-H "Content-Type: application/json" \
-d '{
"commitToken": "3f1d37d6df1a7b076339f1f93da107f4",
"actor": {
"id": "ops-0007",
"name": "Paolo Ricci",
"role": "ops_manager",
"channel": "back_office"
}
}'
HTTP/1.1 200
X-Request-Id: req-0043
{
"kind": "re_accommodation",
"spec": {
"kind": "re_accommodation",
"protection": {
"ruleId": "same-price"
},
"targetPrices": {
"BALCONY-B2": {
"amountMinor": 252000,
"perTraveller": false
}
},
"categoryMapping": {},
"targetDepartureRef": "MED7-2027-04-19"
},
"actor": {
"id": "ops-0007",
"name": "Paolo Ricci",
"role": "ops_manager",
"channel": "back_office"
},
"counts": {
"total": 3,
"pending": 0,
"applied": 3,
"failed": 0,
"needs_person": 0,
"skipped": 0,
"rolled_back": 0,
"rollback_conflict": 0
},
"reason": "Move affected guests to the next departure",
"status": "completed",
"changeId": "mc_510291cb-9863-4349-b300-eaaf0d44f0d6",
"tenantId": "meridian",
"createdAt": "2026-11-02T09:30:54.000Z",
"startedAt": "2026-11-02T09:31:31.000Z",
"updatedAt": "2026-11-02T09:30:54.000Z",
"finishedAt": "2026-11-02T09:31:31.000Z",
"population": {
"lines": 3,
"trips": 3
},
"rollbackOf": null,
"commitToken": "3f1d37d6df1a7b076339f1f93da107f4",
"disruptionId": "dis_6aa95d68-a42d-4d47-8170-95390ba977c3",
"rolledBackBy": null,
"throughputPerSecond": 3000,
"contact": {
"requested": 8,
"trips": 3
}
}
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | The request failed validation. The message names the first failing field; oms-ops and group surfaces also return every issue in details. |
| 401 | unauthenticated | The bearer token is missing, invalid, expired or minted for another purpose. |
| 403 | scope_forbidden | The token is scoped to an agency or an access scope; this surface requires an unscoped tenant token. |
| 404 | oms_ops_not_enabled | Order operations are not enabled for the tenant. |
| 409 | commit_token_mismatch | The commit token does not match the planned change. |
| 409 | mass_change_not_planned | The mass change is not in the planned state. |
| 409 | idempotency_in_progress | A request with this Idempotency-Key is still running. Retry after the Retry-After interval. |
| 422 | idempotency_key_reused | The Idempotency-Key was used for a different request. |
| 500 | internal | Unexpected failure. Retry with the same Idempotency-Key. |
Dry-run the pending items
/oms-ops/mass-changes/{changeId}/dry-runParameters
| Name | In | Required | Notes |
|---|---|---|---|
changeId | path | yes |
Headers: Idempotency-Key, plus X-Request-Id.
Response 200
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | The request failed validation. The message names the first failing field; oms-ops and group surfaces also return every issue in details. |
| 401 | unauthenticated | The bearer token is missing, invalid, expired or minted for another purpose. |
| 403 | scope_forbidden | The token is scoped to an agency or an access scope; this surface requires an unscoped tenant token. |
| 404 | order_operations_not_enabled | The order operations policy section is not configured for the tenant. |
| 404 | oms_ops_not_enabled | Order operations are not enabled for the tenant. |
| 409 | idempotency_in_progress | A request with this Idempotency-Key is still running. Retry after the Retry-After interval. |
| 422 | idempotency_key_reused | The Idempotency-Key was used for a different request. |
| 500 | internal | Unexpected failure. Retry with the same Idempotency-Key. |
List a mass change's items
/oms-ops/mass-changes/{changeId}/itemsParameters
| Name | In | Required | Notes |
|---|---|---|---|
changeId | path | yes | |
status | query | ||
outcome | query | ||
market | query | ||
channel | query | ||
limit | query | Default 200, maximum 1000.; integer | |
offset | query | integer |
Response 200
/oms-ops/mass-changes/mc_510291cb-9863-4349-b300-eaaf0d44f0d6/itemsRead the per-trip itemscurl -X GET "$BASE/oms-ops/mass-changes/mc_510291cb-9863-4349-b300-eaaf0d44f0d6/items" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Request-Id: req-0045"
HTTP/1.1 200
X-Request-Id: req-0045
{
"items": [
{
"changeId": "mc_510291cb-9863-4349-b300-eaaf0d44f0d6",
"tripId": "TRP-F0A893B4CB27",
"status": "applied",
"attempts": 1,
"error": null,
"plan": {
"lines": [
{
"lineId": "L-4364586D534F",
"expectedVersion": 3
}
]
},
"before": [
{
"poId": "PO-16FF30EC289C",
"roles": {
"payer": {
"ref": "CUST-20419",
"name": "Giulia Rossi"
},
"booker": {
"ref": "CUST-20419",
"name": "Giulia Rossi"
},
"travellers": [
{
"ref": "PAX-01"
},
{
"ref": "PAX-02"
}
]
},
"lineId": "L-4364586D534F",
"market": "IT",
"status": "booked",
"tripId": "TRP-F0A893B4CB27",
"channel": "web",
"groupId": null,
"currency": "EUR",
"updatedAt": "2026-11-02T09:06:42.000Z",
"attributes": {
"poType": "standard",
"source": "order_orchestration",
"offerId": "ALL-INCLUSIVE-2027",
"quantity": 1,
"poSequence": 1,
"departureAt": "2027-04-12T17:00:00Z",
"externalRef": "RSV-66676AEB-000001",
"orderVersion": 4,
"promotionIds": [
"EARLY-BOOKING-10"
],
"quoteLineRef": "q-line-1",
"sourceSystem": "reservation",
"billingStatus": "not_billed",
"paymentStatus": "not_due",
"reservationRef": "RSV-66676AEB-000001",
"tripCustomerRef": "CUST-20419",
"fulfilmentStatus": "confirmed"
},
"productRef": "MED7-2027-04-12",
"amountMinor": 238000,
"categoryRef": "BALCONY-B2",
"lineVersion": 3,
"productKind": "cruise",
"departureRef": "MED7-2027-04-12"
}
],
"after": [
{
"poId": "PO-16FF30EC289C",
"roles": {
"payer": {
"ref": "CUST-20419",
"name": "Giulia Rossi"
},
"booker": {
"ref": "CUST-20419",
"name": "Giulia Rossi"
},
"travellers": [
{
"ref": "PAX-01"
},
{
"ref": "PAX-02"
}
]
},
"lineId": "L-4364586D534F",
"market": "IT",
"status": "booked",
"tripId": "TRP-F0A893B4CB27",
"channel": "web",
"groupId": null,
"currency": "EUR",
"updatedAt": "2026-11-02T09:31:31.000Z",
"attributes": {
"poType": "standard",
"source": "order_orchestration",
"offerId": "ALL-INCLUSIVE-2027",
"quantity": 1,
"poSequence": 1,
"departureAt": "2027-04-12T17:00:00Z",
"externalRef": "RSV-66676AEB-000001",
"orderVersion": 4,
"promotionIds": [
"EARLY-BOOKING-10"
],
"quoteLineRef": "q-line-1",
"sourceSystem": "reservation",
"billingStatus": "not_billed",
"paymentStatus": "not_due",
"reservationRef": "RSV-66676AEB-000001",
"reAccommodation": {
"upgradedTo": null,
"disruptionId": "dis_6aa95d68-a42d-4d47-8170-95390ba977c3",
"fromProductRef": "MED7-2027-04-12",
"protectionRule": "same_price",
"fromCategoryRef": "BALCONY-B2",
"fromDepartureRef": "MED7-2027-04-12",
"protectionRuleId": "same-price",
"originalAmountMinor": 238000
},
"tripCustomerRef": "CUST-20419",
"fulfilmentStatus": "confirmed"
},
"productRef": "MED7-2027-04-12",
"amountMinor": 238000,
"categoryRef": "BALCONY-B2",
"lineVersion": 4,
"productKind": "cruise",
"departureRef": "MED7-2027-04-19"
}
],
"outcome": {
"rule": "same_price",
"lines": {
"L-4364586D534F": {
"amountMinor": 238000,
"explanation": [
{
"flag": "individual",
"mode": "select",
"basis": "booking",
"input": 0,
"layer": "target_price",
"reason": {
"key": "price_protection.target_price",
"params": {
"category": "BALCONY-B2",
"departure": "MED7-2027-04-19",
"travellers": 2
}
},
"ruleId": null,
"source": {
"kind": "runtime",
"condition": "target:MED7-2027-04-19"
},
"contribution": 252000,
"runningTotal": 252000
},
{
"flag": "individual",
"mode": "replace",
"basis": "booking",
"input": 252000,
"layer": "price_protection",
"reason": {
"key": "price_protection.same_price",
"params": {
"target": 252000,
"original": 238000
}
},
"ruleId": "same-price",
"source": {
"kind": "runtime",
"condition": "disruption:dis_6aa95d68-a42d-4d47-8170-95390ba977c3"
},
"contribution": -14000,
"runningTotal": 238000
}
],
"targetMinor": 252000,
"originalMinor": 238000
}
},
"ruleId": "same-price",
"priceDeltaMinor": 0
},
"updatedAt": "2026-11-02T09:31:31.000Z"
},
{
"changeId": "mc_510291cb-9863-4349-b300-eaaf0d44f0d6",
"tripId": "TRP-976512C541CF",
"status": "applied",
"attempts": 1,
"error": null,
"plan": {
"lines": [
{
"lineId": "L-A349D3520C1B",
"expectedVersion": 3
}
]
},
"before": [
{
"poId": "PO-B3A0355DEB58",
"roles": {
"travellers": [
{
"ref": "PAX-01"
},
{
"ref": "PAX-02"
}
]
},
"lineId": "L-A349D3520C1B",
"market": "IT",
"status": "booked",
"tripId": "TRP-976512C541CF",
"channel": "web",
"groupId": null,
"currency": "EUR",
"updatedAt": "2026-11-02T09:06:42.000Z",
"attributes": {
"poType": "standard",
"source": "order_orchestration",
"offerId": "ALL-INCLUSIVE-2027",
"quantity": 1,
"poSequence": 1,
"departureAt": "2027-04-12T17:00:00Z",
"externalRef": "RSV-8CA144DF-000002",
"orderVersion": 4,
"promotionIds": [
"EARLY-BOOKING-10"
],
"quoteLineRef": "q-line-1",
"sourceSystem": "reservation",
"billingStatus": "not_billed",
"paymentStatus": "not_due",
"reservationRef": "RSV-8CA144DF-000002",
"tripCustomerRef": "CUST-20533",
"fulfilmentStatus": "confirmed"
},
"productRef": "MED7-2027-04-12",
"amountMinor": 238000,
"categoryRef": "BALCONY-B2",
"lineVersion": 3,
"productKind": "cruise",
"departureRef": "MED7-2027-04-12"
}
],
"after": [
{
"poId": "PO-B3A0355DEB58",
"roles": {
"travellers": [
{
"ref": "PAX-01"
},
{
"ref": "PAX-02"
}
]
},
"lineId": "L-A349D3520C1B",
"market": "IT",
"status": "booked",
"tripId": "TRP-976512C541CF",
"channel": "web",
"groupId": null,
"currency": "EUR",
"updatedAt": "2026-11-02T09:31:31.000Z",
"attributes": {
"poType": "standard",
"source": "order_orchestration",
"offerId": "ALL-INCLUSIVE-2027",
"quantity": 1,
"poSequence": 1,
"departureAt": "2027-04-12T17:00:00Z",
"externalRef": "RSV-8CA144DF-000002",
"orderVersion": 4,
"promotionIds": [
"EARLY-BOOKING-10"
],
"quoteLineRef": "q-line-1",
"sourceSystem": "reservation",
"billingStatus": "not_billed",
"paymentStatus": "not_due",
"reservationRef": "RSV-8CA144DF-000002",
"reAccommodation": {
"upgradedTo": null,
"disruptionId": "dis_6aa95d68-a42d-4d47-8170-95390ba977c3",
"fromProductRef": "MED7-2027-04-12",
"protectionRule": "same_price",
"fromCategoryRef": "BALCONY-B2",
"fromDepartureRef": "MED7-2027-04-12",
"protectionRuleId": "same-price",
"originalAmountMinor": 238000
},
"tripCustomerRef": "CUST-20533",
"fulfilmentStatus": "confirmed"
},
"productRef": "MED7-2027-04-12",
"amountMinor": 238000,
"categoryRef": "BALCONY-B2",
"lineVersion": 4,
"productKind": "cruise",
"departureRef": "MED7-2027-04-19"
}
],
"outcome": {
"rule": "same_price",
"lines": {
"L-A349D3520C1B": {
"amountMinor": 238000,
"explanation": [
{
"flag": "individual",
"mode": "select",
"basis": "booking",
"input": 0,
"layer": "target_price",
"reason": {
"key": "price_protection.target_price",
"params": {
"category": "BALCONY-B2",
"departure": "MED7-2027-04-19",
"travellers": 2
}
},
"ruleId": null,
"source": {
"kind": "runtime",
"condition": "target:MED7-2027-04-19"
},
"contribution": 252000,
"runningTotal": 252000
},
{
"flag": "individual",
"mode": "replace",
"basis": "booking",
"input": 252000,
"layer": "price_protection",
"reason": {
"key": "price_protection.same_price",
"params": {
"target": 252000,
"original": 238000
}
},
"ruleId": "same-price",
"source": {
"kind": "runtime",
"condition": "disruption:dis_6aa95d68-a42d-4d47-8170-95390ba977c3"
},
"contribution": -14000,
"runningTotal": 238000
}
],
"targetMinor": 252000,
"originalMinor": 238000
}
},
"ruleId": "same-price",
"priceDeltaMinor": 0
},
"updatedAt": "2026-11-02T09:31:31.000Z"
},
{
"changeId": "mc_510291cb-9863-4349-b300-eaaf0d44f0d6",
"tripId": "TRP-8232B7FF83C4",
"status": "applied",
"attempts": 1,
"error": null,
"plan": {
"lines": [
{
"lineId": "L-0A54E44D0375",
"expectedVersion": 4
}
]
},
"before": [
{
"poId": "PO-25CE761780FC",
"roles": {
"travellers": [
{
"ref": "PAX-01"
},
{
"ref": "PAX-02"
}
]
},
"lineId": "L-0A54E44D0375",
"market": "IT",
"status": "booked",
"tripId": "TRP-8232B7FF83C4",
"channel": "web",
"groupId": null,
"currency": "EUR",
"updatedAt": "2026-11-02T09:06:42.000Z",
"attributes": {
"poType": "standard",
"source": "order_orchestration",
"offerId": "ALL-INCLUSIVE-2027",
"quantity": 1,
"poSequence": 1,
"departureAt": "2027-04-12T17:00:00Z",
"externalRef": "RSV-078AC95B-000003",
"orderVersion": 4,
"promotionIds": [
"EARLY-BOOKING-10"
],
"quoteLineRef": "q-line-1",
"sourceSystem": "reservation",
"billingStatus": "not_billed",
"paymentStatus": "not_due",
"reservationRef": "RSV-078AC95B-000003",
"tripCustomerRef": "CUST-20617",
"fulfilmentStatus": "confirmed"
},
"productRef": "MED7-2027-04-12",
"amountMinor": 238000,
"categoryRef": "BALCONY-B2",
"lineVersion": 4,
"productKind": "cruise",
"departureRef": "MED7-2027-04-12"
}
],
"after": [
{
"poId": "PO-25CE761780FC",
"roles": {
"travellers": [
{
"ref": "PAX-01"
},
{
"ref": "PAX-02"
}
]
},
"lineId": "L-0A54E44D0375",
"market": "IT",
"status": "booked",
"tripId": "TRP-8232B7FF83C4",
"channel": "web",
"groupId": null,
"currency": "EUR",
"updatedAt": "2026-11-02T09:31:31.000Z",
"attributes": {
"poType": "standard",
"source": "order_orchestration",
"offerId": "ALL-INCLUSIVE-2027",
"quantity": 1,
"poSequence": 1,
"departureAt": "2027-04-12T17:00:00Z",
"externalRef": "RSV-078AC95B-000003",
"orderVersion": 4,
"promotionIds": [
"EARLY-BOOKING-10"
],
"quoteLineRef": "q-line-1",
"sourceSystem": "reservation",
"billingStatus": "not_billed",
"paymentStatus": "not_due",
"reservationRef": "RSV-078AC95B-000003",
"reAccommodation": {
"upgradedTo": null,
"disruptionId": "dis_6aa95d68-a42d-4d47-8170-95390ba977c3",
"fromProductRef": "MED7-2027-04-12",
"protectionRule": "same_price",
"fromCategoryRef": "BALCONY-B2",
"fromDepartureRef": "MED7-2027-04-12",
"protectionRuleId": "same-price",
"originalAmountMinor": 238000
},
"tripCustomerRef": "CUST-20617",
"fulfilmentStatus": "confirmed"
},
"productRef": "MED7-2027-04-12",
"amountMinor": 238000,
"categoryRef": "BALCONY-B2",
"lineVersion": 5,
"productKind": "cruise",
"departureRef": "MED7-2027-04-19"
}
],
"outcome": {
"rule": "same_price",
"lines": {
"L-0A54E44D0375": {
"amountMinor": 238000,
"explanation": [
{
"flag": "individual",
"mode": "select",
"basis": "booking",
"input": 0,
"layer": "target_price",
"reason": {
"key": "price_protection.target_price",
"params": {
"category": "BALCONY-B2",
"departure": "MED7-2027-04-19",
"travellers": 2
}
},
"ruleId": null,
"source": {
"kind": "runtime",
"condition": "target:MED7-2027-04-19"
},
"contribution": 252000,
"runningTotal": 252000
},
{
"flag": "individual",
"mode": "replace",
"basis": "booking",
"input": 252000,
"layer": "price_protection",
"reason": {
"key": "price_protection.same_price",
"params": {
"target": 252000,
"original": 238000
}
},
"ruleId": "same-price",
"source": {
"kind": "runtime",
"condition": "disruption:dis_6aa95d68-a42d-4d47-8170-95390ba977c3"
},
"contribution": -14000,
"runningTotal": 238000
}
],
"targetMinor": 252000,
"originalMinor": 238000
}
},
"ruleId": "same-price",
"priceDeltaMinor": 0
},
"updatedAt": "2026-11-02T09:31:31.000Z"
}
]
}
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | The request failed validation. The message names the first failing field; oms-ops and group surfaces also return every issue in details. |
| 401 | unauthenticated | The bearer token is missing, invalid, expired or minted for another purpose. |
| 403 | scope_forbidden | The token is scoped to an agency or an access scope; this surface requires an unscoped tenant token. |
| 404 | oms_ops_not_enabled | Order operations are not enabled for the tenant. |
| 500 | internal | Unexpected failure. Retry with the same Idempotency-Key. |
Record a guest's choice of the alternative or a refund
/oms-ops/mass-changes/{changeId}/items/{tripId}/guest-choiceParameters
| Name | In | Required | Notes |
|---|---|---|---|
changeId | path | yes | |
tripId | path | yes |
Headers: Idempotency-Key, plus X-Request-Id.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
actor | object | yes | |
actor.channel | string | max 60 chars | |
actor.id | string | yes | 1–200 chars |
actor.name | string | max 200 chars | |
actor.role | string | yes | 1–100 chars |
choice | enum: accept, refund | yes | |
note | string | max 2000 chars |
Schema: apps/journeys/src/oms-ops/contracts.ts (GuestChoiceSchema)
Response 200
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | The request failed validation. The message names the first failing field; oms-ops and group surfaces also return every issue in details. |
| 401 | unauthenticated | The bearer token is missing, invalid, expired or minted for another purpose. |
| 403 | scope_forbidden | The token is scoped to an agency or an access scope; this surface requires an unscoped tenant token. |
| 404 | item_not_found | The mass change has no item for this trip. |
| 404 | oms_ops_not_enabled | Order operations are not enabled for the tenant. |
| 409 | not_offered | The request or offer is not in the offered state. |
| 409 | choice_already_made | The guest has already chosen. |
| 409 | choice_window_closed | The guest-choice window has closed. |
| 409 | idempotency_in_progress | A request with this Idempotency-Key is still running. Retry after the Retry-After interval. |
| 422 | idempotency_key_reused | The Idempotency-Key was used for a different request. |
| 500 | internal | Unexpected failure. Retry with the same Idempotency-Key. |
Retry failed items
/oms-ops/mass-changes/{changeId}/retryParameters
| Name | In | Required | Notes |
|---|---|---|---|
changeId | path | yes |
Headers: Idempotency-Key, plus X-Request-Id.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
actor | object | yes | |
actor.channel | string | max 60 chars | |
actor.id | string | yes | 1–200 chars |
actor.name | string | max 200 chars | |
actor.role | string | yes | 1–100 chars |
Response 200
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | The request failed validation. The message names the first failing field; oms-ops and group surfaces also return every issue in details. |
| 401 | unauthenticated | The bearer token is missing, invalid, expired or minted for another purpose. |
| 403 | scope_forbidden | The token is scoped to an agency or an access scope; this surface requires an unscoped tenant token. |
| 404 | oms_ops_not_enabled | Order operations are not enabled for the tenant. |
| 409 | nothing_to_retry | The mass change has no failed items to retry. |
| 409 | idempotency_in_progress | A request with this Idempotency-Key is still running. Retry after the Retry-After interval. |
| 422 | idempotency_key_reused | The Idempotency-Key was used for a different request. |
| 500 | internal | Unexpected failure. Retry with the same Idempotency-Key. |
Roll back a mass change
/oms-ops/mass-changes/{changeId}/rollbackParameters
| Name | In | Required | Notes |
|---|---|---|---|
changeId | path | yes |
Headers: Idempotency-Key, plus X-Request-Id.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
actor | object | yes | |
actor.channel | string | max 60 chars | |
actor.id | string | yes | 1–200 chars |
actor.name | string | max 200 chars | |
actor.role | string | yes | 1–100 chars |
reason | string | yes | min 1 chars |
Response 200
/oms-ops/mass-changes/mc_510291cb-9863-4349-b300-eaaf0d44f0d6/rollbackRoll the change backcurl -X POST "$BASE/oms-ops/mass-changes/mc_510291cb-9863-4349-b300-eaaf0d44f0d6/rollback" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Request-Id: req-0046" \
-H "Content-Type: application/json" \
-d '{
"actor": {
"id": "ops-0007",
"name": "Paolo Ricci",
"role": "ops_manager",
"channel": "back_office"
},
"reason": "Departure reinstated after repair"
}'
HTTP/1.1 200
X-Request-Id: req-0046
{
"original": {
"kind": "re_accommodation",
"spec": {
"kind": "re_accommodation",
"protection": {
"ruleId": "same-price"
},
"targetPrices": {
"BALCONY-B2": {
"amountMinor": 252000,
"perTraveller": false
}
},
"categoryMapping": {},
"targetDepartureRef": "MED7-2027-04-19"
},
"actor": {
"id": "ops-0007",
"name": "Paolo Ricci",
"role": "ops_manager",
"channel": "back_office"
},
"counts": {
"total": 3,
"pending": 0,
"applied": 0,
"failed": 0,
"needs_person": 0,
"skipped": 0,
"rolled_back": 3,
"rollback_conflict": 0
},
"reason": "Move affected guests to the next departure",
"status": "rolled_back",
"changeId": "mc_510291cb-9863-4349-b300-eaaf0d44f0d6",
"tenantId": "meridian",
"createdAt": "2026-11-02T09:30:54.000Z",
"startedAt": "2026-11-02T09:31:31.000Z",
"updatedAt": "2026-11-02T09:30:54.000Z",
"finishedAt": "2026-11-02T09:31:31.000Z",
"population": {
"lines": 3,
"trips": 3
},
"rollbackOf": null,
"commitToken": "3f1d37d6df1a7b076339f1f93da107f4",
"disruptionId": "dis_6aa95d68-a42d-4d47-8170-95390ba977c3",
"rolledBackBy": "mc_2398b4e3-7636-4cec-b823-19c6ba76c7d7",
"throughputPerSecond": 3000,
"contact": {
"requested": 8,
"trips": 3
}
},
"rollback": {
"kind": "rollback",
"spec": {
"kind": "rollback"
},
"actor": {
"id": "ops-0007",
"name": "Paolo Ricci",
"role": "ops_manager",
"channel": "back_office"
},
"counts": {
"total": 3,
"failed": 0,
"applied": 0,
"pending": 0,
"skipped": 0,
"rolled_back": 3,
"needs_person": 0,
"rollback_conflict": 0
},
"reason": "Departure reinstated after repair",
"status": "completed",
"contact": {
"trips": 3,
"requested": 8
},
"changeId": "mc_2398b4e3-7636-4cec-b823-19c6ba76c7d7",
"tenantId": "meridian",
"createdAt": "2026-11-02T09:33:22.000Z",
"startedAt": "2026-11-02T09:33:22.000Z",
"updatedAt": "2026-11-02T09:33:22.000Z",
"finishedAt": "2026-11-02T09:33:22.000Z",
"population": {
"lines": 3,
"trips": 3
},
"rollbackOf": "mc_510291cb-9863-4349-b300-eaaf0d44f0d6",
"commitToken": "",
"disruptionId": "dis_6aa95d68-a42d-4d47-8170-95390ba977c3",
"rolledBackBy": null,
"throughputPerSecond": 3000
}
}
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | The request failed validation. The message names the first failing field; oms-ops and group surfaces also return every issue in details. |
| 400 | reason_required | This action requires a reason. |
| 401 | unauthenticated | The bearer token is missing, invalid, expired or minted for another purpose. |
| 403 | scope_forbidden | The token is scoped to an agency or an access scope; this surface requires an unscoped tenant token. |
| 404 | oms_ops_not_enabled | Order operations are not enabled for the tenant. |
| 409 | already_rolled_back | The mass change has already been rolled back. |
| 409 | idempotency_in_progress | A request with this Idempotency-Key is still running. Retry after the Retry-After interval. |
| 422 | idempotency_key_reused | The Idempotency-Key was used for a different request. |
| 500 | internal | Unexpected failure. Retry with the same Idempotency-Key. |