Create a quote
POST/v1/trips/{tripId}/quotes
Creates a draft quote on a trip, as the quote editor's + button does: seeded from the trip's design unless seedFromDesign: false, priced for the trip's headcount at the agency's default margin unless marginPct says otherwise. Add or price lines with POST /quotes/{quoteId}/lines. With ?dryRun=true, returns a quote_preview of the seeded lines and writes nothing. Undo with POST /changes/{changeId}/revert while it is still an untouched draft.
Permissions: quotes:write · Kind: write · Cost: 1 unit · Dry run: ?dryRun=true · MCP tool create_quote
Undoable: the response carries Bymundi-Change-Id; revert it with POST /v1/changes/{changeId}/revert.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
tripId |
uuid | yes |
Body
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Defaults to Propuesta N, as the quote editor names them. max 200 chars |
|
mode |
"package" | "per_item" | package: one price, with up to 3 upgrade packages. per_item: a basket the customer picks lines from. Cannot change later. default "package" |
|
seedFromDesign |
boolean | Start with one line per journey and one hotel per stop of the trip's design, as the quote editor does. default true | |
templateId |
uuid | ||
marginPct |
number | Defaults to the agency's default margin. 0–99 | |
finalPriceOverride |
string | ||
validUntil |
string | YYYY-MM-DD | |
ctaUrl |
string | max 500 chars | |
metadata |
object |
Response 201
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