Restaurant tech
The order does the restaurant math.
For teams building POS, online ordering, kiosk, and pay-at-table software. Modifiers, open tabs, tips, split checks, and 86-ing are order operations in Flint, not glue code in your app. Stripe processes every card.
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 names and no prices.
modifiers · tabs · tips · splits · 86-ing · no card-present
01The menu
Modifiers price and tax themselves.
An extra patty is plus 2.50 and taxable. No pickles is zero. Neither is arithmetic your code does before a charge.
{
"data": {
"order_id": "ord_7g2TableTwelve",
"status": "open",
"payment_status": "unpaid",
"line_items": [
{
"order_line_item_id": "li_1kmn0aExample",
"name": "Smash burger",
"modifiers": [
{ "modifier_group_name": "Extras", "name": "Add patty",
"unit_price_delta_money": { "amount": 250, "currency": "USD" },
"show_on_receipt": true, "show_on_fulfillment": true },
{ "modifier_group_name": "Remove", "name": "No pickles",
"unit_price_delta_money": { "amount": 0, "currency": "USD" },
"show_on_receipt": false, "show_on_fulfillment": true },
{ "modifier_group_name": "Kitchen note", "text_value": "no onions",
"unit_price_delta_money": { "amount": 0, "currency": "USD" },
"show_on_receipt": false, "show_on_fulfillment": true }
],
"modifier_total_money": { "amount": 250, "currency": "USD" },
"subtotal_money": { "amount": 1700, "currency": "USD" }
}
]
}
}Names, price deltas and modifier_total_money all came back resolved from the menu. Groups enforce their own min and max selections, so an invalid combination never reaches your totals.
- unit_price_delta_money
- min_selected / max_selected
- modifier_total_money
02The tab
The check stays open. Rounds append.
An order can stay open all night. Every round recomputes the totals on the same record, and the audit trail comes back with a running balance.
# 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" } }
]
}'{
"data": [
{
"order_activity_id": "act_1kmn0aExample",
"activity_type": "payment",
"description": "Payment received",
"balance_delta_money": { "amount": -1825, "currency": "USD" },
"running_balance_money": { "amount": 1823, "currency": "USD" },
"payment_intent_id": "pi_1kmn0aExample",
"created_at": "2026-07-03T21:14:00Z"
}
],
"next_page_token": null
}running_balance_money means you render the tab's state instead of recomputing it after every event.
- POST /v1/orders/{order_id}/line-items
- GET /v1/orders/{order_id}/activities
- running_balance_money
03The tip
Eighteen percent of what, exactly.
One number, one answer. Percent tips compute server-side on the post-discount subtotal, so happy hour cannot change what the tip means.
curl -X POST \
https://api.withflintpay.com/v1/orders/ord_7g2TableTwelve/requested-tip \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{ "requested_tip": { "percent": 18 } }'Percent is a whole number from 1 to 100. Pass amount_money instead for a fixed tip, never both.
{
"data": {
"order_id": "ord_7g2TableTwelve",
"tips": [
{
"order_tip_id": "otip_1kmn0aExample",
"percent": 18,
"status": "requested",
"effective_amount_money": { "amount": 521, "currency": "USD" }
}
],
"pricing_amounts": {
"subtotal_money": { "amount": 2895, "currency": "USD" },
"requested_tip_money": { "amount": 521, "currency": "USD" },
"total_money": { "amount": 3648, "currency": "USD" }
}
}
}A requested tip is intent, not money. It becomes settled when the payment lands, and clearing it flips the status to canceled without deleting the history.
{
"tip": {
"tip_percentages": [15, 18, 20],
"default_tip_percentage": 18,
"is_smart_tips_enabled": false
}
}These presets are exactly what drives the picker on the right. Small totals can use fixed smart-tip amounts instead of percentages.
- POST /v1/orders/{order_id}/requested-tip
- pricing_amounts.requested_tip_money
- settlement_amounts.settled_tip_money
04The split
Two cards. One check.
Several payment intents settle one order. The balance runs down, and the paid signal fires once.
# guest one covers half; the check stays open for the rest
curl -X POST \
https://api.withflintpay.com/v1/orders/ord_7g2TableTwelve/pay \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: table-12-guest-1" \
-d '{
"payment_intents": [
{ "payment_intent_id": "pi_1kmn0aExample",
"payment_method_id": "pm_1kmn0aExample" }
],
"expected_outstanding_money": { "amount": 3648, "currency": "USD" },
"completion_behavior": "partial_payment"
}'expected_outstanding_money is the guard: if the check moved since you read it, the call fails instead of charging the wrong number.
{
"data": {
"order_id": "ord_7g2TableTwelve",
"status": "closed",
"payment_status": "paid",
"pricing_amounts": {
"subtotal_money": { "amount": 2895, "currency": "USD" },
"tax_money": { "amount": 232, "currency": "USD" },
"requested_tip_money": { "amount": 521, "currency": "USD" },
"total_money": { "amount": 3648, "currency": "USD" }
},
"settlement_amounts": {
"settled_tip_money": { "amount": 521, "currency": "USD" },
"paid_money": { "amount": 3648, "currency": "USD" },
"outstanding_money": { "amount": 0, "currency": "USD" }
},
"payment_intent_ids": ["pi_1kmn0aExample", "pi_2bqr7dExample"]
}
}Both legs are on the record. The tip was allocated across them proportionally, with deterministic penny handling, and frozen on the attempt.
- POST /v1/orders/{order_id}/pay
- completion_behavior: partial_payment
- payment_intent_allocations
05Collection
The guest's phone is the payment hardware.
A code on the bill, a link at the counter, or a hosted page from your own flow. All three settle into the same order.
# no API key on this one: it is the buyer-side call behind
# a code printed on a menu, a table tent, or a counter card
curl -X POST \
https://api.withflintpay.com/v1/payment-links/plink_1kmn0aExample/resolve \
-H "Content-Type: application/json" \
-d '{}'No key on this request. That is what lets a printed code work with nothing running behind it.
curl -X POST https://api.withflintpay.com/v1/devices \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"name": "Register 2",
"location_id": "loc_1kmn0aExample",
"hardware_fingerprint": "ipad-a1b2c3"
}'A device is registration and attribution: which surface created which order. It is not hardware control, and it is not card-present.
- POST /v1/payment-links
- POST /v1/checkout-sessions
- POST /v1/devices
06The kitchen
One event stream, not three cron jobs.
Fulfillments attach to the order at line-item level, so the kitchen screen, the expo screen, and the customer text all key off the same events.
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 }
]
}'Pickup, shipment, or service. A ticket is part of the order it came from, not a parallel record you keep in sync.
- order.fulfillment.status_changed
- order.fulfillment.event.created
- order.inventory_exception.created
- order.inventory_exception.resolved
- POST /v1/orders/{order_id}/fulfillments
- type: pickup
- order.fulfillment.status_changed
07Availability
86 the special, everywhere at once.
Stock is tracked per location, so a menu screen can ask whether an item is still on before it offers it, and a checkout can hold the last portions while the guest pays.
# "is the special still on?" Advisory: it claims nothing.
curl -X POST https://api.withflintpay.com/v1/inventory-availability-previews \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"demands": [
{ "demand_key": "special", "inventory_item_id": "invi_1kmn0aExample",
"quantity": 1, "splitting_behavior": "single_location" }
],
"inventory_routing_source": {
"type": "fixed_location", "location_id": "loc_1kmn0aExample"
}
}'Advisory on purpose: it claims nothing, so it is safe to call from a menu screen on every render.
{
"data": {
"observed_at": "2026-07-03T21:02:00Z",
"results": [
{
"demand_key": "special",
"availability_status": "partial",
"locations": [
{ "location_id": "loc_1kmn0aExample",
"availability_status": "partial",
"available_quantity": 3 }
]
}
]
}
}Three portions left at this location. sufficient, partial, or none, per demand and per location.
# hold the last three portions while the guest pays
curl -X POST https://api.withflintpay.com/v1/inventory-reservations \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"type": "standalone",
"demands": [
{ "demand_key": "special", "inventory_item_id": "invi_1kmn0aExample",
"quantity": 3, "splitting_behavior": "single_location" }
],
"owner": {
"type": "merchant",
"key": "ord_7g2TableTwelve",
"expires_at": "2026-07-03T21:17:00Z"
}
}'A reservation is a real claim, not a counter you decrement and hope. It expires within 15 minutes unless you extend it.
# the guest is at the payment step: buy more time on the same hold
curl -X POST \
https://api.withflintpay.com/v1/inventory-reservations/invr_1kmn0aExample/start-payment-window \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"expected_inventory_reservation_revision": 4,
"payment_window_duration_seconds": 900
}'Buys up to 1800 more seconds on the same hold, so a slow card does not lose the last plate to someone else.
- inventory.level.updated
- inventory.reservation.created
- inventory.reservation.committed
- inventory.reservation.hold_expired
- inventory.shortage.detected
- inventory.action_required
- POST /v1/inventory-availability-previews
- POST /v1/inventory-reservations
- available_quantity
- GET /v1/locations/{location_id}/inventory
- inventory_tracking: tracked
- timezone
Tracking is never inferred. A variant declares it, and one that does not declare it sells without a stock check even if it is a physical good. That is deliberate: most menu items should not be gated on a count.
08Limits
What this does not do.
There is no card-present capability. No terminals, no card readers, no tap to pay, no hardware SDK, and no date being promised. If your product leads with dine-in service where a server carries a reader to the table, Flint alone cannot run that workflow. Square, Toast, or Stripe Terminal is the right answer for that today.
Flint is not a POS. You build the screens, the service flow, and the hardware story. This is the layer underneath, and if you want to read data out of a POS your customers already run, that is the vendor's own platform API, not this.
Inventory is stock, not recipes. Availability is tracked per item per location. Selling a burger does not decrement flour, patties, and cheese. If you need recipe-level depletion, that math stays in your kitchen system.
Flint runs on Stripe rails and inherits Stripe underwriting. There is no way to attach a Stripe account you already have, and if Stripe declined your business, Flint cannot approve it. Worth knowing before you write any code.
What teams build here instead: counter service, fast casual, online ordering, and kiosk products where the buyer's phone is the payment hardware. Some run Flint alongside an incumbent's hardware as a second checkout path that routes around it when the primary rail fails, which is written up in the Square outage playbook.
Against hand-rolled restaurant glue
This is not Flint versus Square or Toast, whose hardware and vertical software earn their place. It is Flint versus hand-building these features on a bare charge API, whichever processor is underneath.
Modifier price deltas and their tax treatment are computed on the line item, not in your code before a charge.
modifier_total_moneyAn open tab appends rounds and recomputes itself, instead of being rows you re-sync.
running_balance_moneyThe tip carries what was asked and what was collected as separate numbers.
settled_tip_moneySplit legs pay one balance down to zero, and the tip splits across them for you.
payment_intent_allocationsThe check becoming paid is one event, on any collection surface.
order.paidStock is claimed with a real reservation and a payment window, not a decrement.
inventory.reservation.committedReviewed 2026-07-24 against the published API.
09Start
Open a tab in ten minutes.
Node
CLI
Sandbox keys are free and take no card. Create an order with a modifier on it, append a round, set a tip at 18 percent, then pay it down with two payment intents and watch the balance reach zero. Pay with 4242 4242 4242 4242, any future expiry, any CVC.
FAQ
Questions worth asking first.
Is this the API for integrating with a POS my customers already use?
No, and it is the opposite direction. Toast and Square publish platform APIs for reading data out of their own systems. Flint is the payments and order layer that teams build their own POS, online ordering, kiosk, or pay-at-table product on top of.
Is Flint a POS system?
No. You build the screens, the service flow, and the hardware story. Flint holds the order, computes the totals, collects the payment, and fires the webhooks.
Does Flint support card-present payments or terminals?
No. There is no card-present capability today: no terminals, no readers, no tap to pay, no hardware SDK, and no promised date. Collection runs through QR codes, payment links, and hosted checkout. If integrated card-present is a hard requirement, Square, Toast, or Stripe Terminal is the right answer.
How do split checks work?
Create several payment intents against one order, each covering part of the total. POST to the order's pay route with a payment_intents array naming the legs you are settling, and settlement_amounts.outstanding_money runs down as each one lands. Use completion_behavior partial_payment while the check is still open. The order.paid event fires once, when the whole check is covered.
Who works out the tip on a split check?
Flint does. A requested tip is intent, not money, and when an order settles across several payment intents the pay call allocates the unresolved tip proportionally across the tip-capable legs, with deterministic penny handling, and freezes that allocation on the attempt. You read the result from each tip's payment_intent_allocations. You never set a tip amount on an order-owned payment intent.
Can I 86 an item?
Yes, through inventory. An availability check answers whether an item is still sellable at a location without claiming anything, which is what a menu screen wants. To actually hold stock while a guest pays, create a reservation: it expires within 15 minutes unless you open a payment window, which buys up to 1800 seconds more. Tracking is never inferred, so a variant has to declare it.