Direct Delivery
Direct Delivery API
The Direct Delivery API enables retailers and DSP aggregators to request autonomous delivery quotes, create autonomous deliveries from those quotes, and receive real-time status updates via webhooks.
The flow is two-step: quote first, then create. A quote returns availability, pickup ETA, delivery ETA, and locked delivery cost; the create call promotes that quote into an active delivery, honoring the same locked terms.
Authentication
All requests require an Autolane API key in the Authorization header and a per-site x-retailer-id header. Your organization and retailer context are resolved automatically from these.
Permissions
API keys carry granular permissions:
Create API keys in the Autolane Portal under Integrations > API Keys. Toggle dev mode in the portal to switch between production and sandbox keys; the dev-mode badge confirms which environment your new key targets. See Authentication for the full walk-through.
Request fields
external_delivery_id
A retailer-provided string (1 to 255 characters) that uniquely identifies the delivery in your own system. Scoped per-retailer: two different retailers can use the same external_delivery_id without collision.
The same value is also the idempotency key for the create call. Re-POSTing POST /dd/v1/deliveries with an external_delivery_id whose quote was already consumed returns 409 QUOTE_ALREADY_CONSUMED (see Idempotency below).
customer.phone
A phone number in strict E.164 format: a leading +, then 7 to 15 digits, with the first digit between 1 and 9.
delivery_address
Required fields: street, city, state, zip. Optional unit and location ({ lat, lng }). Provide location to skip server-side geocoding; omit it and the API geocodes the address. A GEOCODE_FAILED response (422) means geocoding came back empty.
ready_by
ISO 8601 timestamp for when the order will be ready at the store. Must be in the future relative to server clock. Failures return 400 INVALID_READY_BY.
order_items
An array of up to 50 items. Each item is a JSON object with no required fields and a 4096-byte stringified-JSON cap per item.
In v1 the field is free-form to support a wide range of retailer SKU shapes. We strongly recommend including at least:
A future API version will enforce these as required fields; integrations that ship them now will not need to change.
pickup_instructions and dropoff_instructions
Optional free text, up to 2000 characters each, accepted on the quote and on PATCH /dd/v1/deliveries/{external_delivery_id}. pickup_instructions is the store-side note shown to the driver from assignment until the order is loaded, such as a pickup or driver code. dropoff_instructions is the customer-side note shown to the driver on the way to and at the customer, such as a gate code or where to leave the order. On PATCH, send one or both fields without status; null or an empty string clears a field, an absent field is left unchanged, and edits are accepted until the delivery reaches COMPLETED, FAILED, CANCELLED, EXPIRED, or a return status. Create, list, and get responses return both fields, as does the PATCH response after an instructions edit; a cancel response carries only external_delivery_id and status.
Integrations created before dropoff_instructions existed may be in legacy mode, where pickup_instructions is treated as the dropoff note and both fields read back the same value; when both are sent, dropoff_instructions is used unless it is null or empty. Ask your Autolane contact to switch your account to the current behavior. In legacy mode the two names address one shared note, so clearing works the same way: send either field on its own as null or an empty string, or send both as null. A body such as { "pickup_instructions": "A", "dropoff_instructions": null } sets the shared note to A, because a null dropoff_instructions never erases a note sent under pickup_instructions.
Delivery Lifecycle
One status vocabulary
A delivery’s status speaks a single vocabulary everywhere it appears — the response to POST /dd/v1/deliveries, the top-level status on GET /dd/v1/deliveries and GET /dd/v1/deliveries/{external_delivery_id}, the list endpoint’s ?status= filter, and the status carried by pickup_status.changed webhooks.
Values: SCHEDULED, ASSIGNED, EN_ROUTE_TO_STORE, ARRIVED_AT_STORE, WAITING_FOR_LOAD, LOADED, EN_ROUTE_TO_CUSTOMER, ARRIVED_AT_CUSTOMER, COMPLETED, FAILED, CANCELLED, EXPIRED, RETURN_IN_PROGRESS, RETURN_ARRIVED, RETURNED.
- A newly created delivery returns
status: "SCHEDULED". EXPIREDis a terminal status for deliveries that were never dispatched beforeready_byplus the grace period; it never appears in webhooks (no dispatch ever happened).- The nested
pickup.statusfield on GET/list responses carries the same value as the top-levelstatusand is retained for response-shape stability — read the top-level field. RETURN_IN_PROGRESS,RETURN_ARRIVED, andRETURNEDappear only on sites with returns enabled, when a cancel lands after the order is loaded.RETURNEDis terminal. See Returns.
The diagram above traces the full lifecycle.
Quote Semantics
- A quote is identified by
(retailer, external_delivery_id). Re-quoting with the same key replaces the prior quote. - A quote is valid for 5 minutes. After
expires_at, callingPOST /dd/v1/deliveriesreturns410 QUOTE_EXPIRED. - A quote can only be consumed once. After a successful create, re-creating with the same
external_delivery_idreturns409 QUOTE_ALREADY_CONSUMED. - A quote with
available: falsecannot be promoted to a delivery. Calling create returns422 QUOTE_NOT_AVAILABLE.
Cancellation
Cancel a delivery via PATCH /dd/v1/deliveries/{external_delivery_id} with { "status": "CANCELLED" }. The body may also carry an optional cancellation_reason: CUSTOMER_CANCELLED, COLD_CHAIN_EXPIRED, or OTHER. Any other value is recorded as OTHER. The reason never decides what happens; the delivery’s status does.
What a cancel does depends on where the delivery is:
Every 409 carries details.current_status with the delivery’s current status. A 200 body is { "success": true, "data": { "external_delivery_id": "...", "status": "..." } }. Returns are enabled per site; ask your Autolane contact to switch them on. The same PATCH endpoint edits pickup_instructions and dropoff_instructions when sent without status; see Request fields above.
Returns
When a site has returns enabled and a cancel arrives at LOADED or later, the vehicle brings the order back to the store it was picked up from. The delivery keeps its external_delivery_id and reports the return through the same pickup_status.changed webhook, with three statuses:
Guarantees:
- The order is always
RETURN_IN_PROGRESS, thenRETURN_ARRIVED, thenRETURNED. Return events for one delivery are sent in that order: a later return event waits until the earlier one was delivered to your endpoint or given up on after its retries. This holds even when the cancel response itself carriesRETURN_ARRIVEDdirectly (vehicle already at the store, see the cancel table above): theRETURN_IN_PROGRESSwebhook still goes out first. - After
RETURN_IN_PROGRESS, the delivery never reportsEN_ROUTE_TO_CUSTOMER,ARRIVED_AT_CUSTOMER, orCOMPLETED. - A cancel that starts a return never reports
CANCELLEDorFAILED. - If the handoff to the customer and the cancel race, the handoff wins: the cancel gets
409 DELIVERY_ALREADY_COMPLETED. The handoff counts as under way once the trunk has opened at the customer’s door, even before the delivery reportsCOMPLETED;details.current_statusisARRIVED_AT_CUSTOMERwhile the handoff is still under way, orCOMPLETEDonce it has finished. stall_idnever changes during a return. The return stall is a separate field,return_stall_id.
Fields on the return statuses, on the webhook and on GET /dd/v1/deliveries/{external_delivery_id}:
Payload examples are on the webhook page.
Idempotency
The Direct Delivery API does not use a separate Idempotency-Key header. The external_delivery_id you set on the quote and create calls is the idempotency key:
POST /dd/v1/deliveries/quotewith the sameexternal_delivery_idreplaces the prior quote in place (race-safe via PostgresON CONFLICT); the response always reflects the latest quote.POST /dd/v1/deliverieswith anexternal_delivery_idwhose quote was already consumed returns409 QUOTE_ALREADY_CONSUMED; the response is final and safe to surface to the caller without retrying.
Webhook delivery has its own idempotency contract; see Idempotency on the webhook page.