Create/Update Orders

Orders

Note: This replaces the /receipts endpoint. /receipts is being deprecated in favor of the
Orders API described below, along with its supporting Revenue Center, Sales Category, and Dining
Option endpoints.

This section focuses on:

  • Structure of PUT requests to the /orders endpoint, including inline payments and line items
  • Structure of PUT requests to the reference-data endpoints (/revenue_centers,
    /sales_categories, /dining_options) that orders and line items refer to
  • Recommended ordering for sending reference data ahead of the orders that use it, and how
    unresolved references are handled with placeholders
  • Historical order backfill expectations
  • How PUT behaves as a full replacement of an order's payments and line items
  • Voiding, deleting, and refunding orders and line items (including partial refunds/voids)
  • Reading orders and reference data back via the GET endpoints

Sending sales data to 7shifts is handled through a PUT request to the 7shifts /orders endpoint.
An order is an aggregate: its payments and line items are submitted inline as part of the same
request, and it can reference reusable revenue center, sales category, and dining option records
by ID.

Request URL

curl --request PUT --url 'https://api.7shifts.com/api/v2/company/12345/location/7890/orders/order-123'

An example PUT request to the /orders endpoint to create or update an order looks as follows:

Request body

{
  "revenue_center_external_id": "bar",
  "dining_option_external_id": "dine-in",
  "order_date": "2023-12-31T15:45:23-06:00",
  "net_total": 4399,
  "gross_total": 4750,
  "status": "closed",
  "payments": [],
  "line_items": []
}

Here's an explanation of each of the required fields in the request body:

ParameterTypeDescription
location_idinteger7shifts Location ID (also present in the request URL)
order_datedatetimeTimestamp the order was placed. UTC in ISO8601 format
net_totalintegerNet total of the order, pre-tax, post-discounts, pre-tips. In cents
gross_totalintegerGross total of the order. In cents
statusstringOrder status. Must be one of the following: open closed voided deleted refund
paymentsarrayPayment line items on the order. Required, may be an empty array
line_itemsarrayOrder line items. Required, may be an empty array

There is no separate order_id field to invent — the external_id used to create and later
update the order is passed as part of the request URL itself: /orders/{external_id}.

The following fields are optional:

ParameterTypeDescription
revenue_center_external_idstringID of the revenue center this order belongs to
dining_option_external_idstringID of the dining option this order belongs to
external_user_idstringID of the user in your system who created the order
closed_datedatetimeTimestamp the order was closed
external_business_datedatetimePOS-provided business date, for daily sales roll-ups that can span past midnight
taxintegerTax total. In cents
discountsintegerDiscount total. In cents
feesintegerFee total. In cents
non_revenue_totalintegerNon-revenue total. In cents
item_countfloatNumber of items on the order
external_createddatetimeYour system's creation timestamp for the order
external_modifieddatetimeYour system's last-modified timestamp for the order

7shifts does not validate or recompute totals for you. net_total/gross_total on the order,
and the totals on each payment/line_item, are stored and reported exactly as sent. If your
line items don't add up to the order's net_total/gross_total, or a payment total doesn't
match what was actually charged, 7shifts has no way to detect or correct that — it's on the
caller's integration to send consistent, correct numbers.

Send orders to 7shifts

Orders need to be created in 7shifts as soon as they are opened in your system, and updated as
they change, in order to ensure consistency between the two platforms.

As soon as an order is opened in your system, make a PUT request to 7shifts' /orders/{external_id}
endpoint following the request structure described above. As the order changes (new payments,
updated totals, closing), send another PUT request with the order's full current state — see
Order PUT is a full replacement below.

Reference-data-first ordering

Revenue centers, sales categories, and dining options are reusable records referenced by orders and
line items, rather than repeated inline on every order. Send them ahead of the orders that use them:

  1. PUT revenue centers for the location.
  2. PUT sales categories for the location.
  3. PUT dining options for the location.
  4. PUT orders, referencing the above by revenue_center_external_id, dining_option_external_id,
    and (on each line item) sales_category_external_ids.

Request URL

curl --request PUT --url 'https://api.7shifts.com/api/v2/company/12345/location/7890/revenue_centers/bar'

Request body

{
  "name": "Bar"
}
ParameterTypeDescription
namestringDisplay name of the revenue center

Sales categories and dining options follow the same shape (name required), at
/sales_categories/{external_id} and /dining_options/{external_id} respectively. Dining options
additionally accept an optional type field (e.g. dine_in, delivery, take_out).

Placeholder behavior for unresolved references

Pushing reference data first keeps names accurate from the start, but an order is never rejected
just because a revenue center, sales category, or dining option external_id hasn't been pushed
yet. Instead:

  • 7shifts creates a placeholder record and links the order (or line item) to it.
  • The PUT /orders/{external_id} response includes a warnings array describing what was
    unresolved:
{
  "warnings": [
    {
      "code": "unresolved_revenue_center",
      "message": "Revenue center has not been pushed yet; a placeholder was created.",
      "external_id": "bar"
    }
  ]
}

Once you PUT the real reference data using the same external_id, the placeholder is updated in
place — no relinking is required on your end, and any orders already pointing at it pick up the
correct name automatically.

Historical order backfill

At least 90 days of historical sales history should be sent when an integration/location is first
connected, so 7shifts' forecasting has enough data to work with.

  • Use the same PUT /orders/{external_id} endpoint for backfill as for live orders — there is no
    separate batch/bulk import endpoint.
  • Set order_date (and external_business_date, if used) to the historical timestamps, not the
    time the backfill request is actually sent.
  • Send your oldest historical order first. The first order sent for a location establishes that
    location's cutover point for this API, and orders dated earlier than it may not be readable back
    through it.
  • Throttle requests (no more than 10 requests per second per token) rather than bursting the full
    90 days at once.

