> ## Documentation Index
> Fetch the complete documentation index at: https://docs.waysdrop.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Api

# API module

Base URLs:

* Live: `https://api.waysdrop.com`
* Staging: `https://staging-api.waysdrop.com`

Base path: `/api`

This module is intended for third‑party integrations. All endpoints require the `api-key` header.

```bash theme={null}
curl -X GET "https://api.waysdrop.com/api/countries?search=nigeria" \
  -H "api-key: wsp_live_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
```

## Response shapes

### Success

```json theme={null}
{
    "success": true,
    "message": "<human-readable message>",
    "data": {}
}
```

### No content

Some endpoints return `204 No Content` with an empty body.

### Errors

Errors are returned using the global error envelope described in [Errors](../get-started/errors).

### Conventions

* **Decimals** — Prisma `Decimal` fields serialize as **strings** in JSON (e.g. `"1650.00"`, `"2.00000"`).
* **Coordinates** — Resolved locations use flat `lat` / `lon` numbers, not a nested `loc` object.
* **Optional fields** — Geo/admin fields on resolved addresses vary by what was geocoded; omitted keys are not returned.

## Origin and destination

Endpoints that accept `origin` and `destination` (`POST /api/route`, `POST /api/pricing`, `POST /api/request`) use the same address model. You can send a **plain string**, a **minimal geocode object**, or a **full resolved object** from your map picker.

### Request input

**Legacy string** — geocoded server-side:

```json theme={null}
"origin": "Agege, Lagos, Nigeria"
```

**Minimal object** — preferred when you have coordinates or a place ID:

```json theme={null}
"origin": {
    "address": "Agege, Lagos, Nigeria",
    "lat": 6.617973099999999,
    "lon": 3.3208916,
    "googlePlaceId": "ChIJNSzoPHOROxARYADX4-24Io0"
}
```

**Full object** — pass the resolved shape from your map picker or a prior `/api/route` response. Extra geo/admin fields are preserved; geocoding uses `address` (or `addressLine1`), `lat`/`lon`, and `googlePlaceId`.

| Input field      | Type              | Description                                                                 |
| ---------------- | ----------------- | --------------------------------------------------------------------------- |
| `address`        | string (optional) | Free-text address for geocoding. Alias for `addressLine1` when pre-resolved |
| `lat` / `lon`    | number (optional) | Coordinates from map picker — preferred over string geocoding               |
| `googlePlaceId`  | string (optional) | Google Place ID from map picker                                             |
| `floor`          | string (optional) | Floor or unit                                                               |
| `contactName`    | string (optional) | Contact at this stop                                                        |
| `contactPhone`   | string (optional) | E.164 phone at this stop                                                    |
| `contactEmail`   | string (optional) | Email at this stop                                                          |
| `additionalNote` | string (optional) | Note for this stop (stored as `note`)                                       |
| `id`             | uuid (optional)   | Saved address ID — skips geocoding when valid                               |

On `POST /api/request`, origin contact fields can also be set at the top level (`originContactName`, `originContactPhone`, `originContactEmail`). Top-level destination contact fields are **required** unless already present on the `destination` object.

### Resolved address object

After geocoding (in `/api/route` responses, delivery records, etc.), `origin` and `destination` resolve to this shape. All fields except `country` are optional — presence depends on the geocode result.

```json theme={null}
{
    "country": "Nigeria",
    "zipCode": "102212",
    "countryCode": "NG",
    "lat": 6.617973099999999,
    "lon": 3.3208916,
    "googlePlaceId": "ChIJNSzoPHOROxARYADX4-24Io0",
    "addressLine1": "Agege, Lagos, Nigeria",
    "addressLine2": "86 Oniwaya Rd, Agege, Lagos 102212, Lagos, Nigeria",
    "timezone": "Africa/Lagos",
    "regionName": "SW",
    "state": "Lagos",
    "lgaOrCity": "AGEGE",
    "region": "SW",
    "subregion": "Western Africa",
    "continentCode": "AF",
    "continentName": "Africa",
    "countryCodeAlpha3": "NGA",
    "adminUnitId": "NG-LAGOS-AGEGE",
    "adminUnitName": "AGEGE",
    "adminUnitType": "admin2",
    "adminUnitParentId": "NG-LAGOS",
    "admin1Id": "NG-LAGOS",
    "admin1Name": "Lagos",
    "admin2Id": "NG-LAGOS-AGEGE",
    "admin2Name": "AGEGE",
    "admin3Name": "Oniwaya/Papa-Uku",
    "operationalRegionId": "NG-SW",
    "h3Index": "8a5882643967fff",
    "phrase": "domeest.ponticello.sidewall",
    "resolution": 10,
    "centerLat": 6.617950538557496,
    "centerLon": 3.320709488840802,
    "contactName": "Raheem Kawojue",
    "contactPhone": "+2348131911964",
    "contactEmail": "kawojue08@gmail.com",
    "note": ""
}
```

**Field groups**

