Skip to content

Create Order

POST {{BASE_HABITAT_URL}}/api/v3/orders

Required scope: orders:write

Creating an order can have a few different outcomes depending on your configuration

Section titled “Creating an order can have a few different outcomes depending on your configuration”
  • If you are not using Dispatch Strategies:

    • the order will appear in Dispatch under the column related to it’s status

    • from here it can be dragged to your fleet, or Pending External Driver to send to a DSP

  • If you are using Dispatch Strategies:

    • the order will appear in Dispatch under Pending External Driver, with the accepted fleet’s badge on it

    • once the order is assigned to a DSP driver, it will move to that runner’s row until completion

The most common reasons why fleets aren’t available are due to bad data:

  • Use real phone numbers for merchant and customer - many DSPs will reject on the basis of a fake phone number (and can be smart about it)

  • Keep the pickup and dropoff addresses in mind based on what you’re trying to test

    • Want as many DSPs as possible? Send the pickup address next door to dropoff.

    • Want as few/no DSPs to response? Send the dropoff address several states over.

Simulating delivery lifecycle webhooks (assign -> delivered)

Section titled “Simulating delivery lifecycle webhooks (assign -> delivered)”

Uber is the only DSP in our fleet network that will send webhooks in sandbox to simulate the delivery lifecycle. This effectively means that orders will not be assigned if:

  • An non-Uber quote_id ovverride is passed into this API request

  • If the configured Dispatch Strategy automation results in a non-Uber DSP being selected

Additionally, the timing of the assign and subsequent fulfilllment webhooks will respect the passed pickup_time / dropoff_time : we recommend passing neither when testing this functionality to expedite the webhooks.

By default, all DSP requests are sent on an ASAP basis. For scheduled orders, you can optionally pass in eitherpickup_time OR dropoff_time (not both!). Either of these dates must be greater than 20 minutes from the time of the request.

The First platform is unopinionated with the values you pass for order_total , tax and diner_delivery_fee - these will just pass through to the Order and Order Export objects for your reporting purposes.

On the other hand, subtotal and tip can have an affect when sending orders to DSPs

- subtotal - is the price of delivery items before any fees. When requesting a refund, the DSP will typically use this amount to calculate the refund total

- tip - is the driver’s tip. Zero/low tips can impact fleet availability, and when accepted by a fleet, cause delays due to drivers declining the order.

Passing dropoff address to maximize Provider accept rate

Section titled “Passing dropoff address to maximize Provider accept rate”

Dropoff address can be passed to us in a structured dropoff object, or as a string ( dropoff.full_address). To maximize provider accept rates, we strongly recommend

- 1: passing dropoff.full_addressthat is ideally the sanitized result of a geocoding API (Mapbox, Google Maps Geocoding / Places Widget); and

- 2: using dropoff.instructions to pass any apartment number, building names, etc.

If you are using the structured object address, you should ideally use dropoff.instructions in the same way as above to maximize Provider Accept rates and overall deliverability.

You may also pass dropoff.unit if you are highly confident that this value will resolve to a geocodable address, but in our experience, passing dropoff.unit typically lowers the percentage of DSP accepts due to bad user input (e.g dropoff instructions, building names, etc in this field)

Using a Quote to override your Dispatch Strategy

Section titled “Using a Quote to override your Dispatch Strategy”

