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 [email protected] 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 by sales channel and date range, or — using the new org-wide query endpoints — across an entire org without needing a salesChannelId.
📘

Related guides

  • Building a POS analytics or back-office integration (forecasting, scheduling, margin)? Start with Build a POS 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 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 for the token exchange.

Concept model

ConceptFormatNotes
Orgorg123Top-level tenant. Every Sales Management path is scoped to an orgId.
Propertyp123A physical location.
Sales channelsc123A named selling surface on a property (POS, kiosk, web, marketplace). Sales belong to a sales channel.
SalesaleId (e.g. AF34FV) + optional externalIdA transaction. saleId is a Flipdish-globally-unique short ID. externalId is your reference, round-tripped on every related event.
Menu revisionUUID + numeric revisionIdEvery sale references the exact menu revision it was placed against.

Authentication

All Sales Management endpoints use the OAuth2 Bearer token:

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). Send it on every request. Never send it from a browser or mobile client — server-side only.

Creating a sale

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

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:

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

Full request with all fields

{
  "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": "[email protected]",
    "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

FieldRequiredNotes
salesChannelIdYesThe channel this sale belongs to.
requestedFulfillmentTimeYesISO 8601 UTC. When the restaurant hands over the food.
dispatchTypeYesDineIn | TakeAway | Collection | Delivery. Note capital A in TakeAway.
menuId / menuRevisionIdYesMust 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)typeDelivery, Service, Tip, Other. Optional itemId for line-level charges.
discounts[]Yes (can be empty)typeVoucher, Loyalty, Spot, Other. code required when type is Voucher or Loyalty.
payments[]Yes (can be empty — implies unpaid)typeSale, Refund. paymentMethodCash, Credit, Online, PhonePayment, ExternalPayment.
externalIdRecommendedYour idempotency key on your own system. Round-tripped on every sale event.
displayIdNoCustomer- and staff-facing short ID (max 15 chars).
sourceNoFree text. E.g. POS, App, ChatGPT.
desiredAsapNoIf true but requestedFulfillmentTime is more than ~1 hour out, it is overridden to false.
customerNoUse contactPhoneNumber (E.164 format, e.g. +353871234567). The phoneNumber field is deprecated.
deliveryConditionalRequired when dispatchType is Delivery. Set deliveredBy to Client (restaurant-managed) or External (third-party driver). Include location when deliveredBy is Client.
dineInConditionalRequired when dispatchType is DineIn. Supply tableId (string) and guests (int).
metadataNoFree-text JSON string for any partner-specific fields.

Cancelling a sale

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

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

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 sales.cancelled.v1 webhook event.

Updating delivery status

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

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

# 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 (channel-scoped)

These endpoints are scoped to a single sales channel. Use them for reconciliation against a known salesChannelId.

📘

Scope of reads (current beta limitation)

The channel-scoped GET endpoints currently only return sales that were originally ingested via this API (POST /salesManagement/orgs/{orgId}/sales). Sales recorded directly on the Flipdish POS or kiosk are not yet exposed. Contact [email protected] if this is blocking your integration.

List sales by date range

GET /salesManagement/orgs/{orgId}/salesChannels/{salesChannelId}/sales?fromDate=YYYY-MM-DD&toDate=YYYY-MM-DD

Returns sale headers for the specified sales channel in an inclusive business-date window. Both query parameters are required.

curl "https://api.flipdish.co/salesManagement/orgs/org123/salesChannels/sc123/sales?fromDate=2025-10-28&toDate=2025-10-28" \
  -H 'Authorization: Bearer <access_token>' \
  -H 'User-Agent: <your_app_name>/<version>'

Response:

{
  "data": [
    {
      "saleId": "AF34FV",
      "dateTime": "2025-10-28T14:30:00Z",
      "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",
      "totalCharges": 1.55,
      "totalDiscounts": 1.00,
      "totalRefunds": 0,
      "totalSaleAmount": 16.04,
      "totalPaidAmount": 16.04
    }
  ]
}

