Skip to main content

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​

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
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.

FieldTypeRequired?Description
customer_idNumberRequiredHopDrive dealer account ID (the rooftop). Must match an authorized rooftop on your bearer token.
pickup_addressStringPer leg†The consumer's pickup address as a single string (e.g. 210 S Mulberry St, Richmond, VA 23220).
pickup_location_idNumberPer leg†HopDrive location ID for the pickup. Used as-is; takes precedence over pickup_address when both are sent.
delivery_addressStringPer leg†The dealership service address as a single string.
delivery_location_idNumberPer leg†HopDrive location ID for the dealership service address. Used as-is; takes precedence over delivery_address.
dateStringRequiredUTC calendar date in YYYY-MM-DD format. Slots are computed for the local timezone of the rooftop.
consumer_actionStringOptionalThe 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​

200 /v1/appointments/availability
{
"rooftop_timezone": "America/Chicago",
"eligible": true,
"reason": null,
"enforcement_mode": "forced",
"offset_increment_minutes": 15,
"offset_max_minutes": 15,
"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,
"earliest_appointment_time": "2026-05-14T13:05:00.000Z",
"latest_appointment_time": "2026-05-14T13:35:00.000Z"
},
{
"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,
"earliest_appointment_time": "2026-05-14T13:45:00.000Z",
"latest_appointment_time": "2026-05-14T14:05:00.000Z"
}
]
}

Response Fields​

FieldTypeDescription
rooftop_timezoneStringIANA timezone of the rooftop. Use this to render slot times in the dealership's local clock without a second round-trip.
eligibleBooleantrue when bookable slots are returned. false for ineligible responses (see error responses below).
reasonStringnull on eligible responses. On ineligible responses, a stable machine-readable reason code (APPOINTMENT_MANAGEMENT_DISABLED, etc.).
slotsArrayBookable slot windows, sorted by appointment_time ascending. Empty array when eligible: false.
enforcement_modeStringforced when the rooftop requires booked times to land inside an offered slot window (writes are validated, see below). suggestion when offered times are advisory and any appointment_time is accepted. Mirrors the setting that locks the time field in the dealer portal.
offset_increment_minutesNumberThe step size the dealer portal's adjustment arrows use. Advisory for API callers: any time inside a slot's window is accepted, aligned to this step or not.
offset_max_minutesNumberThe rooftop's configured cap on shifting a slot from its suggested time. 0 means slots are take-it-or-leave-it. Per-slot windows below already apply this cap.
slots[].pickup_startStringISO 8601 UTC — when the driver leaves the dealer for the consumer's pickup address.
slots[].appointment_timeStringISO 8601 UTC — when the driver arrives at the consumer's pickup address. The customer-facing appointment anchor.
slots[].return_endStringISO 8601 UTC — when the vehicle is back at the dealer. Anchors when service can begin.
slots[].available_driversNumberCount of distinct drivers (or hypothetical gig-labor rows) that could fulfill this slot. Always ≥ 1. Filter on this to require redundancy.
slots[].earliest_appointment_timeStringISO 8601 UTC — the earliest time this slot may be booked at. Starts at appointment_time - offset_max_minutes and shrinks when a neighboring booking, business hours, or a driver's shift leaves less room. Equals appointment_time when offsets are configured off.
slots[].latest_appointment_timeStringISO 8601 UTC — the latest time this slot may be booked at. Same rules as earliest_appointment_time, in the other direction.

Booking against a slot​

When enforcement_mode is forced, POST /appointments (and PUT/PATCH carrying an appointment_time) re-checks availability at write time and rejects with 400 request_invalid unless the time falls inside some slot's [earliest_appointment_time, latest_appointment_time], bounds inclusive. The re-check means a slot another caller booked after your GET stops validating, so handle the 400 by re-querying availability. When enforcement_mode is suggestion, writes accept any appointment_time, exactly like dealership staff typing a custom time in the portal.

Error Responses​

HTTPCodeCause
400REQUEST_INVALIDA required query parameter is missing, malformed, or has the wrong type, or a leg is missing both its _address and _location_id.
400ADDRESS_INVALIDAn _address could not be geocoded, or matched more than one saved location. The error object's reason says which leg and which of the two.
401ACCESS_DENIEDMissing / invalid bearer token, OR the bearer token does not authorize the requested customer_id.
404REQUEST_INVALIDcustomer_id does not match an existing rooftop.
406OUTSIDE_REGIONThe addresses are valid but fall outside the rooftop's service area. Body carries reason: "OUT_OF_SERVICE_AREA" and out_of_service_area.
406REQUEST_INVALIDRooftop is not bookable (appointment management disabled, rooftop closed for the date, no drivers configured). See reason in body.
500UNKNOWNUnexpected server error.
502UNKNOWNDrive-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:

ReasonMeaning
APPOINTMENT_MANAGEMENT_DISABLEDThe 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_CONFIGUREDThe rooftop's driver count is 0. Contact HopDrive support to provision driver capacity.
OUT_OF_SERVICE_AREAAn address is real but too far from the rooftop. The out_of_service_area object names the leg, its straight-line distance, and the rooftop's radius.

Telling a bad address from an unserved one​

These are separate outcomes, and the reason field distinguishes them without parsing prose.

An address HopDrive cannot place on a map returns 400 with code address_invalid and a reason of PICKUP_ADDRESS_INVALID or DELIVERY_ADDRESS_INVALID. Correct the address and retry. When an address matches several saved locations for the rooftop the reason is PICKUP_ADDRESS_AMBIGUOUS / DELIVERY_ADDRESS_AMBIGUOUS, and a more specific address (or the _location_id) resolves it.

400 — address cannot be geocoded
{
"errors": [
{
"type": "validation_error",
"code": "address_invalid",
"message": "pickup_address could not be geocoded — check the address is real and complete",
"reason": "PICKUP_ADDRESS_INVALID",
"doc_url": "https://api.hopdrive.com/docs/error-codes#address_invalid"
}
]
}

An address that geocodes fine but sits beyond the rooftop's serviceable radius returns 406 with reason: "OUT_OF_SERVICE_AREA". The address is correct; HopDrive just doesn't drive that far for this rooftop. Distance is straight-line from the rooftop, the same measure the eligibility endpoint uses, and the radius comes from the rooftop's serviceable_radius_miles setting. Rooftops that leave that setting empty never return this reason.

406 — valid address, outside the service area
{
"rooftop_timezone": "America/Los_Angeles",
"eligible": false,
"reason": "OUT_OF_SERVICE_AREA",
"enforcement_mode": "forced",
"offset_increment_minutes": 10,
"offset_max_minutes": 45,
"out_of_service_area": {
"leg": "pickup",
"distance_miles": 74.6,
"serviceable_radius_miles": 25
},
"slots": [],
"errors": [
{
"type": "api_error",
"code": "outside_region",
"message": "The pickup address is 74.6 miles from this rooftop, outside its 25-mile service area.",
"reason": "OUT_OF_SERVICE_AREA",
"doc_url": "https://api.hopdrive.com/docs/error-codes#outside_region"
}
]
}

Neither of these looks like a rooftop with no capacity left. A day with every slot taken returns 200, eligible: true, and an empty slots array.

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​

406 /v1/appointments/availability
{
"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​

406 /v1/appointments/availability
{
"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:

200 /v1/appointments/availability
{
"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_timezone to render in the dealership's local clock.
  • Slot rounding is fixed at 5-minute intervals.
  • available_drivers is 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.