Edit a quote's lines and packages
POST/v1/quotes/{quoteId}/lines
Applies up to 50 ops to a quote's lines and packages as ONE change: add_line (a flight/transport, hotel or other line; into a package with packageId, or as an upgrade replacing a base line with replacesItemId), update_line, remove_line (a base line's upgrades go with it), add_package (up to 3; package quotes only), rename_package, remove_package (only when no line uses it). A later op may refer to an earlier add by its clientId. Lines that follow the trip's design refuse edits to what the design owns (a linked transport's route, dates and times; a linked hotel's city): change the design, then POST /quotes/{quoteId}/sync-design. Money is a decimal string; only an 'other' line may be negative (a discount). If any op is refused, nothing is written. With ?dryRun=true, returns a quote_preview of the resulting lines and totals.
Permissions: quotes:write · Kind: write · Cost: 1 unit · Dry run: ?dryRun=true · MCP tool edit_quote_lines
Undoable: the response carries Bymundi-Change-Id; revert it with POST /v1/changes/{changeId}/revert.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
quoteId |
uuid | yes |
Body
| Field | Type | Required | Description |
|---|---|---|---|
ops |
object | object | object | object | object | object[] | yes | max 50 items |
expectedUpdatedAt |
datetime | The quote's updatedAt as you read it; the write is refused (409) if the quote changed since. |
Response 200
One of:
quote_edit
| Field | Type | Required | Description |
|---|---|---|---|
object |
"quote_edit" | yes | |
quote |
object | yes | |
quote.object |
"quote" | yes | |
quote.id |
uuid | yes | |
quote.tripId |
uuid | yes | |
quote.code |
string | yes | |
quote.name |
string | yes | |
quote.mode |
"package" | "per_item" | yes | |
quote.status |
"draft" | "sent" | "accepted" | "rejected" | "expired" | "superseded" | yes | |
quote.travelers |
integer | yes | |
quote.marginPct |
number | yes | |
quote.finalPriceOverride |
string | null | yes | |
quote.validUntil |
string | null | yes | |
quote.ctaUrl |
string | null | yes | |
quote.templateId |
string | null | yes | |
quote.packages |
object[] | yes | |
quote.packages[].id |
string | yes | |
quote.packages[].name |
string | yes | |
quote.lines |
object | object | object[] | yes | |
quote.totals |
object | yes | |
quote.totals.currency |
"EUR" | yes | |
quote.totals.net |
string | yes | |
quote.totals.marginAmount |
string | yes | |
quote.totals.total |
string | yes | |
quote.totals.finalPrice |
string | yes | |
quote.totals.perPax |
string | yes | |
quote.totals.finalMargin |
string | yes | |
quote.totals.packages |
object[] | yes | |
quote.textOverrides |
object | yes | |
quote.imageOverrides |
object | yes | |
quote.sentAt |
string | null | yes | |
quote.modifiedSinceSent |
boolean | yes | |
quote.acceptedAt |
string | null | yes | |
quote.acceptedVia |
"prospect_link" | "agent" | null | yes | |
quote.acceptedPackageId |
string | null | yes | |
quote.acceptedLineIds |
string[] | yes | |
quote.acceptance |
object | null | yes | What the customer typed when accepting on the quote page — personal data: present only for a key holding travelers:read, only on quote reads (a write's response carries null; read the quote), and every such read is audited. |
quote.acceptance.name |
string | null | yes | |
quote.acceptance.email |
string | null | yes | |
quote.acceptance.phone |
string | null | yes | |
quote.acceptance.message |
string | null | yes | |
quote.engagement |
object | yes | |
quote.engagement.views |
integer | yes | |
quote.engagement.uniqueSessions |
integer | yes | |
quote.engagement.activeSeconds |
integer | yes | |
quote.engagement.ctaClicks |
integer | yes | |
quote.engagement.maxScrollPct |
number | yes | |
quote.engagement.sectionsSeen |
integer | yes | |
quote.engagement.interactions |
integer | yes | |
quote.engagement.lastSeenAt |
string | null | yes | |
quote.engagement.lastCtaAt |
string | null | yes | |
quote.metadata |
object | yes | |
quote.appUrl |
string | null | yes | |
quote.publicUrl |
string | null | yes | |
quote.createdAt |
string | yes | |
quote.updatedAt |
string | yes | |
created |
object[] | yes | |
created[].clientId |
string | null | yes | |
created[].id |
string | yes | |
created[].kind |
"line" | "package" | yes | |
removed |
string[] | yes |
quote_preview
| Field | Type | Required | Description |
|---|---|---|---|
object |
"quote_preview" | yes | |
quoteId |
uuid | null | yes | |
changes |
object[] | yes | |
changes[].field |
string | yes | |
changes[].from |
any | ||
changes[].to |
any | ||
sync |
object | null | yes | |
sync.added |
integer | yes | |
sync.updated |
integer | yes | |
sync.removed |
integer | yes | |
packages |
object[] | yes | |
packages[].id |
string | yes | |
packages[].name |
string | yes | |
lines |
object | object | object[] | yes | |
totals |
object | yes | |
totals.currency |
"EUR" | yes | |
totals.net |
string | yes | |
totals.marginAmount |
string | yes | |
totals.total |
string | yes | |
totals.finalPrice |
string | yes | |
totals.perPax |
string | yes | |
totals.finalMargin |
string | yes | |
totals.packages |
object[] | yes | |
totals.packages[].id |
string | yes | |
totals.packages[].name |
string | yes | |
totals.packages[].deltaNet |
string | yes | |
totals.packages[].deltaPvp |
string | yes | |
totals.packages[].lineIds |
string[] | yes |
Errors
Errors are problem details. Besides the refusals described above, any call like this one can return:
invalid_request— Invalid requestunauthorized— Missing or invalid API keyinsufficient_scope— Missing permissionrate_limited— Rate limit reachednot_found— Not foundidempotency_key_reused— Idempotency-Key reusedrequest_in_progress— Request in progress