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.