Skip to content

Restaurant tech

Open tabs, tips, and split checks, already built.

Flint is the payments API for teams building restaurant POS, online ordering, kiosk, and pay-at-table software. A check is an order that prices its own modifiers, stays open while rounds are added, computes the tip, and settles across as many cards as the table puts down.

Table 12 at Lucia's: the order on your POS, the same check on the guest's phone with the tip picker, and the receipt after two cards paid it. Every number on all three came back from one order.

01What you skip

Seven systems you never build.

On an API whose unit is an amount, everything a restaurant needs is yours to write and yours to get right: modifier pricing, the tab, the tip math, the split, the 86 board. Each one is a table in your database and a place for a cent to go missing. On Flint, each one is a field on the order.

Written by your team

a charge for $36.48no check behind it
  1. Modifiers

    A modifiers table, price deltas, and tax flags

    re-priced by your code before every charge

  2. Open tabs

    A tabs table and a running total

    recomputed after every round

  3. Tips

    Tip math and a tip ledger

    18% of which subtotal, asked vs collected

  4. Service charges

    Fees faked as line items

    discounted by accident when a promotion lands

  5. Split checks

    Several charges and a balance you reconcile

    penny rounding, stale totals, paid-twice guards

  6. 86-ing

    A counter you decrement

    oversold when two guests order the last one

  7. Refund one dish

    Refund arithmetic by hand

    the dish, its modifiers, its share of tax

7 systems to write · 7 to keep correct · 0 shared records

Already on the order

ord_7g2TableTwelveGET /v1/orders/{order_id}
  1. Modifiers

    modifier_total_moneyline_items[].modifiers
  2. Open tabs

    running_balance_moneyPOST /v1/orders/{order_id}/line-items
  3. Tips

    requested_tipsettled_tip_money
  4. Service charges

    charges[]POST /v1/orders/{order_id}/charges
  5. Split checks

    outstanding_moneyPOST /v1/orders/{order_id}/pay
  6. 86-ing

    available_quantityPOST /v1/inventory-reservations
  7. Refund one dish

    refunded_quantityPOST /v1/refunds

1 order per check · totals computed by Flint · 1 webhook stream

reviewed 2026-09-19 against the orders, tips, inventory, and refunds guides · corrections: Flint Help

02The menu

Modifiers price and tax themselves.

Send a modifier id. The order comes back with the name, the price, and the tax treatment resolved from the menu, and each group enforces its own minimum and maximum selections. Your code never adds 2.50 to a burger.

curl
curl -X POST https://api.withflintpay.com/v1/orders \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: table-12-round-1" \
  -d '{
    "line_items": [
      {
        "name": "Smash burger",
        "quantity": 1,
        "unit_price_money": { "amount": 1450, "currency": "USD" },
        "modifiers": [
          { "modifier_id": "mod_1kmn0aExample" },
          { "modifier_id": "mod_1kmn0bExample" },
          {
            "text": {
              "modifier_group_id": "mg_1kmn0aExample",
              "value": "no onions"
            }
          }
        ]
      }
    ]
  }'

A burger, two menu modifiers by id, and a free-text note for the kitchen. You send no modifier names and no modifier prices.

your order screen
Modifiers are part of the line item, so the screen renders one object instead of joining two.
line_items[0]
Smash burgerunit_price_money$14.50
Add pattyunit_price_delta_money$2.50
No picklesunit_price_delta_money$0.00
Line subtotalsubtotal_money$17.00
Each delta arrives priced from the menu with its own taxable flag, and the line subtotal is what the check charges.
  • unit_price_delta_money
  • min_selected / max_selected
  • modifier_total_money

The catalog setup guide builds a menu item in two sizes, its modifier groups, and a combo, then sells them on one order.

03The tab

The check stays open all night.

Open the order with the first round and append to it until the table asks for the check. Every round reprices the same record, and the activity trail returns a running balance, so your tab screen shows a number it never had to compute.

curl
# the next round appends to the same open order
curl -X POST \
  https://api.withflintpay.com/v1/orders/ord_7g2TableTwelve/line-items \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "line_items": [
      {
        "name": "Buffalo wings",
        "quantity": 1,
        "unit_price_money": { "amount": 1195, "currency": "USD" }
      }
    ]
  }'

The response is the full order with the new totals, so there is no follow-up read.

GET the activity trail
{
  "data": [
    {
      "order_activity_id": "act_1kmn0aExample",
      "activity_type": "payment",
      "description": "Payment received",
      "balance_delta_money": { "amount": -1824, "currency": "USD" },
      "running_balance_money": { "amount": 1824, "currency": "USD" },
      "payment_intent_id": "pi_1kmn0aExample",
      "created_at": "2026-07-03T21:14:00Z"
    }
  ],
  "next_page_token": null
}