| Group                | Fields                                                                                                                                                                    |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Display**          | `addressLine1`, `addressLine2`, `floor`, `label`                                                                                                                          |
| **Coordinates**      | `lat`, `lon`, `centerLat`, `centerLon`, `swLat`, `swLon`, `neLat`, `neLon`                                                                                                |
| **Country / region** | `country`, `countryCode`, `countryCodeAlpha3`, `state`, `lgaOrCity`, `zoneOrDistrict`, `region`, `regionName`, `regionalZone`, `subregion`, `zipCode`, `timezone`         |
| **Continent**        | `continentCode`, `continentName`                                                                                                                                          |
| **Admin hierarchy**  | `adminUnitId`, `adminUnitName`, `adminUnitType`, `adminUnitParentId`, `admin1Id`, `admin1Name`, `admin2Id`, `admin2Name`, `admin3Id`, `admin3Name`, `operationalRegionId` |
| **Place reference**  | `googlePlaceId`                                                                                                                                                           |
| **H3 grid**          | `h3Index`, `phrase`, `resolution`                                                                                                                                         |
| **Contact**          | `contactName`, `contactPhone`, `contactEmail`, `note`                                                                                                                     |
| **Persisted only**   | `id`, `createdAt`, `updatedAt`, `deletedAt` — present on stored delivery addresses                                                                                        |

Persisted addresses on deliveries (`GET /api/deliveries/:deliveryId`, `POST /api/request` → `delivery.origin` / `delivery.destination`) include `id` and timestamps in addition to the fields above.

## Endpoints

### GET /api/countries

Fetch available countries for routing and pricing.

**Query**

* `search` (optional, string)

**200 Response**

```json theme={null}
{
    "success": true,
    "message": "Countries fetched successfully",
    "data": [
        {
            "value": "NG",
            "name": "Nigeria",
            "type": "INTER_COUNTRY",
            "country": "Nigeria",
            "countryCode": "NG",
            "countryCodeAlpha3": "NGA",
            "continentCode": "AF",
            "continentName": "Africa",
            "lat": 9.082,
            "lon": 8.6753
        }
    ]
}
```

**Response fields**

| Field                             | Description                                   |
| --------------------------------- | --------------------------------------------- |
| `value`                           | ISO country code (e.g. `"NG"`)                |
| `name`                            | Country display name                          |
| `type`                            | Always `"INTER_COUNTRY"`                      |
| `country` / `countryCode`         | Country name and ISO code                     |
| `countryCodeAlpha3`               | ISO 3166-1 alpha-3 code                       |
| `continentCode` / `continentName` | Continent metadata                            |
| `lat` / `lon`                     | Country centroid coordinates (when available) |

Without `search`, returns up to 50 results.

### GET /api/states

Fetch available states.

**Query**

* `search` (optional, string)

**200 Response**

```json theme={null}
{
    "success": true,
    "message": "States fetched successfully",
    "data": [
        {
            "value": "ng-admin1-lagos",
            "name": "Lagos",
            "type": "INTER_STATE",
            "state": "Lagos",
            "country": "Nigeria",
            "countryCode": "NG",
            "continentCode": "AF",
            "continentName": "Africa",
            "regionalZone": "South West",
            "admin1Id": "ng-admin1-lagos",
            "admin1Name": "Lagos",
            "lat": 6.5244,
            "lon": 3.3792
        }
    ]
}
```

**Response fields**

| Field                     | Description                   |
| ------------------------- | ----------------------------- |
| `value`                   | State route identifier        |
| `name`                    | State display name            |
| `type`                    | Always `"INTER_STATE"`        |
| `state`                   | State name                    |
| `regionalZone`            | Broad regional grouping       |
| `admin1Id` / `admin1Name` | Admin-1 hierarchy identifiers |

### GET /api/cities

Fetch available cities.

**Query**

* `search` (optional, string)

**200 Response**

```json theme={null}
{
    "success": true,
    "message": "Cities fetched successfully",
    "data": [
        {
            "value": "ChIJ.....",
            "locationId": "ng-lagos-ikeja",
            "googlePlaceId": "ChIJ.....",
            "country": "Nigeria",
            "countryCode": "NG",
            "countryCodeAlpha3": "NGA",
            "lat": 6.6018,
            "lon": 3.3515,
            "lgaOrCity": "Ikeja",
            "state": "Lagos",
            "zoneOrDistrict": "Ikeja",
            "region": "SW",
            "regionName": "South West",
            "regionalZone": "South West",
            "adminUnitName": "Ikeja"
        }
    ]
}
```

**Response notes**

* `value` is `googlePlaceId` when available, otherwise `locationId`.
* `lat` / `lon` are the coordinates used for distance and ETA calculations.
* Without `search`, returns up to 50 results.

### POST /api/route

Get route data between two addresses.

**Body**

```json theme={null}
{
    "origin": {
        "address": "Ladipo Kuku Street, Ikeja Lagos",
        "lat": 6.6018,
        "lon": 3.3515
    },
    "destination": "Adewole Estate, Surulere Lagos"
}
```

**200 Response**

