Node SDK

Use @flintpay/node when your integration runs in Node.js or TypeScript and you want typed request/response shapes, retry behavior, and async pagination helpers.

Install#

Bash
npm install @flintpay/node

Use the SDK from your server, worker, or other trusted backend environment. Do not ship your Flint API key in browser or mobile client code.

If you need to set up a Flint account before you have a normal API key, use the /v1/onboarding/... state machine and POST /v1/merchant-account-sessions over plain HTTP, as walked through in API & Agent Onboarding. The SDK does not currently wrap the onboarding state machine or the merchant-account-session mint; the rest of this guide assumes you already have an API key.

After setup, sandbox-management routes accept a normal external API key. The main Flint client exposes those routes under flint.developer.

What The SDK Wraps#

The SDK is a typed client over Flint's public contract. It maps clean TypeScript methods onto the supported external API surface, but it is not intended to expose every internal proto-only field or transport detail.

Use the SDK when you want:

  • camelCase method params and response fields
  • typed money objects, enums, and dates
  • built-in async pagination
  • consistent retry and error handling

Use raw HTTP when you are integrating from another language, need exact wire-level control, or want to work directly with the documented JSON payloads.

Initialize the Client#

TypeScript
import { Flint } from "@flintpay/node";

const flint = new Flint({
  apiKey: process.env.FLINT_API_KEY!,
});

By default the SDK talks to https://api.withflintpay.com. Use a flint_test_... key for sandbox traffic and a flint_live_... key for live traffic.

For ongoing sandbox automation after setup:

TypeScript
const sandboxes = await flint.developer.listSandboxes();

For disposable sandbox automation, call resetSandbox(), capture the replacement sandboxId from the response, and issue new test keys for it. The pre-reset ID and keys belong to the archived environment.

If you need per-key request visibility for sandbox or CI traffic, issue a key with the developer.request_logs.self.read scope and read logs through flint.developerLogs.

First Calls#

Create an order, then create a payment intent from that order:

TypeScript
const order = await flint.orders.create({
  lineItems: [
    {
      name: "Premium Widget",
      quantity: 1,
      unitPriceMoney: { amount: 2500, currency: "USD" },
    },
  ],
});

const paymentIntent = await flint.paymentIntents.create({
  orderId: order.orderId,
});

The payment intent amount is derived from the current order balance, the same way it is in the HTTP API.

Pagination#

List methods return an async iterable:

TypeScript
for await (const customer of flint.customers.list({ limit: 25 })) {
  console.log(customer.customerId, customer.email);
}

The same pattern works for webhook events and API request logs:

TypeScript
for await (const event of flint.webhooks.listEvents({
  webhookEndpointId: "whep_123",
  status: "failed",
})) {
  console.log(event.webhookEventId, event.lastError);
}

for await (const log of flint.developerLogs.list({
  status: "client_error",
  pathQuery: "/v1/orders",
})) {
  console.log(log.requestId, log.statusCode, log.errorCode);
}

Inspect one webhook event and its delivery attempts with:

TypeScript
const event = await flint.webhooks.getEvent("whev_123");
const attempts = await flint.webhooks.listDeliveryAttempts(event.webhookEventId);

Headless Checkout#

For a merchant-owned checkout, the SDK can hold a checkout credential instead of an API key:

TypeScript
import { Flint } from "@flintpay/node";

const checkout = Flint.forCheckout({
  checkoutSessionId: "cs_1kmn0aExample",
  checkoutSessionSecret: session.checkoutAccess.checkoutAuthToken,
});

FlintConfig is a union: pass apiKey or checkoutSession, never both. Passing both throws. A checkout client sends the X-Checkout-Session-ID and X-Checkout-Session-Secret headers and is scoped to that session's own order, which is what keeps it from becoming a second API key.

Helpers for the collection flow live in the same package:

TypeScript
import {
  stripeElementsBootstrap,
  paymentLegSelections,
  payOrderWithPaymentLegs,
  pendingStripeActions,
  checkoutProblemRemediations,
} from "@flintpay/node";
  • stripeElementsBootstrap(paymentCollection) turns Flint's collection guidance into the arguments for Stripe(...) and stripe.elements(...), lowercasing the currency for you. It throws unless nextStep is create_confirmation_token, so it is for checkout-authenticated collection; a merchant-authenticated read returns collect_payment_source and should go through paymentSource directly.
  • paymentLegSelections(bindings) builds paymentIntents[] for split tender, mixing newly collected ConfirmationTokens and saved payment methods. It rejects duplicate legs and empty credentials rather than letting the API do it.
  • payOrderWithPaymentLegs(flint, orderId, expectedOutstandingMoney, bindings, options) is the multi-leg pay call. payOrderWithConfirmationTokens is the same function narrowed to token-only bindings.
  • pendingStripeActions(attempt) flattens pendingActions[] into the values a browser needs for the named Stripe.js call.
  • checkoutProblemRemediations(session) returns only the problems that carry a remediation, with whether each blocks completion.

Resume a frozen attempt with the ordinary pay method, passing the attempt ID and no start fields:

TypeScript
const result = await flint.orders.pay(orderId, {
  paymentAttemptId: "opat_1kmn0aExample",
});

resumeOrderPayment() does not resume a payment despite its name. It reads the attempt through GetOrderPaymentAttempt. Use it to poll state, and use flint.orders.pay(orderId, { paymentAttemptId }) to actually resume.

A complete backend-for-frontend built on these helpers ships in the repo at sdks/node/examples/headless-checkout-bff.ts. Start there rather than assembling the calls yourself, and see Build your own checkout for the surrounding integration.

What The SDK Does Not Cover Yet#

Two public surfaces have no generated services, so call them over plain HTTP:

  • Customer sessions (POST /v1/customer-sessions and its refresh and revoke routes).
  • The /v1/me namespace, the buyer-scoped read and write surface behind a customer session.

A buyer account built on /v1/me therefore uses fetch today, even though the rest of your integration can use the SDK.

Errors#

SDK requests throw FlintError with Flint's public error metadata:

TypeScript
import { FlintError } from "@flintpay/node";

try {
  await flint.orders.get("ord_missing");
} catch (error) {
  if (error instanceof FlintError) {
    console.error(error.type, error.code, error.message);
  }
}

Retries apply automatically to GET requests and to write requests that are idempotency-backed in the public contract.

Next Steps#

Rate this doc