Booking Recommendations API
The Booking Recommendations API starts a read-only search for ranked delivery windows. It accepts a destination, fulfilling branch, delivery date, and cart, then returns an asynchronous poll handle.
Authentication
Send an account API key with HTTP Basic authentication:
Authorization: Basic <base64(externalUserId:apiKey)>
The fulfillingBranchId must be the API-key account or one of its descendant
accounts. Recommendations only consider routes in that branch's subtree, even
when a parent API key can access a larger account family. Sandbox API keys
produce sandbox-isolated recommendations.
Start a recommendation
POST /v1/booking/recommend
Content-Type: application/json
{
"cart": {
"items": [
{
"description": "Air conditioner",
"dimensionsCm": {
"height": 90,
"length": 120,
"width": 60
},
"quantity": 1,
"weightKg": 75
}
],
"orderValueCents": 12500
},
"contact": {
"email": "[email protected]",
"name": "Ada Buyer"
},
"date": "2026-08-15",
"deliveryMethod": "cargo-van",
"destination": {
"addressLine1": "123 Main St",
"city": "Los Angeles",
"postalCode": "90012",
"state": "CA"
},
"fulfillingBranchId": "acc_branch",
"origin": {
"addressLine1": "456 Branch Ave",
"city": "Oakland",
"postalCode": "94607",
"state": "CA"
},
"timeframeBucket": "FLEXIBLE"
}
Dimensions are centimeters, weight is kilograms, and order value is integer
cents. A single aggregate cart item is valid when SKU-level data is unavailable.
timeframeBucket is optional; accepted values are MORNING, AFTERNOON,
EVENING, AFTER_8PM, and FLEXIBLE. The account's slot policy controls which
buckets are available and supplies the default when the field is omitted.
origin is optional and overrides the pickup address. When it is omitted, the
fulfilling branch's configured location is used. Coordinates may be supplied
with the override; otherwise the API geocodes the address.
A valid request returns 202:
{
"jobId": "recommendation-problem-id",
"problemId": "recommendation-problem-id",
"status": "processing"
}
Submitting identical result-affecting input returns the same deterministic handle. Live and sandbox requests never share a handle.
Poll for the result
Poll with the jobId returned above:
GET /v1/booking/jobs/{jobId}
While the recommendation is still computing, the API returns 202:
{
"status": "processing"
}
The response includes Retry-After: 2; wait at least that many seconds before
polling again.
When it completes, the API returns 200 with the ranked delivery windows:
{
"status": "complete",
"windows": [
{
"deliveryWindowStart": "2026-08-15T14:00:00.000Z",
"deliveryWindowEnd": "2026-08-15T16:00:00.000Z"
}
]
}
Each window exposes only the proposed delivery window. Windows are ordered best first.
If the search completes but no honorable scheduled window is available, the
response still completes successfully with an empty windows array and a
fallback signal indicating the caller should fall back to an on-demand
("rush") delivery:
{
"status": "complete",
"windows": [],
"fallback": "asap"
}
A jobId is only pollable by the account that created it, and results are
retained for 10 minutes after completion.
Errors
Invalid input returns 400 invalid_input. An unknown branch returns
400 branch_not_found; an out-of-scope branch returns
403 branch_out_of_scope. If no usable pickup origin can be resolved, the API
returns 400 origin_unresolved. If the destination postal code has no known
timezone, the API returns 400 timezone_not_found. Rate-limited requests
return 429 with both a Retry-After header and retryAfterSeconds in the
error body.
Slot-policy failures return 400 date_out_of_range, 400 date_blackout,
400 lead_time_not_met, 400 same_day_cutoff_passed, or
400 timeframe_bucket_not_available.
Transient server failures return 500 input_resolution_failed or
500 enqueue_failed; retrying the same request is safe and idempotent.
When polling, an unknown, expired, or out-of-scope jobId returns
404 job_not_found, and a recommendation that failed to produce a usable
result returns 500 optimization_failed (retrying is safe).