API v2 · Quotes

Hotel direct price quote API

Ask the hotel for its best price.

Send a hotel, a stay and what the guest wants. VistaLink emails the hotel from its own address, on behalf of a guest it does not name, and asks for a direct price that beats the best public one we know (or a price_to_beat you send). Most hotels reply within 6 hours: poll the quote or subscribe to quote.updated.

Each priced reply becomes one or more offers: total, room, board, cancellation terms, whether taxes are included, the hotel's own words as evidence, and beats_by against the price to beat. An offer with needs_review is checked by our team before you can accept it.

Accept an offer and we send the hotel one introduction email with your guest copied; the hotel then confirms the booking with the guest directly. hotel_commission_applies says whether the hotel pays a commission on the stay; half of it is yours.

What it costs and needs:

- $0.25 per quote the hotel answers with a price, charged once when the first priced offer arrives. It's reserved from your credit when you create the quote and released if the quote closes without one. Nothing when the hotel declines or doesn't reply.

- Accepting and cancelling are free. A quote cancelled before a priced offer costs nothing; one cancelled after keeps its charge.

- The quotes scope, the Build plan or higher (test keys excepted: the sandbox runs on Free too), and an Idempotency-Key on create. Asking again for the same hotel and stay while a quote is open returns that quote (X-Quote-Existing: true), with no second email and no second charge.

- Test keys never email anyone and cost nothing: a simulated hotel answers within seconds, and sandbox_outcome picks what it does.

Quotes

Request a quote from a hotel

POST/v2/quotes
Mode
Job · 202
Price
$0.25 / quote
SKU
quote.email.received
Auth
Scope quotes

Answers 202 with the Quote; VistaLink then emails the hotel, from its own address and on behalf of a guest it does not name, for a direct price on the stay. Most hotels reply within 6 hours: poll GET /v2/quotes/{quote_id} or subscribe to quote.updated. Costs $0.25 per quote the hotel answers with a price, charged once when the first priced offer arrives (reserved from your credit now, released when the quote closes without one); nothing when the hotel declines or does not reply. Needs the quotes scope, the Build plan or higher and an Idempotency-Key. Test keys never email anyone: a simulated hotel answers within seconds, as sandbox_outcome says.

Billing

Reserved when the quote is created and charged once, when the hotel's first priced offer arrives. Released when the quote closes without one (declined, no reply, cancelled, failed). Returning your open quote for the same hotel and stay is free.

Parameters

NameInTypeDescription
Idempotency-Key
REQUIRED
headerstringRequired. Any unique string. Same key and same body returns the original response; same key and a different body is a 409.

Request body

FieldTypeDescription
hotel_id
REQUIRED
string (uuid)The hotel to ask, as returned by resolve or search.
stay
REQUIRED
StayDates and rooms of a stay, up to 30 nights and 9 rooms.
wantsobject | nullRoom, board and cancellation preferences passed on to the hotel; omit to ask for the best rate.
wants.refundableboolean | nullTrue to ask for a rate with free cancellation, false for the cheapest non-refundable one.
wants.boardstring | nullMeals to ask for: none, breakfast, half (breakfast and dinner), full (all meals) or all_inclusive. One of none, breakfast, half, full, all_inclusive.
wants.room_typestring | nullRoom type to ask for in plain words, for example double or junior suite; up to 60 characters, no links, email addresses or angle brackets (it is quoted to the hotel).
price_to_beatstring | PriceToBeatauto (the default) quotes the best public price we know for the stay as the price to beat, when there is one; {total, currency} uses your own price instead. Default auto.
deadline_hoursintegerHours the hotel has to reply, 1 to 168; default 48. A quote with no priced reply by then closes as no_response and costs nothing. Default 48.
localestring | nullLanguage of the email to the hotel, as a language tag such as en or it; the hotel's own language when omitted.
sandbox_outcomestring | nullTest keys only: how the simulated hotel answers, offer (the default), declined or no_response. Rejected with 400 on a live key. One of offer, declined, no_response.

Example

