> For the complete documentation index, see [llms.txt](https://api-docs.urbanpiper.com/downstream/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://api-docs.urbanpiper.com/downstream/api/endpoints/order-management/order-status-update.md).

# Order Status Update

APIs to exchange the Order States between the systems.

## POS/ERP to UrbanPiper

This endpoint processes a status update for an order which has been *placed* before.

{% hint style="warning" %}
**Swiggy cancellations**

A Swiggy order can be cancelled directly only when both of these are true:

* the order relay payload has `order.details.ext_platforms[].extras.can_reject_order` set to `true`, and
* the request sends `extra.swiggy_direct_cancellation` as `true`, with a `reason_code`.

If `can_reject_order` is `false` or missing, treat it as `false`. In that case we don't cancel the order. We ask Swiggy to call the store instead: Swiggy's merchant support team calls the store and either edits the order or cancels it. The endpoint then returns `400` with the message `Cancellation of Swiggy orders is not allowed for this order. Callback requested instead.`

Don't retry this request. The callback request has already been sent.
{% endhint %}

{% hint style="info" %}
When the POS cancels an order, it can send one of the cancellation reasons below in the `reason_code` attribute. `reason_code` is optional and is only checked when `new_status` is `Cancelled`. Any other value returns `400` with the message `` Invalid value passed for `reason_code` `` .

1. `item_out_of_stock`
2. `store_closed`
3. `store_busy`
4. `rider_not_available`
5. `out_of_delivery_radius`
6. `connectivity_issue`
7. `total_mismatch`
8. `invalid_item`
9. `option_out_of_stock`
10. `invalid_option`
11. `special_instructions`
12. `unspecified`

Send the reason in `reason_code`, not in `message`. `message` is free text and is not validated.
{% endhint %}

{% hint style="warning" %}
**Constraints & Expectations**

A *throttle limit* is applicable on this endpoint limiting the maximum number of requests/min to **100**. If you breach this threshold, the platform will respond with a `429` error response code and you will not be able to make new requests for a duration of **1 min**.
{% endhint %}

<details>

<summary><strong>Sample cURL</strong></summary>

```json
curl --location --request PUT 'https://pos-int.urbanpiper.com/external/api/v1/orders/452345/status/' \
--header 'Authorization: apikey ******:******' \
--header 'Content-Type: application/json' \
--data-raw '{
    "new_status": "Acknowledged",
    "message": "Order Accepted from restaurant",
    "extra": {
    	"prep_time_mins": 15,
        "rider_name": "John",
        "rider_phone_number": "9999999999"
    }
}
'
```

</details>

**Request URL**

<mark style="color:green;">**PUT**</mark> <mark style="color:orange;">`/external/api/v1/orders/:id/status/`</mark>

**Request Path Variable**

|               Parameter               | Value                             |
| :-----------------------------------: | --------------------------------- |
| <mark style="color:red;">`:id`</mark> | Order reference as per our system |

**Request Header**

|                    Parameter                    | Value                                                                |
| :---------------------------------------------: | -------------------------------------------------------------------- |
| <mark style="color:red;">`Authorization`</mark> | API key-based auth header. Refer authentication.                     |
|  <mark style="color:red;">`X-UPR-Biz-Id`</mark> | The Biz ID of the business the request is for. Refer authentication. |

**Request Parameters**

|                   Parameter                  | Type            |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   Value                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| :------------------------------------------: | --------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| <mark style="color:red;">`new_status`</mark> | string          |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           <p>status to which the order needs to be changed.<br>Expected Order Status - Acknowledged", "Food Ready", "Dispatched", "Completed", "Cancelled".</p>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
|                   `message`                  | string          |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         Free-text message for the status change. It is not validated. Don't use it for the cancellation reason; use `reason_code`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
|                 `reason_code`                | string          |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   Optional. The cancellation reason, from the list above. Only checked when `new_status` is `Cancelled`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
|                    `extra`                   | object          |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             extra data that can be passed based on the usage.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
|            `extra.prep_time_mins`            | integer         |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      preparation time for an order (in minutes). This value to be passed at the time of acknowledging an order. This attribute should be used only for Zomato orders.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
|     `extra.accept_customer_cancellation`     | boolean         |                                                                                                                                                                                                                                                                                                                                                                                                                                                             The boolean flag indicates whether the customer cancellation request is accepted by the merchant for an order. This information for customer cancellation is relied via Order Status Update Webhook. Make sure the order status is sent as `Cancelled` when the customer cancellation request was accepted by the merchant. This attribute should be used only for Zomato orders.                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
|              `extra.rider_name`              | string          |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             The rider name to be passed in the case of self/merchant delivered orders. This needs to be passed when marking the order status as "Dispatched".                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
|              `extra.rider_phone`             | string          |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         The rider phone number to be passed in the case of self/merchant delivered orders. This needs to be passed when marking the order status as "Dispatched".                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
|      `extra.swiggy_direct_cancellation`      | boolean         |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       *true* for sending the direct cancellation request to Swiggy by stating the `reason_code` from the list. This is only passed when the order relay payload has `order.details.ext_platforms[].extras.can_reject_order` value is **true**. This attribute should be used only for Swiggy orders.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
|              `extra.unavailable`             | object          |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 extra reason details related to `turn_on_at` and `item_ref_ids` needs to be passed based on the `reason_code` sent for order cancellation of the Swiggy and Zomato orders.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
|       `extra.unavailable.item_ref_ids`       | array\[strings] |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                This attribute is used to pass the item's `ref_id` that are out of stock when passed with the cancellation reason "*item\_out\_of\_stock*".                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
|          `extra.unavailable.options`         | array\[strings] |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       This attribute is used to pass the option's `ref_id` that are out of stock. This is not used for any aggregators as of today.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
|        `extra.unavailable.turn_on_at`        | integer         | <p>Epoch time in <em>milliseconds</em>.<br><br>This needs to passed to identify when the item/store will be turned on.<br>This must be passed when the <code>reason\_code</code> is one of below values,<br><br>for Swiggy:<br>- <em>connectivity\_issue</em><br>- <em>store\_busy</em><br><em>- store\_closed</em><br>- <em>item\_out\_of\_stock</em><br>- <em>rider\_not\_available</em><br><em>- out\_of\_delivery\_radius</em><br><br>for Zomato:<br><em>- item\_out\_of\_stock</em><br><em>- rider\_not\_available</em><br><em>- store\_closed</em><br><em>- store\_busy</em><br><br><em>Please note rider\_not\_available</em> and <em>out\_of\_delivery\_radius</em> are applicable only for self-delivery orders.<br><br>When you pass the <code>reason\_code</code> - <em>connectivity\_issue</em>, <em>store\_busy, store\_closed</em> and <em>rider\_not\_available,</em> the stores will be turned off and it is expected to mention when the store can be enabled using this attribute.<br><br>Similarly, when you pass the <code>reason\_code</code> - <em>item\_out\_of\_stock,</em> the passed <code>item\_ref\_ids</code> will be turned off and it is expected to mention when the item can be enabled using this attribute.<br><br>This is used for only Zomato and Swiggy orders.</p> |

{% hint style="info" %}
The attributes `swiggy_direct_cancellation` is reserved for only Swiggy-specific direct cancellation workflow.\
\
The attribute `accept_customer_cancellation` is reserved for only Zomato orders.
{% endhint %}

{% hint style="warning" %}
Merchants can cancel Zomato orders post-acceptance of the orders for various reasons, with specific cancellation reasons based on the order type (Zomato delivered/Merchant delivered).\
\
For Merchant-delivered orders, cancellation is allowed until the order is *Completed* state.

For Zomato-delivered orders, cancellation is allowed until the order is in the *Food Ready* state.

\
Allowed cancellation reasons for merchant delivery orders - "item\_out\_of\_stock", "store\_busy", "store\_closed", "rider\_not\_available", "out\_of\_delivery\_radius".\
Allowed cancellation reasons for Zomato delivery orders - "item\_out\_of\_stock", "store\_busy", "store\_closed".<br>
{% endhint %}

### Idempotency

Status updates are idempotent. If you send the status the order is already in, the request succeeds and the response includes `err_code` `err_order_already_in_status`. The update isn't sent to the aggregator again.

### Status transition rules

Order statuses rank in this order: *Placed* → *Acknowledged* → *Confirmed* → *Food Ready* → *Dispatched* → *Completed* → *Cancelled*.

* An order can only move forward. Sending an earlier status returns `400` with `err_code` `err_order_already_in_status`.
* *Confirmed* and *Food Ready* can't be sent before the order is *Acknowledged*. Doing so returns `400` with `err_code` `err_order_missing_ack`. Other statuses, such as *Dispatched* or *Completed*, can be sent directly from *Placed*.
* For aggregator-delivered orders of businesses in India, only the aggregator can mark the order *Completed*. Sending *Completed* returns `400` with `err_code` `err_invalid_order_status`. This doesn't apply to self-delivery or pickup orders.

**When an aggregator order can be cancelled**

After these points, a *Cancelled* request returns `400` with `err_code` `err_order_cannot_be_cancelled`:

* Aggregator-delivered orders: up to *Confirmed*.
* Self-delivery orders: any time before *Completed*.

### Error responses

Errors are returned as JSON. Some errors also carry an `err_code`:

```json
{
    "status": "error",
    "message": "Order already in - Completed - state",
    "err_code": "err_order_already_in_status"
}
```

`err_code` is only returned with these four values. All other errors have no `err_code`.

| `err_code`                      | When it is returned                                                                                                                            |
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `err_invalid_order_status`      | `new_status` is not a valid status (`Invalid order state - [status]`), or the POS tried to complete an aggregator-delivered order (see above). |
| `err_order_already_in_status`   | The order is already in this status or a later one (`Order already in - [status] - state`).                                                    |
| `err_order_missing_ack`         | *Confirmed* or *Food Ready* was sent before *Acknowledged* (`Please 'acknowledge' the order first`).                                           |
| `err_order_cannot_be_cancelled` | The order is past the point where it can be cancelled (`Order already in - [status] - state. It cannot be cancelled`).                         |

Other errors:

| Status | Message                                                                                    | When it is returned                                                                                                           |
| ------ | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- |
| `400`  | `Order id [id] invalid.`                                                                   | No order with this ID exists for the business.                                                                                |
| `400`  | `Missing data attribute - new_status`                                                      | `new_status` was not sent.                                                                                                    |
| `400`  | `Please check request data`                                                                | The request body is not valid JSON.                                                                                           |
| `400`  | `` Invalid value passed for `reason_code` ``                                               | `reason_code` is not one of the values listed above.                                                                          |
| `400`  | `Cancellation of Swiggy orders is not allowed for this order. Callback requested instead.` | A Swiggy order can't be cancelled directly (see the Swiggy note above). A callback has been requested.                        |
| `400`  | `Cancellation is not allowed for this order. Callback requested instead.`                  | The Zomato order doesn't allow cancellation. A callback has been requested.                                                   |
| `400`  | `Order state updated ignored.`                                                             | *Food Ready* was sent, but the store marks orders ready automatically at the set preparation time, so the request is ignored. |
| `401`  | `You are not authorised to update orders for this location.`                               | The API user doesn't have access to the store this order belongs to.                                                          |
| `405`  | `[METHOD] not supported`                                                                   | The request didn't use `PUT`.                                                                                                 |
| `429`  |                                                                                            | The throttle limit was exceeded.                                                                                              |

## UrbanPiper to POS (webhook)

External systems can be configured such that when an order state change is received on our platform, a real-time event will trigger a webhook, which in turn will make a **POST** request to the external system.

The request data will be sent in the body of the request and the format will be JSON. Below is a sample of the JSON data and a detailed explanation of the attributes (explained further below)

**NOTE**: It is expected that the API endpoint to which this webhook makes a request returns a status code in the `2xx` series. Otherwise, the platform will mark the delivery as failed and retry later (based on the configured retrial scheme)

<details>

<summary>Webhook Payload</summary>

```json
{
  "additional_info": {
    "customer_cancellation": true,
    "external_channel": {
      "name": "Zomato",
      "order_id": "1189391362"
    },
    "timeout_secs": 180
  },
  "new_state": "customer_cancelled",
  "order_id": 1005226,
  "reference_id": "<REFERENCE_ID>",
  "store_upipr_id": 9876,
  "prev_state": "Placed",
  "timestamp": "30/05/2018 10:32:34",
  "store_id": "STR1",
  "timestamp_unix": 1542889106611,
  "message": "Selected wrong delivery address",
  "updater": {
    "username": "<UPDATER_UPIPR_USERNAME>",
    "name": "<UPDATER_NAME>"
  }
}
```

</details>

#### Request Parameters

|           Parameter           | Type    |                                                                                                                                                                    Description                                                                                                                                                                   |
| :---------------------------: | ------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
|       `additional_info`       | object  |                                                                                                                                                      Additional information about the order                                                                                                                                                      |
|    `customer_cancellation`    | boolean |                                                                                                                         Indicates the customer requested for the cancellation of an order. Specific to Zomato orders only                                                                                                                        |
|         `timeout_secs`        | integer |                                                                                                                        The timeout value received in the request for accepting/rejecting the customer cancellation request                                                                                                                       |
|       `external_channel`      | object  |                                                                                                                                                           External channel information                                                                                                                                                           |
|    `external_channel.name`    | string  |                                                                                                                                                                Name of the channel                                                                                                                                                               |
|  `external_channel.order_id`  | string  |                                                                                                                                                            Order ID as per the channel                                                                                                                                                           |
|          `new_state`          | string  |                                                                                                                                                    New state to which the order got changed to                                                                                                                                                   |
|           `order_id`          | string  |                                                                                                                                                         Order reference as per our system                                                                                                                                                        |
|          `prev_state`         | string  |                                                                                                                                                  Previous state from which the order got changed                                                                                                                                                 |
|          `timestamp`          | string  |                                                                                                                                                      Timestamp on when state change happened                                                                                                                                                     |
|        `timestamp_unix`       | integer |                                                                                                                       The value of this attribute is the epoch timestamp (in millis) in UTC on when state change happened.                                                                                                                       |
|           `message`           | string  |                                                 Message given with the status change. When an aggregator cancels the order, it carries the reason the aggregator sent, or a default text if it sent none. When Swiggy cancels an order because it was edited, it is `Swiggy Order Modification`.                                                 |
|       `from_aggregator`       | boolean | Top-level attribute, sent on every status update except `customer_cancelled`. `true` when the aggregator made the status change, for example when it cancelled the order. Not every aggregator sets it on every update: Swiggy sets it only when it cancels an order, so its *Completed* updates and the cancel of an edited order have `false`. |
|         `reason_code`         | string  |                                                                                                                   The reason code of the status change, if there is one. Otherwise `null`. Not sent with `customer_cancelled`.                                                                                                                   |
|       `merchant_ref_id`       | string  |                                                                           The order's reference ID. It is set by External Order Reference Update, and for some aggregator orders it holds the aggregator's order ID. Can be `null`. Not sent with `customer_cancelled`.                                                                          |
|            `biz_id`           | integer |                                                                                                                                        UrbanPiper ID of the business. Not sent with `customer_cancelled`.                                                                                                                                        |
|         `reference_id`        | string  |                                                                                                                                                       Sent only with `customer_cancelled`.                                                                                                                                                       |
|        `store_upipr_id`       | integer |                                                                                                                                                            UrbanPiper ID of the store.                                                                                                                                                           |
|        `is_test_order`        | boolean |                                                                                                                                                       Sent only for test orders, as `true`.                                                                                                                                                      |
| `additional_info.is_modified` | boolean |                                                                                                        Sent as `true` on the *Cancelled* update of an order that was cancelled because it was modified (for example, a Swiggy order edit).                                                                                                       |
| `additional_info.modified_to` | integer |                                                                                            With `is_modified`, the UrbanPiper ID of the new order. The new order arrives as a separate Order Relay with `modified_from` set to the original order ID.                                                                                            |
|           `store_id`          | string  |                                                                                                                                                  Store ID of which the order is associated with                                                                                                                                                  |
|           `updater`           | object  |                                                                                                                                   Object containing information about the user who triggered the status update.                                                                                                                                  |
|       `updater.username`      | string  |                                                                                                  The unique username registered in our platform of the updater. If it's through an API action, the username will be something like: `biz_adm_*`.                                                                                                 |
|         `updater.name`        | string  |                                                                                                                                                          Registered name of the udpater.                                                                                                                                                         |

The sample above is a Zomato `customer_cancelled` update. Other status updates carry `from_aggregator`, `reason_code`, `merchant_ref_id` and `biz_id` instead of `reference_id`.

{% hint style="success" %}
Possible order states are - "Placed", "Acknowledged", "Food Ready", "Dispatched", "Completed", "Cancelled", "customer\_cancelled". As of now the order state "customer\_cancelled" will be sent to the POS endpoint only in the case of Zomato orders when the customer raised a cancellation request).
{% endhint %}

{% hint style="info" %}
**Note** - This webhook triggers the POS/ERP systems based on the order status change updated in our system (happened via aggregators / POS / in-house applications such as Satellite Prime). It is expected that the POS/ERP systems must ensure the latest order status triggered to their endpoints are updated in their system and send the subsequent order states request to our system.
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://api-docs.urbanpiper.com/downstream/api/endpoints/order-management/order-status-update.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