One row per thing that happened to the check, each with the balance after it.

table 12, all night
Rounds, the tip, and both cards on one timeline, in the order they happened. This is the audit trail a manager asks for when a guest disputes the check.
  • POST /v1/orders/{order_id}/line-items
  • GET /v1/orders/{order_id}/activities
  • running_balance_money

04The tip

Eighteen percent, computed once.

Percent tips compute on the server, on the subtotal after discounts and before tax and fees. A happy hour promotion cannot change what 18% means, and the amount the guest asked to tip stays separate from the amount collected, so tip reports reconcile to the payment.

curl
curl -X PATCH \
  https://api.withflintpay.com/v1/orders/ord_7g2TableTwelve \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{ "requested_tip": { "percent": 18 } }'

From your own tip screen. Send a percent or a fixed amount. Sending it again replaces the tip in place, so the guest can change their mind.

Or let hosted checkout ask
{
  "tip": {
    "enabled": true,
    "tip_percent_options": [15, 18, 20],
    "default_tip_percent": 18,
    "is_custom_tip_enabled": true
  }
}

These presets are what the picker beside this shows. For small totals, switch to fixed amounts like $1, $2, and $5.

the guest's phone
Add a tip
Subtotal$28.95
Tax$2.32
Tip$5.21
Total$36.48
The tip picker hosted checkout renders. Try it: each chip shows its dollar amount, so nobody does arithmetic at the table, and the total follows the choice.
  • PATCH /v1/orders/{order_id} requested_tip
  • pricing_amounts.requested_tip_money
  • settlement_amounts.settled_tip_money

Coming from Stripe Checkout? How to add a tip to an online Stripe checkout covers the workarounds and their coupon, tax, and refund traps.

Service charges that a promotion cannot shrink

A large-party service charge, a packaging fee, a delivery fee. Each is its own object on the order with its own tax flag, so a 10% promotion on the food leaves the fee alone, and you can refund the fee without touching the meal.

curl
# party of eight: a 20% service charge, set by you
curl -X POST \
  https://api.withflintpay.com/v1/orders/ord_4p9TableFour/charges \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: table-4-service-charge" \
  -d '{
    "charge": {
      "name": "Service charge",
      "type": "service_fee",
      "percent": 20,
      "calculation_basis": "subtotal_post_discount",
      "tax": { "taxable": false }
    }
  }'

A percent charge names its basis, before or after discounts, so the math is stated rather than assumed.

Never a fake line item

Promotions discount products. A charge is not a product, so the fee your operator set is the fee the guest pays.

Taxed on its own terms

Each charge says whether it is taxable. Its tax lands on the charge and rolls into the order's tax total.

Charge types for restaurants

  • service_fee
  • delivery_fee
  • packaging_fee
  • small_order_fee
  • rush_fee
  • reservation_fee

05The split

As many cards as the table puts down.

Create a payment leg for each guest and settle them against one order. The balance runs down to zero, the tip is shared across the cards to the cent, and the paid event fires once. If the check changed after the guest saw the total, Flint rejects the payment before it charges anyone.

One leg per guest
# one leg per guest. your code picks the amounts.
curl -X POST \
  https://api.withflintpay.com/v1/orders/ord_7g2TableTwelve/payment-intents \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: table-12-guest-1-leg" \
  -d '{ "amount_money": { "amount": 1824, "currency": "USD" } }'
curl
# both cards are on the table: settle both legs in one call
curl -X POST \
  https://api.withflintpay.com/v1/orders/ord_7g2TableTwelve/pay \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: table-12-close" \
  -d '{
    "action": "confirm_payment_intents",
    "payment_intents": [
      {
        "payment_intent_id": "pi_1kmn0aExample",
        "token": "pm_1kmn0aExample"
      },
      {
        "payment_intent_id": "pi_2bqr7dExample",
        "token": "pm_2bqr7dExample"
      }
    ],
    "expected_outstanding_money": {
      "amount": 3648,
      "currency": "USD"
    },
    "completion_behavior": "complete_order"
  }'

expected_outstanding_money is the guard. A round added since the guest saw $36.48 returns ORDER_CHANGED_REFRESH_REQUIRED, and no card is charged.