Request
curl -X POST 'https://api.vistalink.com/v2/quotes' \
  -H "Authorization: Bearer $VISTALINK_API_KEY" \
  -H 'Idempotency-Key: 4f1c2b8e-booking-ada-rossi' \
  -H "Content-Type: application/json" \
  -d '{
  "hotel_id": "43d0be71-5f3c-4c8e-9a52-1d7e2b6f0a19",
  "stay": {
    "check_in": "2026-11-14",
    "check_out": "2026-11-16",
    "rooms": [
      {
        "adults": 2,
        "children_ages": []
      }
    ]
  },
  "wants": {
    "refundable": true,
    "board": "breakfast",
    "room_type": "double"
  },
  "price_to_beat": "auto",
  "deadline_hours": 48,
  "locale": "en"
}'
Response
{
  "anchor": {
    "currency": "EUR",
    "nightly": "481.00",
    "source": "third_party",
    "total": "962.00"
  },
  "created_at": "2026-10-07T09:30:00Z",
  "deadline_at": "2026-10-09T09:30:00Z",
  "hotel_id": "43d0be71-5f3c-4c8e-9a52-1d7e2b6f0a19",
  "next_step": "Email sent to the hotel; most hotels reply within 6 hours.",
  "offers": [],
  "quote_id": "qte_7c2e9a4b1d8f3e6a0c5b",
  "status": "waiting",
  "stay": {
    "check_in": "2026-11-14",
    "check_out": "2026-11-16",
    "rooms": [
      {
        "adults": 2,
        "children_ages": []
      }
    ]
  },
  "updated_at": "2026-10-07T09:30:04Z",
  "wants": {
    "board": "breakfast",
    "refundable": true,
    "room_type": "double"
  }
}

Response 200

Your open quote for the same hotel and stay, returned instead of a new one (header X-Quote-Existing: true); no new email, no charge.

FieldTypeDescription
request_idstring | nullId of the request that returned this quote; null in webhook payloads.
quote_id
REQUIRED
stringQuote id, qte_ plus 20 hex characters.
status
REQUIRED
stringWhere the quote is: queued, contacting (the email is going out), waiting (sent, awaiting the reply), offer_received (see offers), then completed (the deadline passed with offers), accepted (your guest was introduced), no_response, declined, cancelled or failed. One of queued, contacting, waiting, offer_received, completed, no_response, declined, cancelled, failed, accepted.
hotel_id
REQUIRED
string (uuid)The hotel asked.
stay
REQUIRED
objectThe stay asked about.
stay.check_in
REQUIRED
string (date)Arrival date, YYYY-MM-DD.
stay.check_out
REQUIRED
string (date)Departure date, YYYY-MM-DD.
stay.roomsOccupancy[]One entry per room.
stay.rooms[].adults
REQUIRED
integerAdults in the room.
stay.rooms[].children_agesinteger[]Age of each child in the room; empty for adults only.
wantsobject | nullThe preferences sent, or null.
wants.refundableboolean | nullFree cancellation asked for, or null.
wants.boardstring | nullMeals asked for, or null. One of none, breakfast, half, full, all_inclusive.
wants.room_typestring | nullRoom type asked for, or null.
anchorobject | nullThe price the hotel was asked to beat, or null when there was none.
anchor.total
REQUIRED
stringPrice for the whole stay and party, a decimal string in currency.
anchor.nightlystring | nulltotal divided by the number of nights.
anchor.currency
REQUIRED
stringISO 4217 currency of total.
anchor.source
REQUIRED
stringthird_party when it is the best public online price we know, partner when you sent it. One of third_party, partner.
deadline_at
REQUIRED
string (date-time)When the hotel's time to reply ends, RFC 3339 UTC.
next_step
REQUIRED
stringOne plain sentence on what happens next, suitable to show in your own interface.
created_at
REQUIRED
string (date-time)When the quote was requested, RFC 3339 UTC.
updated_at
REQUIRED
string (date-time)When the quote last changed, RFC 3339 UTC.
offersQuoteOffer[]Priced offers from the hotel's replies, oldest first.
offers[].offer_id
REQUIRED
stringOffer id, qof_ plus 20 hex characters; pass it to accept.
offers[].sourcestringAlways email: the hotel's emailed reply. Always email.
offers[].room_namestring | nullRoom the hotel offered, as it named it.
offers[].rate_namestring | nullShort summary of the terms, for example Breakfast included, free cancellation.
offers[].total
REQUIRED
stringPrice for the whole stay and party, a decimal string in currency.
offers[].currency
REQUIRED
stringISO 4217 currency the hotel quoted in; never converted.
offers[].nightly_avgstring | nulltotal divided by the number of nights.
offers[].includes_taxesboolean | nullTrue when the hotel said taxes are included, false when not, null when it did not say.
offers[].boardstring | nullMeals included, or null when the hotel did not say. One of none, breakfast, half, full, all_inclusive.
offers[].free_cancellationboolean | nullTrue when the offer can be canceled free of charge, false when not, null when unknown.
offers[].cancellation_deadlinestring (date-time) | nullLast moment to cancel free of charge, RFC 3339, when the hotel gave one.
offers[].valid_untilstring (date) | nullLast day the hotel holds this price, YYYY-MM-DD, when it said so.
offers[].offer_linkstring (uri) | nullA booking link the hotel included in its reply, or null.
offers[].beats_bystring | nullHow much cheaper than the anchor price this offer is, in currency; null without a comparable anchor.
offers[].evidencestring | nullThe sentence of the hotel's reply the price was read from, up to 280 characters.
offers[].confidencenumber | nullHow sure the reading of the reply is, 0 to 1.
offers[].needs_reviewbooleanTrue when the reply was ambiguous and the price is being checked: show it with care, it cannot be accepted yet (409 offer_needs_review) and it is not charged for. The hotel's first offer with needs_review false is the one the quote fee is charged on. Default false.
accepted_offer_idstring | nullThe offer you accepted; null until status is accepted.
hotel_commission_appliesboolean | nullAfter acceptance: true when the hotel pays a commission on the booking (half of it is yours), false when the offer was a special rate it pays no commission on; null before acceptance or when not yet known.
failure_reasonstring | nullWhy the quote ended without offers when status is failed or declined: no_contact_email, hotel_suppressed, hotel_daily_ceiling (try again tomorrow), invalid_stay, opted_out, unreachable or rejected; null otherwise. One of no_contact_email, hotel_suppressed, hotel_daily_ceiling, invalid_stay, invalid_request, opted_out, unreachable, rejected.
billingnullAlways null on responses: the quote fee is charged once, when the hotel's first priced offer arrives, and shows in usage.

