---
updatedAt: 2026-09-11T16:53:33.000Z
---

Fetch the complete documentation index at: https://developers.flipdish.com/llms.txt. Use this file to discover all available pages before exploring further. Append .md to any documentation page URL to get its markdown version.

# Sales Management API

How to create, cancel, and query sales using the v3.0 Sales Management API — including the new org-wide query endpoints

> 🚧 Beta
>
> The Sales Management API is currently in **beta**. Endpoints may change. Contact <integrations@flipdish.com> to request access.

## Introduction

The Sales Management API is the v3.0 surface for ingesting and querying sales. Use it to:

* **Push sales** from an external system (POS, marketplace, kiosk, ordering platform) into Flipdish so they appear in reporting, reconciliation, and the audit trail.
* **Cancel a sale** when a customer or restaurant voids an order.
* **Update delivery status** as an order moves from kitchen to customer.
* **Read sales back** across an entire org using the org-wide query endpoints — no `salesChannelId` needed. The previous sales-channel-scoped read endpoints are deprecated; see [Reading sales (deprecated)](#reading-sales-deprecated) below.

> 📘 Related guides
>
> * Building a **POS analytics or back-office integration** (forecasting, scheduling, margin)? Start with [Build a POS sales analytics integration](/docs/build-a-sales-analytics-integration) — it covers the full onboarding, menu sync, and reconciliation pipeline on top of this API.
> * Building a **marketplace integration** that also needs menu sync, item snooze, and operations status updates? See [Build a marketplace integration](/docs/build-a-marketplace-integration) for the end-to-end flow covering those extra surfaces.

All paths live under `https://api.flipdish.co` and require `Authorization: Bearer <token>` (OAuth2 client-credentials). See [Getting Started](/docs/getting-started) for the token exchange.

## Concept model

| Concept           | Format                                                                           | Notes                                                                                                                                                                                                                                                                        |
| ----------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Org**           | `org123`                                                                         | Top-level tenant. Every Sales Management path is scoped to an `orgId`.                                                                                                                                                                                                       |
| **Brand**         | `br123` — legacy: `fd123`, or a plain slug (e.g. `mexicancafe`)                  | The distinct identity (name, logo) associated with one or more sales channels.                                                                                                                                                                                               |
| **Property**      | `p123`                                                                           | A physical location.                                                                                                                                                                                                                                                         |
| **Sales channel** | `sc123` — legacy: a plain integer with no prefix (e.g. `123`)                    | A named selling surface on a property (POS, kiosk, web, marketplace). Sales belong to a sales channel.                                                                                                                                                                       |
| **Sale**          | `saleId` (e.g. `AF34FV`, Crockford base32) + optional `externalId` / `displayId` | A transaction. `saleId` is a Flipdish-globally-unique short ID. `externalId` is your platform's opaque machine-readable reference (correlation key, round-tripped on every related event). `displayId` is the short human-readable label shown to staff on KDS and receipts. |
| **Menu revision** | UUID + numeric `revisionId`                                                      | Every sale references the exact menu revision it was placed against.                                                                                                                                                                                                         |

> 📘 Don't parse or validate ID shapes
>
> `saleId`, `brandId`, and `salesChannelId` are opaque strings — no format is enforced, and orgs/brands/channels created before the current naming convention may not match the common shape shown above. Compare them by exact string equality only; never assume a prefix, length, or character set.

## Authentication

All Sales Management endpoints use the OAuth2 Bearer token:

```bash
curl https://api.flipdish.co/salesManagement/orgs/org123/sales \
  -H 'Authorization: Bearer <access_token>' \
  -H 'User-Agent: <your_app_name>/<version>'
```

Obtain the token via the client-credentials grant (see [Getting Started](/docs/getting-started)). Send it on every request. Never send it from a browser or mobile client — server-side only.

## Creating a sale

```http
POST /salesManagement/orgs/{orgId}/sales
```

Records a completed sale against the org. The `x-idempotency-key` header is **required**; supply a fresh GUID (UUID v4) per logical sale. If a request with the same key was already processed, the original response is returned rather than creating a duplicate — this makes the call safe to retry on network failures.

### Minimal example

```bash
curl -X POST https://api.flipdish.co/salesManagement/orgs/org123/sales \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Content-Type: application/json' \
  -H 'x-idempotency-key: 11111111-1111-1111-1111-111111111111' \
  -H 'User-Agent: <your_app_name>/<version>' \
  -d '{
    "salesChannelId": "sc123",
    "requestedFulfillmentTime": "2025-10-28T14:30:00Z",
    "desiredAsap": true,
    "dispatchType": "TakeAway",
    "menuId": "123e4567-e89b-12d3-a456-426614174000",
    "menuRevisionId": "23",
    "items":     [ { "menuItemId": "12a85f64-5717-4562-b3fc-2c963f66afb5", "quantity": 1, "unitPrice": 10.00, "modifierItems": [] } ],
    "charges":   [],
    "discounts": [],
    "payments":  [ { "type": "Sale", "paymentMethod": "Cash", "amount": 10.00, "paidAt": "2025-10-28T14:30:00Z" } ]
  }'
```

Response:

```json
{
  "data": {
    "saleId": "AF34FV",
    "createdAt": "2025-10-28T14:25:10Z",
    "dispatchTime": "2025-10-28T14:30:00Z"
  }
}
```

### Full request with all fields

```json
{
  "salesChannelId": "sc123",
  "source": "POS",
  "requestedFulfillmentTime": "2025-10-28T14:30:00Z",
  "desiredAsap": false,
  "dispatchType": "DineIn",
  "externalId": "your-platform-order-12345",
  "displayId": "2A003",
  "menuId": "123e4567-e89b-12d3-a456-426614174000",
  "menuRevisionId": "23",
  "customer": {
    "name": "Jane Smith",
    "contactPhoneNumber": "+353871234567",
    "emailAddress": "jane@example.com",
    "externalId": "customer_456"
  },
  "dineIn": {
    "tableId": "12",
    "guests": 2
  },
  "notes": "Allergen note: contains nuts",
  "items": [
    {
      "menuItemId": "12a85f64-5717-4562-b3fc-2c963f66afb5",
      "quantity": 1,
      "unitPrice": 13.99,
      "notes": "No onions",
      "modifierItems": [
        { "menuItemId": "10a85f64-5717-4562-b3fc-2c963f66afb3", "quantity": 1, "unitPrice": 1.50 }
      ]
    }
  ],
  "charges":   [ { "type": "Service", "amount": 1.55 } ],
  "discounts": [ { "type": "Spot", "amount": 1.00 } ],
  "payments": [
    {
      "type": "Sale",
      "paymentMethod": "Credit",
      "amount": 16.04,
      "paidAt": "2025-10-28T14:25:00Z",
      "description1": "Visa ****4921",
      "description2": "VM278412312"
    }
  ],
  "metadata": "{\"posTerminal\":\"till-2\"}"
}
```

### Field reference

| Field                       | Required                            | Notes                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| --------------------------- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `salesChannelId`            | Yes                                 | The channel this sale belongs to.                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `requestedFulfillmentTime`  | Yes                                 | ISO 8601 UTC. When the restaurant hands over the food.                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `dispatchType`              | Yes                                 | `DineIn` \| `TakeAway` \| `Collection` \| `Delivery`. Note capital A in `TakeAway`.                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `menuId` / `menuRevisionId` | Yes                                 | Must match the published revision in use. Pass the revision the customer ordered against.                                                                                                                                                                                                                                                                                                                                                                                                             |
| `items[]`                   | Yes (min 1)                         | Recursive — `modifierItems[]` follow the same shape. `menuItemId` + `quantity` + `unitPrice` required per item.                                                                                                                                                                                                                                                                                                                                                                                       |
| `charges[]`                 | Yes (can be empty)                  | `type` ∈ `Delivery`, `Service`, `Tip`, `Other`. Optional `itemId` for line-level charges.                                                                                                                                                                                                                                                                                                                                                                                                             |
| `discounts[]`               | Yes (can be empty)                  | `type` ∈ `Voucher`, `Loyalty`, `Spot`, `Other`. `code` required when type is `Voucher` or `Loyalty`.                                                                                                                                                                                                                                                                                                                                                                                                  |
| `payments[]`                | Yes (can be empty — implies unpaid) | `type` ∈ `Sale`, `Refund`. `paymentMethod` ∈ `Cash`, `Credit`, `Online`, `PhonePayment`, `ExternalPayment`.                                                                                                                                                                                                                                                                                                                                                                                           |
| `externalId`                | Recommended                         | Your platform's own opaque reference for this sale (e.g. a marketplace order UUID or POS ticket ID). Stored by Flipdish and included in webhook events that carry a `sale` object (`sale.created.v1` and `sale.updated.v1`) — use it to correlate Flipdish sales back to your system. **Not shown to restaurant staff by default.** If `displayId` is omitted, `externalId` is used as the fallback back-of-house display label — so if `externalId` is a long UUID, also supply a short `displayId`. |
| `displayId`                 | No                                  | Short, human-readable label shown to restaurant staff on kitchen display systems (KDS) and printed on receipts (max 15 chars). Unlike `externalId` (an opaque machine reference), `displayId` is designed to be glanced at quickly — for example `2A003`.                                                                                                                                                                                                                                             |
| `source`                    | No                                  | Free text. E.g. `POS`, `App`, `ChatGPT`.                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `desiredAsap`               | No                                  | If true but `requestedFulfillmentTime` is more than \~1 hour out, it is overridden to false.                                                                                                                                                                                                                                                                                                                                                                                                          |
| `customer`                  | No                                  | Use `contactPhoneNumber` (E.164 format, e.g. `+353871234567`). The `phoneNumber` field is deprecated.                                                                                                                                                                                                                                                                                                                                                                                                 |
| `delivery`                  | Conditional                         | Required when `dispatchType` is `Delivery`. Set `deliveredBy` to `Client` (restaurant-managed) or `External` (third-party driver). Include `location` when `deliveredBy` is `Client`.                                                                                                                                                                                                                                                                                                                 |
| `dineIn`                    | Conditional                         | Required when `dispatchType` is `DineIn`. Supply `tableId` (string) and `guests` (int).                                                                                                                                                                                                                                                                                                                                                                                                               |
| `notes`                     | No                                  | Free-text note for restaurant/kitchen staff (e.g. an allergen warning). Not shown to the customer. Distinct from `delivery.notes`, which carries the customer's own instructions.                                                                                                                                                                                                                                                                                                                     |
| `metadata`                  | No                                  | Free-text JSON string for any partner-specific fields.                                                                                                                                                                                                                                                                                                                                                                                                                                                |

## Sale object schema

The shapes above (`salesChannelId`, `items`, `charges`, …) form the **`PublicSale` object** — one consistent schema used in three places:

* The request body for `POST .../sales`.
* The response body for every org-wide `GET` sale endpoint (the deprecated channel-scoped `GET` endpoints return the same shape).
* The `sale` field embedded in `sale.created.v1` / `sale.updated.v1` webhook events (see [Sale webhook events](/docs/sale-events)).

This section documents every field of the nested `customer`, `delivery`, `dineIn`, `items`, `charges`, `discounts`, and `payments` objects. The top-level fields are covered in the [field reference](#field-reference) above.

### `customer`

Optional. Identifies the end customer placing the sale.

| Field                | Type   | Notes                                                                                                                                                                                                                                                                                                           |
| -------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                 | UUID   | Flipdish customer UUID. If provided, must reference an existing customer — omit or leave empty if unknown.                                                                                                                                                                                                      |
| `externalId`         | string | The integrating partner's own identifier for this customer in their system (e.g. the customer's ID in the marketplace or ordering platform that submitted the sale). Use this to carry your platform's customer reference alongside the sale. Distinct from `id`, which is the Flipdish-internal customer UUID. |
| `name`               | string | Customer's full name.                                                                                                                                                                                                                                                                                           |
| `contactPhoneNumber` | string | E.164 format with a leading `+` (e.g. `+353871234567`). The legacy `phoneNumber` field (no `+`, no formatting) is deprecated — use `contactPhoneNumber` for new integrations. See [contactPhoneNumber availability](#contactphonenumber-availability) for when this field is populated.                         |
| `contactMaskingCode` | string | Numeric code some marketplaces (e.g. Deliveroo) issue alongside a relay phone number. Entering this code when calling the relay number routes the call through to the customer's real phone. Only present when the originating marketplace obfuscates the customer's number.                                    |
| `emailAddress`       | string | Customer email address. See [emailAddress availability](#emailaddress-availability) for when this field is populated.                                                                                                                                                                                           |

#### `contactPhoneNumber` availability

`contactPhoneNumber` is always in E.164 format (leading `+`, e.g. `+353871234567`). Local-format numbers (with a leading `0`) cannot be normalised to E.164 without a country code and are dropped.

**When `contactPhoneNumber` IS present:**

| Scenario                                          | Detail                                                                                                         |
| ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| Integrator-submitted sale                         | The integrator supplied `contactPhoneNumber` (or legacy `phoneNumber`) in the Create/Update Sale request body. |
| Marketplace order with international-format phone | UberEats, Deliveroo, JustEat, etc. forward a phone already in international format (no leading `0`).           |

**When `contactPhoneNumber` is ABSENT:**

| Scenario                         | Detail                                                                                                                                                                              |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `customer` object omitted        | No customer data at all — `contactPhoneNumber` cannot exist.                                                                                                                        |
| Customer present, no phone       | Name/email/ID provided but no phone was collected.                                                                                                                                  |
| Local-format phone (leading `0`) | e.g. UK `07533 006 408` — cannot safely normalise to E.164 without a country code, so dropped. `contactMaskingCode` may still be set.                                               |
| Marketplace relay/masking only   | Platforms like Deliveroo obfuscate the real number; only a relay number + masking code is provided. The relay is not the customer's real number, so `contactPhoneNumber` is absent. |
| Kiosk / self-service             | `FlipdishKIOSK`/`KIOSK` — customers don't enter contact details.                                                                                                                    |
| Dine-in                          | Phone not typically collected.                                                                                                                                                      |

#### `emailAddress` availability

**When `emailAddress` IS present:**

| Scenario                          | Detail                                                                                               |
| --------------------------------- | ---------------------------------------------------------------------------------------------------- |
| Integrator-submitted sale         | The integrator supplied `emailAddress` in the Create/Update Sale request body.                       |
| Flipdish-powered Web or App order | The customer placed the order through a Flipdish-powered channel and has a registered email address. |
| Marketplace with email sharing    | Some marketplace integrations forward the customer's email if their platform exposes it.             |

**When `emailAddress` is ABSENT:**

| Scenario                             | Detail                                                                                                                                                                         |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Marketplace without email sharing    | Most major marketplaces (UberEats, Deliveroo, JustEat) do not share customer email addresses for privacy/contractual reasons — `emailAddress` will be absent for these orders. |
| Guest checkout / no registered email | The customer completed the order without providing an email address.                                                                                                           |
| Kiosk / dine-in                      | Customer contact details are typically not collected.                                                                                                                          |

### `delivery`

Required when `dispatchType` is `Delivery`.

| Field         | Type   | Notes                                                                                                                                                                                                           |
| ------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `deliveredBy` | enum   | `Client` — the restaurant fulfils the delivery itself. `External` — a marketplace or third-party courier delivers.                                                                                              |
| `location`    | object | The drop-off address. **Required when `deliveredBy` is `Client`** — not needed for `External`, since the courier's own platform handles routing. See `delivery.location` below.                                 |
| `notes`       | string | Free-text delivery/prep instructions from the customer (e.g. "Leave at door, don't ring the bell"). Distinct from the sale-level `notes` field, which is for restaurant/kitchen staff rather than the customer. |

#### `delivery.location`

| Field                                            | Type   | Notes                                                                                                                                                                                                             |
| ------------------------------------------------ | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `addressFields.line1`                            | string | Required. First address line.                                                                                                                                                                                     |
| `addressFields.line2` / `addressFields.line3`    | string | Optional further address lines.                                                                                                                                                                                   |
| `addressFields.postCode`                         | string | Postal code.                                                                                                                                                                                                      |
| `countryCode`                                    | string | Required. ISO 3166-1 alpha-2 country code (e.g. `IE`, `GB`, `US`). Validated against the real ISO 3166-1 alpha-2 list, not just checked for length — a well-formed but non-existent code (e.g. `ZZ`) is rejected. |
| `coordinates.latitude` / `coordinates.longitude` | number | Optional geographic coordinates for the drop-off point.                                                                                                                                                           |

> 📘 `UK` is not a valid country code
>
> The ISO 3166-1 alpha-2 code for the United Kingdom is `GB`, not `UK`. `delivery.location.countryCode: "UK"` is rejected — use `GB`.

### `dineIn`

Required when `dispatchType` is `DineIn`.

| Field     | Type    | Notes                         |
| --------- | ------- | ----------------------------- |
| `tableId` | string  | Table identifier (free text). |
| `guests`  | integer | Number of guests, `>= 0`.     |

### `items[]`

Usually contains at least one item, but **may be empty for charge-only sales** (e.g. a sale carrying only a manually-added delivery charge). Items nest recursively: a `modifierItems[]` entry has the exact same shape as its parent item, so any depth of modifier (e.g. pizza → size → topping → extra-cheese) is expressed as nested items rather than a separate schema.

| Field             | Type    | Notes                                                                                                         |
| ----------------- | ------- | ------------------------------------------------------------------------------------------------------------- |
| `menuItemId`      | UUID    | Must match an item in the menu/revision referenced by the sale's `menuId`/`menuRevisionId`.                   |
| `quantity`        | integer | `> 0`.                                                                                                        |
| `unitPrice`       | number  | Price of one unit of this item (or modifier).                                                                 |
| `notes`           | string  | Free-text instruction for this line, e.g. "No onions please".                                                 |
| `modifierItems[]` | array   | Modifiers applied to this item — same schema as `items[]`, so entries can themselves carry `modifierItems[]`. |

### `charges[]`

Required on create, but the array can be empty. Additional monetary lines beyond item prices.

| Field    | Type   | Notes                                                                                                                                                                                         |
| -------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`   | enum   | `Delivery`, `Service`, `Tip`, `Other`.                                                                                                                                                        |
| `amount` | number | `>= 0`.                                                                                                                                                                                       |
| `itemId` | UUID   | Optional. Set only when the charge is attached to a specific item (e.g. a bag or container deposit) rather than the whole sale. Omit for sale-level charges (e.g. an overall service charge). |

### `discounts[]`

Required on create, but the array can be empty. Discounts always apply at the sale level — there is no per-line discount. Every discount carries `amount` (number, `>= 0`) and `type`; whether `code` is required depends on `type`:

| `type`    | `code`   | Notes                                                                                |
| --------- | -------- | ------------------------------------------------------------------------------------ |
| `Voucher` | Required | The voucher code the customer redeemed.                                              |
| `Loyalty` | Required | The loyalty program code.                                                            |
| `Spot`    | Optional | An ad-hoc/manual discount applied in the moment; `code` is just a label if supplied. |
| `Other`   | Optional | Catch-all category; `code` is a free-text label if supplied.                         |

### `payments[]`

Required on create, but the array can be empty — an empty array means the sale is unpaid. A refund is recorded as its own line rather than mutating the original payment: expect a `Sale`-type line for the original charge and a separate `Refund`-type line when money is returned.

| Field           | Type         | Notes                                                                       |
| --------------- | ------------ | --------------------------------------------------------------------------- |
| `type`          | enum         | `Sale` (money collected) or `Refund` (money returned to the customer).      |
| `paymentMethod` | enum         | `Cash`, `Credit`, `Online`, `PhonePayment`, `ExternalPayment`.              |
| `amount`        | number       | `>= 0`.                                                                     |
| `paidAt`        | ISO 8601 UTC | Required. When the payment was made.                                        |
| `description1`  | string       | Free-text payment detail, e.g. `Visa ****4921`.                             |
| `description2`  | string       | Free-text secondary detail, e.g. an authorization code or reference number. |

## Cancelling a sale

```http
POST /salesManagement/orgs/{orgId}/sales/{saleId}/cancel
```

Marks an existing sale as cancelled. Cancellation is final — create a new sale if the customer reorders.

```bash
curl -X POST https://api.flipdish.co/salesManagement/orgs/org123/sales/AF34FV/cancel \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Content-Type: application/json' \
  -H 'User-Agent: <your_app_name>/<version>' \
  -d '{
    "salesChannelId": "sc123",
    "cancellationReason": "Cancelled by customer",
    "cancellationNotes": "Customer called to cancel — out of area"
  }'
```

`cancellationReason` is required. Allowed values: `Cancelled by customer`, `Cancelled by restaurant`, `Cancelled by marketplace`, `Cancelled by integration partner`, `Cancelled by system`, `Other`. `cancellationNotes` is free text (max 255 chars) and is round-tripped on the `sale.status.updated.v1` webhook event (status `SALE_CANCELLED`).

## Updating delivery status

```http
POST /salesManagement/orgs/{orgId}/sales/{saleId}/deliveryStatus
```

Records a delivery state transition as the order progresses through fulfilment. Send transitions in order.

```bash
# Mark as dispatched (left kitchen with driver)
curl -X POST https://api.flipdish.co/salesManagement/orgs/org123/sales/AF34FV/deliveryStatus \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Content-Type: application/json' \
  -H 'User-Agent: <your_app_name>/<version>' \
  -d '{ "salesChannelId": "sc123", "status": "SALE_DISPATCHED" }'

# Mark as delivered (customer received)
curl -X POST https://api.flipdish.co/salesManagement/orgs/org123/sales/AF34FV/deliveryStatus \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Content-Type: application/json' \
  -H 'User-Agent: <your_app_name>/<version>' \
  -d '{ "salesChannelId": "sc123", "status": "SALE_DELIVERED" }'
```

`status` is `SALE_DISPATCHED` or `SALE_DELIVERED`. Both transitions surface in operational dashboards and downstream reporting.

## Reading sales (deprecated)

> 🚧 Deprecated
>
> The sales-channel-scoped read endpoints below are **deprecated** and have been removed from this reference — they no longer appear in the sidebar or in `reference/sales-management-api.json`'s navigation. They remain callable for existing integrations and will keep working, but won't receive further updates. New integrations should use the [org-wide query endpoints](#org-wide-queries-beta) instead, which cover the same use cases without requiring a `salesChannelId`.

* `GET /salesManagement/orgs/{orgId}/salesChannels/{salesChannelId}/sales?fromDate=YYYY-MM-DD&toDate=YYYY-MM-DD` (formerly "List sales by date range") → use [List sales (org-wide, full payload)](#list-sales-org-wide-full-payload).
* `GET /salesManagement/orgs/{orgId}/salesChannels/{salesChannelId}/sales/{saleId}` (formerly "Get a sale") → use [Get a sale (org-wide)](#get-a-sale-org-wide).

## Org-wide queries (Beta)

> 🚧 Beta
>
> These endpoints are currently in **beta** — they may change without notice.

The org-wide endpoints do not require a `salesChannelId`. Use them when you need to query or look up sales across all channels of an org (or scoped to one property), or when you don't know which sales channel a sale belongs to. Every org-wide list/get endpoint returns the **full payload** — header fields plus the nested `sale` object — in one call; there's no separate headers-only variant.

### List sales (org-wide, full payload)

```http
GET /sales/orgs/{orgId}/sales
```

Returns paginated full sale payloads across all channels of the org, sorted by creation time descending. All query parameters are optional and combinable.

| Parameter          | Type               | Notes                                                              |
| ------------------ | ------------------ | ------------------------------------------------------------------ |
| `pageSize`         | 50–200             | Number of records per page. Default 50.                            |
| `cursor`           | string             | Opaque pagination token from the previous response's `nextCursor`. |
| `after` / `before` | ISO 8601 date-time | Bound the creation time range.                                     |

```bash
# Most-recent 50 sales across the org
curl "https://api.flipdish.co/sales/orgs/org123/sales" \
  -H 'Authorization: Bearer <access_token>' \
  -H 'User-Agent: <your_app_name>/<version>'

# Last 24 hours
curl "https://api.flipdish.co/sales/orgs/org123/sales?after=2025-10-27T00:00:00Z&before=2025-10-28T00:00:00Z" \
  -H 'Authorization: Bearer <access_token>' \
  -H 'User-Agent: <your_app_name>/<version>'
```

Response:

```json
{
  "pageSize": 50,
  "hasMoreRecords": true,
  "nextCursor": "<opaque token>",
  "data": [
    {
      "saleId": "AF34FV",
      "orgId": "org123",
      "brandId": "br123",
      "propertyId": "p789",
      "salesChannelId": "sc123",
      "salesChannelType": "POS",
      "externalId": "your-platform-order-12345",
      "source": "POS",
      "dispatchType": "DineIn",
      "status": "created",
      "createdAt": "2025-10-28T14:25:10Z",
      "updatedAt": "2025-10-28T14:25:10Z",
      "sale": {
        "salesChannelId": "sc123",
        "source": "POS",
        "requestedFulfillmentTime": "2025-10-28T14:30:00Z",
        "dispatchType": "DineIn",
        "menuId": "123e4567-e89b-12d3-a456-426614174000",
        "menuRevisionId": "23",
        "items": [ { "menuItemId": "12a85f64-5717-4562-b3fc-2c963f66afb5", "quantity": 1, "unitPrice": 13.99, "modifierItems": [] } ],
        "charges": [],
        "discounts": [],
        "payments": [ { "type": "Sale", "paymentMethod": "Cash", "amount": 13.99, "paidAt": "2025-10-28T14:25:00Z" } ]
      }
    }
  ]
}
```

**Paginating**: when `hasMoreRecords` is `true`, pass `nextCursor` as the `cursor` query parameter on the next request together with the same filter parameters. Cursors are not portable across different endpoints (org-wide vs. property-scoped) or different filter combinations.

### List sales for a property (full payload)

```http
GET /sales/orgs/{orgId}/properties/{propertyId}/sales
```

Same shape and query parameters (`pageSize`, `cursor`, `after`, `before`) as [List sales (org-wide, full payload)](#list-sales-org-wide-full-payload), scoped to one property.

```bash
curl "https://api.flipdish.co/sales/orgs/org123/properties/p789/sales?pageSize=100" \
  -H 'Authorization: Bearer <access_token>' \
  -H 'User-Agent: <your_app_name>/<version>'
```

> 📘 Cursor portability
>
> Cursors are tied to both the endpoint and the filters used when they were issued — a cursor from the org-wide list isn't portable to the property-scoped list (or vice versa), and cursors aren't portable between different `propertyId` filters.

### Response fields (org-wide header)

Every item in `data` above wraps a `SaleHeaderResponse` plus a nested `sale` object (the full [Sale object schema](#sale-object-schema)):

| Field                                 | Type          | Notes                                                                                            |
| ------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------ |
| `saleId`                              | string        | Flipdish sale identifier.                                                                        |
| `orgId` / `brandId` / `propertyId`    | string        | The org, brand, and property the sale belongs to.                                                |
| `salesChannelId` / `salesChannelType` | string / enum | The channel the sale was placed on, and its type (`POS`, `KIOSK`, `UberEats`, `Deliveroo`, etc). |
| `externalId`                          | string        | Partner/platform reference, if supplied at create time.                                          |
| `source`                              | string        | Free-text origin set at create time (e.g. `POS`, `App`).                                         |
| `dispatchType`                        | string        | Fulfilment mode: `DineIn`, `TakeAway`, `Collection`, or `Delivery`.                              |
| `status`                              | enum          | `created` \| `preparedByKitchen` \| `dispatched` \| `onTheWay` \| `delivered` \| `cancelled`.    |
| `createdAt` / `updatedAt`             | ISO 8601 UTC  | When the sale was created, and when it last changed.                                             |
| `cancelledAt`                         | ISO 8601 UTC  | Only present once the sale has been cancelled.                                                   |

### Get a sale (org-wide)

```http
GET /sales/orgs/{orgId}/sales/{saleId}
```

Returns the full projected sale for the given org and `saleId` without requiring a `salesChannelId`. Use this when you have a `saleId` from a webhook event and don't want to look up which sales channel it belongs to.

```bash
curl https://api.flipdish.co/sales/orgs/org123/sales/AF34FV \
  -H 'Authorization: Bearer <access_token>' \
  -H 'User-Agent: <your_app_name>/<version>'
```

Response — the [header fields](#response-fields-org-wide-header) plus a nested `sale` object (the full [Sale object schema](#sale-object-schema)):

```json
{
  "saleId": "AF34FV",
  "orgId": "org123",
  "brandId": "br123",
  "propertyId": "p789",
  "salesChannelId": "sc123",
  "salesChannelType": "POS",
  "externalId": "your-platform-order-12345",
  "source": "POS",
  "dispatchType": "DineIn",
  "status": "created",
  "createdAt": "2025-10-28T14:25:10Z",
  "updatedAt": "2025-10-28T14:25:10Z",
  "sale": {
    "salesChannelId": "sc123",
    "source": "POS",
    "requestedFulfillmentTime": "2025-10-28T14:30:00Z",
    "desiredAsap": false,
    "dispatchType": "DineIn",
    "externalId": "your-platform-order-12345",
    "displayId": "2A003",
    "menuId": "123e4567-e89b-12d3-a456-426614174000",
    "menuRevisionId": "23",
    "customer": {
      "name": "Jane Smith",
      "contactPhoneNumber": "+353871234567"
    },
    "dineIn": { "tableId": "12", "guests": 2 },
    "items": [
      { "menuItemId": "12a85f64-5717-4562-b3fc-2c963f66afb5", "quantity": 1, "unitPrice": 13.99, "modifierItems": [] }
    ],
    "charges": [ { "type": "Service", "amount": 1.55 } ],
    "discounts": [],
    "payments": [
      { "type": "Sale", "paymentMethod": "Credit", "amount": 15.54, "paidAt": "2025-10-28T14:25:00Z" }
    ]
  }
}
```

`cancelledAt` is omitted here since the sale hasn't been cancelled; it appears (ISO 8601 UTC) once `status` is `cancelled`.

## Webhook events

Flipdish fires sale-related webhook events for every sale created via this API. See [Sale webhook events](/docs/sale-events) for event schemas, payload structure, and how to subscribe.

## Idempotency and retries

| Concern                    | How to handle it                                                                                                                                      |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Duplicate creates**      | Supply a unique `x-idempotency-key` (GUID) per intended sale. Retrying with the same key returns the original `saleId` response.                      |
| **Duplicate webhooks**     | Dedupe on `saleId` + event type (or on `eventId` from `X-Flipdish-Idempotency-Key` if present). Webhooks are at-least-once.                           |
| **Missed webhooks**        | Run a periodic reconciler using the [org-wide list endpoints](#org-wide-queries-beta).                                                                |
| **Webhook timeout**        | Return `2xx` within 10 seconds. Persist and process asynchronously. Only `2xx` counts — redirects do not.                                             |
| **Disabled subscriptions** | Subscriptions that fail persistently are auto-disabled. Re-enable via `POST /webhooks/orgs/{orgId}/subscriptions/{subId}` with `{ "enabled": true }`. |

## Pre-launch checklist

* [ ] Access token obtained via client-credentials grant and stored securely server-side
* [ ] `Authorization: Bearer <access_token>` sent on every request
* [ ] `User-Agent` header set on every request
* [ ] Per-sale `x-idempotency-key` (fresh GUID) sent on every `POST .../sales` call
* [ ] `externalId` set on every sale for round-trip deduplication
* [ ] `menuId` and `menuRevisionId` match the published revision in use
* [ ] `dispatchType`, `delivery`, and `dineIn` are consistent (delivery location present when `deliveredBy` is `Client`; `tableId` and `guests` present for `DineIn`)
* [ ] Cancel path tested end-to-end on a sandbox sale
* [ ] Delivery status transitions tested (`SALE_DISPATCHED` → `SALE_DELIVERED`) on a sandbox sale
* [ ] Reconciler runs periodically using the org-wide list endpoint (beta)
* [ ] Webhook endpoint verifies HMAC over the raw body using a timing-safe compare
* [ ] Webhook endpoint returns `2xx` within 10 seconds; heavy work is queued
* [ ] Handlers idempotent on `saleId` / `eventId`
* [ ] Re-enable logic in place if a subscription is auto-disabled

## Further reading

* [Getting Started](/docs/getting-started) — OAuth2 credential setup
* [Sale webhook events](/docs/sale-events) — event schemas, payload structure, and how to subscribe
* [Subscribe to Flipdish Events (v3)](/docs/webhooks-v3) — webhook delivery contract and signature verification
* [User Agents](/docs/user-agents) — required `User-Agent` format
* [Build a POS sales analytics integration](/docs/build-a-sales-analytics-integration) — full analytics pipeline: onboarding, menu sync, canonical schema mapping, App Store distribution
* [Build a marketplace integration](/docs/build-a-marketplace-integration) — marketplace-specific guide (menu sync, operations updates, snooze handling)
* [API Reference (v3.0)](https://developers.flipdish.com/v3.0/reference/) — every v3 endpoint and model