Reservations
Reservations API
The Reservations API lets fleet partners reserve an arrival window at an Autolane stall so a runner can load an order into an autonomous vehicle or sidewalk robot. Discover reservable sites, zones, and retailers, create and manage reservations, and register webhook endpoints that receive a signed event each time a reservation changes status.
Authentication
All requests require an Autolane API key in the Authorization header:
Create API keys in the Autolane Portal under Integrations > API Keys. Keys are environment-specific: a sandbox key only works against the sandbox host, and a production key only against the production host. Toggle dev mode in the portal to choose which environment a new key targets. See Authentication for the full walk-through.
Your key must also belong to an organization enrolled as an Autolane fleet partner; other keys receive 403 NOT_FLEET_PARTNER even with the right permission. Contact Autolane to enroll.
Permissions
Each endpoint checks a permission on your key:
Webhook endpoints receive whole reservations, so pointing one at a URL costs the scope that reads them. Registering an endpoint with POST /rs/v1/webhooks needs reservations:read as well as webhooks:manage. The same holds for a PUT /rs/v1/webhooks/{id} that changes url or subscribed_events, or sets is_active to true. What counts is the change, not the field you send: a value you send back unchanged costs nothing. Setting is_active to false and rotating the secret need webhooks:manage alone, so you can always stop a stream or replace a leaked secret. That also means a key holding webhooks:manage alone can mint a new secret and read it in full, and that secret is the key that signs reservation events into your endpoint; grant the scope accordingly. A 403 names the fields that asked for more.
Hosts
Sandbox site
The sandbox contains one synthetic site so you can integrate before any real configuration exists. Its ids are stable and safe to reference in tests:
Real sites and retailers configured for sandbox testing appear alongside it in the same response.
Reservation lifecycle
A reservation moves through requested, confirmed, arrived, completed, or canceled:
Rules that follow from the lifecycle:
- The arrival window can change (
PATCH /rs/v1/reservations/{id}) only while the reservation isrequestedorconfirmed. Once it isarrived,completed, orcanceled, the window is fixed. Moving a window keeps the assigned stall and changes nothing else, and the new window is checked against the same rules as create. - Cancel (
DELETE /rs/v1/reservations/{id}) works from any status exceptcompletedand is safe to retry: canceling an already canceled reservation returns200with the same reservation. A cancel through the API recordscanceled_by: partner, and the optionalreasonquery parameter (up to 500 characters) is stored on the reservation and echoed back ascancel_reason. - Every status change fires a
reservation.status_changedwebhook. Moving a window fires nothing, because the window is not a status and you already know about the change. A cancel retry that finds the reservation already canceled returns200and fires nothing: treat the200as your confirmation and never wait on the webhook.
Creating a reservation
POST /rs/v1/reservations books an arrival window in a zone for one retailer order:
Create-time rules:
- Zone and retailer.
zone_idandretailer_idcome fromGET /rs/v1/retailers, and the retailer must be actively supported in the zone. Poll discovery to keep your picker current; zones with no supported retailers are omitted from its response. - Arrival window. The window must start in the future, start no more than 24 hours ahead, and be between 5 and 120 minutes wide. Those three limits are deployment defaults and can be tuned per environment.
- Phone.
end_user_phonemust be E.164, for example+15125550123. - Open link.
open_link_urlis the HTTPS endpoint our backend calls to open the vehicle door or trunk. It must usehttps://and resolve to a public address; we check that at create and reject anything else with422 INVALID_OPEN_LINK. - Stall assignment. A stall is assigned by soft hold: the active stall holding the fewest reservations that overlap your window and are not yet completed or canceled. Capacity never rejects a booking, so a busy zone stacks reservations on its least loaded stall.
In this version a reservation is confirmed synchronously, so the 201 response already carries status: "confirmed". That confirm is a real status change, so a successful create also fires a reservation.status_changed webhook carrying confirmed for the reservation you just got a 201 for. Expect it; it is not a duplicate.
Idempotent retries
Send the optional Idempotency-Key header (1 to 255 characters) to make retries safe. Use one key per reservation you intend to create, and resend the same value when you retry. A retry with a key you already used returns the stored reservation with 200 instead of creating a second one. A retry whose zone, retailer, order number, end user name, end user phone, or open link differs from the stored reservation is rejected with 422 IDEMPOTENCY_KEY_REUSED; the arrival window is excluded from that comparison because it can be changed later with PATCH.
If create answers 503 URL_VALIDATION_UNAVAILABLE, our capacity to validate open_link_url is exhausted; that is not a problem with your URL. Retry the same request, reusing your Idempotency-Key if you sent one.
Errors
Every error response has one shape:
code is stable and machine-readable; branch on it, never on the error text. The codes:
Webhooks
Register HTTPS endpoints that receive reservation events. Autolane signs every delivery with the secret belonging to the endpoint it is sent to.
Registering an endpoint
Registration needs reservations:read as well as webhooks:manage (see Permissions):
The URL must resolve to a public address; we check that at registration. The response carries the endpoint’s signing secret (prefixed whsec_) in full for the only time. Store it before you move on: every later read masks it as whsec_…cdef, showing only the last four characters, and the only way to get a usable secret again is to rotate it.
An organization may hold at most 10 endpoints, and deactivated endpoints still count against that limit because endpoints cannot be deleted. To move traffic to a new URL once you are at the limit, point an existing endpoint at it with PUT /rs/v1/webhooks/{id}.
Unlike creating a reservation, this endpoint ignores the Idempotency-Key header. A create you retry after a lost response can register a second endpoint that then receives every event alongside the first, turning each status change into two deliveries. Duplicates show up in GET /rs/v1/webhooks and can be switched off with PUT.
Event payload
The only event today is reservation.status_changed. Each delivery is a POST to your URL:
The reservation object is the same shape the reservation endpoints return, as it stood at the moment the event occurred.
Each request carries these headers:
Verifying signatures
Every delivery carries X-Autolane-Signature: t=<unix-seconds>,v1=<hex>. The hex is an HMAC-SHA256 over the exact string "<t>.<raw request body>". The key is the endpoint secret as a whole string, whsec_ prefix included, taken as UTF-8 bytes: do not strip the prefix, and do not hex-decode the 48 characters after it. Verify by recomputing that HMAC over the raw bytes you received, before any JSON parsing, and comparing constant-time.
A matching hex is not enough. Check that t is close to your own clock and reject the delivery when it is not. Five minutes either way is a sound tolerance and leaves room for clock skew. Skip this step and a captured delivery stays valid forever. The timestamp is signed along with the body, so once the hex matches you know t came from us and not from whoever replayed it.
A rejection spends a retry attempt, and the retry schedule below runs out: a delivery your clock rejected 8 times is dropped for good. There is no replay endpoint, so if you have rejected deliveries or suspect your clock is off, read current state from GET /rs/v1/reservations instead of waiting for us to send it again.
Delivery is at-least-once: dedup on two keys
Every delivery carries X-Autolane-Delivery-Id, and every payload carries event_id. They are different keys and you need both:
- The delivery id names one delivery. Delivery is at-least-once, so the same delivery id can arrive more than once, and a repeat is not a new event. Dedup on the delivery id to drop retries.
- The event id names one status change. Two of your endpoints subscribed to the same event (a retried create can leave two of them on one URL) turn a single change into two deliveries carrying two delivery ids and one shared event id. Dedup on the event id to collapse one event that reached you through more than one endpoint.
Ordering
Deliveries are not ordered. Each one retries on its own schedule and several are sent at once, so a later event can land before an earlier one. Order by occurred_at, never by arrival. Every payload carries the reservation as it stood at that moment, so applying deliveries in the order they turn up can walk a reservation backwards.
Retries
Up to 8 attempts per delivery. Any non-2xx response fails an attempt, and redirects are not followed, so a 3xx fails too. Each attempt is given 10 seconds. Backoff doubles between attempts (2, 4, 8 and so on, in minutes) and is capped at 60 minutes; after the eighth attempt the delivery is dropped. Acknowledge with a 2xx quickly and do heavy processing after you respond.
Managing endpoints
PUT /rs/v1/webhooks/{id} changes the URL, the subscribed events, or the active flag, and rotates the secret. Every field is optional, but the body must carry at least one of them. Concurrent PUTs are last write wins: every field you send is written whether or not it changed, so a body built from a stale GET puts back what it read.
- Pausing. Set
is_activetofalseto stop deliveries without giving up the slot. While an endpoint is inactive no new event is queued for it, but a delivery already queued gets no promise either way: it may be discarded, or let through if you switch back on quickly. Do not build on either outcome. - Repointing. Point an existing endpoint at a new URL to reuse a slot, but repoint only to a host you control: the URL is read when a delivery is sent, not when it is queued, so every pending delivery built up under the old URL goes to the new host, and a backlog spanning about three hours of the retry schedule can arrive as a burst.
- Rotating the secret. Send
rotate_secret: trueto mint a new secret. The response returns it in full exactly once, and new deliveries are signed with it at once, but a delivery already picked up for sending keeps the old secret for up to 90 seconds. Accept both signatures for a few minutes after a rotation rather than cutting the old one off. Rotation is not idempotent, and this endpoint ignoresIdempotency-Key: aPUTyou retry after a lost response rotates a second time, and the secret from the first rotation is gone for good. If a rotate response goes missing, rotate again and use the secret you get back.
Sandbox walkthrough
POST /rs/v1/reservations/{id}/advance exists only on the sandbox host. It walks a reservation one lifecycle step per call (confirmed to arrived to completed) so you can drive the whole lifecycle without a vehicle, and each step fires the same reservation.status_changed webhook a real arrival would. On production the route always answers 404 NOT_FOUND, before your key is even checked.
Drive a reservation end to end against the sandbox:
1. Create a reservation using the sandbox zone and retailer (see Creating a reservation for the full request). The 201 response carries status: "confirmed", and your webhook endpoint receives a reservation.status_changed event carrying confirmed.
2. Advance to arrived:
The response carries status: "arrived" and your endpoint receives the matching event.
3. Advance to completed: run the same call again. The response carries status: "completed", the final event arrives, and the reservation is done.
4. Advance past the end: a third call answers 409 INVALID_STATE, because a completed reservation has no next step. The same 409 comes back for a canceled reservation.
To exercise the cancel path instead, stop after step 1 or 2 and call DELETE /rs/v1/reservations/RESERVATION_ID; the event that follows carries canceled with canceled_by: "partner".