Response 202

Successful Response

FieldTypeDescription
request_idstring | nullId of the request that returned this quote; null in webhook payloads.
quote_id
REQUIRED
stringQuote id, qte_ plus 20 hex characters.
status
REQUIRED
stringWhere the quote is: queued, contacting (the email is going out), waiting (sent, awaiting the reply), offer_received (see offers), then completed (the deadline passed with offers), accepted (your guest was introduced), no_response, declined, cancelled or failed. One of queued, contacting, waiting, offer_received, completed, no_response, declined, cancelled, failed, accepted.
hotel_id
REQUIRED
string (uuid)The hotel asked.
stay
REQUIRED
objectThe stay asked about.
stay.check_in
REQUIRED
string (date)Arrival date, YYYY-MM-DD.
stay.check_out
REQUIRED
string (date)Departure date, YYYY-MM-DD.
stay.roomsOccupancy[]One entry per room.
stay.rooms[].adults
REQUIRED
integerAdults in the room.
stay.rooms[].children_agesinteger[]Age of each child in the room; empty for adults only.
wantsobject | nullThe preferences sent, or null.
wants.refundableboolean | nullFree cancellation asked for, or null.
wants.boardstring | nullMeals asked for, or null. One of none, breakfast, half, full, all_inclusive.
wants.room_typestring | nullRoom type asked for, or null.
anchorobject | nullThe price the hotel was asked to beat, or null when there was none.
anchor.total
REQUIRED
stringPrice for the whole stay and party, a decimal string in currency.
anchor.nightlystring | nulltotal divided by the number of nights.
anchor.currency
REQUIRED
stringISO 4217 currency of total.
anchor.source
REQUIRED
stringthird_party when it is the best public online price we know, partner when you sent it. One of third_party, partner.
deadline_at
REQUIRED
string (date-time)When the hotel's time to reply ends, RFC 3339 UTC.
next_step
REQUIRED
stringOne plain sentence on what happens next, suitable to show in your own interface.
created_at
REQUIRED
string (date-time)When the quote was requested, RFC 3339 UTC.
updated_at
REQUIRED
string (date-time)When the quote last changed, RFC 3339 UTC.
offersQuoteOffer[]Priced offers from the hotel's replies, oldest first.
offers[].offer_id
REQUIRED
stringOffer id, qof_ plus 20 hex characters; pass it to accept.
offers[].sourcestringAlways email: the hotel's emailed reply. Always email.
offers[].room_namestring | nullRoom the hotel offered, as it named it.
offers[].rate_namestring | nullShort summary of the terms, for example Breakfast included, free cancellation.
offers[].total
REQUIRED
stringPrice for the whole stay and party, a decimal string in currency.
offers[].currency
REQUIRED
stringISO 4217 currency the hotel quoted in; never converted.
offers[].nightly_avgstring | nulltotal divided by the number of nights.
offers[].includes_taxesboolean | nullTrue when the hotel said taxes are included, false when not, null when it did not say.
offers[].boardstring | nullMeals included, or null when the hotel did not say. One of none, breakfast, half, full, all_inclusive.
offers[].free_cancellationboolean | nullTrue when the offer can be canceled free of charge, false when not, null when unknown.
offers[].cancellation_deadlinestring (date-time) | nullLast moment to cancel free of charge, RFC 3339, when the hotel gave one.
offers[].valid_untilstring (date) | nullLast day the hotel holds this price, YYYY-MM-DD, when it said so.
offers[].offer_linkstring (uri) | nullA booking link the hotel included in its reply, or null.
offers[].beats_bystring | nullHow much cheaper than the anchor price this offer is, in currency; null without a comparable anchor.
offers[].evidencestring | nullThe sentence of the hotel's reply the price was read from, up to 280 characters.
offers[].confidencenumber | nullHow sure the reading of the reply is, 0 to 1.
offers[].needs_reviewbooleanTrue when the reply was ambiguous and the price is being checked: show it with care, it cannot be accepted yet (409 offer_needs_review) and it is not charged for. The hotel's first offer with needs_review false is the one the quote fee is charged on. Default false.
accepted_offer_idstring | nullThe offer you accepted; null until status is accepted.
hotel_commission_appliesboolean | nullAfter acceptance: true when the hotel pays a commission on the booking (half of it is yours), false when the offer was a special rate it pays no commission on; null before acceptance or when not yet known.
failure_reasonstring | nullWhy the quote ended without offers when status is failed or declined: no_contact_email, hotel_suppressed, hotel_daily_ceiling (try again tomorrow), invalid_stay, opted_out, unreachable or rejected; null otherwise. One of no_contact_email, hotel_suppressed, hotel_daily_ceiling, invalid_stay, invalid_request, opted_out, unreachable, rejected.
billingnullAlways null on responses: the quote fee is charged once, when the hotel's first priced offer arrives, and shows in usage.

