Quotes
A quote is a priced proposal built from the same line items as a booking β products, bundles, experiences, store products β that the customer reviews and accepts (or declines) through a private link. Accepting converts the quote into a normal confirmed booking at the quoted total.
Quote pricing runs through the exact same engine as booking pricing, so a quote's totals always match what the equivalent booking would cost.
Endpointsβ
| Method | Endpoint | Description |
|---|---|---|
| GET | /v1/quotes | List quotes |
| GET | /v1/quotes/{id} | Get a single quote |
| POST | /v1/quotes | Create a quote |
| PATCH | /v1/quotes/{id} | Update a quote |
| DELETE | /v1/quotes/{id} | Decline (archive) a quote |
| POST | /v1/quotes/{id}/send | Send the quote to the customer |
| POST | /v1/quotes/{id}/accept | Accept a quote |
| POST | /v1/quotes/{id}/decline | Decline a quote |
| POST | /v1/quotes/{id}/duplicate | Duplicate a quote |
Scopesβ
| Scope | Grants |
|---|---|
quotes:read | View quotes |
quotes:write | Create, send and decide quotes |
Quote Lifecycleβ
A quote's state is derived from its timestamps β it is never set directly:
| State | Meaning |
|---|---|
draft | Created but not sent yet |
sent | Emailed to the customer, not opened yet |
viewed | The customer has opened the quote page |
accepted | Converted into a confirmed booking |
declined | Declined by the customer or on their behalf |
expired | Its valid_until date passed before a decision |
cancelled | Accepted, then the booking it became was cancelled |
draft ββsendβββΆ sent ββcustomer opensβββΆ viewed
β β
βββββββββacceptβββββββββββββ€βββΆ accepted (now a booking)
βββββββββdeclineββββββββββββ€βββΆ declined
βββββvalid_until passesβββββ΄βββΆ expired
accepted ββbooking cancelledβββΆ cancelled
Key Conceptsβ
Holding dates (reserves_dates)β
A quote can optionally hold its dates (quote_reserves: true on create). While held, the quoted items and spots are blocked from other bookings until the customer decides. Held dates are released automatically when the quote is declined or expires.
A quote that does not hold its dates leaves availability open β if the dates are taken before acceptance, Accept returns 409 dates_unavailable and nothing is booked.
Expiry (valid_until)β
quote_valid_until is optional. A quote with valid_until: null never expires. A dated quote lapses automatically once the date passes (in the supplier's timezone): its state becomes expired, any held dates are released, and the supplier is notified. An optional customer reminder email can be configured a set number of days before expiry (Settings β Quotes in the portal).
Agreementsβ
A quote can carry several agreements β published waiver/form documents (quote_agreement_form_ids, an array of form IDs) β which the customer reads and signs on the quote page before accepting. Each agreement gets its own signing link on the customer page, and acceptance stays blocked until every agreement is signed. The quote resource reports them under agreements (one entry per form with signed and signed_at); the legacy single-form agreement field mirrors the first attachment for older integrations.
Customer link (customer_url)β
Each quote gets a private, tokenized customer page (no login required). The link is minted on first send β until then customer_url is null. Reads never mint a link.
Quotes are never hard-deletedβ
DELETE /v1/quotes/{id} declines the quote rather than deleting it β decided quotes stay on record. There is no way to hard-delete a quote via the API.
The Quote Objectβ
{
"id": 123,
"quote_number": "Q-123",
"state": "sent",
"reserves_dates": false,
"valid_until": "2026-03-01",
"sent_at": "2026-02-01T10:05:00+00:00",
"viewed_at": null,
"decided_at": null,
"decline_reason": null,
"agreements": [
{
"form_id": 12,
"form_name": "Rental Agreement",
"signed": false,
"signed_at": null
},
{
"form_id": 15,
"form_name": "Liability Agreement",
"signed": true,
"signed_at": "2026-02-02T15:12:00+00:00"
}
],
"agreement": {
"form_id": 12,
"form_name": "Rental Agreement",
"signed": false
},
"customer_url": "https://app.equipdash.com/quote/AbC123dEf456...",
"type": "rental",
"start_date": "2026-02-15T09:00:00+00:00",
"end_date": "2026-02-17T17:00:00+00:00",
"currency": "USD",
"pricing": {
"subtotal": 205.00,
"total": 225.50
},
"customer": {
"id": 456,
"first_name": "Jane",
"last_name": "Doe",
"full_name": "Jane Doe",
"is_organization": false,
"organization_name": null,
"display_name": "Jane Doe",
"email": "jane@example.com",
"phone": "+1234567890",
"phone_country_code": "+1",
"bookings_count": 3,
"created_at": "2025-06-01T09:00:00+00:00"
},
"products": [
{
"id": 45,
"product_id": 1,
"product_name": "Mountain Bike",
"variation_id": null,
"variation_name": null,
"bundle_in_booking_id": null,
"quantity": 2,
"price": 120.00
}
],
"experiences": [],
"bundles": [],
"store_products": [],
"created_at": "2026-02-01T10:00:00+00:00",
"updated_at": "2026-02-01T10:05:00+00:00"
}
Quote Fieldsβ
| Field | Type | Description |
|---|---|---|
id | integer | Unique quote identifier |
quote_number | string | Human-readable number, e.g. "Q-123" |
state | string | Derived lifecycle state (see Quote Lifecycle) |
reserves_dates | boolean | Whether the quote holds its dates while the customer decides |
valid_until | string|null | Expiry date (YYYY-MM-DD). null = the quote never expires |
sent_at | string|null | When the quote was last sent (ISO 8601) |
viewed_at | string|null | When the customer first opened the quote page (ISO 8601) |
decided_at | string|null | When the quote was accepted, declined, or expired (ISO 8601) |
decline_reason | string|null | The decline reason. Empty string when declined without a reason; null while undecided |
agreements | array | Every attached agreement: form_id, form_name, signed (boolean), signed_at (ISO 8601|null). Empty array when none are attached |
agreement | object|null | Legacy single-agreement field β mirrors the FIRST attachment (form_id, form_name, signed). null when no agreements are attached |
customer_url | string|null | The customer's private quote link. null until the quote is first sent |
type | string | Booking type of the underlying cart (rental, experience, store, service, mixed) |
start_date | string|null | Start date/time (ISO 8601) |
end_date | string|null | End date/time (ISO 8601) |
currency | string | Currency code (ISO 4217) |
pricing | object | subtotal and total (tax-inclusive) |
customer | object | Customer summary |
products | array | Rental product lines: id, product_id, product_name, variation_id, variation_name, bundle_in_booking_id, quantity, price |
experiences | array | Experience lines: id, experience_id, experience_name, schedule_id, rate_category_id, bundle_in_booking_id, quantity, booked_participants, price |
bundles | array | Bundle lines: id, bundle_id, bundle_name, quantity, price |
store_products | array | Store product lines: id, store_product_id, product_name, variation_id, quantity, unit_price, total_price |
created_at | string | Creation timestamp (ISO 8601) |
updated_at | string | Last update timestamp (ISO 8601) |
Typical Workflowβ
- Create the quote with the same line-item payload as a booking:
POST /v1/quotes - Send it to the customer:
POST /v1/quotes/{id}/send - Track it:
GET /v1/quotes/{id}β watchstate,viewed_at, and theagreements[].signedflags - Decide it (if the customer confirms out-of-band):
POST /v1/quotes/{id}/acceptor/decline - Re-quote when dates change or a quote lapses:
POST /v1/quotes/{id}/duplicate