Commerce API
Six vendors, one record.
A composable stack assigns a vendor to every primitive: cart, pricing, promotions, tax, billing, and a payment provider wired in last. Flint ships those primitives on one API, and they all operate on the same order.
{
"data": {
"order_id": "ord_1kmn0aExample",
"pricing_amounts": {
"subtotal_money": { "amount": 7300, "currency": "USD" },
"discount_money": { "amount": 1460, "currency": "USD" },
"tax_money": { "amount": 467, "currency": "USD" },
"total_money": { "amount": 6307, "currency": "USD" }
}
}
}Priced by the catalog, discounted by a promotion, taxed on the discounted base. Three services in a stitched stack, three fields on one record here.
84 resource families · 596 endpoints · one auth scheme
01The cart service
Orders are the cart.
Not a missing feature. There is no cart-to-order conversion because there are not two resources to convert between.
# there is no cart resource to convert. the order is the cart.
curl -X POST https://api.withflintpay.com/v1/orders \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{ "line_items": [ { "name": "Wool beanie", "quantity": 2,
"unit_price_money": { "amount": 2400, "currency": "USD" } } ] }'
# the buyer adds another item twenty minutes later
curl -X POST \
https://api.withflintpay.com/v1/orders/ord_1kmn0aExample/line-items \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{ "line_items": [ { "name": "Canvas tote", "quantity": 1,
"unit_price_money": { "amount": 2500, "currency": "USD" } } ] }'The same record twenty minutes apart. Totals recompute server-side on every change, so nothing has to be kept in sync.
- POST /v1/orders
- POST /v1/orders/{order_id}/line-items
- pricing_amounts
02The pricing service
Prices that assemble themselves.
Variants, modifiers and bundles price and tax on the line item, so the number your checkout shows is the number the API computed.
Catalog
Products carry variants; variants carry sku, unit price, tax category, and whether they are sellable. Exact-SKU lookup resolves a variant or a bundle in one call.
- GET /v1/catalog/by-sku/{sku}
- unit_price_delta_money
- modifier_total_money
Modifiers and bundles
Modifier sets attach to products and carry their own price deltas and taxable flags, with min and max selections enforced. Bundles compose variants into a single sellable thing.
- POST /v1/bundles
- POST /v1/bundles/{bundle_id}/components
- POST /v1/modifier-sets
03The promotions service
A discount rule engine, not a coupon table.
This is the part most teams assume they will have to keep. Automatic application, code campaigns, buy-X-get-Y, eligibility rules, and conflict control are all on the API.
curl -X POST https://api.withflintpay.com/v1/promotions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: promo-launch-20" \
-d '{
"name": "Launch promotion",
"display_name": "20% off your order",
"redemption_type": "code",
"discount_class": "order",
"application_method": {
"type": "percent_off",
"percent_off": 20
},
"codes": [ { "code": "LAUNCH20" } ]
}'The promotion, its rule, and the code LAUNCH20 in one create. Swap redemption_type to automatic and it applies during order pricing with no code at all.
{
"display_name": "Buy 2 tees, get 1 half off",
"redemption_type": "code",
"discount_class": "line_item",
"application_method": {
"type": "buy_x_get_y",
"buy_min_quantity": 2,
"get_quantity": 1,
"get_percent_off": 50,
"max_applications_per_order": 1
},
"combines_with": { "order": false },
"exclusivity": { "group": "launch", "selection": "best_of" },
"stacking_mode": "stop_after"
}combines_with restricts which discount classes may stack, exclusivity groups keep one winner, and stop_after lets an admitted promotion end the contest.
# what would this do to the order, without doing it
curl -X POST \
https://api.withflintpay.com/v1/orders/ord_1kmn0aExample/discounts/preview \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{ "code": "LAUNCH20" }'The first question a discount engine gets: can I show the buyer what a code would do before committing it to the order.
{
"data": { "order_id": "ord_1kmn0aExample" },
"meta": {
"warnings": [
{ "code": "not_combinable" }
]
}
}Losing conflict resolution is a 200 with a warning, not an error. The code was fine, it just did not win, and those need different copy in your UI.
- POST /v1/promotions
- eligibility_rules
- stacking_mode
- POST /v1/promotions/{promotion_id}/codes
- GET /v1/promotions/by-code/{code}
- max_uses / uses_count
Coupons are the same engine with a deliberately small API shape, for when all you want is a reusable code with a percent or an amount off. Reach for promotions when the discount needs rules. The promotions guide covers eligibility and stacking in full.
04The tax service
Tax computes where the discount already landed.
In a stitched stack the tax service prices a total it never fully sees. Here it runs on the order, after discounts, so the question has one answer.
{
"data": {
"order_id": "ord_1kmn0aExample",
"pricing_amounts": {
"subtotal_money": { "amount": 7300, "currency": "USD" },
"discount_money": { "amount": 1460, "currency": "USD" },
"tax_money": { "amount": 467, "currency": "USD" },
"total_money": { "amount": 6307, "currency": "USD" }
}
}
}Subtotal, then discount, then tax on what remains. No service had to be told what another service did.
- POST /v1/orders/{order_id}/tax
- pricing_amounts.discount_money
- pricing_amounts.tax_money
05The billing service
Every renewal is an order.
Recurring revenue is not a parallel system with its own customer object and its own reporting. It is the same record, created on a schedule.
Plans and subscriptions
Plans define the billing interval, trials, and line items from the same catalog. Each cycle creates an order and charges the saved payment method.
- POST /v1/subscription-plans
- POST /v1/subscriptions
- billing_interval
Invoices
A hosted invoice document with its own checkout session, settling into the same order the invoice describes.
- POST /v1/invoices
- POST /v1/invoices/{invoice_id}/checkout-session
- invoice_id
Billing events
- subscription.activated
- subscription.payment_succeeded
- subscription.payment_failed
- subscription.past_due
- subscription.canceled
06The payment provider
Already inside.
Payments are part of the API rather than a connector wired in last. They settle the balance on the order everything else has been operating on.
Payment intents, hosted checkout, payment links and invoices all collect against the same order, so the sale and the money are one record rather than two systems you reconcile nightly. Stripe processes every card underneath.
Why that shape matters, and what it changes about refunds and webhooks, is its own page.
- POST /v1/orders/{order_id}/pay
- settlement_amounts.outstanding_money
- order.paid
07Inventory
Stock lives at a location.
Per location, with real claims rather than a counter you decrement and hope.
Availability and reservations
An availability check answers whether something is sellable without claiming it, which is what a product page wants. A reservation is a real hold, with an expiry and an optional payment window for the checkout step.
- POST /v1/inventory-availability-previews
- POST /v1/inventory-reservations
- inventory_tracking: tracked
Locations
A location holds stock and carries its own address and timezone. An inventory level is the item-and-location pair, and a variant points at an inventory item rather than carrying a count, so one pool can back several products.
- POST /v1/locations
- GET /v1/locations/{location_id}/inventory
- available_quantity
Tracking is never inferred: a variant declares it. The inventory guide follows one unit from receipt to a paid, fulfilled order.
08The comparison
Against the stitched stack.
One auth scheme, not one per vendor.
Authorization: BearerOne webhook stream, in one envelope format, deduplicated on one event id.
webhook_event_idOne customer object, shared by orders, subscriptions and invoices.
customer_idDiscounts and tax compute against each other, because they are on the same record.
pricing_amountsOne timeline to read when something looks wrong, instead of six dashboards.
GET /v1/orders/{order_id}/activitiesReviewed 2026-07-25 against the published API, which currently exposes 596 endpoints across 84 resource families.
09Start
Model one order end to end.
Node
CLI
Create a product with a modifier, put it on an order, add an automatic promotion, and watch the discount and the tax settle against each other on one record. That is the whole argument, and it takes about ten minutes to disprove.
Then read these
FAQ
Questions worth asking first.
Does Flint have a cart API?
No, and not as a gap: orders are the cart. Creating an order gives you an open record you mutate, appending line items, applying codes, setting a tip, and pricing recomputes server-side on every change. Collection happens against that same record through checkout sessions, payment links, invoices, or payment intents. There is no cart-to-order conversion step because there are not two resources.
Does Flint have a promotions engine?
Yes. Promotions are a discount rule engine: automatic application evaluated during order pricing, code campaigns with many codes under one promotion, buy-X-get-Y, percent and amount off, eligibility rules on the order, the customer, or specific line items, and conflict control through combines_with, exclusivity groups, and stacking mode. Coupons are the same engine with a deliberately small API shape, for when you just want a reusable code.
Can Flint replace my composable commerce stack?
It depends which parts you actually use. Flint covers catalog with variants, bundles and modifiers, orders, promotions and coupons, subscriptions, invoices, per-location inventory, tax computed on orders, payments, refunds, and webhooks on one API. It does not cover storefronts, product search beyond exact SKU lookup, localization, B2B price lists, or tax jurisdiction configuration. If your stack exists to deliver those, keep it. If it exists to make several services behave like one commerce system, that is the part Flint replaces.
How do subscriptions relate to orders?
Every renewal is an order. Plans define the billing interval, trials, and line items from the same catalog. Each cycle Flint creates an order, charges the saved payment method, and emits subscription.payment_succeeded or subscription.payment_failed. Refunds, receipts, analytics, and reconciliation treat recurring revenue exactly like any other order, because it is one.
Do I still need a payment provider?
No. Payments are part of the API rather than a connector: payment intents, hosted checkout, payment links, and invoices all settle into the order record. Stripe processes every card underneath, with the same PCI scope, fraud detection, and dispute handling as a direct Stripe integration. There is no way to attach a Stripe account you already have, and Flint inherits Stripe underwriting, so if Stripe declined your business, Flint cannot approve it.
Is inventory single-location?
No, it is per location. A location holds stock, an inventory level is the item-and-location pair with its quantities, and a catalog variant points at an inventory item rather than carrying a count, so one pool can back several sellable products. Availability checks answer whether something is sellable without claiming it, and reservations hold real stock with an expiry and an optional payment window.