Errors

StatusCodeWhen
400bad_requestValidation errors carry details.fields[].
401unauthorizedNo key, or the key is unknown or revoked.
402insufficient_creditNot enough credit for the call. Checked before any work starts, so nothing was billed.
403forbiddenMissing scope, or details.reason tier_required: this needs the Build plan or higher.
429rate_limitedOver the per-minute limit for the tier. Wait Retry-After seconds.
503unavailableWe're temporarily unable to serve the call. Retry with backoff.
Quotes

Get a quote

GET/v2/quotes/{quote_id}
Mode
Sync · 200
Price
Free
Auth
Scope quotes

Synchronous and free; needs the quotes scope. The quote's status, the hotel's offers and next_step, one sentence you can show as is.

Parameters

NameInTypeDescription
quote_id
REQUIRED
pathstringThe quote_id returned by POST /v2/quotes.

Example

Request
curl 'https://api.vistalink.com/v2/quotes/qte_7c2e9a4b1d8f3e6a0c5b' \
  -H "Authorization: Bearer $VISTALINK_API_KEY"
Response
{
  "anchor": {
    "currency": "EUR",
    "nightly": "481.00",
    "source": "third_party",
    "total": "962.00"
  },
  "created_at": "2026-10-07T09:30:00Z",
  "deadline_at": "2026-10-09T09:30:00Z",
  "hotel_id": "43d0be71-5f3c-4c8e-9a52-1d7e2b6f0a19",
  "next_step": "Email sent to the hotel; most hotels reply within 6 hours.",
  "offers": [],
  "quote_id": "qte_7c2e9a4b1d8f3e6a0c5b",
  "status": "waiting",
  "stay": {
    "check_in": "2026-11-14",
    "check_out": "2026-11-16",
    "rooms": [
      {
        "adults": 2,
        "children_ages": []
      }
    ]
  },
  "updated_at": "2026-10-07T09:30:04Z",
  "wants": {
    "board": "breakfast",
    "refundable": true,
    "room_type": "double"
  }
}

Response 200

Successful Response

