Skip to content

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

@flintpay/node
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.

one order, three calls
POST /v1/orders2 line items · tax computed201
POST /v1/checkout-sessionshosted checkout · pays $19.43201
POST /v1/refundsCroissant ×1 · −$5.36201
flintord_1kmn0aExample
Cold brew x 2$13.00
Croissant$4.95
↳ refunded x 1-$5.36
Subtotal$17.95
Tax$1.48
Total$19.43
Paid$19.43
Refunded-$5.36
balance $14.07 · partially_refunded
Partial refund
one record · printed by the API
The receipt the API prints for that order: totals computed, paid through hosted checkout, then one croissant refunded with its share of the tax. Tear it off to replay.

one 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.

  1. Create the order

    POST /v1/orders

    Line 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.
  2. Create a checkout session

    POST /v1/checkout-sessions

    Pass 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.

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

  3. 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.com
    The hosted page for the order above, built from the components the product ships. Every figure on it came from the API.
  4. 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

amount + currencywhat the charge records
  1. Totals

    An orders table and the code that sums it

    has to match the charged amount to the cent

  2. Tax

    A tax lookup, stored per line item

    recomputed on every edit and every refund

  3. Refunds

    Support tooling that maps a charge back to items

    plus each item's share of the tax

  4. Partial payments

    A balance column and the bookkeeping behind it

    updated on every payment and every refund

  5. Declines

    A parser for processor decline strings

    one more mapping for each payment method

  6. Webhooks

    Dedupe, replay, and retry-safe writes

    a processed-events table to maintain

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

ord_1kmn0aExampleGET /v1/orders/{order_id}
  1. Totals

    pricing_amountsPOST /v1/orders
  2. Tax

    tax_moneypricing_amounts.tax_money
  3. Refunds

    line_item_allocationsPOST /v1/refunds
  4. Partial payments

    outstanding_moneysettlement_amounts.outstanding_money
  5. Declines

    last_payment_errorPOST /v1/orders/{order_id}/pay
  6. Webhooks

    webhook_event_idorder.paid
  7. Reconciliation

    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
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 }
    ]
  }'
201 Created
{
  "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_1kmn0aExamplepaidafter 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
The order after the refund. The croissant and $0.41 of the tax went back, and the rest of the sale stands.
app.withflintpay.com
The same order in the dashboard. The refunded line is struck through on the record the payment settled against.

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.

  1. Hosted checkout

    POST /v1/checkout-sessions

    Flint 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.

  2. Your own form

    POST /v1/orders/{order_id}/payment-intents

    The 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.

  3. Your server

    POST /v1/orders/{order_id}/pay

    Your 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.

Your own form: what Elements needs, in one response
{
  "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.

Your server: POST /v1/orders/{order_id}/pay
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.

Embedded payments guide →

The hosted page on a phone, with the wallets as express buttons.

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.

The failed attempt, in the pay response
{
  "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 →

One attempt, start to finish
3D Secure arrives as a typed pending action on the attempt. You run it and resume.

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.

POST /webhooks/flint
{
  "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

flint listen --forward-to http://localhost:8080/webhooks/flint

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.

app.withflintpay.com/payments
The merchant dashboard's payments view, built from the components it ships. All 20 sections in the sidebar are there the first time you sign in.

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.

574
API operations on one set of keys
92
resource families, orders to payouts
20
dashboard sections before you write a line
568
tools an agent can call over MCP today

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

MCP servers and agent permissions →

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

What alpha accounts get →

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

npm install @flintpay/node

CLI

npm install -g @flintpay/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.

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