Update a quote
PATCH/v1/quotes/{quoteId}
Changes the quote's own fields, as the quote editor's header and copy editor do: name, travelers (must equal the trip's headcount), marginPct, finalPriceOverride, validUntil, ctaUrl, templateId, and the template's editable texts and images (textOverrides, imageOverrides: merged key by key, null restores the template's). metadata merges key by key. Lines have their own endpoint. Pass the updatedAt you read as expectedUpdatedAt to refuse a stale write with a 409. With ?dryRun=true, returns a quote_preview with each field's from/to and the new totals.
Permissions: quotes:write · Kind: write · Cost: 1 unit · Dry run: ?dryRun=true · MCP tool update_quote
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 |
|---|---|---|---|
name |
string | max 200 chars | |
travelers |
integer | Must equal the trip's headcount. 1–40 | |
marginPct |
number | A % of the SALE price. 0–99 | |
finalPriceOverride |
string | null | The rounded price the customer is asked to pay; null = the computed total. | |
validUntil |
string | null | ||
ctaUrl |
string | null | Where the public page's button leads (WhatsApp, email, a URL). | |
templateId |
uuid | null | A quote template; null = the agency's default. | |
textOverrides |
object | The template's editable texts, by key; null restores the template's text. | |
imageOverrides |
object | The template's editable images (URLs), by key; null restores the template's image. | |
expectedUpdatedAt |
datetime | The quote's updatedAt as you read it; the write is refused (409) if the quote changed since. | |
metadata |
object |
Response 200
One of:
quote
| Field | Type | Required | Description |
|---|---|---|---|
object |
"quote" | yes | |
id |
uuid | yes | |
tripId |
uuid | yes | |
code |
string | yes | |
name |
string | yes | |
mode |
"package" | "per_item" | yes | |
status |
"draft" | "sent" | "accepted" | "rejected" | "expired" | "superseded" | yes | |
travelers |
integer | yes | |
marginPct |
number | yes | |
finalPriceOverride |
string | null | yes | |
validUntil |
string | null | yes | |
ctaUrl |
string | null | yes | |
templateId |
string | null | 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 | |
textOverrides |
object | yes | |
imageOverrides |
object | yes | |
sentAt |
string | null | yes | |
modifiedSinceSent |
boolean | yes | |
acceptedAt |
string | null | yes | |
acceptedVia |
"prospect_link" | "agent" | null | yes | |
acceptedPackageId |
string | null | yes | |
acceptedLineIds |
string[] | yes | |
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. |
acceptance.name |
string | null | yes | |
acceptance.email |
string | null | yes | |
acceptance.phone |
string | null | yes | |
acceptance.message |
string | null | yes | |
engagement |
object | yes | |
engagement.views |
integer | yes | |
engagement.uniqueSessions |
integer | yes | |
engagement.activeSeconds |
integer | yes | |
engagement.ctaClicks |
integer | yes | |
engagement.maxScrollPct |
number | yes | |
engagement.sectionsSeen |
integer | yes | |
engagement.interactions |
integer | yes | |
engagement.lastSeenAt |
string | null | yes | |
engagement.lastCtaAt |
string | null | yes | |
metadata |
object | yes | |
appUrl |
string | null | yes | |
publicUrl |
string | null | yes | |
createdAt |
string | yes | |
updatedAt |
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