```json theme={null}
{
    "success": true,
    "message": "Route data fetched successfully",
    "data": {
        "distance": { "distanceKm": 12.743, "etaSeconds": 1649 },
        "routeType": "INTER_CITY",
        "origin": {
            "country": "Nigeria",
            "zipCode": "102212",
            "countryCode": "NG",
            "lat": 6.617973099999999,
            "lon": 3.3208916,
            "googlePlaceId": "ChIJNSzoPHOROxARYADX4-24Io0",
            "addressLine1": "Agege, Lagos, Nigeria",
            "addressLine2": "86 Oniwaya Rd, Agege, Lagos 102212, Lagos, Nigeria",
            "timezone": "Africa/Lagos",
            "regionName": "SW",
            "state": "Lagos",
            "lgaOrCity": "AGEGE",
            "region": "SW",
            "subregion": "Western Africa",
            "continentCode": "AF",
            "continentName": "Africa",
            "countryCodeAlpha3": "NGA",
            "adminUnitId": "NG-LAGOS-AGEGE",
            "adminUnitName": "AGEGE",
            "adminUnitType": "admin2",
            "adminUnitParentId": "NG-LAGOS",
            "admin1Id": "NG-LAGOS",
            "admin1Name": "Lagos",
            "admin2Id": "NG-LAGOS-AGEGE",
            "admin2Name": "AGEGE",
            "admin3Name": "Oniwaya/Papa-Uku",
            "operationalRegionId": "NG-SW",
            "h3Index": "8a5882643967fff",
            "phrase": "domeest.ponticello.sidewall",
            "resolution": 10,
            "centerLat": 6.617950538557496,
            "centerLon": 3.320709488840802
        },
        "destination": {
            "country": "Nigeria",
            "zipCode": "102212",
            "countryCode": "NG",
            "lat": 6.5244,
            "lon": 3.3792,
            "googlePlaceId": "ChIJ6cPolh-QOxAR_rCHmFNKObg",
            "addressLine1": "Gbagada, Lagos, Nigeria",
            "addressLine2": "Gbagada, Lagos, Nigeria",
            "timezone": "Africa/Lagos",
            "regionName": "SW",
            "state": "Lagos",
            "lgaOrCity": "GBAGADA",
            "region": "SW",
            "subregion": "Western Africa",
            "continentCode": "AF",
            "continentName": "Africa",
            "countryCodeAlpha3": "NGA",
            "admin1Id": "NG-LAGOS",
            "admin1Name": "Lagos",
            "operationalRegionId": "NG-SW"
        }
    }
}
```

**Response notes**

