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.

$curl -X POST https://api.goautolane.com/dd/v1/deliveries/quote \
> -H "Authorization: Bearer YOUR_API_KEY" \
> -H "x-retailer-id: a1b2c3d4-e5f6-7890-1234-567890abcdef" \
> -H "Content-Type: application/json" \
> -d '{ ... }'

Permissions

API keys carry granular permissions:

PermissionGrants
deliveries:readList and view deliveries.
deliveries:writeRequest quotes, create deliveries, and update or cancel them. Required for any DSP-aggregator integration that books deliveries.
webhooks:manageRegister, update, and delete webhook endpoints.

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.

+15551234567 valid
+442072343456 valid
15551234567 rejected (missing leading +)
+0123456 rejected (leading digit is 0)

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.

1{
2 "order_items": [
3 { "name": "12oz Latte", "quantity": 1, "sku": "BEV-1024", "unit_price_cents": 525 },
4 { "name": "Croissant", "quantity": 2 }
5 ]
6}

In v1 the field is free-form to support a wide range of retailer SKU shapes. We strongly recommend including at least:

FieldRecommended value
nameShort human-readable item name surfaced to the driver during pickup.
quantityInteger count.
skuYour internal SKU identifier; useful for reconciliation and (future) returns flows.
unit_price_centsPer-unit price in cents. Enables future receipt/refund flows and per-item reporting.

A future API version will enforce these as required fields; integrations that ship them now will not need to change.

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.

  • A newly created delivery returns status: "SCHEDULED".
  • EXPIRED is a terminal status for deliveries that were never dispatched before ready_by plus the grace period; it never appears in webhooks (no dispatch ever happened).
  • The nested pickup.status field on GET/list responses carries the same value as the top-level status and is retained for response-shape stability — read the top-level field.

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, calling POST /dd/v1/deliveries returns 410 QUOTE_EXPIRED.
  • A quote can only be consumed once. After a successful create, re-creating with the same external_delivery_id returns 409 QUOTE_ALREADY_CONSUMED.
  • A quote with available: false cannot be promoted to a delivery. Calling create returns 422 QUOTE_NOT_AVAILABLE.

Cancellation

Cancel a delivery via PATCH /dd/v1/deliveries/{external_delivery_id} with { "status": "CANCELLED" }. Cancellation is allowed while the delivery is in the pre-load window (SCHEDULED through WAITING_FOR_LOAD). Once the order is loaded (LOADED and later) or in a terminal state (COMPLETED, FAILED, CANCELLED, EXPIRED), the request returns 409 and details.current_status carries the delivery’s current status. If a pickup is in progress within the cancellable window, it is cancelled along with the delivery.

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/quote with the same external_delivery_id replaces the prior quote in place (race-safe via Postgres ON CONFLICT); the response always reflects the latest quote.
  • POST /dd/v1/deliveries with an external_delivery_id whose quote was already consumed returns 409 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.

Error Codes

HTTPCodeWhen
400VALIDATION_ERRORRequest body fails Zod validation. details carries per-field messages.
400INVALID_READY_BYready_by is in the past.
400MISSING_RETAILER_IDx-retailer-id header is missing.
400INVALID_RETAILER_IDx-retailer-id is not a valid UUID.
401INVALID_API_KEYBearer token missing or invalid.
403PERMISSION_DENIEDAPI key lacks the required deliveries:* or webhooks:* scope.
403WRONG_ENV_KEYProduction key used against sandbox host or vice versa.
403ORG_NOT_MAPPEDAPI key’s organization isn’t mapped to a brand in Autolane.
403RETAILER_NOT_AUTHORIZEDThe site identified by x-retailer-id isn’t authorized for this organization.
403RETAILER_INACTIVEThe retailer resolved from x-retailer-id is not active.
404QUOTE_NOT_FOUNDCreate-delivery called for an external_delivery_id with no quote.
404NOT_FOUNDGET or PATCH for a delivery that doesn’t exist.
409QUOTE_ALREADY_CONSUMEDCreate-delivery called for a quote that has already been consumed.
410QUOTE_EXPIREDCreate-delivery called for a quote past its expires_at.
422QUOTE_NOT_AVAILABLECreate-delivery called for a quote where available: false.
422GEOCODE_FAILEDQuote address could not be geocoded and no location was provided.
500INTERNAL_ERRORUnexpected server error. Retry-safe for idempotent operations; please report repeats.
503RETAILER_NOT_CONFIGUREDRetailer is missing service-area or delivery_price_cents configuration.