The list endpoint returns headers only. For the full items, charges, discounts, payments, and customer breakdown, use the detail endpoint below.

Get a sale (channel-scoped)

GET /salesManagement/orgs/{orgId}/salesChannels/{salesChannelId}/sales/{saleId}

Returns the full payload for a specific sale. The response shape mirrors the create-sale request body, allowing a round-trip verification of what Flipdish has on record.

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

Org-wide queries (Preview)

🚧

Preview

These endpoints are served from the new sale-service and are currently in preview — they may change without notice and are intended for internal evaluation until promoted to the main Sales Management reference.

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 when you don't know which sales channel a sale belongs to.

List sales — headers only

GET /salesManagement/orgs/{orgId}/sales

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

ParameterTypeNotes
propertyIdp123Filter to one property.
brandIdbr123Filter to one brand.
salesChannelIdsc123Filter to one sales channel.
sourcestringFilter by the source string set at create time.
dispatchTypestringFilter by dispatch type.
externalIdstringLook up by your platform's order reference.
statusenumcreated | preparedByKitchen | dispatched | onTheWay | delivered | cancelled
customerIdintegerFilter by Flipdish customer ID.
after / beforeISO 8601 date-timeBound the creation time range.
pageSize50–200Number of records per page. Default 50.
cursorstringOpaque pagination token from the previous response's nextCursor.
# Most-recent 50 sales across the org
curl "https://api.flipdish.co/salesManagement/orgs/org123/sales" \
  -H 'Authorization: Bearer <access_token>' \
  -H 'User-Agent: <your_app_name>/<version>'

# Filter to one property, last 24 hours
curl "https://api.flipdish.co/salesManagement/orgs/org123/sales?propertyId=p789&after=2025-10-27T00:00:00Z&before=2025-10-28T00:00:00Z" \
  -H 'Authorization: Bearer <access_token>' \
  -H 'User-Agent: <your_app_name>/<version>'

# Look up a sale by your externalId
curl "https://api.flipdish.co/salesManagement/orgs/org123/sales?externalId=your-platform-order-12345" \
  -H 'Authorization: Bearer <access_token>' \
  -H 'User-Agent: <your_app_name>/<version>'

Response:

{
  "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"
    }
  ]
}

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 between different filter combinations or between this endpoint and /sales/full.

List sales — full payload

GET /salesManagement/orgs/{orgId}/sales/full

Returns paginated full sale payloads including the nested sale object. Heavier than the headers-only endpoint — use it for export, bulk sync, or when you need the complete sale in a single call.

Supports propertyId, after, before, pageSize, and cursor filters (see List sales — headers only above for descriptions).

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

Response has the same envelope shape as the headers endpoint (pageSize, hasMoreRecords, nextCursor, data), but each item in data includes a sale object with the full payload alongside the header fields.

📘

Cursor portability

Cursors from /sales/full are not portable to /sales (headers only) or vice versa, and are tied to the propertyId filter used when they were issued. Always pass the same propertyId when following a cursor.

Get a sale (org-wide)

GET /salesManagement/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.

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

Webhook events

Flipdish fires sale-related webhook events for every sale created via this API. There are two event families to choose from — a legacy identifier-only family (sales.*) and a newer full-payload family (sale.*, closed beta). See Sale webhook events (beta) for the full comparison, event schemas, and how to subscribe.

Idempotency and retries

ConcernHow to handle it
Duplicate createsSupply a unique x-idempotency-key (GUID) per intended sale. Retrying with the same key returns the original saleId response.
Duplicate webhooksDedupe on saleId + event type (or on eventId from X-Flipdish-Idempotency-Key if present). Webhooks are at-least-once.
Missed webhooksRun a periodic reconciler using List sales by date range or the org-wide list endpoints.
Webhook timeoutReturn 2xx within 10 seconds. Persist and process asynchronously. Only 2xx counts — redirects do not.
Disabled subscriptionsSubscriptions 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_DISPATCHEDSALE_DELIVERED) on a sandbox sale
  • Reconciler runs periodically using list-by-date-range (channel-scoped) or org-wide list (preview)
  • 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