table 12, closing out
CardShare of tipCharged
Guest 1$2.61$18.24
Guest 2$2.60$18.24
Choose how many cards the table puts down. The $5.21 tip does not divide evenly, so the odd cents go out by the same fixed rule the server uses, and the shares still add up to the tip exactly.
the balance
Check totaltotal_money$36.48
Guest 1's cardbalance_delta_money$18.24
Guest 2's cardbalance_delta_money$18.24
Still owedoutstanding_money$0.00
The check closes when the last card takes the balance to zero, and that is the one moment the paid event fires.
  • order.partially_paid
  • order.paid
  • order.closed
  • POST /v1/orders/{order_id}/payment-intents
  • POST /v1/orders/{order_id}/pay
  • expected_outstanding_money
  • payment_intent_allocations

06Pay at the table

The guest's phone is the card reader.

Create a hosted checkout for the open check and print its URL as a QR code on the bill. The guest scans it, picks a tip, and pays with Apple Pay, Google Pay, or a card. The payment lands on the order your POS already has open, so there is nothing to reconcile, no reader to ship, and no trip back to the register with someone’s card.

curl
curl -X POST https://api.withflintpay.com/v1/checkout-sessions \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: table-12-check" \
  -d '{
    "order_id": "ord_7g2TableTwelve",
    "tip": {
      "enabled": true,
      "tip_percent_options": [15, 18, 20],
      "default_tip_percent": 18,
      "is_custom_tip_enabled": true
    }
  }'

Call it when the table asks for the check. The session points at the order that already exists, so the guest sees the totals your POS shows.

  • POST /v1/checkout-sessions
  • checkout_session.url
The printed check. The code is the session's URL.
What the guest sees after scanning: the check, a tip picker, and one button.

Your own kiosk or ordering app

Keep the payment form inside your product. The order tells the browser how to collect a card, wallets included, and your backend pays the order with the result.

  • payment_collection
  • POST /v1/orders/{order_id}/pay

A printed code at the counter

A payment link is one URL every guest can open, for the lunch special on a counter card or a catering deposit. Each payment creates its own order. checkout.withflintpay.com/pay/pl_1kmn0aExample

  • POST /v1/payment-links
  • origin: payment_link

Every register accounted for

Register each iPad, kiosk, and handheld against its location, and every order says which one rang it up.

  • POST /v1/devices
  • location_id

07The kitchen

Tickets and the 86 board come off the same order.

A fulfillment attaches to the order's line items, so the kitchen screen, the expo screen, and the text to the guest all follow one event stream. Stock is kept per location, and the last portion of the special cannot be sold twice.

Fire the ticket
curl -X POST \
  https://api.withflintpay.com/v1/orders/ord_7g2TableTwelve/fulfillments \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "type": "pickup",
    "line_items": [
      { "order_line_item_id": "li_1kmn0aExample", "quantity": 1 }
    ]
  }'

The ticket is part of the order it came from, so there is no second record to keep in sync.

What your screens subscribe to

  • order.fulfillment.status_changed
  • order.fulfillment.event.created
  • order.inventory_exception.created
  • order.inventory_exception.resolved
Same order, two papers. The menu decides which modifiers the kitchen needs and which the guest sees: no pickles and the onion note print for the line and stay off the receipt.
  • show_on_fulfillment
  • show_on_receipt

86 the special at every location that ran out

A menu screen reads the level before it offers the dish. A checkout holds the last portions while the guest pays. If the stock is gone when the order is paid, the payment fails with INVENTORY_UNAVAILABLE before any money moves, so you never refund a dish the kitchen could not make.

curl
# "is the special still on?" Read the current level.
curl "https://api.withflintpay.com/v1/inventory-levels?inventory_item_id=invi_1kmn0aExample&location_id=loc_1kmn0aExample" \
  -H "Authorization: Bearer YOUR_API_KEY"

Reading a level claims nothing, so a menu screen can refresh it as often as it likes.

the 86 board
One special, three restaurants. A chain reads availability per location, because that is where the stock is.
inventory.shortage.detectedMission is down to 3 portions. Your manager app gets the event and the board turns amber before a guest orders the fourth.
  • inventory.level.updated
  • inventory.reservation.created
  • inventory.reservation.committed
  • inventory.reservation.hold_expired
  • inventory.shortage.detected
  • inventory.action_required
  • GET /v1/inventory-levels
  • POST /v1/inventory-reservations
  • available_quantity

08After the meal

Refund the wings, not a number.

Name the line item and a quantity. Flint works out what that dish settled for, including its share of the tax, and the order records what came back. The guest gets an emailed receipt when the check is paid, and every check is in your dashboard with its payments, refunds, and timeline.

