Sales Management API
How to create, cancel, and query sales using the v3.0 Sales Management API — including the new org-wide query endpoints
BetaThe 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
| Concept | Format | Notes |
|---|---|---|
| Org | org123 | Top-level tenant. Every Sales Management path is scoped to an orgId. |
| Property | p123 | A physical location. |
| Sales channel | sc123 | A named selling surface on a property (POS, kiosk, web, marketplace). Sales belong to a sales channel. |
| Sale | saleId (e.g. AF34FV) + optional externalId | A transaction. saleId is a Flipdish-globally-unique short ID. externalId is your reference, round-tripped on every related event. |
| Menu revision | UUID + numeric revisionId | Every 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}/salesRecords 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
| 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 idempotency key on your own system. Round-tripped on every sale event. |
displayId | No | Customer- and staff-facing short ID (max 15 chars). |
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). |
metadata | No | Free-text JSON string for any partner-specific fields. |
Cancelling a sale
POST /salesManagement/orgs/{orgId}/sales/{saleId}/cancelMarks 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}/deliveryStatusRecords 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
GETendpoints 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-DDReturns 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)
PreviewThese endpoints are served from the new
sale-serviceand 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}/salesReturns paginated sale headers across all channels of the org, sorted by creation time descending. All query parameters are optional and combinable.
| Parameter | Type | Notes |
|---|---|---|
propertyId | p123 | Filter to one property. |
brandId | br123 | Filter to one brand. |
salesChannelId | sc123 | Filter to one sales channel. |
source | string | Filter by the source string set at create time. |
dispatchType | string | Filter by dispatch type. |
externalId | string | Look up by your platform's order reference. |
status | enum | created | preparedByKitchen | dispatched | onTheWay | delivered | cancelled |
customerId | integer | Filter by Flipdish customer ID. |
after / before | ISO 8601 date-time | Bound the creation time range. |
pageSize | 50–200 | Number of records per page. Default 50. |
cursor | string | Opaque 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/fullReturns 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 portabilityCursors from
/sales/fullare not portable to/sales(headers only) or vice versa, and are tied to thepropertyIdfilter used when they were issued. Always pass the samepropertyIdwhen 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
| 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 List sales by date range or the org-wide list endpoints. |
| 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-Agentheader set on every request - Per-sale
x-idempotency-key(fresh GUID) sent on everyPOST .../salescall -
externalIdset on every sale for round-trip deduplication -
menuIdandmenuRevisionIdmatch the published revision in use -
dispatchType,delivery, anddineInare consistent (delivery location present whendeliveredByisClient;tableIdandguestspresent forDineIn) - 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 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
2xxwithin 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 — OAuth2 credential setup
- Sale webhook events (beta) — both event families, full schemas, and how to subscribe
- Subscribe to Flipdish Events (v3) — webhook delivery contract and signature verification
- User Agents — required
User-Agentformat - Build a POS sales analytics integration — full analytics pipeline: onboarding, menu sync, canonical schema mapping, App Store distribution
- Build a marketplace integration — marketplace-specific guide (menu sync, operations updates, snooze handling)
- API Reference (v3.0) — every v3 endpoint and model