FieldTypeDescription
request_idstring | nullId of the request that returned this quote; null in webhook payloads.
quote_id
REQUIRED
stringQuote id, qte_ plus 20 hex characters.
status
REQUIRED
stringWhere the quote is: queued, contacting (the email is going out), waiting (sent, awaiting the reply), offer_received (see offers), then completed (the deadline passed with offers), accepted (your guest was introduced), no_response, declined, cancelled or failed. One of queued, contacting, waiting, offer_received, completed, no_response, declined, cancelled, failed, accepted.
hotel_id
REQUIRED
string (uuid)The hotel asked.
stay
REQUIRED
objectThe stay asked about.
stay.check_in
REQUIRED
string (date)Arrival date, YYYY-MM-DD.
stay.check_out
REQUIRED
string (date)Departure date, YYYY-MM-DD.
stay.roomsOccupancy[]One entry per room.
stay.rooms[].adults
REQUIRED
integerAdults in the room.
stay.rooms[].children_agesinteger[]Age of each child in the room; empty for adults only.
wantsobject | nullThe preferences sent, or null.
wants.refundableboolean | nullFree cancellation asked for, or null.
wants.boardstring | nullMeals asked for, or null. One of none, breakfast, half, full, all_inclusive.
wants.room_typestring | nullRoom type asked for, or null.
anchorobject | nullThe price the hotel was asked to beat, or null when there was none.
anchor.total
REQUIRED
stringPrice for the whole stay and party, a decimal string in currency.
anchor.nightlystring | nulltotal divided by the number of nights.
anchor.currency
REQUIRED
stringISO 4217 currency of total.
anchor.source
REQUIRED
stringthird_party when it is the best public online price we know, partner when you sent it. One of third_party, partner.
deadline_at
REQUIRED
string (date-time)When the hotel's time to reply ends, RFC 3339 UTC.
next_step
REQUIRED
stringOne plain sentence on what happens next, suitable to show in your own interface.
created_at
REQUIRED
string (date-time)When the quote was requested, RFC 3339 UTC.
updated_at
REQUIRED
string (date-time)When the quote last changed, RFC 3339 UTC.
offersQuoteOffer[]Priced offers from the hotel's replies, oldest first.
offers[].offer_id
REQUIRED
stringOffer id, qof_ plus 20 hex characters; pass it to accept.
offers[].sourcestringAlways email: the hotel's emailed reply. Always email.
offers[].room_namestring | nullRoom the hotel offered, as it named it.
offers[].rate_namestring | nullShort summary of the terms, for example Breakfast included, free cancellation.
offers[].total
REQUIRED
stringPrice for the whole stay and party, a decimal string in currency.
offers[].currency
REQUIRED
stringISO 4217 currency the hotel quoted in; never converted.
offers[].nightly_avgstring | nulltotal divided by the number of nights.
offers[].includes_taxesboolean | nullTrue when the hotel said taxes are included, false when not, null when it did not say.
offers[].boardstring | nullMeals included, or null when the hotel did not say. One of none, breakfast, half, full, all_inclusive.
offers[].free_cancellationboolean | nullTrue when the offer can be canceled free of charge, false when not, null when unknown.
offers[].cancellation_deadlinestring (date-time) | nullLast moment to cancel free of charge, RFC 3339, when the hotel gave one.
offers[].valid_untilstring (date) | nullLast day the hotel holds this price, YYYY-MM-DD, when it said so.
offers[].offer_linkstring (uri) | nullA booking link the hotel included in its reply, or null.
offers[].beats_bystring | nullHow much cheaper than the anchor price this offer is, in currency; null without a comparable anchor.
offers[].evidencestring | nullThe sentence of the hotel's reply the price was read from, up to 280 characters.
offers[].confidencenumber | nullHow sure the reading of the reply is, 0 to 1.
offers[].needs_reviewbooleanTrue when the reply was ambiguous and the price is being checked: show it with care, it cannot be accepted yet (409 offer_needs_review) and it is not charged for. The hotel's first offer with needs_review false is the one the quote fee is charged on. Default false.
accepted_offer_idstring | nullThe offer you accepted; null until status is accepted.
hotel_commission_appliesboolean | nullAfter acceptance: true when the hotel pays a commission on the booking (half of it is yours), false when the offer was a special rate it pays no commission on; null before acceptance or when not yet known.
failure_reasonstring | nullWhy the quote ended without offers when status is failed or declined: no_contact_email, hotel_suppressed, hotel_daily_ceiling (try again tomorrow), invalid_stay, opted_out, unreachable or rejected; null otherwise. One of no_contact_email, hotel_suppressed, hotel_daily_ceiling, invalid_stay, invalid_request, opted_out, unreachable, rejected.
billingnullAlways null on responses: the quote fee is charged once, when the hotel's first priced offer arrives, and shows in usage.

Errors

StatusCodeWhen
400bad_requestValidation errors carry details.fields[].
401unauthorizedNo key, or the key is unknown or revoked.
403forbiddenMissing scope.
404not_foundThe hotel, offer, booking, job or webhook doesn't exist for this key.
429rate_limitedOver the per-minute limit for the tier. Wait Retry-After seconds.
503unavailableWe're temporarily unable to serve the call. Retry with backoff.
Quotes

Cancel a quote

POST/v2/quotes/{quote_id}/cancel
Mode
Sync · 200
Price
Free
Auth
Scope quotes

Synchronous and free; needs the quotes scope. Stops further emails to the hotel while the quote is queued, contacting, waiting or offer_received; cancelling twice returns the cancelled quote. A quote cancelled before a priced offer costs nothing; one cancelled after keeps its charge. A closed quote answers 409 conflict.

Parameters

NameInTypeDescription
quote_id
REQUIRED
pathstringThe quote_id returned by POST /v2/quotes.

Example

Request
curl -X POST 'https://api.vistalink.com/v2/quotes/qte_7c2e9a4b1d8f3e6a0c5b/cancel' \
  -H "Authorization: Bearer $VISTALINK_API_KEY"
Response
{
  "anchor": {
    "currency": "EUR",
    "nightly": "481.00",
    "source": "third_party",
    "total": "962.00"
  },
  "created_at": "2026-10-07T09:30:00Z",
  "deadline_at": "2026-10-09T09:30:00Z",
  "hotel_id": "43d0be71-5f3c-4c8e-9a52-1d7e2b6f0a19",
  "next_step": "Email sent to the hotel; most hotels reply within 6 hours.",
  "offers": [],
  "quote_id": "qte_7c2e9a4b1d8f3e6a0c5b",
  "status": "waiting",
  "stay": {
    "check_in": "2026-11-14",
    "check_out": "2026-11-16",
    "rooms": [
      {
        "adults": 2,
        "children_ages": []
      }
    ]
  },
  "updated_at": "2026-10-07T09:30:04Z",
  "wants": {
    "board": "breakfast",
    "refundable": true,
    "room_type": "double"
  }
}

