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

# Idempotency And Errors

> Retry aggregator order requests safely and handle structured API errors.

Order creation is idempotent by the resolved `type` source and `externalOrderId`.

Also send a stable `Idempotency-Key` for each external order:

```http theme={null}
Idempotency-Key: talabat-TLB-84721
```

Keep the key at 200 characters or fewer.

## Safe Retry

When Plato receives the same order identity again, it returns the original order with `200 OK`:

```json theme={null}
{
  "data": {
    "id": "cm_order_123",
    "externalOrderId": "TLB-84721",
    "appSlug": "talabat",
    "type": "PICK_UP",
    "referenceNumber": 1043,
    "status": "PENDING",
    "paymentStatus": "PAID",
    "total": 65,
    "currency": "AED",
    "createdAt": "2026-07-28T12:30:03.120Z"
  },
  "meta": {
    "idempotentReplay": true,
    "requestId": "my-trace-84721"
  }
}
```

Retry network timeouts and `500` responses with the same `externalOrderId` and `Idempotency-Key`.

## Existing Orders

When an order already exists, Plato returns the stored order instead of creating another one. Generate a new external order id only for a genuinely new order.

## Validation Error

Invalid fields and inconsistent totals return `422 Unprocessable Entity` with field paths:

```json theme={null}
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The order payload failed validation",
    "details": [
      {
        "path": "pricing.subtotal",
        "message": "pricing.subtotal must equal the sum of item totals"
      }
    ]
  },
  "requestId": "my-trace-84721"
}
```

## Endpoint Status Codes

If an aggregator value in `type` does not belong to an app installed in the API key workspace, Plato does not create or update an order:

```json theme={null}
{
  "error": {
    "code": "AGGREGATOR_APP_NOT_INSTALLED",
    "message": "Aggregator app 'talabat' is not installed in this workspace",
    "details": {
      "appSlug": "talabat"
    }
  },
  "requestId": "my-trace-84721"
}
```

| Status | Code                                 | Action                                                          |
| ------ | ------------------------------------ | --------------------------------------------------------------- |
| `201`  | —                                    | New order created. Store the Plato `data.id`.                   |
| `200`  | —                                    | Existing order returned for a safe retry.                       |
| `400`  | `INVALID_JSON`                       | Fix malformed JSON or the idempotency key.                      |
| `401`  | `MISSING_API_KEY`, `INVALID_API_KEY` | Supply or rotate the API key.                                   |
| `403`  | `INSUFFICIENT_PERMISSION`            | Add `restaurant:order:create` to the API key role.              |
| `409`  | `AGGREGATOR_APP_NOT_INSTALLED`       | Install the selected aggregator app in the API key workspace.   |
| `413`  | `PAYLOAD_TOO_LARGE`                  | Keep the JSON body at or below 1 MB.                            |
| `415`  | `UNSUPPORTED_MEDIA_TYPE`             | Send `Content-Type: application/json`.                          |
| `422`  | `VALIDATION_ERROR`                   | Correct the named fields.                                       |
| `500`  | `INTERNAL_ERROR`                     | Retry with exponential backoff and the same idempotency values. |

## Recommended Backoff

For a timeout or `500`, retry after approximately 1, 2, 5, 10, and 30 seconds. Stop after a bounded number of attempts and place the order in a reconciliation queue.

Log the `X-Request-Id`, external order id, HTTP status, and Plato order id. Do not log the API key or the full customer payload.
