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.
/v2/quotes/v2/quotes/{quote_id}/v2/quotes/{quote_id}/cancel/v2/quotes/{quote_id}/offers/{offer_id}/acceptRequest a quote from a hotel
/v2/quotesAnswers 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.
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
| Name | In | Type | Description |
|---|---|---|---|
Idempotency-KeyREQUIRED | header | string | Required. Any unique string. Same key and same body returns the original response; same key and a different body is a 409. |
Request body
| Field | Type | Description |
|---|---|---|
hotel_idREQUIRED | string (uuid) | The hotel to ask, as returned by resolve or search. |
stayREQUIRED | Stay | Dates and rooms of a stay, up to 30 nights and 9 rooms. |
wants | object | null | Room, board and cancellation preferences passed on to the hotel; omit to ask for the best rate. |
wants.refundable | boolean | null | True to ask for a rate with free cancellation, false for the cheapest non-refundable one. |
wants.board | string | null | Meals 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_type | string | null | Room 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_beat | string | PriceToBeat | auto (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_hours | integer | Hours 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. |
locale | string | null | Language of the email to the hotel, as a language tag such as en or it; the hotel's own language when omitted. |
sandbox_outcome | string | null | Test 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
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"
}'vistalink quotes create \
--idempotency-key 4f1c2b8e-booking-ada-rossi \
--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 enconst res = await fetch("https://api.vistalink.com/v2/quotes", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.VISTALINK_API_KEY}`,
"Idempotency-Key": "4f1c2b8e-booking-ada-rossi",
"Content-Type": "application/json",
},
body: JSON.stringify({
"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"
}),
});
if (!res.ok) throw new Error((await res.json()).error.message);
const data = await res.json();import os
import requests
resp = requests.post(
"https://api.vistalink.com/v2/quotes",
headers={"Authorization": f"Bearer {os.environ['VISTALINK_API_KEY']}", "Idempotency-Key": "4f1c2b8e-booking-ada-rossi"},
json={
"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",
},
)
resp.raise_for_status()
data = resp.json(){
"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"
}
}{
"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": "The hotel replied with an offer. Accept it to introduce your guest.",
"offers": [
{
"beats_by": "82.00",
"board": "breakfast",
"cancellation_deadline": "2026-11-12T23:59:00Z",
"confidence": 0.93,
"currency": "EUR",
"evidence": "We can offer the Superior Double with breakfast for EUR 880 for both nights, free cancellation until 12 November.",
"free_cancellation": true,
"includes_taxes": true,
"needs_review": false,
"nightly_avg": "440.00",
"offer_id": "qof_4a9d2c7e1b8f5a3d6e0c",
"rate_name": "Breakfast included, free cancellation",
"room_name": "Superior Double",
"source": "email",
"total": "880.00",
"valid_until": "2026-10-10"
}
],
"quote_id": "qte_7c2e9a4b1d8f3e6a0c5b",
"status": "offer_received",
"stay": {
"check_in": "2026-11-14",
"check_out": "2026-11-16",
"rooms": [
{
"adults": 2,
"children_ages": []
}
]
},
"updated_at": "2026-10-07T14:52:10Z",
"wants": {
"board": "breakfast",
"refundable": true,
"room_type": "double"
}
}{
"accepted_offer_id": "qof_4a9d2c7e1b8f5a3d6e0c",
"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_commission_applies": false,
"hotel_id": "43d0be71-5f3c-4c8e-9a52-1d7e2b6f0a19",
"next_step": "Your guest has been introduced to the hotel by email; the hotel replies to them directly to confirm the booking.",
"offers": [
{
"beats_by": "82.00",
"board": "breakfast",
"cancellation_deadline": "2026-11-12T23:59:00Z",
"confidence": 0.93,
"currency": "EUR",
"evidence": "We can offer the Superior Double with breakfast for EUR 880 for both nights, free cancellation until 12 November.",
"free_cancellation": true,
"includes_taxes": true,
"needs_review": false,
"nightly_avg": "440.00",
"offer_id": "qof_4a9d2c7e1b8f5a3d6e0c",
"rate_name": "Breakfast included, free cancellation",
"room_name": "Superior Double",
"source": "email",
"total": "880.00",
"valid_until": "2026-10-10"
}
],
"quote_id": "qte_7c2e9a4b1d8f3e6a0c5b",
"status": "accepted",
"stay": {
"check_in": "2026-11-14",
"check_out": "2026-11-16",
"rooms": [
{
"adults": 2,
"children_ages": []
}
]
},
"updated_at": "2026-10-07T15:10:31Z",
"wants": {
"board": "breakfast",
"refundable": true,
"room_type": "double"
}
}{
"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"
}
}{
"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": "The hotel replied with an offer. Accept it to introduce your guest.",
"offers": [
{
"beats_by": "82.00",
"board": "breakfast",
"cancellation_deadline": "2026-11-12T23:59:00Z",
"confidence": 0.93,
"currency": "EUR",
"evidence": "We can offer the Superior Double with breakfast for EUR 880 for both nights, free cancellation until 12 November.",
"free_cancellation": true,
"includes_taxes": true,
"needs_review": false,
"nightly_avg": "440.00",
"offer_id": "qof_4a9d2c7e1b8f5a3d6e0c",
"rate_name": "Breakfast included, free cancellation",
"room_name": "Superior Double",
"source": "email",
"total": "880.00",
"valid_until": "2026-10-10"
}
],
"quote_id": "qte_7c2e9a4b1d8f3e6a0c5b",
"status": "offer_received",
"stay": {
"check_in": "2026-11-14",
"check_out": "2026-11-16",
"rooms": [
{
"adults": 2,
"children_ages": []
}
]
},
"updated_at": "2026-10-07T14:52:10Z",
"wants": {
"board": "breakfast",
"refundable": true,
"room_type": "double"
}
}{
"accepted_offer_id": "qof_4a9d2c7e1b8f5a3d6e0c",
"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_commission_applies": false,
"hotel_id": "43d0be71-5f3c-4c8e-9a52-1d7e2b6f0a19",
"next_step": "Your guest has been introduced to the hotel by email; the hotel replies to them directly to confirm the booking.",
"offers": [
{
"beats_by": "82.00",
"board": "breakfast",
"cancellation_deadline": "2026-11-12T23:59:00Z",
"confidence": 0.93,
"currency": "EUR",
"evidence": "We can offer the Superior Double with breakfast for EUR 880 for both nights, free cancellation until 12 November.",
"free_cancellation": true,
"includes_taxes": true,
"needs_review": false,
"nightly_avg": "440.00",
"offer_id": "qof_4a9d2c7e1b8f5a3d6e0c",
"rate_name": "Breakfast included, free cancellation",
"room_name": "Superior Double",
"source": "email",
"total": "880.00",
"valid_until": "2026-10-10"
}
],
"quote_id": "qte_7c2e9a4b1d8f3e6a0c5b",
"status": "accepted",
"stay": {
"check_in": "2026-11-14",
"check_out": "2026-11-16",
"rooms": [
{
"adults": 2,
"children_ages": []
}
]
},
"updated_at": "2026-10-07T15:10:31Z",
"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.
| Field | Type | Description |
|---|---|---|
request_id | string | null | Id of the request that returned this quote; null in webhook payloads. |
quote_idREQUIRED | string | Quote id, qte_ plus 20 hex characters. |
statusREQUIRED | string | Where 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_idREQUIRED | string (uuid) | The hotel asked. |
stayREQUIRED | object | The stay asked about. |
stay.check_inREQUIRED | string (date) | Arrival date, YYYY-MM-DD. |
stay.check_outREQUIRED | string (date) | Departure date, YYYY-MM-DD. |
stay.rooms | Occupancy[] | One entry per room. |
stay.rooms[].adultsREQUIRED | integer | Adults in the room. |
stay.rooms[].children_ages | integer[] | Age of each child in the room; empty for adults only. |
wants | object | null | The preferences sent, or null. |
wants.refundable | boolean | null | Free cancellation asked for, or null. |
wants.board | string | null | Meals asked for, or null. One of none, breakfast, half, full, all_inclusive. |
wants.room_type | string | null | Room type asked for, or null. |
anchor | object | null | The price the hotel was asked to beat, or null when there was none. |
anchor.totalREQUIRED | string | Price for the whole stay and party, a decimal string in currency. |
anchor.nightly | string | null | total divided by the number of nights. |
anchor.currencyREQUIRED | string | ISO 4217 currency of total. |
anchor.sourceREQUIRED | string | third_party when it is the best public online price we know, partner when you sent it. One of third_party, partner. |
deadline_atREQUIRED | string (date-time) | When the hotel's time to reply ends, RFC 3339 UTC. |
next_stepREQUIRED | string | One plain sentence on what happens next, suitable to show in your own interface. |
created_atREQUIRED | string (date-time) | When the quote was requested, RFC 3339 UTC. |
updated_atREQUIRED | string (date-time) | When the quote last changed, RFC 3339 UTC. |
offers | QuoteOffer[] | Priced offers from the hotel's replies, oldest first. |
offers[].offer_idREQUIRED | string | Offer id, qof_ plus 20 hex characters; pass it to accept. |
offers[].source | string | Always email: the hotel's emailed reply. Always email. |
offers[].room_name | string | null | Room the hotel offered, as it named it. |
offers[].rate_name | string | null | Short summary of the terms, for example Breakfast included, free cancellation. |
offers[].totalREQUIRED | string | Price for the whole stay and party, a decimal string in currency. |
offers[].currencyREQUIRED | string | ISO 4217 currency the hotel quoted in; never converted. |
offers[].nightly_avg | string | null | total divided by the number of nights. |
offers[].includes_taxes | boolean | null | True when the hotel said taxes are included, false when not, null when it did not say. |
offers[].board | string | null | Meals included, or null when the hotel did not say. One of none, breakfast, half, full, all_inclusive. |
offers[].free_cancellation | boolean | null | True when the offer can be canceled free of charge, false when not, null when unknown. |
offers[].cancellation_deadline | string (date-time) | null | Last moment to cancel free of charge, RFC 3339, when the hotel gave one. |
offers[].valid_until | string (date) | null | Last day the hotel holds this price, YYYY-MM-DD, when it said so. |
offers[].offer_link | string (uri) | null | A booking link the hotel included in its reply, or null. |
offers[].beats_by | string | null | How much cheaper than the anchor price this offer is, in currency; null without a comparable anchor. |
offers[].evidence | string | null | The sentence of the hotel's reply the price was read from, up to 280 characters. |
offers[].confidence | number | null | How sure the reading of the reply is, 0 to 1. |
offers[].needs_review | boolean | True 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_id | string | null | The offer you accepted; null until status is accepted. |
hotel_commission_applies | boolean | null | After 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_reason | string | null | Why 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. |
billing | null | Always 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
| Field | Type | Description |
|---|---|---|
request_id | string | null | Id of the request that returned this quote; null in webhook payloads. |
quote_idREQUIRED | string | Quote id, qte_ plus 20 hex characters. |
statusREQUIRED | string | Where 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_idREQUIRED | string (uuid) | The hotel asked. |
stayREQUIRED | object | The stay asked about. |
stay.check_inREQUIRED | string (date) | Arrival date, YYYY-MM-DD. |
stay.check_outREQUIRED | string (date) | Departure date, YYYY-MM-DD. |
stay.rooms | Occupancy[] | One entry per room. |
stay.rooms[].adultsREQUIRED | integer | Adults in the room. |
stay.rooms[].children_ages | integer[] | Age of each child in the room; empty for adults only. |
wants | object | null | The preferences sent, or null. |
wants.refundable | boolean | null | Free cancellation asked for, or null. |
wants.board | string | null | Meals asked for, or null. One of none, breakfast, half, full, all_inclusive. |
wants.room_type | string | null | Room type asked for, or null. |
anchor | object | null | The price the hotel was asked to beat, or null when there was none. |
anchor.totalREQUIRED | string | Price for the whole stay and party, a decimal string in currency. |
anchor.nightly | string | null | total divided by the number of nights. |
anchor.currencyREQUIRED | string | ISO 4217 currency of total. |
anchor.sourceREQUIRED | string | third_party when it is the best public online price we know, partner when you sent it. One of third_party, partner. |
deadline_atREQUIRED | string (date-time) | When the hotel's time to reply ends, RFC 3339 UTC. |
next_stepREQUIRED | string | One plain sentence on what happens next, suitable to show in your own interface. |
created_atREQUIRED | string (date-time) | When the quote was requested, RFC 3339 UTC. |
updated_atREQUIRED | string (date-time) | When the quote last changed, RFC 3339 UTC. |
offers | QuoteOffer[] | Priced offers from the hotel's replies, oldest first. |
offers[].offer_idREQUIRED | string | Offer id, qof_ plus 20 hex characters; pass it to accept. |
offers[].source | string | Always email: the hotel's emailed reply. Always email. |
offers[].room_name | string | null | Room the hotel offered, as it named it. |
offers[].rate_name | string | null | Short summary of the terms, for example Breakfast included, free cancellation. |
offers[].totalREQUIRED | string | Price for the whole stay and party, a decimal string in currency. |
offers[].currencyREQUIRED | string | ISO 4217 currency the hotel quoted in; never converted. |
offers[].nightly_avg | string | null | total divided by the number of nights. |
offers[].includes_taxes | boolean | null | True when the hotel said taxes are included, false when not, null when it did not say. |
offers[].board | string | null | Meals included, or null when the hotel did not say. One of none, breakfast, half, full, all_inclusive. |
offers[].free_cancellation | boolean | null | True when the offer can be canceled free of charge, false when not, null when unknown. |
offers[].cancellation_deadline | string (date-time) | null | Last moment to cancel free of charge, RFC 3339, when the hotel gave one. |
offers[].valid_until | string (date) | null | Last day the hotel holds this price, YYYY-MM-DD, when it said so. |
offers[].offer_link | string (uri) | null | A booking link the hotel included in its reply, or null. |
offers[].beats_by | string | null | How much cheaper than the anchor price this offer is, in currency; null without a comparable anchor. |
offers[].evidence | string | null | The sentence of the hotel's reply the price was read from, up to 280 characters. |
offers[].confidence | number | null | How sure the reading of the reply is, 0 to 1. |
offers[].needs_review | boolean | True 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_id | string | null | The offer you accepted; null until status is accepted. |
hotel_commission_applies | boolean | null | After 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_reason | string | null | Why 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. |
billing | null | Always null on responses: the quote fee is charged once, when the hotel's first priced offer arrives, and shows in usage. |
Errors
| Status | Code | When |
|---|---|---|
| 400 | bad_request | Validation errors carry details.fields[]. |
| 401 | unauthorized | No key, or the key is unknown or revoked. |
| 402 | insufficient_credit | Not enough credit for the call. Checked before any work starts, so nothing was billed. |
| 403 | forbidden | Missing scope, or details.reason tier_required: this needs the Build plan or higher. |
| 429 | rate_limited | Over the per-minute limit for the tier. Wait Retry-After seconds. |
| 503 | unavailable | We're temporarily unable to serve the call. Retry with backoff. |
Get a quote
/v2/quotes/{quote_id}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
| Name | In | Type | Description |
|---|---|---|---|
quote_idREQUIRED | path | string | The quote_id returned by POST /v2/quotes. |
Example
curl 'https://api.vistalink.com/v2/quotes/qte_7c2e9a4b1d8f3e6a0c5b' \
-H "Authorization: Bearer $VISTALINK_API_KEY"vistalink quotes get qte_7c2e9a4b1d8f3e6a0c5bconst res = await fetch("https://api.vistalink.com/v2/quotes/qte_7c2e9a4b1d8f3e6a0c5b", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.VISTALINK_API_KEY}`,
},
});
if (!res.ok) throw new Error((await res.json()).error.message);
const data = await res.json();import os
import requests
resp = requests.get(
"https://api.vistalink.com/v2/quotes/qte_7c2e9a4b1d8f3e6a0c5b",
headers={"Authorization": f"Bearer {os.environ['VISTALINK_API_KEY']}"},
)
resp.raise_for_status()
data = resp.json(){
"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"
}
}{
"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": "The hotel replied with an offer. Accept it to introduce your guest.",
"offers": [
{
"beats_by": "82.00",
"board": "breakfast",
"cancellation_deadline": "2026-11-12T23:59:00Z",
"confidence": 0.93,
"currency": "EUR",
"evidence": "We can offer the Superior Double with breakfast for EUR 880 for both nights, free cancellation until 12 November.",
"free_cancellation": true,
"includes_taxes": true,
"needs_review": false,
"nightly_avg": "440.00",
"offer_id": "qof_4a9d2c7e1b8f5a3d6e0c",
"rate_name": "Breakfast included, free cancellation",
"room_name": "Superior Double",
"source": "email",
"total": "880.00",
"valid_until": "2026-10-10"
}
],
"quote_id": "qte_7c2e9a4b1d8f3e6a0c5b",
"status": "offer_received",
"stay": {
"check_in": "2026-11-14",
"check_out": "2026-11-16",
"rooms": [
{
"adults": 2,
"children_ages": []
}
]
},
"updated_at": "2026-10-07T14:52:10Z",
"wants": {
"board": "breakfast",
"refundable": true,
"room_type": "double"
}
}{
"accepted_offer_id": "qof_4a9d2c7e1b8f5a3d6e0c",
"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_commission_applies": false,
"hotel_id": "43d0be71-5f3c-4c8e-9a52-1d7e2b6f0a19",
"next_step": "Your guest has been introduced to the hotel by email; the hotel replies to them directly to confirm the booking.",
"offers": [
{
"beats_by": "82.00",
"board": "breakfast",
"cancellation_deadline": "2026-11-12T23:59:00Z",
"confidence": 0.93,
"currency": "EUR",
"evidence": "We can offer the Superior Double with breakfast for EUR 880 for both nights, free cancellation until 12 November.",
"free_cancellation": true,
"includes_taxes": true,
"needs_review": false,
"nightly_avg": "440.00",
"offer_id": "qof_4a9d2c7e1b8f5a3d6e0c",
"rate_name": "Breakfast included, free cancellation",
"room_name": "Superior Double",
"source": "email",
"total": "880.00",
"valid_until": "2026-10-10"
}
],
"quote_id": "qte_7c2e9a4b1d8f3e6a0c5b",
"status": "accepted",
"stay": {
"check_in": "2026-11-14",
"check_out": "2026-11-16",
"rooms": [
{
"adults": 2,
"children_ages": []
}
]
},
"updated_at": "2026-10-07T15:10:31Z",
"wants": {
"board": "breakfast",
"refundable": true,
"room_type": "double"
}
}Response 200
Successful Response
| Field | Type | Description |
|---|---|---|
request_id | string | null | Id of the request that returned this quote; null in webhook payloads. |
quote_idREQUIRED | string | Quote id, qte_ plus 20 hex characters. |
statusREQUIRED | string | Where 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_idREQUIRED | string (uuid) | The hotel asked. |
stayREQUIRED | object | The stay asked about. |
stay.check_inREQUIRED | string (date) | Arrival date, YYYY-MM-DD. |
stay.check_outREQUIRED | string (date) | Departure date, YYYY-MM-DD. |
stay.rooms | Occupancy[] | One entry per room. |
stay.rooms[].adultsREQUIRED | integer | Adults in the room. |
stay.rooms[].children_ages | integer[] | Age of each child in the room; empty for adults only. |
wants | object | null | The preferences sent, or null. |
wants.refundable | boolean | null | Free cancellation asked for, or null. |
wants.board | string | null | Meals asked for, or null. One of none, breakfast, half, full, all_inclusive. |
wants.room_type | string | null | Room type asked for, or null. |
anchor | object | null | The price the hotel was asked to beat, or null when there was none. |
anchor.totalREQUIRED | string | Price for the whole stay and party, a decimal string in currency. |
anchor.nightly | string | null | total divided by the number of nights. |
anchor.currencyREQUIRED | string | ISO 4217 currency of total. |
anchor.sourceREQUIRED | string | third_party when it is the best public online price we know, partner when you sent it. One of third_party, partner. |
deadline_atREQUIRED | string (date-time) | When the hotel's time to reply ends, RFC 3339 UTC. |
next_stepREQUIRED | string | One plain sentence on what happens next, suitable to show in your own interface. |
created_atREQUIRED | string (date-time) | When the quote was requested, RFC 3339 UTC. |
updated_atREQUIRED | string (date-time) | When the quote last changed, RFC 3339 UTC. |
offers | QuoteOffer[] | Priced offers from the hotel's replies, oldest first. |
offers[].offer_idREQUIRED | string | Offer id, qof_ plus 20 hex characters; pass it to accept. |
offers[].source | string | Always email: the hotel's emailed reply. Always email. |
offers[].room_name | string | null | Room the hotel offered, as it named it. |
offers[].rate_name | string | null | Short summary of the terms, for example Breakfast included, free cancellation. |
offers[].totalREQUIRED | string | Price for the whole stay and party, a decimal string in currency. |
offers[].currencyREQUIRED | string | ISO 4217 currency the hotel quoted in; never converted. |
offers[].nightly_avg | string | null | total divided by the number of nights. |
offers[].includes_taxes | boolean | null | True when the hotel said taxes are included, false when not, null when it did not say. |
offers[].board | string | null | Meals included, or null when the hotel did not say. One of none, breakfast, half, full, all_inclusive. |
offers[].free_cancellation | boolean | null | True when the offer can be canceled free of charge, false when not, null when unknown. |
offers[].cancellation_deadline | string (date-time) | null | Last moment to cancel free of charge, RFC 3339, when the hotel gave one. |
offers[].valid_until | string (date) | null | Last day the hotel holds this price, YYYY-MM-DD, when it said so. |
offers[].offer_link | string (uri) | null | A booking link the hotel included in its reply, or null. |
offers[].beats_by | string | null | How much cheaper than the anchor price this offer is, in currency; null without a comparable anchor. |
offers[].evidence | string | null | The sentence of the hotel's reply the price was read from, up to 280 characters. |
offers[].confidence | number | null | How sure the reading of the reply is, 0 to 1. |
offers[].needs_review | boolean | True 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_id | string | null | The offer you accepted; null until status is accepted. |
hotel_commission_applies | boolean | null | After 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_reason | string | null | Why 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. |
billing | null | Always null on responses: the quote fee is charged once, when the hotel's first priced offer arrives, and shows in usage. |
Errors
| Status | Code | When |
|---|---|---|
| 400 | bad_request | Validation errors carry details.fields[]. |
| 401 | unauthorized | No key, or the key is unknown or revoked. |
| 403 | forbidden | Missing scope. |
| 404 | not_found | The hotel, offer, booking, job or webhook doesn't exist for this key. |
| 429 | rate_limited | Over the per-minute limit for the tier. Wait Retry-After seconds. |
| 503 | unavailable | We're temporarily unable to serve the call. Retry with backoff. |
Cancel a quote
/v2/quotes/{quote_id}/cancelSynchronous 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
| Name | In | Type | Description |
|---|---|---|---|
quote_idREQUIRED | path | string | The quote_id returned by POST /v2/quotes. |
Example
curl -X POST 'https://api.vistalink.com/v2/quotes/qte_7c2e9a4b1d8f3e6a0c5b/cancel' \
-H "Authorization: Bearer $VISTALINK_API_KEY"vistalink quotes cancel qte_7c2e9a4b1d8f3e6a0c5bconst res = await fetch("https://api.vistalink.com/v2/quotes/qte_7c2e9a4b1d8f3e6a0c5b/cancel", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.VISTALINK_API_KEY}`,
},
});
if (!res.ok) throw new Error((await res.json()).error.message);
const data = await res.json();import os
import requests
resp = requests.post(
"https://api.vistalink.com/v2/quotes/qte_7c2e9a4b1d8f3e6a0c5b/cancel",
headers={"Authorization": f"Bearer {os.environ['VISTALINK_API_KEY']}"},
)
resp.raise_for_status()
data = resp.json(){
"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"
}
}{
"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": "The hotel replied with an offer. Accept it to introduce your guest.",
"offers": [
{
"beats_by": "82.00",
"board": "breakfast",
"cancellation_deadline": "2026-11-12T23:59:00Z",
"confidence": 0.93,
"currency": "EUR",
"evidence": "We can offer the Superior Double with breakfast for EUR 880 for both nights, free cancellation until 12 November.",
"free_cancellation": true,
"includes_taxes": true,
"needs_review": false,
"nightly_avg": "440.00",
"offer_id": "qof_4a9d2c7e1b8f5a3d6e0c",
"rate_name": "Breakfast included, free cancellation",
"room_name": "Superior Double",
"source": "email",
"total": "880.00",
"valid_until": "2026-10-10"
}
],
"quote_id": "qte_7c2e9a4b1d8f3e6a0c5b",
"status": "offer_received",
"stay": {
"check_in": "2026-11-14",
"check_out": "2026-11-16",
"rooms": [
{
"adults": 2,
"children_ages": []
}
]
},
"updated_at": "2026-10-07T14:52:10Z",
"wants": {
"board": "breakfast",
"refundable": true,
"room_type": "double"
}
}{
"accepted_offer_id": "qof_4a9d2c7e1b8f5a3d6e0c",
"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_commission_applies": false,
"hotel_id": "43d0be71-5f3c-4c8e-9a52-1d7e2b6f0a19",
"next_step": "Your guest has been introduced to the hotel by email; the hotel replies to them directly to confirm the booking.",
"offers": [
{
"beats_by": "82.00",
"board": "breakfast",
"cancellation_deadline": "2026-11-12T23:59:00Z",
"confidence": 0.93,
"currency": "EUR",
"evidence": "We can offer the Superior Double with breakfast for EUR 880 for both nights, free cancellation until 12 November.",
"free_cancellation": true,
"includes_taxes": true,
"needs_review": false,
"nightly_avg": "440.00",
"offer_id": "qof_4a9d2c7e1b8f5a3d6e0c",
"rate_name": "Breakfast included, free cancellation",
"room_name": "Superior Double",
"source": "email",
"total": "880.00",
"valid_until": "2026-10-10"
}
],
"quote_id": "qte_7c2e9a4b1d8f3e6a0c5b",
"status": "accepted",
"stay": {
"check_in": "2026-11-14",
"check_out": "2026-11-16",
"rooms": [
{
"adults": 2,
"children_ages": []
}
]
},
"updated_at": "2026-10-07T15:10:31Z",
"wants": {
"board": "breakfast",
"refundable": true,
"room_type": "double"
}
}Response 200
Successful Response
| Field | Type | Description |
|---|---|---|
request_id | string | null | Id of the request that returned this quote; null in webhook payloads. |
quote_idREQUIRED | string | Quote id, qte_ plus 20 hex characters. |
statusREQUIRED | string | Where 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_idREQUIRED | string (uuid) | The hotel asked. |
stayREQUIRED | object | The stay asked about. |
stay.check_inREQUIRED | string (date) | Arrival date, YYYY-MM-DD. |
stay.check_outREQUIRED | string (date) | Departure date, YYYY-MM-DD. |
stay.rooms | Occupancy[] | One entry per room. |
stay.rooms[].adultsREQUIRED | integer | Adults in the room. |
stay.rooms[].children_ages | integer[] | Age of each child in the room; empty for adults only. |
wants | object | null | The preferences sent, or null. |
wants.refundable | boolean | null | Free cancellation asked for, or null. |
wants.board | string | null | Meals asked for, or null. One of none, breakfast, half, full, all_inclusive. |
wants.room_type | string | null | Room type asked for, or null. |
anchor | object | null | The price the hotel was asked to beat, or null when there was none. |
anchor.totalREQUIRED | string | Price for the whole stay and party, a decimal string in currency. |
anchor.nightly | string | null | total divided by the number of nights. |
anchor.currencyREQUIRED | string | ISO 4217 currency of total. |
anchor.sourceREQUIRED | string | third_party when it is the best public online price we know, partner when you sent it. One of third_party, partner. |
deadline_atREQUIRED | string (date-time) | When the hotel's time to reply ends, RFC 3339 UTC. |
next_stepREQUIRED | string | One plain sentence on what happens next, suitable to show in your own interface. |
created_atREQUIRED | string (date-time) | When the quote was requested, RFC 3339 UTC. |
updated_atREQUIRED | string (date-time) | When the quote last changed, RFC 3339 UTC. |
offers | QuoteOffer[] | Priced offers from the hotel's replies, oldest first. |
offers[].offer_idREQUIRED | string | Offer id, qof_ plus 20 hex characters; pass it to accept. |
offers[].source | string | Always email: the hotel's emailed reply. Always email. |
offers[].room_name | string | null | Room the hotel offered, as it named it. |
offers[].rate_name | string | null | Short summary of the terms, for example Breakfast included, free cancellation. |
offers[].totalREQUIRED | string | Price for the whole stay and party, a decimal string in currency. |
offers[].currencyREQUIRED | string | ISO 4217 currency the hotel quoted in; never converted. |
offers[].nightly_avg | string | null | total divided by the number of nights. |
offers[].includes_taxes | boolean | null | True when the hotel said taxes are included, false when not, null when it did not say. |
offers[].board | string | null | Meals included, or null when the hotel did not say. One of none, breakfast, half, full, all_inclusive. |
offers[].free_cancellation | boolean | null | True when the offer can be canceled free of charge, false when not, null when unknown. |
offers[].cancellation_deadline | string (date-time) | null | Last moment to cancel free of charge, RFC 3339, when the hotel gave one. |
offers[].valid_until | string (date) | null | Last day the hotel holds this price, YYYY-MM-DD, when it said so. |
offers[].offer_link | string (uri) | null | A booking link the hotel included in its reply, or null. |
offers[].beats_by | string | null | How much cheaper than the anchor price this offer is, in currency; null without a comparable anchor. |
offers[].evidence | string | null | The sentence of the hotel's reply the price was read from, up to 280 characters. |
offers[].confidence | number | null | How sure the reading of the reply is, 0 to 1. |
offers[].needs_review | boolean | True 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_id | string | null | The offer you accepted; null until status is accepted. |
hotel_commission_applies | boolean | null | After 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_reason | string | null | Why 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. |
billing | null | Always null on responses: the quote fee is charged once, when the hotel's first priced offer arrives, and shows in usage. |
Errors
| Status | Code | When |
|---|---|---|
| 400 | bad_request | Validation errors carry details.fields[]. |
| 401 | unauthorized | No key, or the key is unknown or revoked. |
| 403 | forbidden | Missing scope. |
| 404 | not_found | The hotel, offer, booking, job or webhook doesn't exist for this key. |
| 409 | conflict | The quote is already closed. |
| 429 | rate_limited | Over the per-minute limit for the tier. Wait Retry-After seconds. |
| 503 | unavailable | We're temporarily unable to serve the call. Retry with backoff. |
Accept a quote offer
/v2/quotes/{quote_id}/offers/{offer_id}/acceptFree; 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
| Name | In | Type | Description |
|---|---|---|---|
quote_idREQUIRED | path | string | The quote_id returned by POST /v2/quotes. |
offer_idREQUIRED | path | string | The offer_id of one of the quote's offers. |
Idempotency-Key | header | string | Any unique string. Same key and same body returns the original response; same key and a different body is a 409. |
Request body
| Field | Type | Description |
|---|---|---|
guestREQUIRED | object | The guest to introduce to the hotel. |
guest.first_nameREQUIRED | string | Guest's first name, up to 80 characters. |
guest.last_nameREQUIRED | string | Guest's last name, up to 80 characters. |
guest.emailREQUIRED | string (email) | Guest's email address; the guest is copied on the introduction email and the hotel replies to it. |
Example
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"
}
}'vistalink quotes accept qte_7c2e9a4b1d8f3e6a0c5b qof_4a9d2c7e1b8f5a3d6e0c \
--idempotency-key 4f1c2b8e-booking-ada-rossi \
--guest '{"first_name":"Ada","last_name":"Rossi","email":"ada@example.com"}'const res = await fetch("https://api.vistalink.com/v2/quotes/qte_7c2e9a4b1d8f3e6a0c5b/offers/qof_4a9d2c7e1b8f5a3d6e0c/accept", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.VISTALINK_API_KEY}`,
"Idempotency-Key": "4f1c2b8e-booking-ada-rossi",
"Content-Type": "application/json",
},
body: JSON.stringify({
"guest": {
"first_name": "Ada",
"last_name": "Rossi",
"email": "ada@example.com"
}
}),
});
if (!res.ok) throw new Error((await res.json()).error.message);
const data = await res.json();import os
import requests
resp = requests.post(
"https://api.vistalink.com/v2/quotes/qte_7c2e9a4b1d8f3e6a0c5b/offers/qof_4a9d2c7e1b8f5a3d6e0c/accept",
headers={"Authorization": f"Bearer {os.environ['VISTALINK_API_KEY']}", "Idempotency-Key": "4f1c2b8e-booking-ada-rossi"},
json={
"guest": {
"first_name": "Ada",
"last_name": "Rossi",
"email": "ada@example.com",
},
},
)
resp.raise_for_status()
data = resp.json(){
"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"
}
}{
"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": "The hotel replied with an offer. Accept it to introduce your guest.",
"offers": [
{
"beats_by": "82.00",
"board": "breakfast",
"cancellation_deadline": "2026-11-12T23:59:00Z",
"confidence": 0.93,
"currency": "EUR",
"evidence": "We can offer the Superior Double with breakfast for EUR 880 for both nights, free cancellation until 12 November.",
"free_cancellation": true,
"includes_taxes": true,
"needs_review": false,
"nightly_avg": "440.00",
"offer_id": "qof_4a9d2c7e1b8f5a3d6e0c",
"rate_name": "Breakfast included, free cancellation",
"room_name": "Superior Double",
"source": "email",
"total": "880.00",
"valid_until": "2026-10-10"
}
],
"quote_id": "qte_7c2e9a4b1d8f3e6a0c5b",
"status": "offer_received",
"stay": {
"check_in": "2026-11-14",
"check_out": "2026-11-16",
"rooms": [
{
"adults": 2,
"children_ages": []
}
]
},
"updated_at": "2026-10-07T14:52:10Z",
"wants": {
"board": "breakfast",
"refundable": true,
"room_type": "double"
}
}{
"accepted_offer_id": "qof_4a9d2c7e1b8f5a3d6e0c",
"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_commission_applies": false,
"hotel_id": "43d0be71-5f3c-4c8e-9a52-1d7e2b6f0a19",
"next_step": "Your guest has been introduced to the hotel by email; the hotel replies to them directly to confirm the booking.",
"offers": [
{
"beats_by": "82.00",
"board": "breakfast",
"cancellation_deadline": "2026-11-12T23:59:00Z",
"confidence": 0.93,
"currency": "EUR",
"evidence": "We can offer the Superior Double with breakfast for EUR 880 for both nights, free cancellation until 12 November.",
"free_cancellation": true,
"includes_taxes": true,
"needs_review": false,
"nightly_avg": "440.00",
"offer_id": "qof_4a9d2c7e1b8f5a3d6e0c",
"rate_name": "Breakfast included, free cancellation",
"room_name": "Superior Double",
"source": "email",
"total": "880.00",
"valid_until": "2026-10-10"
}
],
"quote_id": "qte_7c2e9a4b1d8f3e6a0c5b",
"status": "accepted",
"stay": {
"check_in": "2026-11-14",
"check_out": "2026-11-16",
"rooms": [
{
"adults": 2,
"children_ages": []
}
]
},
"updated_at": "2026-10-07T15:10:31Z",
"wants": {
"board": "breakfast",
"refundable": true,
"room_type": "double"
}
}Response 200
Successful Response
| Field | Type | Description |
|---|---|---|
request_id | string | null | Id of the request that returned this quote; null in webhook payloads. |
quote_idREQUIRED | string | Quote id, qte_ plus 20 hex characters. |
statusREQUIRED | string | Where 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_idREQUIRED | string (uuid) | The hotel asked. |
stayREQUIRED | object | The stay asked about. |
stay.check_inREQUIRED | string (date) | Arrival date, YYYY-MM-DD. |
stay.check_outREQUIRED | string (date) | Departure date, YYYY-MM-DD. |
stay.rooms | Occupancy[] | One entry per room. |
stay.rooms[].adultsREQUIRED | integer | Adults in the room. |
stay.rooms[].children_ages | integer[] | Age of each child in the room; empty for adults only. |
wants | object | null | The preferences sent, or null. |
wants.refundable | boolean | null | Free cancellation asked for, or null. |
wants.board | string | null | Meals asked for, or null. One of none, breakfast, half, full, all_inclusive. |
wants.room_type | string | null | Room type asked for, or null. |
anchor | object | null | The price the hotel was asked to beat, or null when there was none. |
anchor.totalREQUIRED | string | Price for the whole stay and party, a decimal string in currency. |
anchor.nightly | string | null | total divided by the number of nights. |
anchor.currencyREQUIRED | string | ISO 4217 currency of total. |
anchor.sourceREQUIRED | string | third_party when it is the best public online price we know, partner when you sent it. One of third_party, partner. |
deadline_atREQUIRED | string (date-time) | When the hotel's time to reply ends, RFC 3339 UTC. |
next_stepREQUIRED | string | One plain sentence on what happens next, suitable to show in your own interface. |
created_atREQUIRED | string (date-time) | When the quote was requested, RFC 3339 UTC. |
updated_atREQUIRED | string (date-time) | When the quote last changed, RFC 3339 UTC. |
offers | QuoteOffer[] | Priced offers from the hotel's replies, oldest first. |
offers[].offer_idREQUIRED | string | Offer id, qof_ plus 20 hex characters; pass it to accept. |
offers[].source | string | Always email: the hotel's emailed reply. Always email. |
offers[].room_name | string | null | Room the hotel offered, as it named it. |
offers[].rate_name | string | null | Short summary of the terms, for example Breakfast included, free cancellation. |
offers[].totalREQUIRED | string | Price for the whole stay and party, a decimal string in currency. |
offers[].currencyREQUIRED | string | ISO 4217 currency the hotel quoted in; never converted. |
offers[].nightly_avg | string | null | total divided by the number of nights. |
offers[].includes_taxes | boolean | null | True when the hotel said taxes are included, false when not, null when it did not say. |
offers[].board | string | null | Meals included, or null when the hotel did not say. One of none, breakfast, half, full, all_inclusive. |
offers[].free_cancellation | boolean | null | True when the offer can be canceled free of charge, false when not, null when unknown. |
offers[].cancellation_deadline | string (date-time) | null | Last moment to cancel free of charge, RFC 3339, when the hotel gave one. |
offers[].valid_until | string (date) | null | Last day the hotel holds this price, YYYY-MM-DD, when it said so. |
offers[].offer_link | string (uri) | null | A booking link the hotel included in its reply, or null. |
offers[].beats_by | string | null | How much cheaper than the anchor price this offer is, in currency; null without a comparable anchor. |
offers[].evidence | string | null | The sentence of the hotel's reply the price was read from, up to 280 characters. |
offers[].confidence | number | null | How sure the reading of the reply is, 0 to 1. |
offers[].needs_review | boolean | True 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_id | string | null | The offer you accepted; null until status is accepted. |
hotel_commission_applies | boolean | null | After 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_reason | string | null | Why 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. |
billing | null | Always null on responses: the quote fee is charged once, when the hotel's first priced offer arrives, and shows in usage. |
Errors
| Status | Code | When |
|---|---|---|
| 400 | bad_request | Validation errors carry details.fields[]. |
| 401 | unauthorized | No key, or the key is unknown or revoked. |
| 403 | forbidden | Missing scope, or details.reason tier_required: this needs the Build plan or higher. |
| 404 | not_found | Quote or offer. |
| 409 | conflict | The quote is closed, or another offer was accepted. |
| 429 | rate_limited | Over the per-minute limit for the tier. Wait Retry-After seconds. |
| 503 | unavailable | We're temporarily unable to serve the call. Retry with backoff. |
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.
| Field | Type | Description |
|---|---|---|
check_inREQUIRED | string (date) | Arrival date, YYYY-MM-DD. |
check_outREQUIRED | string (date) | Departure date, YYYY-MM-DD, after check_in and at most 30 nights later. |
rooms | RoomOccupancy[] | One entry per room, 1 to 9; defaults to one room for two adults. At most 9. |
rooms[].adults | integer | Adults in this room, 1 to 16. Default 2. |
rooms[].children_ages | integer[] | Age of each child in this room, 0 to 17, one entry per child; empty for adults only. At most 8. |