Response 200

Successful Response

FieldTypeDescription
request_idstring | nullId of the request that returned this quote; null in webhook payloads.
quote_id
REQUIRED
stringQuote id, qte_ plus 20 hex characters.
status
REQUIRED
stringWhere the quote is: queued, contacting (the email is going out), waiting (sent, awaiting the reply), offer_received (see offers), then completed (the deadline passed with offers), accepted (your guest was introduced), no_response, declined, cancelled or failed. One of queued, contacting, waiting, offer_received, completed, no_response, declined, cancelled, failed, accepted.
hotel_id
REQUIRED
string (uuid)The hotel asked.
stay
REQUIRED
objectThe stay asked about.
stay.check_in
REQUIRED
string (date)Arrival date, YYYY-MM-DD.
stay.check_out
REQUIRED
string (date)Departure date, YYYY-MM-DD.
stay.roomsOccupancy[]One entry per room.
stay.rooms[].adults
REQUIRED
integerAdults in the room.
stay.rooms[].children_agesinteger[]Age of each child in the room; empty for adults only.
wantsobject | nullThe preferences sent, or null.
wants.refundableboolean | nullFree cancellation asked for, or null.
wants.boardstring | nullMeals asked for, or null. One of none, breakfast, half, full, all_inclusive.
wants.room_typestring | nullRoom type asked for, or null.
anchorobject | nullThe price the hotel was asked to beat, or null when there was none.
anchor.total
REQUIRED
stringPrice for the whole stay and party, a decimal string in currency.
anchor.nightlystring | nulltotal divided by the number of nights.
anchor.currency
REQUIRED
stringISO 4217 currency of total.
anchor.source
REQUIRED
stringthird_party when it is the best public online price we know, partner when you sent it. One of third_party, partner.
deadline_at
REQUIRED
string (date-time)When the hotel's time to reply ends, RFC 3339 UTC.
next_step
REQUIRED
stringOne plain sentence on what happens next, suitable to show in your own interface.
created_at
REQUIRED
string (date-time)When the quote was requested, RFC 3339 UTC.
updated_at
REQUIRED
string (date-time)When the quote last changed, RFC 3339 UTC.
offersQuoteOffer[]Priced offers from the hotel's replies, oldest first.
offers[].offer_id
REQUIRED
stringOffer id, qof_ plus 20 hex characters; pass it to accept.
offers[].sourcestringAlways email: the hotel's emailed reply. Always email.
offers[].room_namestring | nullRoom the hotel offered, as it named it.
offers[].rate_namestring | nullShort summary of the terms, for example Breakfast included, free cancellation.
offers[].total
REQUIRED
stringPrice for the whole stay and party, a decimal string in currency.
offers[].currency
REQUIRED
stringISO 4217 currency the hotel quoted in; never converted.
offers[].nightly_avgstring | nulltotal divided by the number of nights.
offers[].includes_taxesboolean | nullTrue when the hotel said taxes are included, false when not, null when it did not say.
offers[].boardstring | nullMeals included, or null when the hotel did not say. One of none, breakfast, half, full, all_inclusive.
offers[].free_cancellationboolean | nullTrue when the offer can be canceled free of charge, false when not, null when unknown.
offers[].cancellation_deadlinestring (date-time) | nullLast moment to cancel free of charge, RFC 3339, when the hotel gave one.
offers[].valid_untilstring (date) | nullLast day the hotel holds this price, YYYY-MM-DD, when it said so.
offers[].offer_linkstring (uri) | nullA booking link the hotel included in its reply, or null.
offers[].beats_bystring | nullHow much cheaper than the anchor price this offer is, in currency; null without a comparable anchor.
offers[].evidencestring | nullThe sentence of the hotel's reply the price was read from, up to 280 characters.
offers[].confidencenumber | nullHow sure the reading of the reply is, 0 to 1.
offers[].needs_reviewbooleanTrue when the reply was ambiguous and the price is being checked: show it with care, it cannot be accepted yet (409 offer_needs_review) and it is not charged for. The hotel's first offer with needs_review false is the one the quote fee is charged on. Default false.
accepted_offer_idstring | nullThe offer you accepted; null until status is accepted.
hotel_commission_appliesboolean | nullAfter acceptance: true when the hotel pays a commission on the booking (half of it is yours), false when the offer was a special rate it pays no commission on; null before acceptance or when not yet known.
failure_reasonstring | nullWhy the quote ended without offers when status is failed or declined: no_contact_email, hotel_suppressed, hotel_daily_ceiling (try again tomorrow), invalid_stay, opted_out, unreachable or rejected; null otherwise. One of no_contact_email, hotel_suppressed, hotel_daily_ceiling, invalid_stay, invalid_request, opted_out, unreachable, rejected.
billingnullAlways null on responses: the quote fee is charged once, when the hotel's first priced offer arrives, and shows in usage.