app.withflintpay.com
Flint dashboard order detail: line items, totals with a line-item refund, the payment attempt and an activity timeline on one $19.43 order record
An order in the Flint dashboard: what sold, the totals, a refund against a single line item, the payment attempt, and the timeline. Your support team answers a disputed check from this screen.
curl
# the wings came out cold. refund the wings.
curl -X POST https://api.withflintpay.com/v1/refunds \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: table-12-wings" \
  -d '{
    "order_id": "ord_7g2TableTwelve",
    "line_items": [
      { "order_line_item_id": "li_2bqr7dExample", "quantity": 1 }
    ]
  }'

No amount in the request. The refund is computed from what the line settled for.

the refund
Buffalo wings × 1line_items[].quantity$11.95
Its share of the taxtax_money$0.96
Back to the guestamount_money$12.91
The order's line item picks up refunded_quantity and refunded_money, so the dashboard, your reports, and your support tool agree on what happened.
  • POST /v1/refunds
  • line_item_allocations
  • refunded_quantity
  • refunded_tip_money

What comes with the key

Your team ships the restaurant product. The check is done.

7
restaurant systems you never build
1
order per check, from the first round to the refund
5
ways for a guest to pay on the hosted check
574
API operations under the same key

Stripe processes every card · card data goes straight to Stripe · one processing fee per payment · samples validated against the published OpenAPI spec at build time

09Start

From a sandbox key to a paid tab in ten minutes.

Node

npm install @flintpay/node

CLI

npm install -g @flintpay/cli

Sandbox keys are free and need no credit card. Create an order with a modifier on it, append a round, set the tip to 18 percent, then pay it down with two legs and watch the balance reach zero. Pay with 4242 4242 4242 4242, any future expiry, any CVC. Then refund the wings.

Then read these

  1. 01
  2. 02
  3. 03
  4. 04
  5. 05
  6. 06
  7. 07
  8. 08
  9. 09

Skip the wait: npm install -g @flintpay/cli && flint signup creates your account and a sandbox key from the terminal. CLI guide

FAQ

Questions worth asking first.

What do restaurant tech teams build on Flint?

POS systems, online ordering, kiosks, and QR pay-at-table products. You build the screens and the service flow. Flint holds the check as an order, prices the modifiers, computes tax and tip, collects the payment, and sends the webhooks your kitchen and guest screens follow.

How does an open tab work?

Create an order with the first round and leave it open. Each later round is a POST to the order's line-items route, and the response is the full order with totals recomputed. The activity trail returns every change with a running balance, so a tab screen renders the state without computing it. The order closes when the check is paid.

How do split checks work?

Create one payment leg per guest with POST /v1/orders/{order_id}/payment-intents, then settle them through the order's pay route with a payment_intents array. Send expected_outstanding_money with the call: if the check changed since the guest saw it, Flint rejects the payment before charging. The balance runs down as legs land, and order.paid fires once, when the whole check is covered.

Who works out the tip on a split check?

Flint does. A requested tip is intent until the payment lands. When several legs settle in one pay call, Flint shares the tip across them in proportion to each leg's amount, assigns the odd cents by a fixed rule, and freezes the result on the payment attempt. You read each card's share from the tip's payment_intent_allocations. Your code never sets a tip amount on a leg.

How do guests pay at the table?

Create a checkout session for the open order with tipping enabled, and print the returned checkout_session.url as a QR code on the check. The guest scans it, picks a tip, and pays with Apple Pay, Google Pay, or a card on their own phone. The payment settles into the same order your POS has open, and order.paid tells every screen the table is closed.

Can I add a service charge or an automatic gratuity?

Yes. A charge is its own object on the order, separate from line items and from tips. Add it as a fixed amount or as a percent with an explicit basis, before or after discounts, and state whether it is taxable. Promotions on the menu never shrink it, and it can be refunded on its own. Charge types include service_fee, delivery_fee, packaging_fee, small_order_fee, and rush_fee.

Can I 86 an item?

Yes. Mark the variant as tracked, and stock is kept per location. Read the inventory level to show whether a dish is still on, and create a reservation to hold the last portions for up to 15 minutes while a guest pays. If the stock is gone when the order is paid, the payment fails before any money moves.

Who processes the cards?

Stripe processes every card, and card details go from the guest's browser straight to Stripe. Hosted checkout includes Apple Pay and Google Pay, and you pay one processing fee per payment.

What does it cost to start?

Card payments are 3.79% + 35¢, with no monthly fee and unlimited test mode. The sandbox is free and needs no credit card, so you can open a tab, split it across two test cards, and refund a dish before you decide anything.