| Payment | Amount | Status | Method |
|---|---|---|---|
| pi_1kmn0aExample | $19.43 | Succeeded | Visa ··4242 |
| pi_1kmn0bExample | $128.00 | Succeeded | Apple Pay |
| pi_1kmn0cExample | $44.28 | Requires action | Visa ··0341 |
| pi_1kmn0dExample | $96.00 | Declined | Mastercard ··0002 |
| pi_1kmn0eExample | $212.50 | Succeeded | Bank ··6789 |
| pi_1kmn0fExample | $61.00 | Refunded | Google Pay |
Payments API · public alpha
A payments API with the order system built in.
Send line items. Flint computes the tax and the total, hosts the checkout, and charges the card through Stripe. Refunds go by line item, a decline comes back as one of 18 codes, and your backend gets one webhook when the order is paid. The order tables, tax math, and refund tooling you would build around a charge API are already running when you get your keys.
3.79% + 35¢ on cards · no monthly fee · free sandbox · no credit card
import { Client } from "@flintpay/node";
const flint = new Client({
baseUrl: "https://api.withflintpay.com",
apiKey: process.env.FLINT_API_KEY!,
});
// 1. line items in. No amount: Flint computes it.
const order = await flint.orders.create({
line_items: [
{ name: "Cold brew", quantity: "2",
unit_price_money: { amount: "650", currency: "USD" },
tax: { taxable: true } },
{ name: "Croissant", quantity: "1",
unit_price_money: { amount: "495", currency: "USD" },
tax: { taxable: true } },
],
tax: {
enabled: true,
calculation: { mode: "automatic", price_mode: "additive" },
},
});
// 2. a hosted checkout page for that order
const launch = await flint.checkoutSessions.create({
order_id: order.order_id,
redirects: { success_redirect_url: "https://example.com/thanks" },
});
// 3. send the buyer to launch.checkout_session.url
// 4. confirm from your backend before you fulfill
const paid = await flint.orders.get(order.order_id);The whole integration. You never send an amount: Flint computes it from the line items, and the checkout collects whatever the order still owes.
POST /v1/orders2 line items · tax computed201POST /v1/checkout-sessionshosted checkout · pays $19.43201POST /v1/refundsCroissant ×1 · −$5.36201one host · one /v1 · the API key picks test or live
01Start here
Four calls from line items to a paid order.
The whole path runs on a free sandbox key in about ten minutes, and none of it needs a frontend. Each call returns something you would otherwise have built.
Create the order
POST /v1/ordersLine items go in. Subtotal, tax, total, and the balance still owed come back computed, and every payment after this settles against that balance.
201 Created{ "data": { "order_id": "ord_1kmn0aExample", "status": "open", "payment_status": "unpaid", "pricing_amounts": { "subtotal_money": { "amount": 1795, "currency": "USD" }, "tax_money": { "amount": 148, "currency": "USD" }, "total_money": { "amount": 1943, "currency": "USD" } }, "settlement_amounts": { "paid_money": { "amount": 0, "currency": "USD" }, "outstanding_money": { "amount": 1943, "currency": "USD" } } } }what Flint computed Cold brew, 2 at $6.50 $13.00 Croissant, 1 at $4.95 $4.95 Subtotalpricing_amounts.subtotal_money $17.95 Taxpricing_amounts.tax_money $1.48 Totalpricing_amounts.total_money $19.43 Still owedsettlement_amounts.outstanding_money $19.43 Tax comes from the order's tax location, which for a counter sale is your business address. Change a line item and every figure is recomputed. Create a checkout session
POST /v1/checkout-sessionsPass the order id and where to send the buyer afterward. There is no amount in the request, because the session collects what the order owes.
curlcurl -X POST https://api.withflintpay.com/v1/checkout-sessions \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Idempotency-Key: checkout-cafe-001" \ -d '{ "order_id": "ord_1kmn0aExample", "redirects": { "success_redirect_url": "https://example.com/thanks" } }'201 Created{ "data": { "checkout_session": { "checkout_session_id": "cs_1kmn0aExample", "status": "open", "surface": "hosted", "order_id": "ord_1kmn0aExample", "url": "https://checkout.withflintpay.com/checkout/cs_1kmn0aExample#checkout_token=..." }, "checkout_access": { "checkout_auth_token": "ckat_1kmn0aExample" } } }Send the buyer to checkout_session.url exactly as returned. The token in the fragment is what admits them, so nobody reaches the page by guessing an id.
Send the buyer to the page
Flint hosts it under your business name. Every payment method you have enabled is already on it, along with 3D Secure and the receipt, so there is no payment form for you to build.
checkout.withflintpay.comThe hosted page for the order above, built from the components the product ships. Every figure on it came from the API. Confirm from your backend
GET /v1/orders/{order_id}Read the order, or subscribe to order.paid, and fulfill when payment_status is paid. A buyer who closes the tab before the redirect still gets their order, because you never depended on the redirect.
200 OK, after the buyer pays{ "data": { "order_id": "ord_1kmn0aExample", "status": "closed", "payment_status": "paid", "settlement_amounts": { "paid_money": { "amount": 1943, "currency": "USD" }, "outstanding_money": { "amount": 0, "currency": "USD" } } } }Nothing outstanding, and the order closed itself. An abandoned checkout leaves the order open and unpaid, and the same URL can still collect it.
02What you skip
The code around the charge is already written.
A charge API records an amount and a currency. What was sold, the tax on it, what went back, and what is still owed live in code you write and keep in sync with the processor, and that code grows every month you run it. On Flint all of it is the order, and the API maintains it.
Around a charge API, you build
Totals
An orders table and the code that sums it
has to match the charged amount to the cent
Tax
A tax lookup, stored per line item
recomputed on every edit and every refund
Refunds
Support tooling that maps a charge back to items
plus each item's share of the tax
Partial payments
A balance column and the bookkeeping behind it
updated on every payment and every refund
Declines
A parser for processor decline strings
one more mapping for each payment method
Webhooks
Dedupe, replay, and retry-safe writes
a processed-events table to maintain
Reconciliation
A nightly job diffing your database against the processor
and someone to read what it finds
7 systems to build, test, and keep in sync
On Flint, you read the order
Totals
pricing_amountsPOST /v1/ordersTax
tax_moneypricing_amounts.tax_moneyRefunds
line_item_allocationsPOST /v1/refundsPartial payments
outstanding_moneysettlement_amounts.outstanding_moneyDeclines
last_payment_errorPOST /v1/orders/{order_id}/payWebhooks
webhook_event_idorder.paidReconciliation
balance transactionsGET /v1/balance-transactions
1 order · 1 API key · 1 webhook stream
reviewed 2026-09-19 against the published API · corrections: Flint Help
03Refunds
Refund one item. The tax comes back with it.
Name the line item and the quantity. Flint works out what that line settled for, adds its share of the tax, records the split on the order, and rejects any refund larger than what was paid. Your support team and your reports read the same record.
curl -X POST https://api.withflintpay.com/v1/refunds \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: refund-ord-003" \
-d '{
"order_id": "ord_1kmn0aExample",
"reason": "requested_by_customer",
"line_items": [
{ "order_line_item_id": "li_1kmn0bExample", "quantity": 1 }
]
}'{
"data": {
"refund_id": "ref_1kmn0aExample",
"status": "succeeded",
"amount_money": { "amount": 536, "currency": "USD" },
"line_item_allocations": [
{
"order_line_item_id": "li_1kmn0bExample",
"quantity": 1,
"amount_money": { "amount": 495, "currency": "USD" },
"tax_money": { "amount": 41, "currency": "USD" }
}
]
}
}$4.95 for the croissant plus $0.41 of tax. You sent a line item id and a quantity, and no amounts.
| order ord_1kmn0aExample | paid | after refund | |
|---|---|---|---|
| Cold brew, 2 | $13.00 | $13.00 | |
| Croissant, 1amount_money | $4.95 | $0.00 | |
| Taxtax_money | $1.48 | $1.07 | |
| Collectedsettlement_amounts | $19.43 | $14.07 | |
Refund by amount, by line item, or by charge, with restocking fees and withheld amounts as adjustments on top. Refunds guide →
04Collection
Hosted page, your own form, or your server.
All three pay the same order, so refunds, webhooks, and reporting work the same whichever you pick. Start on the hosted page and move to your own form later without touching the rest of your integration.
Hosted checkout
POST /v1/checkout-sessionsFlint hosts the page under your business name, with every payment method you have enabled already on it. You redirect to the URL the API returns.
Your own form
POST /v1/orders/{order_id}/payment-intentsThe response carries the connected account and the publishable key, so Stripe Elements mounts in your UI under the right account with no lookup on your side.
Your server
POST /v1/orders/{order_id}/payYour server holds the payment token and confirms. Send the balance the buyer approved as expected_outstanding_money, and Flint refuses to charge if the order changed since.
Payment methods, on all three
- ach_debit
- affirm
- apple_pay
- card
- google_pay
Wallets render as express buttons above the card form. ACH debit uses instant bank verification, so the buyer never types a routing number. Every method settles into the same order.
{
"data": {
"payment_intent": {
"payment_intent_id": "pi_1kmn0aExample",
"status": "requires_confirmation",
"amount_money": { "amount": 1943, "currency": "USD" }
},
"payment_collection": {
"stripe": {
"account_id": "acct_1kmn0aExample",
"publishable_key": "pk_test_1kmn0aExample",
"elements": {
"mode": "payment",
"amount_money": { "amount": 1943, "currency": "USD" },
"payment_method_types": ["card"],
"digital_wallets": ["apple_pay", "google_pay"],
"payment_method_creation": "manual"
}
}
}
}
}Account, publishable key, amount, and the wallets to offer. Your frontend mounts Stripe Elements from this and never stores a credential.
curl -X POST \
https://api.withflintpay.com/v1/orders/ord_1kmn0aExample/pay \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: pay-ord-1kmn0a-1" \
-d '{
"action": "confirm_payment_intents",
"payment_intents": [
{
"payment_intent_id": "pi_1kmn0aExample",
"token": "pm_1kmn0aExample"
}
],
"expected_outstanding_money": {
"amount": 1943,
"currency": "USD"
},
"completion_behavior": "complete_order"
}'expected_outstanding_money is the balance the buyer approved. If the order changed after they saw it, Flint refuses before charging.
05Declines
Every decline comes with a code you can branch on.
When a payment fails, the pay call returns the attempt, the payment that failed, and the reason in the same response. The reason is one of 18 codes covering cards and bank debits, so your retry logic never parses a processor string and never makes a second request to learn why.
{
"data": {
"order": {
"order_id": "ord_1kmn0aExample",
"status": "open",
"payment_status": "unpaid"
},
"payment_attempt": {
"order_payment_attempt_id": "opat_1kmn0aExample",
"status": "failed",
"is_resumable": false,
"payment_intents": [
{
"payment_intent_id": "pi_1kmn0aExample",
"status": "requires_payment_method",
"amount_money": { "amount": 1943, "currency": "USD" },
"last_payment_error": {
"code": "insufficient_funds",
"message": "The card was declined for insufficient funds."
}
}
]
}
}
}is_resumable tells your code whether to continue this attempt or start another, so a dropped connection never charges the buyer twice.
last_payment_error.code · closed set of 18
- card_declined
- insufficient_funds
- expired_card
- incorrect_cvc
- authentication_required
- processing_error
- payment_blocked
- payment_method_declined
- payment_method_unavailable
- payment_method_temporarily_unavailable
- payment_not_completed
- payment_action_expired
- bank_account_closed
- bank_account_not_found
- bank_debit_not_authorized
- bank_account_restricted
- bank_debit_limit_exceeded
- payment_failed
Flint maps provider-specific reasons into this set before they reach you. Branch on the code and write your own buyer-facing copy. What each issuer decline code means, and whether a retry can work, is in the card decline codes reference. Declines guide →
06After the payment
One webhook when the order is paid. A dashboard on day one.
order.paid fires once, when the whole balance is covered, however many payments it took. Your team gets the dashboard that comes with the keys, with payments, orders, disputes, balances, and analytics already in it, so you never build an admin panel.
{
"webhook_event_id": "whev_1kmn0aExample",
"event_type": "order.paid",
"payload_version": 1,
"mode": "test",
"merchant_id": "mer_1kmn0aExample",
"created_at": "2026-09-15T14:00:00Z",
"data": {
"order_id": "ord_1kmn0aExample",
"status": "closed",
"payment_status": "paid",
"total_money": { "amount": 1943, "currency": "USD" },
"paid_money": { "amount": 1943, "currency": "USD" },
"outstanding_money": { "amount": 0, "currency": "USD" },
"order_payment_intent_ids": ["pi_1kmn0aExample"]
}
}Every delivery carries a stable webhook_event_id. Retries and manual resends reuse it, so it is your deduplication key, and the same value arrives in a header before you parse the body.
Events you subscribe to
- order.paid
- order.partially_paid
- order.refunded
- payment_intent.succeeded
- payment_intent.requires_action
- payment_intent.payment_failed
- refund.created
- dispute.created
- payout.paid
Each event is a fact about one object, with a snapshot of that object in the body. Webhooks guide →
Receive them on localhost
No tunnel and no public URL. The CLI mints a signing secret for the session, so you test the same signature check you will run in production.
What comes with the keys
Stripe processes every card. You sign up once and hold one set of keys.
Card data goes straight to Stripe, with the same PCI scope, fraud detection, and dispute handling as a direct integration. Flint provisions and runs the processing account, so you never open a Stripe account, and you pay one processing fee per payment. Moving an existing Stripe integration? The Stripe migration guide maps each Stripe object to its Flint equivalent, one payment flow at a time.
API counts come from the published OpenAPI spec · every code sample is checked against it at build time
07AI agents
An agent reads the order before it moves money.
An amount and a currency tell an agent nothing about what was sold. On Flint it reads the line items, the balance, and the refund state first, and every money-moving method carries machine-readable hints about side effects and when a person has to confirm.
agent_hints · POST /v1/refunds
- Side effect
- Financial
- Human confirmation
- Required
- Idempotent
- Yes, per Idempotency-Key
- Terminal states
- succeeded · failed
The docs MCP server hands a coding agent exact endpoint schemas while it writes the calls. The CLI MCP server runs on your machine with your credential and requires confirmation for destructive operations and for sensitive operations in live mode.
What an agent reads before it refunds
- order.line_items
- order.pricing_amounts.tax_money
- order.settlement_amounts.outstanding_money
- order.refund_status
Public alpha
Alpha accounts get new features first.
Merchants take real payments on Flint today, and new features reach alpha accounts before general rollout. The order you create this week is the record you keep.
shipped 2026-09-29 · Codes for saved details default to the auto channel
Building now?
Create an account from your terminal with flint signup. The sandbox is free, with no credit card.
08Surface
Everything else is on the same keys.
Each of these reads and writes the order you already created. There is no second product to buy and no second integration.
Orders
Line items, discounts, tips, and tax on one record, with totals computed server-side and a balance that runs down as money lands.
- POST /v1/orders
- pricing_amounts.total_money
- settlement_amounts.outstanding_money
Payments
Automatic or manual capture. Several payments can settle one order, which is how split tender and partial payment work.
- POST /v1/orders/{order_id}/pay
- POST /v1/payment-intents
- capture_method
Refunds
By amount, by line item, or by charge. Over-refunds are blocked against settled payments, so discounts and tips are already accounted for.
- POST /v1/refunds
- line_items[].order_line_item_id
- line_item_allocations
Attempts
Every try at paying an order is inspectable, including the failed ones, with the reason and whether it can be resumed.
- GET /v1/orders/{order_id}/payment-attempts
- is_resumable
- last_payment_error
Disputes
Each dispute links back to the payment and the order it came from, with its evidence deadline and a dispute.created event the moment it opens.
- GET /v1/disputes
- evidence_due_at
- evidence_response_allowed
Money out
Balances, payouts, and the ledger behind them. Each payment carries its processing fee, any add-on fees, and merchant_net_money, so you never compute net yourself.
- GET /v1/balances
- GET /v1/balance-transactions
- processing_fee_money
- add_on_fees
- merchant_net_money
Conventions that hold everywhere
- Money
- { "amount": 1943, "currency": "USD" }
- Pagination
- page_size, page_token, next_page_token
- Retries
- Idempotency-Key on any write, honored 24h
- Tracing
- request_id on every response and every error
- Environments
- one host, one /v1, the key picks the mode
What core payments cost
- Cards and wallets
- 3.79% + 35¢
- Bank debit
- 1.00%, $1 minimum, no cap
- Monthly
- no monthly fee
- Sandbox
- free, no credit card
One fee per payment covers Flint and the card processing underneath, so there is no separate processor bill. Pricing →
09Start
From a sandbox key to a paid test order in ten minutes.
Node
CLI
Test keys are prefixed flint_test_ and bound to a sandbox with its own data, so nothing you do there touches live money. Live keys are prefixed flint_live_, on the same host and the same paths. Pay with 4242 4242 4242 4242, any future expiry, any CVC.
Then read these, in order
- 01
- 02
- 03
- 04
FAQ
Before you sign up.
What does an order-first payments API change for me?
The amount is derived, never submitted. You send line items and Flint computes subtotal, tax, and total server-side. Payments settle against that balance, refunds target line items by id and quantity with the tax share computed for you, declines come back as a normalized code in the same response, and webhooks report the sale becoming paid rather than a charge succeeding. Around a charge API, each of those is code you write and data you keep in sync with the processor.
Is there a payments API with an orders API built in?
Yes. Flint provides the order layer as its core product: orders with server-computed totals, item-level refunds, and order-level webhooks, with Stripe processing every card underneath. Stripe itself no longer has one: it deprecated its original Orders API in October 2019 and removed the 2022 replacement beta before it reached general availability, and its documentation now starts new integrations at payment surfaces like Checkout Sessions.
Who processes the cards?
Stripe processes every card. Card data goes directly to Stripe, with the same PCI scope, fraud detection, and dispute handling as a direct Stripe integration. Flint provisions and operates the processing account, so there is one contract, one set of keys, and one fee per payment. You integrate with Flint for both commerce and payment processing.
Which payment methods can buyers use?
ACH debit, Affirm, Apple Pay, Card, Google Pay. Every method settles into the same order, so refunds, webhooks, and reporting behave identically no matter how the money arrived. ACH debit is USD, on-session, and uses instant bank verification. Affirm appears when the merchant enables it and the transaction is eligible.
Do I have to use the hosted checkout?
No. The same order can be paid by mounting Stripe Elements in your own UI, by confirming from your server with a payment token, or through a payment link or invoice. The order’s payment collection tells the browser what to collect and under which account, so Elements mounts without a lookup on your side. Embedded payments guide
Can several payments settle one order?
Yes. An order can hold several payment intents, each covering part of the balance, and outstanding_money runs down as each one lands. Pay with completion_behavior partial_payment while the order is still open. The order.paid event fires once, when the whole balance is covered, however many payments it took.
How do I know a payment really succeeded?
From the webhook or a backend read of the order, never from the browser redirect. A buyer can close the tab before the redirect and anyone can open the success URL directly. Treat order.paid, or payment_status paid on a GET of the order, as the signal to fulfill. Payments are asynchronous in the cases that matter: ACH can sit in processing after the buyer is done, and 3D Secure hands control to the issuer mid-flow.
Do I have to adopt the whole order model on day one?
No. Charge a card with one POST /v1/payment-intents call and stop there if that’s all you need today. Orders, hosted checkout, refunds by line item, subscriptions, and payouts are the same API and the same keys when you want them. No migration and no second account.
What does it cost?
Cards cost 3.79% + 35¢, with no monthly fee. The sandbox is free and needs no credit card. One processing fee per payment covers Flint and the card processing underneath, so there is no separate processor bill.
Is there an SDK or a CLI?
Yes. @flintpay/node is the TypeScript and JavaScript SDK and flintpay/flint is the PHP SDK, both for server-side use. @flintpay/cli is a command-line client whose API commands each map to a documented /v1 route; it forwards live sandbox webhook events to localhost with flint listen and exposes its commands to AI agents as MCP tools through flint mcp serve. The API itself is plain HTTP and JSON, so none of them are required.
How do I get started?
Request access and we email you a link to create your account, or create one now from your terminal with flint signup. The sandbox is free, with no credit card and no sales call. Flint is in public alpha and onboards US businesses today; buyers can pay from anywhere. The quickstart creates a payment link and pays it with a test card in about ten minutes.
Create your first order today.
Free sandbox, no credit card, no sales call. The first order you create in the sandbox is the same record your dashboard, your buyers, and your agents will read in production.
Skip the wait: npm install -g @flintpay/cli && flint signup creates your account and a sandbox key from the terminal. CLI guide
POST /v1/orders · create your first order