Errors

StatusCodeWhen
400bad_requestValidation errors carry details.fields[].
401unauthorizedNo key, or the key is unknown or revoked.
403forbiddenMissing scope.
404not_foundThe hotel, offer, booking, job or webhook doesn't exist for this key.
409conflictThe quote is already closed.
429rate_limitedOver the per-minute limit for the tier. Wait Retry-After seconds.
503unavailableWe're temporarily unable to serve the call. Retry with backoff.
Quotes

Accept a quote offer

POST/v2/quotes/{quote_id}/offers/{offer_id}/accept
Mode
Sync · 200
Price
Free
Auth
Scope quotes

Free; needs the quotes scope and the Build plan or higher. Sends the hotel one introduction email with your guest copied, on the same thread as the quote, and answers with the Quote in status accepted; the hotel then confirms the booking with the guest directly. Allowed while the quote is offer_received or completed. Repeating the same accept returns the accepted quote. hotel_commission_applies says whether the hotel pays a commission on the stay (half of it is yours).

Parameters

NameInTypeDescription
quote_id
REQUIRED
pathstringThe quote_id returned by POST /v2/quotes.
offer_id
REQUIRED
pathstringThe offer_id of one of the quote's offers.
Idempotency-KeyheaderstringAny unique string. Same key and same body returns the original response; same key and a different body is a 409.

Request body

FieldTypeDescription
guest
REQUIRED
objectThe guest to introduce to the hotel.
guest.first_name
REQUIRED
stringGuest's first name, up to 80 characters.
guest.last_name
REQUIRED
stringGuest's last name, up to 80 characters.
guest.email
REQUIRED
string (email)Guest's email address; the guest is copied on the introduction email and the hotel replies to it.

Example

Request
curl -X POST 'https://api.vistalink.com/v2/quotes/qte_7c2e9a4b1d8f3e6a0c5b/offers/qof_4a9d2c7e1b8f5a3d6e0c/accept' \
  -H "Authorization: Bearer $VISTALINK_API_KEY" \
  -H 'Idempotency-Key: 4f1c2b8e-booking-ada-rossi' \
  -H "Content-Type: application/json" \
  -d '{
  "guest": {
    "first_name": "Ada",
    "last_name": "Rossi",
    "email": "ada@example.com"
  }
}'
Response
{
  "anchor": {
    "currency": "EUR",
    "nightly": "481.00",
    "source": "third_party",
    "total": "962.00"
  },
  "created_at": "2026-10-07T09:30:00Z",
  "deadline_at": "2026-10-09T09:30:00Z",
  "hotel_id": "43d0be71-5f3c-4c8e-9a52-1d7e2b6f0a19",
  "next_step": "Email sent to the hotel; most hotels reply within 6 hours.",
  "offers": [],
  "quote_id": "qte_7c2e9a4b1d8f3e6a0c5b",
  "status": "waiting",
  "stay": {
    "check_in": "2026-11-14",
    "check_out": "2026-11-16",
    "rooms": [
      {
        "adults": 2,
        "children_ages": []
      }
    ]
  },
  "updated_at": "2026-10-07T09:30:04Z",
  "wants": {
    "board": "breakfast",
    "refundable": true,
    "room_type": "double"
  }
}

Response 200

Successful Response