* `origin` and `destination` use the [resolved address object](#resolved-address-object) shape. Contact fields are not included unless you sent them on input.
* `routeType` is one of `INTER_CITY`, `INTER_STATE`, `INTER_REGION`, `INTER_COUNTRY`, or `INTER_CONTINENT`.
* `distance.distanceKm` and `distance.etaSeconds` use the max of line and road distance for safety.

### GET /api/fleet-types

Fetch available fleet types.

**200 Response**

```json theme={null}
{
    "success": true,
    "message": "Fleet types fetched successfully",
    "data": [
        {
            "id": "uuid",
            "name": "Bike",
            "icon": "https://cdn.waysdrop.com/fleets/bike.png",
            "description": "Small packages"
        }
    ]
}
```

Only active fleet types are returned. `icon` and `description` may be `null`.

### POST /api/pricing

Get pricing for a delivery request. Two pricing modes are supported — use one or the other.

**Mode A — by package IDs (preferred)**

Resolves weight and value from your saved packages. Matches the pricing logic used by `POST /api/request`. Requires an authenticated user (API key owner).

```json theme={null}
{
    "origin": "Ladipo Kuku Street, Ikeja Lagos",
    "destination": "Adewole Estate, Surulere Lagos",
    "packagesId": ["uuid-1", "uuid-2"],
    "urgencyType": "PRIORITY",
    "fleetTypeId": "uuid",
    "courierSelection": "ANYONE",
    "deliveryFeePayer": "CREATOR"
}
```

**Mode B — by weight and value**

Provide totals directly when you do not have saved packages.

```json theme={null}
{
    "origin": "Ladipo Kuku Street, Ikeja Lagos",
    "destination": "Adewole Estate, Surulere Lagos",
    "totalWeight": 100,
    "totalValue": 1000,
    "urgencyType": "PRIORITY",
    "fleetTypeId": "uuid",
    "courierSelection": "ANYONE"
}
```

**Body fields**

| Field              | Type             | Required  | Description                                                                         |
| ------------------ | ---------------- | --------- | ----------------------------------------------------------------------------------- |
| `origin`           | string \| object | Yes       | Pickup address (see [Origin and destination](#origin-and-destination))              |
| `destination`      | string \| object | Yes       | Drop-off address                                                                    |
| `packagesId`       | string\[]        | Mode A    | Package UUIDs — preferred; resolves weight/value automatically                      |
| `totalWeight`      | number           | Mode B    | Total weight in kg. Required when `packagesId` is omitted                           |
| `totalValue`       | number           | Mode B    | Total value in NGN. Required when `packagesId` is omitted                           |
| `urgencyType`      | enum             | No        | `PRIORITY`, `STANDARD`, or `SCHEDULED`                                              |
| `deliveryDate`     | string           | SCHEDULED | Date in `YYYY-MM-DD` format                                                         |
| `deliverySlot`     | string           | SCHEDULED | Time window, e.g. `04:00 - 14:00`                                                   |
| `slot`             | string           | SCHEDULED | Alias for `deliverySlot`                                                            |
| `fleetTypeId`      | uuid             | No        | Fleet type override. If incompatible with weight, the first compatible type is used |
| `courierSelection` | enum             | No        | `ANYONE` (default) or `SPECIFIC`                                                    |
| `courierId`        | uuid             | SPECIFIC  | Courier profile ID when `courierSelection` is `SPECIFIC`                            |
| `courierProfileId` | uuid             | No        | Alias for `courierId`                                                               |
| `deliveryFeePayer` | enum             | No        | `CREATOR` (default), `RECIPIENT`, or `SPLIT`. Who pays the delivery fee             |

#### Mode A response — `packagesId`

**200 Response**

```json theme={null}
{
    "success": true,
    "message": "Pricing fetched successfully",
    "data": {
        "deliveryCost": {
            "distance": { "distanceKm": 12.743, "etaSeconds": 1649 },
            "routeType": "INTER_CITY",
            "tierIds": {
                "countryPricingId": "uuid",
                "statePricingId": "uuid",
                "cityPricingId": "uuid"
            },
            "costs": {
                "base": "1200.00",
                "fleet": "100.00",
                "deliverySubtotal": "1550.00",
                "total": "1700.00",
                "weight": "150.00",
                "insurance": "0.00",
                "surcharge": "100.00",
                "serviceFee": "150.00"
            },
            "breakdown": {
                "baseCharge": "Base distance charge for 12.74km",
                "weightCharge": "Additional charge for 9kg",
                "insuranceCharge": null,
                "fleetCharge": "Fleet charge for Bike delivery",
                "surchargeCharge": "Surcharge for PRIORITY:PRIORITY",
                "serviceFeeCharge": "Service fee (flat) → 150.00"
            },
            "courierPricingRule": null,
            "totalWeight": 9,
            "totalValue": 250000
        },
        "promotion": null,
        "deliveryFee": {
            "payer": "CREATOR",
            "gross": {
                "delivery": "1550.00",
                "serviceFee": "150.00",
                "hubCommission": "0.00",
                "bundledTotal": "1700.00"
            },
            "creator": {
                "amount": "1700.00",
                "paymentRequired": true,
                "paid": false
            },
            "recipient": {
                "amount": "0.00",
                "paymentRequired": false,
                "paid": false,
                "checkoutUrl": null
            },
            "promotion": {
                "settlementMode": null,
                "platformFunded": false,
                "settledAmount": "0.00",
                "discountAmount": "0.00",
                "isApplied": false
            }
        },
        "matchingEligible": true,
        "payOnDelivery": {
            "enabled": false,
            "collectAmount": "250000.00",
            "collectTarget": "PACKAGE_VALUE"
        }
    }
}
```

**Mode A response notes**

* `costs.total` = `deliverySubtotal` + `serviceFee` (+ hub commission when applicable).
* `deliveryFee` describes who pays what before a request is created.
* `matchingEligible` is `true` when courier matching can start immediately after payment.
* When `courierSelection` is `SPECIFIC`, a `courier` object may also be included.
* Auto-promotions are **not** applied on this endpoint.

#### Mode B response — `totalWeight` + `totalValue`

**200 Response**

```json theme={null}
{
    "success": true,
    "message": "Pricing fetched successfully",
    "data": {
        "distance": { "distanceKm": 12.743, "etaSeconds": 1649 },
        "routeType": "INTER_CITY",
        "tierIds": {
            "countryPricingId": "uuid",
            "statePricingId": "uuid",
            "cityPricingId": "uuid"
        },
        "costs": {
            "base": "1200.00",
            "fleet": "100.00",
            "deliverySubtotal": "1550.00",
            "total": "1700.00",
            "weight": "150.00",
            "insurance": "0.00",
            "surcharge": "100.00",
            "serviceFee": "150.00"
        },
        "breakdown": {
            "baseCharge": "Base distance charge for 12.74km",
            "weightCharge": "Additional charge for 100kg",
            "insuranceCharge": null,
            "fleetCharge": "Fleet charge for Bike delivery",
            "surchargeCharge": "Surcharge for PRIORITY:PRIORITY",
            "serviceFeeCharge": "Service fee (flat) → 150.00"
        },
        "courierPricingRule": null
    }
}
```

When a specific courier has a matching pricing rule, `courierPricingRule` is populated and weight/fleet/surcharge may be zeroed:

```json theme={null}
"courierPricingRule": {
    "id": "uuid",
    "capBy": "LOCATION",
    "from": "Lagos",
    "to": "Ibadan",
    "withinKmRadius": null,
    "isPerKg": false
}
```

**Common errors**

* `400` `Provide packagesId or both totalWeight and totalValue`
* `400` `Authenticated user is required when pricing by packagesId`

### POST /api/request

Create a delivery request (P2P).

**Body**

```json theme={null}
{
    "origin": {
        "country": "Nigeria",
        "zipCode": "102212",
        "countryCode": "NG",
        "lat": 6.617973099999999,
        "lon": 3.3208916,
        "googlePlaceId": "ChIJNSzoPHOROxARYADX4-24Io0",
        "addressLine1": "Agege, Lagos, Nigeria",
        "addressLine2": "86 Oniwaya Rd, Agege, Lagos 102212, Lagos, Nigeria",
        "contactName": "Raheem Kawojue",
        "contactPhone": "+2348131911964",
        "contactEmail": "kawojue08@gmail.com"
    },
    "destination": {
        "country": "Nigeria",
        "countryCode": "NG",
        "lat": 6.5244,
        "lon": 3.3792,
        "googlePlaceId": "ChIJ6cPolh-QOxAR_rCHmFNKObg",
        "addressLine1": "Gbagada, Lagos, Nigeria",
        "contactName": "Jane Recipient",
        "contactPhone": "+2348098765432",
        "contactEmail": "jane@example.com"
    },
    "packagesId": ["uuid-1", "uuid-2"],
    "type": "PICKUP",
    "courierSelection": "ANYONE",
    "urgencyType": "PRIORITY",
    "deliveryDate": "2026-01-24",
    "deliverySlot": "04:00 - 14:00",
    "fleetTypeId": "uuid",
    "requireCollectionProofCode": true,
    "requireDeliveryProofCode": true,
    "deliveryFeePayer": "CREATOR",
    "payOnDelivery": false,
    "paymentMethodType": "WALLET",
    "link3rdPartyByContactInfo": true,
    "note": "Any additional notes"
}
```

When using the full object from your map picker, contact fields on `origin` / `destination` satisfy contact requirements — top-level `originContact*` / `destinationContact*` fields are optional in that case.

**Body fields**

| Field                        | Type             | Required  | Description                                                                                                                              |
| ---------------------------- | ---------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `origin`                     | string \| object | Yes       | Pickup address (see [Origin and destination](#origin-and-destination))                                                                   |
| `destination`                | string \| object | Yes       | Drop-off address                                                                                                                         |
| `originContactEmail`         | string           | No        | Defaults to your account email if omitted                                                                                                |
| `originContactPhone`         | string           | No        | Defaults to your account phone if omitted                                                                                                |
| `originContactName`          | string           | No        | Defaults to your account name if omitted                                                                                                 |
| `destinationContactEmail`    | string           | Yes       | Recipient email                                                                                                                          |
| `destinationContactPhone`    | string           | Yes       | Recipient phone (E.164)                                                                                                                  |
| `destinationContactName`     | string           | Yes       | Recipient name                                                                                                                           |
| `note`                       | string           | No        | Delivery notes (max 128 chars)                                                                                                           |
| `link3rdPartyByContactInfo`  | boolean          | No        | Default `false`. If `true`, notifies the recipient on the Waysdrop app and links the delivery to their account when contact info matches |
| `notify3rdParty`             | boolean          | No        | Alias for `link3rdPartyByContactInfo`                                                                                                    |
| `packagesId`                 | string\[]        | Yes       | Package UUIDs to deliver                                                                                                                 |
| `type`                       | enum             | Yes       | `PICKUP` or `DROP_OFF`                                                                                                                   |
| `urgencyType`                | enum             | No        | `PRIORITY`, `STANDARD`, or `SCHEDULED`                                                                                                   |
| `deliveryDate`               | string           | SCHEDULED | Required when `urgencyType` is `SCHEDULED`                                                                                               |
| `deliverySlot`               | string           | SCHEDULED | Time window, e.g. `04:00 - 14:00`                                                                                                        |
| `fleetTypeId`                | uuid             | No        | Fleet type override                                                                                                                      |
| `requireCollectionProofCode` | boolean          | No        | Default `true`. Sends a collection proof code to origin contact                                                                          |
| `requireDeliveryProofCode`   | boolean          | No        | Default `true`. Ignored when `payOnDelivery` is `true` (POD uses its own proof flow)                                                     |
| `deliveryFeePayer`           | enum             | No        | Default `CREATOR`. `CREATOR` debits Merchant Wallet; `RECIPIENT` or `SPLIT` for other arrangements                                       |
| `payOnDelivery`              | boolean          | No        | Default `false`. Enable pay-on-delivery collection                                                                                       |
| `podRemitTo`                 | enum             | POD       | Required when `payOnDelivery` is `true`. `MY_WALLET` (default) or `COURIER_WALLET`                                                       |
| `podCourierProfileId`        | uuid             | POD       | Required when `podRemitTo` is `COURIER_WALLET`                                                                                           |
| `paymentMethodType`          | enum             | No        | Default `WALLET`. `WALLET` or `CHECKOUT`                                                                                                 |
| `courierSelection`           | enum             | Yes       | `ANYONE`, `SPECIFIC`, or `MERCHANT_SPECIAL_COURIERS`                                                                                     |
| `courierId`                  | uuid             | SPECIFIC  | Required when `courierSelection` is `SPECIFIC`                                                                                           |

**Courier selection**

* `ANYONE` — any available courier on the platform
* `SPECIFIC` — assign a courier you already have in the system (`courierId` required)
* `MERCHANT_SPECIAL_COURIERS` — assign from your merchant-specific courier list (requires couriers added in the merchant dashboard)

#### Response — `paymentMethodType: WALLET` (default)

**201 Response**

```json theme={null}
{
    "success": true,
    "message": "Delivery request created successfully",
    "data": {
        "id": "uuid",
        "deliveryId": "uuid",
        "userId": "uuid",
        "profileId": "uuid",
        "type": "PICKUP",
        "status": "PENDING",
        "totalWeight": "9.00000",
        "totalValue": "250000.00",
        "createdAt": "2026-01-24T12:34:56.789Z",
        "updatedAt": "2026-01-24T12:34:56.789Z",
        "deletedAt": null,
        "delivery": {
            "id": "uuid",
            "userId": "uuid",
            "trackingId": "P2P-XXXXX",
            "type": "P2P",
            "status": "REQUEST_CREATED",
            "routeType": "INTER_CITY",
            "fleetTypeId": "uuid",
            "apiKeyId": "uuid",
            "distanceKm": 12.743,
            "etaSeconds": 1649,
            "deliveryFee": "1550.00",
            "deliveryFeeTotal": "1550.00",
            "serviceFee": "150.00",
            "weightFee": "150.00",
            "fleetFee": "100.00",
            "surchargeFee": "100.00",
            "insuranceFee": "0.00",
            "baseOrDistanceFee": "1200.00",
            "deliveryFeePayer": "CREATOR",
            "courierMatching": "ANYONE",
            "origin": {
                "id": "uuid",
                "country": "Nigeria",
                "zipCode": "102212",
                "countryCode": "NG",
                "lat": 6.617973099999999,
                "lon": 3.3208916,
                "googlePlaceId": "ChIJNSzoPHOROxARYADX4-24Io0",
                "addressLine1": "Agege, Lagos, Nigeria",
                "addressLine2": "86 Oniwaya Rd, Agege, Lagos 102212, Lagos, Nigeria",
                "timezone": "Africa/Lagos",
                "regionName": "SW",
                "state": "Lagos",
                "lgaOrCity": "AGEGE",
                "region": "SW",
                "subregion": "Western Africa",
                "continentCode": "AF",
                "continentName": "Africa",
                "countryCodeAlpha3": "NGA",
                "adminUnitId": "NG-LAGOS-AGEGE",
                "adminUnitName": "AGEGE",
                "adminUnitType": "admin2",
                "adminUnitParentId": "NG-LAGOS",
                "admin1Id": "NG-LAGOS",
                "admin1Name": "Lagos",
                "admin2Id": "NG-LAGOS-AGEGE",
                "admin2Name": "AGEGE",
                "admin3Name": "Oniwaya/Papa-Uku",
                "operationalRegionId": "NG-SW",
                "h3Index": "8a5882643967fff",
                "phrase": "domeest.ponticello.sidewall",
                "resolution": 10,
                "centerLat": 6.617950538557496,
                "centerLon": 3.320709488840802,
                "contactName": "Raheem Kawojue",
                "contactPhone": "+2348131911964",
                "contactEmail": "kawojue08@gmail.com",
                "note": "",
                "createdAt": "2026-01-24T12:34:56.789Z",
                "updatedAt": "2026-01-24T12:34:56.789Z"
            },
            "destination": {
                "id": "uuid",
                "country": "Nigeria",
                "zipCode": "102212",
                "countryCode": "NG",
                "lat": 6.5244,
                "lon": 3.3792,
                "googlePlaceId": "ChIJ6cPolh-QOxAR_rCHmFNKObg",
                "addressLine1": "Gbagada, Lagos, Nigeria",
                "addressLine2": "Gbagada, Lagos, Nigeria",
                "timezone": "Africa/Lagos",
                "regionName": "SW",
                "state": "Lagos",
                "lgaOrCity": "GBAGADA",
                "region": "SW",
                "subregion": "Western Africa",
                "continentCode": "AF",
                "continentName": "Africa",
                "countryCodeAlpha3": "NGA",
                "admin1Id": "NG-LAGOS",
                "admin1Name": "Lagos",
                "operationalRegionId": "NG-SW",
                "contactName": "Jane Recipient",
                "contactPhone": "+2348098765432",
                "contactEmail": "jane@example.com",
                "note": "",
                "createdAt": "2026-01-24T12:34:56.789Z",
                "updatedAt": "2026-01-24T12:34:56.789Z"
            }
        },
        "deliveryFee": {
            "payer": "CREATOR",
            "gross": {
                "delivery": "1550.00",
                "serviceFee": "150.00",
                "hubCommission": "0.00",
                "bundledTotal": "1700.00"
            },
            "creator": {
                "amount": "1700.00",
                "paymentRequired": true,
                "paid": true
            },
            "recipient": {
                "amount": "0.00",
                "paymentRequired": false,
                "paid": false,
                "checkoutUrl": null
            },
            "promotion": {
                "settlementMode": null,
                "platformFunded": false,
                "settledAmount": "0.00",
                "discountAmount": "0.00",
                "isApplied": false
            }
        },
        "matchingEligible": true
    }
}
```

#### Response — `paymentMethodType: CHECKOUT`

Returns a Paystack checkout payload instead of debiting the wallet immediately:

```json theme={null}
{
    "success": true,
    "message": "Delivery request created successfully",
    "data": {
        "status": true,
        "message": "Authorization URL created",
        "data": {
            "authorization_url": "https://checkout.paystack.com/...",
            "access_code": "...",
            "reference": "..."
        },
        "deliveryFee": { "...": "..." },
        "matchingEligible": false
    }
}
```

Courier matching starts after payment succeeds.

#### Response — `payOnDelivery: true`

Adds a `payOnDelivery` block:

```json theme={null}
"payOnDelivery": {
    "enabled": true,
    "collectAmount": "250000.00",
    "collectTarget": "PACKAGE_VALUE",
    "remitTo": "MY_WALLET"
}
```

**Response notes**

* `totalWeight` and `totalValue` on the P2P record are **strings** (Decimal).
* `delivery.distanceKm` is a **number** (float).
* `delivery.status` is always `REQUEST_CREATED` on create; `status` on the P2P record reflects payment state (`PENDING`, `PENDING_PAYMENT`, etc.).
* Full `origin` / `destination` use the [resolved address object](#resolved-address-object) plus `id` and timestamps.

**Common errors**

* `422` `Insufficient balance`
* `400` `Invalid packages selection`
* `400` `Origin and destination cannot be the same`
* `404` `Fleet type not found`
* `404` `No merchant specific couriers found` (when using `MERCHANT_SPECIAL_COURIERS` without configured couriers)

### POST /api/request/:deliveryId/cancel

Cancel an eligible delivery request.

**Path params**

* `deliveryId` (uuid)

**201 Response**

```json theme={null}
{
    "success": true,
    "message": "Delivery request has been canceled",
    "data": {
        "delivery": { "id": "uuid" }
    }
}
```

**Common errors**

* `404` `Delivery request not found`
* `400` `Delivery request can not be canceled due to its current status`
* `400` `You can only cancel a delivery request twice every 3 hours`

### POST /api/package

Create or edit a package.

**Body**

Use this endpoint to create a new package or update an existing one.

* To **create**, omit `packageId` and provide all required fields.
* To **update**, include `packageId` and provide only the fields you want to change.

**Create example**

```json theme={null}
{
    "packageType": "Electronics",
    "image": "https://cdn.waysdrop.com/packages/laptop.jpg",
    "name": "Laptop",
    "description": "Package description",
    "quantity": 1,
    "size": "SMALL",
    "value": 250000
}
```

**Update example**

```json theme={null}
{
    "packageId": "uuid",
    "description": "Updated description"
}
```

**Fields**

| Field         | Type   | Required on create | Description                                            |
| ------------- | ------ | ------------------ | ------------------------------------------------------ |
| `packageId`   | uuid   | No                 | Include to update an existing package                  |
| `packageType` | string | Yes                | Category label                                         |
| `image`       | url    | No                 | Must be hosted on `cdn.waysdrop.com`                   |
| `name`        | string | Yes                | Package name                                           |
| `description` | string | No                 | Max 128 chars                                          |
| `quantity`    | int    | Yes                | Number of items                                        |
| `size`        | enum   | Yes                | `SMALL` (≈2 kg), `MEDIUM` (≈7 kg), or `LARGE` (≈15 kg) |
| `value`       | number | Yes                | Total value in NGN                                     |

Weight is derived from `size` on create — you do not send `weight` directly.

**201 Response**

```json theme={null}
{
    "success": true,
    "message": "Package saved successfully",
    "data": {
        "id": "uuid",
        "userId": "uuid",
        "p2pDeliveryId": null,
        "errandDeliveryId": null,
        "errandStoreId": null,
        "productId": null,
        "variantCombinationId": null,
        "packageType": "Electronics",
        "name": "Laptop",
        "image": "https://cdn.waysdrop.com/....jpg",
        "description": "Package description",
        "quantity": 1,
        "weight": "2.00000",
        "value": "250000.00",
        "size": "SMALL",
        "type": "PACKAGE_ITEM",
        "selectedOptions": [],
        "createdAt": "2026-01-24T12:34:56.789Z",
        "updatedAt": "2026-01-24T12:34:56.789Z",
        "deletedAt": null
    }
}
```

### DELETE /api/package/:packageId

Delete a package.

**Path params**

* `packageId` (uuid)

**204 Response**

* No content

### GET /api/packages

Get packages that have not been assigned to a delivery (for the authenticated user).

**200 Response**

```json theme={null}
{
    "success": true,
    "message": "Packages retrieved successfully",
    "data": [
        {
            "id": "uuid",
            "userId": "uuid",
            "p2pDeliveryId": null,
            "packageType": "Electronics",
            "name": "Laptop",
            "image": "https://cdn.waysdrop.com/....jpg",
            "description": "Package description",
            "quantity": 1,
            "weight": "2.00000",
            "value": "250000.00",
            "size": "SMALL",
            "type": "PACKAGE_ITEM",
            "selectedOptions": [],
            "createdAt": "2026-01-24T12:34:56.789Z",
            "updatedAt": "2026-01-24T12:34:56.789Z",
            "deletedAt": null
        }
    ]
}
```

Returns the full `DeliveryPackage` record for each unassigned package.

### GET /api/deliveries/:deliveryId

Get a delivery (for the authenticated user).

**Path params**

* `deliveryId` (uuid)

**200 Response**

```json theme={null}
{
    "success": true,
    "message": "Delivery retrieved successfully",
    "data": {
        "id": "uuid",
        "userId": "uuid",
        "trackingId": "P2P-XXXXX",
        "type": "P2P",
        "status": "IN_TRANSIT",
        "routeType": "INTER_CITY",
        "distanceKm": 12.743,
        "etaSeconds": 1649,
        "deliveryFee": "1550.00",
        "deliveryFeeTotal": "1550.00",
        "serviceFee": "150.00",
        "weightFee": "150.00",
        "fleetFee": "100.00",
        "surchargeFee": "100.00",
        "insuranceFee": "0.00",
        "baseOrDistanceFee": "1200.00",
        "deliveryFeePayer": "CREATOR",
        "courierMatching": "ANYONE",
        "requireCollectionProofCode": true,
        "requireDeliveryProofCode": true,
        "link3rdPartyByContactInfo": true,
        "urgencyType": "PRIORITY",
        "deliveryDate": "2026-01-24T00:00:00.000Z",
        "deliverySlot": "04:00 - 14:00",
        "fleetTypeId": "uuid",
        "createdAt": "2026-01-24T12:34:56.789Z",
        "updatedAt": "2026-01-24T12:40:00.000Z",
        "fleetType": {
            "id": "uuid",
            "name": "Bike",
            "icon": "https://cdn.waysdrop.com/..."
        },
        "p2pDelivery": {
            "id": "uuid",
            "deliveryId": "uuid",
            "userId": "uuid",
            "profileId": "uuid",
            "type": "PICKUP",
            "status": "PENDING",
            "totalWeight": "9.00000",
            "totalValue": "250000.00",
            "createdAt": "2026-01-24T12:34:56.789Z",
            "updatedAt": "2026-01-24T12:34:56.789Z"
        },
        "origin": {
            "id": "uuid",
            "country": "Nigeria",
            "zipCode": "102212",
            "countryCode": "NG",
            "lat": 6.617973099999999,
            "lon": 3.3208916,
            "googlePlaceId": "ChIJNSzoPHOROxARYADX4-24Io0",
            "addressLine1": "Agege, Lagos, Nigeria",
            "addressLine2": "86 Oniwaya Rd, Agege, Lagos 102212, Lagos, Nigeria",
            "timezone": "Africa/Lagos",
            "regionName": "SW",
            "state": "Lagos",
            "lgaOrCity": "AGEGE",
            "region": "SW",
            "subregion": "Western Africa",
            "continentCode": "AF",
            "continentName": "Africa",
            "countryCodeAlpha3": "NGA",
            "adminUnitId": "NG-LAGOS-AGEGE",
            "adminUnitName": "AGEGE",
            "adminUnitType": "admin2",
            "adminUnitParentId": "NG-LAGOS",
            "admin1Id": "NG-LAGOS",
            "admin1Name": "Lagos",
            "admin2Id": "NG-LAGOS-AGEGE",
            "admin2Name": "AGEGE",
            "admin3Name": "Oniwaya/Papa-Uku",
            "operationalRegionId": "NG-SW",
            "h3Index": "8a5882643967fff",
            "phrase": "domeest.ponticello.sidewall",
            "resolution": 10,
            "centerLat": 6.617950538557496,
            "centerLon": 3.320709488840802,
            "contactName": "Raheem Kawojue",
            "contactPhone": "+2348131911964",
            "contactEmail": "kawojue08@gmail.com",
            "note": "",
            "createdAt": "2026-01-24T12:34:56.789Z",
            "updatedAt": "2026-01-24T12:34:56.789Z"
        },
        "destination": {
            "id": "uuid",
            "country": "Nigeria",
            "zipCode": "102212",
            "countryCode": "NG",
            "lat": 6.5244,
            "lon": 3.3792,
            "googlePlaceId": "ChIJ6cPolh-QOxAR_rCHmFNKObg",
            "addressLine1": "Gbagada, Lagos, Nigeria",
            "addressLine2": "Gbagada, Lagos, Nigeria",
            "timezone": "Africa/Lagos",
            "regionName": "SW",
            "state": "Lagos",
            "lgaOrCity": "GBAGADA",
            "region": "SW",
            "subregion": "Western Africa",
            "continentCode": "AF",
            "continentName": "Africa",
            "countryCodeAlpha3": "NGA",
            "admin1Id": "NG-LAGOS",
            "admin1Name": "Lagos",
            "operationalRegionId": "NG-SW",
            "contactName": "Jane Recipient",
            "contactPhone": "+2348098765432",
            "contactEmail": "jane@example.com",
            "note": "",
            "createdAt": "2026-01-24T12:34:56.789Z",
            "updatedAt": "2026-01-24T12:34:56.789Z"
        },
        "proofs": [
            {
                "id": "uuid",
                "type": "COLLECTION",
                "code": null,
                "isActive": false,
                "scannedAt": null,
                "createdAt": "2026-01-24T12:34:56.789Z"
            }
        ],
        "deliverySteps": [
            {
                "id": "uuid",
                "step": "REQUEST_CREATED",
                "active": false,
                "updatedAt": "2026-01-24T12:34:56.789Z"
            },
            {
                "id": "uuid",
                "step": "IN_TRANSIT",
                "active": true,
                "updatedAt": "2026-01-24T12:40:00.000Z"
            }
        ],
        "courier": {
            "name": "Courier name",
            "phone": "+2348012345678",
            "profilePhoto": "https://cdn.waysdrop.com/....jpg",
            "currentLocation": {
                "country": "Nigeria",
                "state": "Lagos",
                "lgaOrCity": "Ikeja",
                "lat": 6.6018,
                "lon": 3.3515
            },
            "eta": {
                "from": "2026-01-24T12:50:00.000Z",
                "to": "2026-01-24T13:30:00.000Z"
            },
            "fleet": {
                "regNo": "ABC-123",
                "make": "Honda",
                "model": "CBR",
                "colour": "Red",
                "typeId": "uuid"
            }
        }
    }
}
```

**Response notes**

* The delivery object includes all scalar `Delivery` fields — the example shows the most useful ones.
* `origin` and `destination` use the full [resolved address object](#resolved-address-object) with `id` and timestamps.
* `courier` is **omitted** (not `null`) until a courier is assigned.
* `currentLocation` uses flat `lat` / `lon`, not a nested `loc` object.
* Proof `code` is `null` until the proof has been scanned (`scannedAt` is set).
* `deliverySteps` only includes visible steps (`hidden` steps are excluded).
* `p2pDelivery.totalWeight` and `totalValue` are **strings** (Decimal).
