# Delivery companies

Offer your delivery service at checkout with its price, and report each shipment so the merchant and the customer can follow it.

## What your app needs

| You want to | Scope | Where |
|---|---|---|
| Appear at checkout with your price | `shipping.options:write` | `POST /delivery-options` |
| Report the company, tracking number and stage | `orders.shipment:write` | `PATCH /orders/{id}/shipment` |
| See paid orders, items, weight and amounts | `orders:read` | `GET /orders` and the `order.paid` event |
| Get the recipient's name, phone and address | `customers:read` | Inside the order |
| Get the store's pickup address | `store:read` | `GET /store` |
| Tell the customer about a stage | `customers.notify:write` | `POST /orders/{id}/notifications` |

## Offer delivery at checkout

Send your whole list. It replaces what your app sent before.

```bash
curl -X POST "https://larm.sa/stores/$STORE_ID/v1/delivery-options" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"options":[{"key":"one-hour","company_name":"Fast Courier","label":"Delivery within an hour","eta":"Within an hour","price":"25.00","cities":[]}]}'
```

- **The merchant decides.** A new option stays off until the merchant turns it on in shipping settings. It goes back to off when you change its price or company name; other edits keep it on.
- **The price is fixed.** Each option has one price from 0 to 1000 SAR, sent as a string. Larm does not call your server for a live rate.
- **Cities.** An empty `cities` list means every city in Saudi Arabia. To limit an option, list each city in Arabic, the way customers write it in their address; an English name does not match. Options are not offered for addresses outside Saudi Arabia.
- **It can leave checkout.** Your options stop showing when your app is suspended or uninstalled, or the merchant revokes the scope.

Read `enabled` from `GET /delivery-options` to see what the merchant turned on.

## When a customer chooses your option

The price is added to the order's `totals.shipping`, and the order carries your option:

```json
"delivery": {
  "source": "app",
  "app_id": "1f2e3d4c-5b6a-4978-8695-a4b3c2d1e0f9",
  "option_id": "one-hour",
  "label": "Delivery within an hour",
  "carrier": "Fast Courier",
  "status": "pending",
  "tracking_number": null,
  "tracking_url": null,
  "updated_at": "2026-11-02T09:30:00.000Z"
}
```

Listen for [`order.paid`](/events/order-paid) and start delivery only when `delivery.app_id` is your app. `delivery` shows any shipment, whoever handles it, and `delivery.source` says who: `app`, `merchant` or `larm`. It is `null` before any shipment.

Items carry `sku`, `options` and `weight_grams`, and with `customers:read` the order carries `customer.phone` and `shipping_address`. Collect from `pickup_address` in [`GET /store`](/api/store-get).

## Report the shipment

```bash
curl -X PATCH "https://larm.sa/stores/$STORE_ID/v1/orders/200123/shipment" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"status":"out_for_delivery","tracking_number":"FC-20145","tracking_url":"https://track.example.com/FC-20145","label_url":"https://labels.example.com/FC-20145.pdf"}'
```

| `status` | What happens |
|---|---|
| `shipped` | The customer sees it on the way. The first movement sends the shipping notice once |
| `out_for_delivery` | The customer sees it out for delivery |
| `delivery_attempted` | The customer sees that the attempt failed |
| `delivered` | The order is completed and the customer is told it arrived. No update is accepted after this |
| `returned` | The customer sees it returned. Any refund is the merchant's decision |

`carrier`, `tracking_number`, `tracking_url` and `label_url` keep their last value when you omit them. `carrier` is required only when the order has no delivery option of yours. Both links must be https. The merchant prints the label from the order. Only your own reply carries `label_url`: other apps and the customer never see it.

Other apps with `orders:read` receive every change as [`order.shipment.updated`](/events/order-shipment-updated).

## Tell the customer

Your first `shipped` or `out_for_delivery` sends the shipping notice by email and text message, once. For the later stages, send a text message yourself:

```bash
curl -X POST "https://larm.sa/stores/$STORE_ID/v1/orders/200123/notifications" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"channel":"sms","template":"out_for_delivery"}'
```

The templates are `order_shipped`, `out_for_delivery` and `delivered`. Each goes once per order, the merchant pays for it from their message package and can turn it off, and the wording is fixed. To say more, send an email with your own text. See [Notify the order customer](/api/orders-notifications-create).

## Who owns a shipment

The first to report owns it. You receive `SHIPMENT_OWNED_ELSEWHERE` when the merchant recorded the shipment or corrected yours, or when another app reported it. The merchant can always correct a shipment, and yours stops updating from then on.

## Money

The customer pays your price with the order, and it stays with the merchant in their settlement. Larm charges nothing on these orders and does not book them with its own carriers. Bill the merchant under your own agreement.