Order PUT is a full replacement

A PUT to /orders/{external_id} is not a patch. payments and line_items are required on
every request (they may be empty arrays) and represent the complete, current state of the order's
children — whatever you send replaces whatever was previously stored. If a payment or line item
existed on a prior write and is left out of the current one, it's removed from the order.

Always send the order's full current set of payments and line items on every update, not just the
ones that changed since the last request.

Child external IDs are optional

Payments and line items don't need their own external_id. If you have a stable ID for one in
your own system, include it (useful for reconciliation and so it round-trips on reads); if not,
omit it — 7shifts will still store it correctly.

Either way, nesting a payment or line item inside an order's payments/line_items array is what
associates it with that order — you never need to set an order_external_id on a child when
writing it. (order_external_id is populated for convenience when reading a payment or line item
back, but it's never required or interpreted on write.) Line items reference sales categories the
same way, via sales_category_external_ids, resolved server-side with the same placeholder
behavior described above.

Update orders in 7shifts

Just like sales data creation, orders need to be updated in 7shifts as soon as they change in your
system, in order to ensure consistency. Send another PUT /orders/{external_id} request using the
structure described above, with the order's full current state.

Order voiding

PUT does not hard-delete an order, so to void one, set the order status to voided:

{
    "net_total": 4399,
    "gross_total": 4750,
    "status": "voided",
    "payments": [],
    "line_items": []
}

Order DELETE

To remove an order entirely, use DELETE /orders/{external_id} rather than PUT with a deleted
status. It's a true soft-delete:

  • It sets the order's status to deleted and cascades that same soft-delete to all of the
    order's payments and line items — you don't need to delete children separately.
  • It's idempotent — calling DELETE on an order that's already deleted is a no-op success.
  • It returns 404 Not Found if no order matches the external ID for that location.

Refunds

Both order status and line item status accept a refund value, distinct from
open/closed/voided/deleted, for representing a refund:

  • Send the refunded amount as a negative net_total/gross_total on the order (and on any
    affected line_items) — sales totals are summed directly from whatever you send, so a negative
    amount is what causes the refund to reduce reported sales rather than add to them.
  • You don't need to zero out or otherwise adjust quantity/item_count on a refund-status
    order or line item — 7shifts already excludes refund-status records from item-count
    calculations, regardless of the quantity value sent.
  • A refund can either update an existing order (PUT the same external_id with status: "refund" and the corrected/negative totals) or be sent as its own order with a unique
    external_id, if the refund isn't tied to a specific previously-sent order.

For example, a $35 refund sent as its own order (not tied to a previously-sent order):

{
    "order_date": "2023-12-31T18:20:00-06:00",
    "net_total": -3500,
    "gross_total": -3500,
    "status": "refund",
    "payments": [
      {
        "total": -3500,
        "payment_type": "credit",
        "status": "closed"
      }
    ],
    "line_items": [
      {
        "sales_category_external_ids": ["food"],
        "quantity": 1,
        "net_total": -3500,
        "gross_total": -3500,
        "is_revenue_item": true,
        "status": "refund"
      }
    ]
}

Partial refunds and partial voids

status is set per line item, not just at the order level, so a partial refund or partial void
affecting only some items on an order doesn't require voiding/refunding the whole order. The
order's own status can stay closed (or whatever it already was) while only the affected line
items change status. Since PUT is a full replacement, resend the order's complete line_items
array with the affected item(s) updated and the rest unchanged, and update the order's own
net_total/gross_total to reflect the new state.

  • Partial void — set status: "voided" on just the voided line item(s). voided line items
    are excluded from sales totals entirely, so reduce the order's net_total/gross_total by that
    item's original amount (rather than sending a negative amount for it).
  • Partial refund — set status: "refund" on just the refunded line item(s), with that item's
    net_total/gross_total set to the negative refunded amount. Reduce the order's own
    net_total/gross_total by the same amount.

For example, an order with two line items where the second one ($10) is fully voided after the
order was already closed:

{
    "order_date": "2023-12-31T15:45:23-06:00",
    "net_total": 2000,
    "gross_total": 2000,
    "status": "closed",
    "payments": [
      { "total": 2000, "payment_type": "credit", "status": "closed" }
    ],
    "line_items": [
      {
        "sales_category_external_ids": ["food"],
        "quantity": 1,
        "net_total": 2000,
        "gross_total": 2000,
        "is_revenue_item": true,
        "status": "closed"
      },
      {
        "sales_category_external_ids": ["drink"],
        "quantity": 1,
        "net_total": 1000,
        "gross_total": 1000,
        "is_revenue_item": true,
        "status": "voided"
      }
    ]
}

Note net_total/gross_total on the order reflect only the still-active $20 item; the voided $10
item is included in line_items (so it's visible on reads) but no longer counted toward the
order's own totals or 7shifts' sales totals.

Reading orders back

  • GET /orders/{external_id} returns a single order with its full payments and line_items
    arrays included.
  • GET /orders (list) returns orders matching your filters, each with its complete payments and
    line_items arrays embedded. Pagination (limit + cursor) applies to the orders themselves,
    not to the number of payments or line items nested inside any one order.
  • GET /orders is always bounded to the last 90 days of order_date, regardless of any other
    filters supplied, and additionally supports order_date_gte/order_date_lte,
    modified_gte/modified_lte, modified_since (mutually exclusive with the
    modified_gte/modified_lte pair), status, and external_user_id.
  • Revenue centers, sales categories, and dining options each expose the same pair of read
    endpoints — a single-item GET /{resource}/{external_id} (returns 404 Not Found if no match
    exists) and a cursor-paginated GET /{resource} list — scoped to the location.

Did this page help you?