GET /appointments/availability
Query bookable appointment slots for a given rooftop, route, and date. Returns the times a HopDrive driver could be at the consumer's pickup address — the times you can offer the consumer for their appointment.
Use this endpoint to populate a time-picker UI in your SLT (Service Lane Tool) or to surface availability via your scheduling integration before creating an appointment with POST /appointments.
Example Request
Authorization: Bearer <jwt>
Query Params
For each leg of the route (pickup and delivery), provide either an address string or a HopDrive location ID — whichever you have. Pass an address string and HopDrive resolves (and, when necessary, creates) the underlying location for you, the same way the move-creation endpoints accept addresses. Pass a location ID — e.g. one you got from GET /locations — and that leg is used as-is with no address lookup. The two legs are independent: you can supply a location ID for one and an address for the other.
| Field | Type | Required? | Description |
|---|---|---|---|
| customer_id | Number | Required | HopDrive dealer account ID (the rooftop). Must match an authorized rooftop on your bearer token. |
| pickup_address | String | Per leg† | The consumer's pickup address as a single string (e.g. 210 S Mulberry St, Richmond, VA 23220). |
| pickup_location_id | Number | Per leg† | HopDrive location ID for the pickup. Used as-is; takes precedence over pickup_address when both are sent. |
| delivery_address | String | Per leg† | The dealership service address as a single string. |
| delivery_location_id | Number | Per leg† | HopDrive location ID for the dealership service address. Used as-is; takes precedence over delivery_address. |
| date | String | Required | UTC calendar date in YYYY-MM-DD format. Slots are computed for the local timezone of the rooftop. |
| consumer_action | String | Optional | The scenario variant: concierge-pu, concierge-pu-no-rideshare, concierge-ret, concierge-ret-no-rideshare, concierge-loaner-pu, or concierge-loaner-ret. |
† For each leg you must supply one of its two inputs: pickup_address or pickup_location_id for the pickup, and delivery_address or delivery_location_id for the delivery. If both are sent for a leg the location ID is used and the address is ignored. If neither is sent for a leg, the request is rejected with REQUEST_INVALID.
The scheduling strategy and customer tier are not caller-configurable — both are derived from the rooftop's record on each request.
Example Response
{
"rooftop_timezone": "America/Chicago",
"eligible": true,
"reason": null,
"slots": [
{
"pickup_start": "2026-05-14T13:00:00.000Z",
"appointment_time": "2026-05-14T13:20:00.000Z",
"return_end": "2026-05-14T13:45:00.000Z",
"available_drivers": 2
},
{
"pickup_start": "2026-05-14T13:30:00.000Z",
"appointment_time": "2026-05-14T13:50:00.000Z",
"return_end": "2026-05-14T14:15:00.000Z",
"available_drivers": 1
}
]
}
Response Fields
| Field | Type | Description |
|---|---|---|
| rooftop_timezone | String | IANA timezone of the rooftop. Use this to render slot times in the dealership's local clock without a second round-trip. |
| eligible | Boolean | true when bookable slots are returned. false for ineligible responses (see error responses below). |
| reason | String | null on eligible responses. On ineligible responses, a stable machine-readable reason code (APPOINTMENT_MANAGEMENT_DISABLED, etc.). |
| slots | Array | Bookable slot windows, sorted by appointment_time ascending. Empty array when eligible: false. |
| slots[].pickup_start | String | ISO 8601 UTC — when the driver leaves the dealer for the consumer's pickup address. |
| slots[].appointment_time | String | ISO 8601 UTC — when the driver arrives at the consumer's pickup address. The customer-facing appointment anchor. |
| slots[].return_end | String | ISO 8601 UTC — when the vehicle is back at the dealer. Anchors when service can begin. |
| slots[].available_drivers | Number | Count of distinct drivers (or hypothetical gig-labor rows) that could fulfill this slot. Always ≥ 1. Filter on this to require redundancy. |
Error Responses
| HTTP | Code | Cause |
|---|---|---|
| 400 | REQUEST_INVALID | A required query parameter is missing, malformed, or has the wrong type; a leg is missing both its _address and _location_id; or an _address could not be geocoded / matched more than one location. |
| 401 | ACCESS_DENIED | Missing / invalid bearer token, OR the bearer token does not authorize the requested customer_id. |
| 404 | REQUEST_INVALID | customer_id does not match an existing rooftop. |
| 406 | ADDRESS_INVALID | Rooftop is not bookable (appointment management disabled, rooftop closed for the date, no drivers configured). See reason in body. |
| 500 | UNKNOWN | Unexpected server error. |
| 502 | UNKNOWN | Drive-time computation against Google Routes failed or timed out. Retry shortly. |
Ineligible Response Reasons
When HTTP 406 is returned, the response body's reason field carries a stable code:
| Reason | Meaning |
|---|---|
| APPOINTMENT_MANAGEMENT_DISABLED | The rooftop has not enabled appointment management. Contact HopDrive support to enable. |
| CLOSED:<closure-name> | The rooftop is closed for date (e.g., a holiday, PTO, or recurring weekly closure). The closure name follows the colon. |
| NO_DRIVERS_CONFIGURED | The rooftop's driver count is 0. Contact HopDrive support to provision driver capacity. |
Worked Examples
Cold lane (first call for this consumer pickup ↔ dealer pair)
The very first availability query for a new (pickup, dealer) tuple triggers an address-resolution step (geocoding + location lookup/creation) plus a Google Routes call inline to compute drive times. Expect ~200–500 ms response time on cold calls.
GET /v1/appointments/availability?customer_id=1234&pickup_address=210 S Mulberry St, Richmond, VA 23220&delivery_address=11161 Research Plaza Way, Richmond, VA 23236&date=2026-05-14
Response is identical in shape to the warm case; the resolved locations and drive times are now cached for subsequent calls. Re-sending the same address strings resolves to the same locations without re-creating them.
Warm lane (subsequent calls for the same pair)
After the first call, drive times are cached in HopDrive's lane table. Subsequent availability queries against the same (pickup, dealer) pair are sub-100 ms — they read the cached drive time and run only the slot-packing algorithm.
Disabled rooftop
{
"rooftop_timezone": "America/Chicago",
"eligible": false,
"reason": "APPOINTMENT_MANAGEMENT_DISABLED",
"slots": [],
"errors": [
{
"code": "ADDRESS_INVALID",
"message": "APPOINTMENT_MANAGEMENT_DISABLED"
}
]
}
The rooftop hasn't enabled appointment management. Coordinate with HopDrive support.
Rooftop closed for the date
{
"rooftop_timezone": "America/Chicago",
"eligible": false,
"reason": "CLOSED:Memorial Day",
"slots": [],
"errors": [
{
"code": "ADDRESS_INVALID",
"message": "CLOSED:Memorial Day"
}
]
}
The rooftop is closed for date due to the named closure. Suggest a different date to the consumer.
Saturated day (all slots booked)
A fully-booked rooftop returns an empty slots array but eligible: true:
{
"rooftop_timezone": "America/Chicago",
"eligible": true,
"reason": null,
"slots": []
}
This signals "the rooftop is bookable in principle, but no slots are available on this date." Different from eligible: false, which means the rooftop can't be booked at all.
Notes
- All timestamps are UTC ISO 8601. Use
rooftop_timezoneto render in the dealership's local clock. - Slot rounding is fixed at 5-minute intervals.
available_driversis informational. The endpoint does not reserve drivers — that happens at POST /appointments. A slot that's available now may not be at booking time.- Drive times are computed against Google Routes in traffic-unaware mode for consistency across calls.
- Lane / route data is cached indefinitely once computed. Road-network changes are rare enough that this is acceptable; contact HopDrive support if you suspect stale routing.