Skip to content

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.

One order, after everything touched it
{
  "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.

The same numbers, as the buyer sees them. Nothing was recomputed to render this.

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.

curl
# 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.

your storefront
An open order, rendered. The state is read, not derived.
  • 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.

A code campaign
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.

Buy X get Y, with stacking rules
{
  "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.

Price it without applying it
# 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.

the buyer's order
LAUNCH2020% off your order
Subtotal$73.00
Discount-$14.60
Tax$4.67
Total$63.07
The real promotion-code field. Codes stack because stackability is the server's decision, not the field's, so it always offers to add another.
A valid code that lost
{
  "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.

One record, in 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" }
    }
  }
}

Subtotal, then discount, then tax on what remains. No service had to be told what another service did.

Computed here, configured elsewhereThere is no jurisdiction configuration API. Registrations and nexus are set in the dashboard, not through this surface.
  • 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
the buyer's invoice
The hosted invoice document, as sent.
signup
What the buyer agrees to at subscription signup.

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: Bearer

One webhook stream, in one envelope format, deduplicated on one event id.

webhook_event_id

One customer object, shared by orders, subscriptions and invoices.

customer_id

Discounts and tax compute against each other, because they are on the same record.

pricing_amounts

One timeline to read when something looks wrong, instead of six dashboards.

GET /v1/orders/{order_id}/activities

Reviewed 2026-07-25 against the published API, which currently exposes 596 endpoints across 84 resource families.

09Start

Model one order end to end.

Node

npm install @flintpay/node

CLI

brew install flintpay/tap/flint

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.

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.