FieldTypeDescription
request_idstring | nullId of the request that returned this quote; null in webhook payloads.
quote_id
REQUIRED
stringQuote id, qte_ plus 20 hex characters.
status
REQUIRED
stringWhere the quote is: queued, contacting (the email is going out), waiting (sent, awaiting the reply), offer_received (see offers), then completed (the deadline passed with offers), accepted (your guest was introduced), no_response, declined, cancelled or failed. One of queued, contacting, waiting, offer_received, completed, no_response, declined, cancelled, failed, accepted.
hotel_id
REQUIRED
string (uuid)The hotel asked.
stay
REQUIRED
objectThe stay asked about.
stay.check_in
REQUIRED
string (date)Arrival date, YYYY-MM-DD.
stay.check_out
REQUIRED
string (date)Departure date, YYYY-MM-DD.
stay.roomsOccupancy[]One entry per room.
stay.rooms[].adults
REQUIRED
integerAdults in the room.
stay.rooms[].children_agesinteger[]Age of each child in the room; empty for adults only.
wantsobject | nullThe preferences sent, or null.
wants.refundableboolean | nullFree cancellation asked for, or null.
wants.boardstring | nullMeals asked for, or null. One of none, breakfast, half, full, all_inclusive.
wants.room_typestring | nullRoom type asked for, or null.
anchorobject | nullThe price the hotel was asked to beat, or null when there was none.
anchor.total
REQUIRED
stringPrice for the whole stay and party, a decimal string in currency.
anchor.nightlystring | nulltotal divided by the number of nights.
anchor.currency
REQUIRED
stringISO 4217 currency of total.
anchor.source
REQUIRED
stringthird_party when it is the best public online price we know, partner when you sent it. One of third_party, partner.
deadline_at
REQUIRED
string (date-time)When the hotel's time to reply ends, RFC 3339 UTC.
next_step
REQUIRED
stringOne plain sentence on what happens next, suitable to show in your own interface.
created_at
REQUIRED
string (date-time)When the quote was requested, RFC 3339 UTC.
updated_at
REQUIRED
string (date-time)When the quote last changed, RFC 3339 UTC.
offersQuoteOffer[]Priced offers from the hotel's replies, oldest first.
offers[].offer_id
REQUIRED
stringOffer id, qof_ plus 20 hex characters; pass it to accept.
offers[].sourcestringAlways email: the hotel's emailed reply. Always email.
offers[].room_namestring | nullRoom the hotel offered, as it named it.
offers[].rate_namestring | nullShort summary of the terms, for example Breakfast included, free cancellation.
offers[].total
REQUIRED
stringPrice for the whole stay and party, a decimal string in currency.
offers[].currency
REQUIRED
stringISO 4217 currency the hotel quoted in; never converted.
offers[].nightly_avgstring | nulltotal divided by the number of nights.
offers[].includes_taxesboolean | nullTrue when the hotel said taxes are included, false when not, null when it did not say.
offers[].boardstring | nullMeals included, or null when the hotel did not say. One of none, breakfast, half, full, all_inclusive.
offers[].free_cancellationboolean | nullTrue when the offer can be canceled free of charge, false when not, null when unknown.
offers[].cancellation_deadlinestring (date-time) | nullLast moment to cancel free of charge, RFC 3339, when the hotel gave one.
offers[].valid_untilstring (date) | nullLast day the hotel holds this price, YYYY-MM-DD, when it said so.
offers[].offer_linkstring (uri) | nullA booking link the hotel included in its reply, or null.
offers[].beats_bystring | nullHow much cheaper than the anchor price this offer is, in currency; null without a comparable anchor.
offers[].evidencestring | nullThe sentence of the hotel's reply the price was read from, up to 280 characters.
offers[].confidencenumber | nullHow sure the reading of the reply is, 0 to 1.
offers[].needs_reviewbooleanTrue when the reply was ambiguous and the price is being checked: show it with care, it cannot be accepted yet (409 offer_needs_review) and it is not charged for. The hotel's first offer with needs_review false is the one the quote fee is charged on. Default false.
accepted_offer_idstring | nullThe offer you accepted; null until status is accepted.
hotel_commission_appliesboolean | nullAfter acceptance: true when the hotel pays a commission on the booking (half of it is yours), false when the offer was a special rate it pays no commission on; null before acceptance or when not yet known.
failure_reasonstring | nullWhy the quote ended without offers when status is failed or declined: no_contact_email, hotel_suppressed, hotel_daily_ceiling (try again tomorrow), invalid_stay, opted_out, unreachable or rejected; null otherwise. One of no_contact_email, hotel_suppressed, hotel_daily_ceiling, invalid_stay, invalid_request, opted_out, unreachable, rejected.
billingnullAlways null on responses: the quote fee is charged once, when the hotel's first priced offer arrives, and shows in usage.

Errors

StatusCodeWhen
400bad_requestValidation errors carry details.fields[].
401unauthorizedNo key, or the key is unknown or revoked.
403forbiddenMissing scope, or details.reason tier_required: this needs the Build plan or higher.
404not_foundQuote or offer.
409conflictThe quote is closed, or another offer was accepted.
429rate_limitedOver the per-minute limit for the tier. Wait Retry-After seconds.
503unavailableWe're temporarily unable to serve the call. Retry with backoff.
Objects

Shared objects.

Objects that appear in more than one place. Billing is on the Billing page and ErrorBody under Errors & limits.

Stay

Dates and rooms of a stay, up to 30 nights and 9 rooms.

FieldTypeDescription
check_in
REQUIRED
string (date)Arrival date, YYYY-MM-DD.
check_out
REQUIRED
string (date)Departure date, YYYY-MM-DD, after check_in and at most 30 nights later.
roomsRoomOccupancy[]One entry per room, 1 to 9; defaults to one room for two adults. At most 9.
rooms[].adultsintegerAdults in this room, 1 to 16. Default 2.
rooms[].children_agesinteger[]Age of each child in this room, 0 to 17, one entry per child; empty for adults only. At most 8.