> For the complete documentation index, see [llms.txt](https://routelift.gitbook.io/routelift-api-integration/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://routelift.gitbook.io/routelift-api-integration/api-reference/editor.md).

# Create Delivery

## Create a Delivery

Create a delivery request by submitting the pickup and drop-off details, plus the delivery type you want.

{% hint style="warning" %}
**Estimate first, then create.** The `deliveryType` you send here must be one that the estimate endpoint returned for this exact route. Sending anything else is rejected with a `400`. See Choosing a `deliveryType`.
{% endhint %}

### The two-step flow

1. `POST /v1/deliveries/estimate/v2` — price the route and find out which delivery types are available for it.
2. `POST /v1/deliveries` — create the delivery, passing a `deliveryType` taken from step 1.

### Choosing a `deliveryType`

Take the value from the estimate response. There are exactly two valid sources:

| Source in the estimate response            | Use it when                                                  |
| ------------------------------------------ | ------------------------------------------------------------ |
| `data.recommendedOption.deliveryType`      | Default. Use this unless the customer picked something else. |
| `data.otherDeliveryOptions[].deliveryType` | The customer chose one of the alternatives you displayed.    |

```js
// Recommended option
const { recommendedOption, otherDeliveryOptions } = estimate.data;
let deliveryType = recommendedOption.deliveryType;

// …or an alternative the customer picked, validated against what was offered
const chosen = otherDeliveryOptions.find(o => o.deliveryType === userChoice);
if (chosen) deliveryType = chosen.deliveryType;
```

**Do not** hardcode a delivery type, keep a static dropdown of types, or reuse a type from a previous route. Availability is computed per request from:

* the **service area** the pickup falls in — tiers are piloted area by area,
* the **distance** between pickup and drop-off,
* the **weight** and **package size** of the shipment,
* RouteLift's global rollout state for that tier.

The same code that works in Ikeja today may have only Same-Day available in another area, or gain Express next month. Always render the options the estimate actually returned.

{% hint style="info" %}
Same-Day is available everywhere RouteLift operates and is always safe as a fallback. Every other type is opt-in per area.
{% endhint %}

#### Delivery types

These are the values that can appear in an estimate response.

| `deliveryType` | Label    | Meaning                                                              |
| -------------- | -------- | -------------------------------------------------------------------- |
| `express`      | Express  | Fastest — dispatched immediately.                                    |
| `same-day`     | Same-Day | Delivered today. Available in every area.                            |
| `economy`      | Economy  | Next-day, lower cost.                                                |
| `local`        | Local    | Hyperlocal bicycle delivery, short distance and light packages only. |

### Endpoint

```
POST /v1/deliveries
```

| Environment | Base URL                                                   |
| ----------- | ---------------------------------------------------------- |
| Test        | `https://sandbox-integrations.routelift.com/v1/deliveries` |
| Live        | `https://integrations.routelift.com/v1/deliveries`         |

### Request

**Method:** `POST` **Content-Type:** `application/json` **Authorization:** `Bearer <apiKey>`

```json
{
  "pickups": [
    {
      "senderAddress": "12 Allen Avenue, Ikeja",
      "senderName": "John Doe",
      "senderPhone": "08012345678",
      "senderEmail": "john@example.com",
      "senderNote": "Call when outside",
      "senderAltPhone": "08098765432",
      "senderGeoCoord": {
        "latitude": 6.6018,
        "longitude": 3.3515
      },
      "packageSize": "medium",
      "weight": 50,
      "dimensions": {
        "length": 50,
        "width": 30,
        "height": 20
      },
      "vehicle": "bike",
      "scheduledPickUpDay": "2025-05-22T10:00:00Z",
      "quantity": 1,
      "categories": ["Food and Beverages", "Shoes", "Clothing", "Electronics"]
    }
  ],
  "receiver": {
    "receiverAddress": "45 Admiralty Way, Lekki Phase 1",
    "receiverName": "Jane Doe",
    "receiverPhone": "08123456789",
    "receiverAltPhone": "07011223344",
    "receiverEmail": "jane@example.com",
    "receiverNote": "Deliver before 5PM",
    "receiverGeoCoord": {
      "latitude": 6.4301,
      "longitude": 3.4291
    }
  },
  "orderId": "ORD12345",
  "paymentMethod": "wallet",
  "deliveryType": "same-day"
}
```

#### Top-level fields

| Field               | Type   | Required | Notes                                                                           |
| ------------------- | ------ | -------- | ------------------------------------------------------------------------------- |
| `pickups`           | array  | Yes      | One entry per pickup point. At least one.                                       |
| `receiver`          | object | Yes      | The single drop-off.                                                            |
| `deliveryType`      | string | Yes      | Must come from the estimate response — see above.                               |
| `paymentMethod`     | string | No       | `wallet` (debited immediately) or `invoice` (billed against your credit limit). |
| `orderId`           | string | No       | Your own reference. Echoed back in the response and in webhooks.                |
| `discountCode`      | string | No       | Discount code to apply.                                                         |
| `logisticCompanyId` | string | No       | Route to a specific logistics partner.                                          |

#### `pickups[]`

| Field                | Type      | Required | Notes                                                                                                                                      |
| -------------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `senderAddress`      | string    | Yes      | Pickup address.                                                                                                                            |
| `senderName`         | string    | Yes      | Contact at the pickup.                                                                                                                     |
| `senderPhone`        | string    | Yes      | Contact phone at the pickup.                                                                                                               |
| `senderGeoCoord`     | object    | Yes      | `{ latitude, longitude }`. Required for API integrations — a request without it is rejected.                                               |
| `packageSize`        | string    | Yes      | `small`, `medium` or `large`.                                                                                                              |
| `quantity`           | integer   | Yes      | Number of items at this pickup.                                                                                                            |
| `categories`         | string\[] | Yes      | At least one category, e.g. `["Electronics"]`.                                                                                             |
| `vehicle`            | string    | Yes      | `car`, `van`, `bus`, `truck`, `bike`, `motorcycle`, `bicycle` or `auto`. The vehicle that actually runs the job is the accepting driver's. |
| `weight`             | number    | No       | Kilograms, minimum `0.1`. Rounded **up** to the next whole kg for pricing.                                                                 |
| `dimensions`         | object    | No       | `{ length, width, height }` in cm, each minimum `1`.                                                                                       |
| `senderEmail`        | string    | No       | Must be a valid email if supplied.                                                                                                         |
| `senderAltPhone`     | string    | No       | Backup contact number.                                                                                                                     |
| `senderNote`         | string    | No       | Instructions for the rider at pickup.                                                                                                      |
| `scheduledPickUpDay` | date      | No       | ISO 8601. Requested pickup day for this pickup.                                                                                            |

#### `receiver`

| Field              | Type   | Required | Notes                                                                                        |
| ------------------ | ------ | -------- | -------------------------------------------------------------------------------------------- |
| `receiverAddress`  | string | Yes      | Drop-off address.                                                                            |
| `receiverName`     | string | Yes      | Contact at the drop-off.                                                                     |
| `receiverPhone`    | string | Yes      | Contact phone at the drop-off.                                                               |
| `receiverGeoCoord` | object | Yes      | `{ latitude, longitude }`. Required for API integrations — a request without it is rejected. |
| `receiverAltPhone` | string | No       | Backup contact number.                                                                       |
| `receiverEmail`    | string | No       | Delivery notifications.                                                                      |
| `receiverNote`     | string | No       | Instructions for the rider at drop-off.                                                      |

{% hint style="info" %}
Use the **same** coordinates, package size and weight you sent to the estimate endpoint. If they differ, the available delivery types and the final price can differ from the quote you showed the customer.
{% endhint %}

### Response

**Content-Type:** `application/json`

```json
{
  "success": true,
  "data": [
    {
      "order_id": "ORD12345",
      "trackingCode": "UHSQAD",
      "deliveryOtp": "12342",
      "price": 4000
    },
    {
      "order_id": "ORD12345",
      "trackingCode": "EQ6NJH",
      "deliveryOtp": "12342",
      "price": 4000
    }
  ],
  "message": "Delivery created successfully"
}
```

#### Response fields

`data` is an array with **one entry per pickup** in your request. A request with two pickups returns two tracked jobs.

| Field          | Type   | Notes                                                                                                                  |
| -------------- | ------ | ---------------------------------------------------------------------------------------------------------------------- |
| `order_id`     | string | The `orderId` you supplied, echoed back.                                                                               |
| `trackingCode` | string | RouteLift's tracking reference for this job. Use it with the tracking and cancel endpoints.                            |
| `deliveryOtp`  | string | Code the receiver gives the rider to confirm delivery. `null` if not issued.                                           |
| `price`        | number | The amount actually charged for this job, in naira (NGN). This — not the estimate quote — is the authoritative figure. |

### Errors

#### Delivery type not available for this route

Sent when the `deliveryType` you requested cannot be run for this pickup, distance, weight or package size. The payload lists what *is* available, so you can re-prompt the customer without a second estimate call.

```json
{
  "success": false,
  "message": "Express delivery is not supported for this delivery. Please select one of the available delivery types.",
  "requestedDeliveryType": "express",
  "availableDeliveryTypes": [
    {
      "deliveryType": "same-day",
      "label": "Same-Day",
      "description": "Delivered today"
    },
    {
      "deliveryType": "economy",
      "label": "Economy",
      "description": "Next-day, lower cost"
    }
  ]
}
```

Handle it by falling back to `same-day`, or by re-showing the options in `availableDeliveryTypes`.

#### Other errors

| Status | Message / cause                                                                                                                                               |
| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | `Coordinate payloads (senderGeoCoord and receiverGeoCoord) must be provided` — every pickup needs `senderGeoCoord` and the receiver needs `receiverGeoCoord`. |
| `400`  | Validation failure. The message names the offending field, e.g. `deliveryType must be one of the following: …`.                                               |
| `400`  | Insufficient wallet balance (`paymentMethod: "wallet"`) or credit limit exceeded (`paymentMethod: "invoice"`).                                                |
| `401`  | Missing or invalid API key.                                                                                                                                   |

### Webhooks

Once the delivery is created, RouteLift sends a `delivery.created` event to your configured webhook URL:

```json
{
  "event": "delivery.created",
  "timestamp": "2025-05-21T12:34:56Z",
  "eventId": "3b59132c-9281-4b9b-bf09-1b93a3e362cc",
  "data": {
    "trackingCode": "dlv_7890",
    "orderId": "AB6237QS",
    "price": 500
  }
}
```

| Field               | Notes                                                                           |
| ------------------- | ------------------------------------------------------------------------------- |
| `eventId`           | Unique per event. Store it and ignore repeats — RouteLift may retry a delivery. |
| `data.orderId`      | The `orderId` you sent when creating the delivery.                              |
| `data.trackingCode` | The job's tracking reference.                                                   |
| `data.price`        | Amount charged for that job, in naira (NGN).                                    |

Respond with a `2xx` status code. One event is sent per created job, so a two-pickup request produces two `delivery.created` events.

### Full example

```js
const BASE = 'https://sandbox-integrations.routelift.com/v1';
const headers = {
  'Content-Type': 'application/json',
  Authorization: `Bearer ${API_KEY}`,
};

const pickup = {
  latitude: 6.6018,
  longitude: 3.3515,
  address: '12 Allen Avenue, Ikeja',
};
const dropoff = {
  latitude: 6.4301,
  longitude: 3.4291,
  address: '45 Admiralty Way, Lekki Phase 1',
};

// 1. Estimate — this tells you which delivery types this route supports
const estimate = await fetch(`${BASE}/deliveries/estimate/v2`, {
  method: 'POST',
  headers,
  body: JSON.stringify({
    pickupLocations: [
      { ...pickup, packageSize: 'medium', packageDescription: 'Boxed shoes', weight: 4 },
    ],
    receiverLocation: dropoff,
  }),
}).then(r => r.json());

const { recommendedOption, otherDeliveryOptions } = estimate.data;

// Show recommendedOption plus otherDeliveryOptions to the customer, then:
const deliveryType = recommendedOption.deliveryType;

// 2. Create, using a type that came from the estimate
const delivery = await fetch(`${BASE}/deliveries`, {
  method: 'POST',
  headers,
  body: JSON.stringify({
    pickups: [
      {
        senderAddress: pickup.address,
        senderName: 'John Doe',
        senderPhone: '08012345678',
        senderGeoCoord: { latitude: pickup.latitude, longitude: pickup.longitude },
        packageSize: 'medium',
        weight: 4,
        vehicle: 'bike',
        quantity: 1,
        categories: ['Shoes'],
      },
    ],
    receiver: {
      receiverAddress: dropoff.address,
      receiverName: 'Jane Doe',
      receiverPhone: '08123456789',
      receiverGeoCoord: { latitude: dropoff.latitude, longitude: dropoff.longitude },
    },
    orderId: 'ORD12345',
    paymentMethod: 'wallet',
    deliveryType,
  }),
}).then(r => r.json());

console.log(delivery.data[0].trackingCode, delivery.data[0].price);
```