Passing the id of a quote (see Get Provider Quotes) into this request as quote_id will attempt to create a delivery with that quote’s DSP, regardless of your strategy or any bans. This is useful if your end-users are able to choose a provider (e.g in the event of a cancel, hand-pick a different provider.

When sending a request with a quote_id, the order of operations is:

  1. Attempt to create a delivery using the exact quote

  2. If the quote/provider attempt fails, record the error and fall back to the strategy-ranked providers

> If the Quote flow is part of your end-user experience, we strongly encourage re-fetching quotes before the order is placed to minimize the possibility of changes in Provider pricing and/or availability.

Using a strategy_id to override your Dispatch Strategy

Section titled “Using a strategy_id to override your Dispatch Strategy”

Passing strategy_id (a strategy UUID) uses that dispatch strategy for this order instead of the merchant’s default. If the id does not exist, the request fails with strategy_id not found.

Strategy hours still apply. If the chosen strategy is outside its active hours, the order is created but not auto-dispatched.

Quote expiration varies depending on the DSP (some don’t even have the concept!) - so we stay unopinionated and don’t enforce a standard. A good rule of thumb timeframe is after ~5 minutes, you may start to get quote rejection errors. For the best user experience, we recommend calling the quote endpoint a second time directly before placing the order - this allows you to let the user confirm a DSP, price, or time discrepancy before placing.

If you’re using External Fleet mode and Strategies - a lot can happen between validation errors, DSP rejects, etc. auto_route object will tell you what happened:

  • enabled indicates that EXT. FLEETS is enabled

  • success false means the order was not successfully routed to a DSP

  • errors is an array of messages that tells you what our system / DSPs rejected

  • Phone validation: Some providers will validate the merchant and customer phone numbers for validity and/or by country

  • Address formatting: when passing full_address, placement of unit number, exclusion of state, zip or country, etc - can cause a Provider Reject. We highly recommend using full_address in combination with a geocoded result (e.g from Mapbox, Google APIs). Otherwise, pass us the address as an object (see fields below)

Name Optional (Default) Description
merchant_id The ID of the merchant to send the order to
quote_id Yes Quote ID from POST /v3/quotes response - used to override strategy
strategy_id Yes UUID of a dispatch strategy. When set, this order uses that strategy instead of the merchant’s default. An unknown id returns strategy_id not found.
customer_phone End-customer’s phone
customer_name End-customer’s name
subtotal Package cost before taxes and fees
tax Yes Tax
tip No Tip amount for runner
order_total Yes Total cost after all fees
diner_delivery_fee Yes. Defaults to territory. Delivery fee diner pays - only used for data exports
runner_commission Yes. Defaults to territory. Per-order amount to pay runner - does not apply to orders sent to DSP fleet network
payment_type Payment type. online credit_card orcash or catering - Online is the most common. Cash is not supported by all DSPs and requires additional config.
dropoff Dropoff address and instructions. See example payloads for structured vs. string
dropoff_time Yes. Defaults to ASAP. This overrides dropoff_time specified in the (optional) quote’s ID passed in
pickup_time Yes. Defaults to ASAP. This overrides dropoff_time specified in the (optional) quote’s ID passed in
pickup.instructions Pickup instructions for driver
contents name , quantity price required for certain DSP refund eligibility <br> <br>- price should be the SKU price, not SKU x quantity <br>- price fields are in dollars/local denomination - not cents! <br>- we use metric: height length width are centimeters, weight is grams. These four values are only used for Doordash and Uber. &#123; <br>"name": string, <br>"price": number, "quantity": number, <br>"height": number, "width": number, "modifiers": &#123; "name": string, "price": number <br>&#125;[] <br>&#125;[]
skip_routing_strategy Yes (false) If set to true, the order will not auto-dispatch to a fleet based on Strategy configuration.
options Yes Fulfillment options. Not every fleet supports every option; see Fleet-Specific Behavior below.
options.requires_signature Yes (false)
options.requires_dropoff_image Yes (false)
options.alcohol_contained Yes (false)
options.requires_pin Yes (false) Require the customer to give the courier a PIN at drop-off. Supported by Uber and Just Eat. For Uber, the PIN is the last 4 digits of customer_phone; see Fleet-Specific Behavior below.
options.contains_hemp Yes Flags the order as containing hemp/CBD products. When set, takes precedence over any account-level pre-configuration. See Hemp / CBD Caveats below.
external_id Yes Your primary key for the order, only used for your system’s utility (e.g exports)
external_order_reference Yes Your diner-friendly key for the order, used for customer support, UI.
Field Description
id Primary key for the order - used in all other endpoints and webhooks. We will return this on order creation (or you can optionally pass in a uuid V4).
quote_id The ID of the quote you accepted from the external DSPs /v3/quotes
dsps_delivery_id External DSP’s native ID for the order (if being fulfilled by First DSP fleet network). This is the ID you would use if contacting a DSP’s customer service for support.
provider External DSP fulfilling the order
_request_id Meta field for debugging specific requests

When options.contains_hemp: true is passed, the following DSP-specific fields are set automatically:

  • DoorDash — sets order_contains_list: ["hemp_items"] on the delivery request, which DoorDash requires to permit hemp product delivery.

  • Ally — adds "CBD" to controlledContents, and sets license: "on_file", contains_thc: "0.1%", and package_sealed: true on the delivery request.

> This flag supersedes any account-level pre-configuration. Explicitly passing options.contains_hemp: false will suppress these DSP fields even if your account has hemp pre-configured. Omitting the field entirely falls back to the account-level setting.

Fleets differ in which options they honor, what can be changed after an order is created, and which courier details they return. If the fleet that accepts an order doesn’t support an option, that option is not applied.

> Uber drop-off PIN: When options.requires_pin is true and Uber fulfills the order, the courier can’t complete the hand-off until the customer gives them a 4-digit PIN. Uber texts the PIN to the customer. The PIN is always the last 4 digits of customer_phone (for example, +15106809269 → 9269). If a customer calls because they can’t find the text, give them the last 4 digits of the phone number on the order. The PIN is not currently returned on the order or in webhooks.

Uber

  • requires_signature: the courier collects a signature. Without it, the courier takes a drop-off photo.
  • alcohol_contained: the courier checks ID (minimum age 21).
  • Orders with a signature, PIN, or alcohol requirement are returned to the store if they can’t be delivered. Other undeliverable orders are left at the door.
  • pickup.instructions and dropoff.instructions are truncated to 279 characters.
  • After creation, tip and instructions can be updated. Pickup and dropoff addresses can’t.
  • The courier’s phone_number_for_merchant can look like +13127666835,07728656. Dial the number, then enter the digits after the comma as an extension. This is not the customer’s drop-off PIN.

DoorDash

  • requires_pin is not supported.
  • requires_signature and alcohol_contained are supported. Orders with neither, and not paid in cash, may be left unattended at the door.
  • If both pickup_time and dropoff_time are sent, pickup_time is dropped and dropoff_time is used.
  • customer_name has unsupported characters removed and is truncated to 50 characters.
  • After creation, address, instructions, and tip can be updated.

Grubhub

  • requires_signature, requires_pin, and alcohol_contained are not supported. The courier tries to hand off the order and leaves it at the door if the customer doesn’t answer. Grubhub does not return undeliverable orders to the store.
  • A pickup_time or dropoff_time less than 75 minutes away is sent as an ASAP delivery. Times 75 minutes or more away are sent as a scheduled delivery.
  • After creation, only tip, dropoff instructions, and customer phone can be updated.

Just Eat (UK)

  • requires_pin: Just Eat generates the PIN and sends it to the customer. Don’t assume it matches the phone number. Orders that can’t be delivered are returned to the store.
  • alcohol_contained: the courier checks age (18+). Orders that can’t be delivered are returned to the store.
  • requires_signature is not supported.
  • customer_phone must be a valid UK number. Invalid numbers are replaced with a placeholder, so the courier won’t be able to call the customer.
  • dropoff.instructions is truncated to 275 characters.
  • Times at least 60 minutes away are sent as scheduled deliveries. Anything sooner is sent ASAP.
  • Orders with a total item weight of 40 kg or more request a car.
  • Orders can’t be updated after creation; cancel and recreate instead. Cancellation through the API only works before the courier collects the order.
  • Courier phone numbers aren’t provided. Webhooks show 0000000000.

Wolt

  • requires_signature, requires_pin, and alcohol_contained are not supported.
  • Tips are only passed to the courier when tipping is enabled for your account. Contact support to enable it.
  • Orders can’t be updated after creation. Cancellation only works before the courier starts pickup.
  • Courier name and phone aren’t provided. Webhooks show Wolt Driver and 0000000000.

Relay (US)

  • The courier always takes a drop-off photo, and alcohol_contained adds an ID scan. requires_signature and requires_pin are not supported.
  • customer_phone must be a valid US number, or the order is rejected.
  • After creation, address, customer phone, and tip can be updated.
  • If a rider drops the order, it goes back to awaiting assignment while Relay finds another rider. It is not cancelled.
{
"contents": [
{
"name": "Jigowatt Nachos",
"price": 9.99,
"quantity": 1
}
],
"customer_name": "David Baldwin",
"customer_phone": "+13179385775",
"diner_delivery_fee": 0.0,
"dropoff": {
"full_address": "2166 e norris st., philadelphia, PA",
"instructions": "Testing"
},
// "external_id": "018dd152-6174-b221-c0b5-7dfaf6fa0967",
"merchant_id": "{{$merchantId}}",
"order_total": 13.69,
"payment_type": "online",
// "partner_name": "toastpos",
// "strategy_id": "your-strategy-uuid",
"pickup": {
"instructions": "Pickup at the front door"
},
"subtotal": 9.99,
"tax": 0.7,
"tip": 3.0
}
Terminal window
curl --request POST '${BASE_HABITAT_URL}/api/v3/orders' \
--data '{
"contents": [
{
"name": "Jigowatt Nachos",
"price": 9.99,
"quantity": 1
}
],
"customer_name": "David Baldwin",
"customer_phone": "+13179385775",
"diner_delivery_fee": 0.0,
"dropoff": {
"full_address": "2166 e norris st., philadelphia, PA",
"instructions": "Testing"
},
// "external_id": "018dd152-6174-b221-c0b5-7dfaf6fa0967",
"merchant_id": "{{$merchantId}}",
"order_total": 13.69,
"payment_type": "online",
// "partner_name": "toastpos",
// "strategy_id": "your-strategy-uuid",
"pickup": {
"instructions": "Pickup at the front door"
},
"subtotal": 9.99,
"tax": 0.7,
"tip": 3.0
}'
{
"type": "success",
"order": {
"id": "3e151480-e51b-11ed-9b9c-bbd02a81f6bc",
"quote_id": "3e081c30-e51b-11ed-9b9c-bbd02a81f6bc",
"external_order_id": "your-unique-indentifier-for-order",
"external_order_reference": "your-customer-facing-identifier-for-order",
"dsps_delivery_id": null,
"_request_id": "eb8a169e-218e-4139-bc28-5c72e464da59",
"dsps_name": null,
"created_at": "2023-04-27T16:47:48.686Z",
"status": "pending_assign",
"method": "Delivery",
"timezone": "America/New_York",
"partner_name": null,
"pickup_name": "Mike's Mustard",
"pickup_address": "2000 S Christopher Columbus Blvd, Philadelphia, PA 19148, USA",
"pickup_phone": "2677660550",
"pickup_instructions": null,
"pickup_lat": 39.92,
"pickup_lng": -75.14,
"dropoff_lat": 39.98,
"dropoff_lng": -75.13,
"route_distance_miles": 5.84,
"crow_flies_distance_miles": 4,
"dropoff_address": "2166 E Norris St. Philadelphia, PA 19125",
"dropoff_suite": null,
"dropoff_instructions": "Take me to the river, drop me in the water",
"customer_name": "Steve",
"customer_phone": "2333344999",
"payment_type": "credit_card",
"pickup_estimate": "2023-04-27T17:02:48.199Z",
"dropoff_estimate": "2023-04-27T17:18:48.199Z",
"runner_assigned_at": null,
"runner_accepted_at": null,
"runner_arrived_origin": null,
"runner_picked_up": null,
"runner_arrived_dropoff": null,
"runner_dropped_off": null,
"cancelled_at": null,
"contents": [
{
"itemName": "Cheeszteak",
"quantity": 2,
"itemPrice": 5.99,
"modifiersText": [
{
"name": "Provolone",
"price": 2
}
]
}
],
"tip": 2,
"order_total": 9,
"runner": null,
"merchant_id": "751b26bf-0bdc-4d6f-804d-fe4b89e0027a",
"runner_id": null,
"skip_routing_strategy": false,
"diner_delivery_fee": null,
"runner_commission": 1.5,
"billable_merchant_fee": 5.99,
"dsps_delivery_fee": null
}
}