# Flint Pay - Public HTTP API Reference (SKILL.md)
This file is designed for AI agents integrating with Flint Pay.
It reflects the public HTTP API documented under `/v1/...`.
Important:
- Use REST paths like `POST /v1/orders`, not legacy RPC paths like `POST /flint.v1.../Method`.
- Use snake_case JSON field names.
- Authenticated resource responses use a `data` envelope.
## Base URLs and Auth
Production:
```text
https://api.withflintpay.com
```
Sandbox / staging:
```text
https://api.staging.withflintpay.com
```
Auth for the normal `/v1` resource API:
```http
Authorization: Bearer <api_key>
```
You can also send:
```http
X-API-Key: <api_key>
```
Developer bootstrap uses the same host, but only on `/v1/developer/...` routes.
Bootstrap auth after verification:
```http
Authorization: Bearer <developer_session_token>
```
or:
```http
X-Developer-Session-Token: <developer_session_token>
```
## Shared Conventions
### JSON style
- Requests and responses use snake_case.
- Timestamps are ISO 8601 strings.
- Money uses integer minor units plus currency:
```json
{ "amount": 2500, "currency": "USD" }
```
### Response envelopes
Single resource:
```json
{
  "data": { "...": "..." },
  "request_id": "req_123"
}
```
List response:
```json
{
  "data": [{ "...": "..." }],
  "next_page_token": "opaque-cursor",
  "request_id": "req_123"
}
```
Special cases:
- `POST /v1/api-keys` and `POST /v1/developer/initial-api-key` also return top-level `secret_key`
- `POST /v1/webhooks` and `POST /v1/webhooks/{webhook_endpoint_id}/rotate-secret` return webhook `secret` inside `data`
- `POST /v1/devices` may also return `already_existed: true`
- `POST /v1/orders/{order_id}/pay` may also return `pending_actions`
### Pagination
Most list endpoints accept:
- `page_size` from 1 to 100, default 20
- `page_token` from the previous response
If `next_page_token` is omitted or empty, there are no more results.
### Idempotency
Retry-safe writes support:
```http
Idempotency-Key: <unique-key>
```
Rules:
- Reuse the same key when retrying the same operation
- A different body with the same key returns the original cached response
- Keys are cached for 24 hours
- Replayed responses may include:
```http
Idempotency-Replayed: true
```
Check the endpoint-specific section when behavior matters. For example, invoice send, void, reminders, manual payments, and link regeneration are idempotency-backed, while `POST /v1/invoices/{invoice_id}/checkout-session` is not.
### Sparse update semantics
Most `PATCH` endpoints are sparse:
- omitted field = unchanged
- present field = replace
- requests with no mutable fields fail with `NO_FIELDS_TO_UPDATE`
Clear rules used across the API:
- strings often clear with `""`
- some nullable fields use `clear_*: true`
- `metadata` merges by key
- `metadata: {}` clears all metadata
- a metadata entry with value `""` deletes just that key
### Error envelope
Errors use:
```json
{
  "error": {
    "type": "validation_error",
    "code": "INVALID_REQUEST",
    "message": "One or more fields are invalid.",
    "request_id": "req_abc123",
    "param": "url",
    "details": [
      {
        "code": "REQUIRED",
        "param": "url",
        "message": "Field 'url' is required."
      }
    ]
  }
}
```
Common `error.type` values:
- `authentication_error` -> 401
- `authorization_error` -> 403
- `validation_error` -> 400
- `not_found_error` -> 404
- `conflict_error` -> 409
- `rate_limit_error` -> 429
- `internal_error` -> 500
- `external_service_error` -> 502 or 503
- `timeout_error` -> 504
- `unavailable_error` -> 503
Do not retry 4xx validation/auth/conflict errors unchanged. Retry 429 and 5xx with backoff and jitter.
### Rate limits
Current default route classes:
- read: 60/sec per key, 3000/min per key
- write: 20/sec per key, 600/min per key
- sensitive_write: 10/sec per key, 120/min per key
- external_provider_action: 5/sec per key, 120/min per key
Unauthenticated `/v1` traffic is also IP-limited.
Bootstrap endpoints are stricter than normal resource routes.
## ID Prefixes
Common public prefixes:
- `org_` organization
- `mer_` merchant
- `usr_` user
- `loc_` location
- `dev_` device
- `key_` API key
- `cus_` customer
- `ord_` order
- `li_` order line item
- `ad_` applied discount
- `tip_` tip
- `item_` item
- `cpn_` coupon
- `pi_` payment intent
- `pm_` payment method
- `ref_` refund
- `cs_` checkout session
- `pl_` payment link
- `set_` settings
- `sub_` subscription
- `plan_` subscription plan
- `inv_` invoice
- `invpa_` invoice payment attempt
- `indel_` invoice delivery attempt
- `inevt_` invoice event
- `whep_` webhook endpoint
- `whev_` webhook event
## Developer Bootstrap
Use these only before you have your first external API key.
### Methods
- `POST /v1/developer/session/start`
- `POST /v1/developer/session/complete`
- `POST /v1/developer/onboarding/session`
- `GET /v1/developer/onboarding/status`
- `POST /v1/developer/initial-api-key`
### Start session
No auth.
Body:
- `email` required
- `first_name` required
- `last_name` required
Response data:
- `verification_started`
- `verification_token`
- `expires_at`
### Complete session
No auth.
Body:
- `verification_token` required
- `verification_code` required
Response data:
- `developer_session_token`
- `user`
- `merchant`
- `bootstrap_state`
- `merchant_created`
- `stripe_account_created`
- `charges_enabled`
- `payouts_enabled`
- `details_submitted`
- `can_issue_api_key`
- `next_action`
### Onboarding session
Auth: `developer_session_token`
Body:
- `mode` required: `hosted` or `embedded`
- `include_future_requirements` optional
- `return_url` optional
- `refresh_url` optional
Hosted mode returns `data.session.hosted.url`.
Embedded mode returns `data.session.embedded.client_secret`.
### Onboarding status
Auth: `developer_session_token`
Returns:
- `merchant_created`
- `stripe_account_created`
- `charges_enabled`
- `payouts_enabled`
- `details_submitted`
- `can_issue_api_key`
- `bootstrap_state`
- `next_action`
- `current_deadline`
- `has_past_due`
- `currently_due`
- `past_due`
- `eventually_due`
- `pending_verification`
### Initial API key
Auth: `developer_session_token`
Body:
- `name` required
- `scopes` optional
If `scopes` is omitted, Flint applies the bootstrap starter scope set.
If `scopes` is provided during bootstrap, it must stay within that starter set.
Response status is `201 Created`.
Response includes top-level `secret_key`.
## OpenAPI
- `GET /v1/openapi.json`
No auth required.
Returns the public OpenAPI 3.1 schema for the `/v1` API.
## Customers
### Methods
- `POST /v1/customers`
- `GET /v1/customers/{customer_id}`
- `PATCH /v1/customers/{customer_id}`
- `GET /v1/customers`
### Create
Body:
- `name` required
- `email` required
- `phone_number` optional
- `billing_address` optional
- `shipping_address` optional
- `merchant_customer_id` optional
- `buyer_note` optional
- `merchant_note` optional
- `tax_exempt` optional
- `metadata` optional
### Update
Mutable:
- `name`
- `billing_address`
- `shipping_address`
- `merchant_customer_id`
- `buyer_note`
- `phone_number`
- `merchant_note`
- `tax_exempt`
- `metadata`
- `clear_billing_address`
- `clear_shipping_address`
Notes:
- `email` is immutable
- clear strings with `""`
### List filters
- `query`
- `email`
- `page_size`
- `page_token`
- `sort_by`
- `sort_direction`
- `created_after`
- `created_before`
- `updated_after`
- `updated_before`
Sort options:
- `name`
- `email`
- `created_at`
- `updated_at`
### Address shape
```json
{
  "line1": "123 Main St",
  "line2": "Apt 4",
  "city": "New York",
  "state": "NY",
  "postal_code": "10001",
  "country": "US"
}
```
## Orders
### Methods
- `POST /v1/orders`
- `GET /v1/orders/{order_id}`
- `GET /v1/orders`
- `PATCH /v1/orders/{order_id}`
- `POST /v1/orders/{order_id}/close`
- `POST /v1/orders/{order_id}/pay`
- `POST /v1/orders/{order_id}/line-items/add`
- `PATCH /v1/orders/{order_id}/line-items/{order_line_item_id}`
- `POST /v1/orders/{order_id}/line-items/remove`
- `POST /v1/orders/{order_id}/discounts/apply-coupon`
- `POST /v1/orders/{order_id}/discounts/remove`
- `POST /v1/orders/{order_id}/tips/add`
- `PATCH /v1/orders/{order_id}/tips/{tip_id}`
- `POST /v1/orders/{order_id}/tips/remove`
- `POST /v1/orders/{order_id}/tax/apply`
### Create
Body:
- `customer_id` optional
- `line_items` optional
- `tips` optional
- `discounts` optional
- `buyer_note` optional
- `merchant_note` optional
- `metadata` optional
`line_items[]`:
- `name` required
- `quantity` required integer >= 1
- `unit_price_money` required
- `item_id` optional
- `description` optional
- `sku` optional
- `image_url` optional
- `metadata` optional
`tips[]`:
- exactly one of `amount_money` or `percent`
- optional `name`
- optional `description`
- optional `metadata`
`discounts[]` supports:
- coupon discount: `coupon_id` or `coupon_code`
- manual discount: `name` + `amount_money` + optional `scope` + optional `order_line_item_ids`
### Update
Mutable:
- `customer_id`
- `buyer_note`
- `merchant_note`
- `metadata`
### Pay order
Body:
- `payment_intent_ids` optional
- `payment_source_tokens` optional map `payment_intent_id -> token`
- `payment_method_ids` optional map `payment_intent_id -> payment_method_id`
- `buyer_email` optional
- `setup_payment_source_token` optional
- `buyer_phone` optional
- `billing_address` optional
- `shipping_address` optional
Rules:
- for the same payment intent, do not send both `payment_method_ids` and `payment_source_tokens`
- response may include `pending_actions` with `payment_intent_id` and `client_secret`
### Line item mutations
`POST /line-items/add` body:
- `line_items`
`PATCH /line-items/{order_line_item_id}` mutable:
- `quantity`
- `name`
- `unit_price_money`
- `description`
- `sku`
- `metadata`
`POST /line-items/remove` body:
- `order_line_item_ids` required
### Discount mutations
`POST /discounts/apply-coupon` body:
- exactly one of `coupon_id` or `coupon_code`
- `order_line_item_ids` optional
`POST /discounts/remove` body:
- `applied_discount_ids` required
### Tip mutations
`POST /tips/add` body:
- `tips`
`PATCH /tips/{tip_id}` mutable:
- `amount_money`
- `percent`
- `name`
- `description`
- `metadata`
Do not send both `amount_money` and `percent`.
`POST /tips/remove` body:
- `order_tip_ids` required
### Tax application
`POST /tax/apply` body:
- `tax_address` optional
- `device_id` optional
### List filters
- `customer_id`
- `status`
- `order_number`
- `source`
- `subscription_id`
- `query`
- `page_size`
- `page_token`
- `sort_by`
- `sort_direction`
- `created_after`
- `created_before`
- `updated_after`
- `updated_before`
Sort options:
- `created_at`
- `updated_at`
- `due_money`
### Response highlights
Order objects include:
- `order_id`
- `status`
- `line_items`
- `discounts`
- `tips`
- `payment_intent_ids`
- `refund_ids`
- `checkout_session_ids`
- `customer_id`
- `buyer_email`
- `net_amounts`
- `activities`
- `order_number`
- `closed_reason`
- `buyer_note`
- `source`
- `next_actions`
- `subscription_id`
- `subscription_plan_id`
`net_amounts` includes:
- `subtotal_money`
- `discount_money`
- `tax_money`
- `tip_money`
- `paid_money`
- `refunded_money`
- `balance_money`
## Items
### Methods
- `POST /v1/items`
- `GET /v1/items/{item_id}`
- `PATCH /v1/items/{item_id}`
- `GET /v1/items`
### Create
Body:
- `name` required
- `unit_price_money` required
- `type` required: `product`, `service`, `fee`, `digital`
- `sku` optional
- `description` optional
- `categories` optional
- `image_url` optional
- `status` optional
- `quantity_available` optional
- `enforce_inventory_limit` optional
- `enforce_inventory_limit_on_subscriptions` optional
- `taxable` optional
- `tax_category` optional
- `metadata` optional
### Update
Mutable:
- `name`
- `description`
- `unit_price_money`
- `sku`
- `image_url`
- `taxable`
- `tax_category`
- `enforce_inventory_limit`
- `enforce_inventory_limit_on_subscriptions`
- `status`
- `type`
- `metadata`
Clearable string fields:
- `description`
- `sku`
- `image_url`
- `tax_category`
### List filters
- `sku`
- `query`
- `type`
- `status`
- `category`
- `only_in_stock`
- `page_size`
- `page_token`
- `sort_by`
- `sort_direction`
- `created_after`
- `created_before`
- `updated_after`
- `updated_before`
Sort options:
- `name`
- `created_at`
- `updated_at`
- `unit_price`
Statuses:
- `active`
- `inactive`
- `deleted`
## Coupons
### Methods
- `POST /v1/coupons`
- `GET /v1/coupons/{coupon_id}`
- `PATCH /v1/coupons/{coupon_id}`
- `GET /v1/coupons`
### Create
Body:
- `name` required
- exactly one of `percent_off` or `amount_off_money`
- `coupon_code` optional
- `max_uses` optional
- `expires_at` optional
- `is_stackable` optional
- `minimum_purchase_amount` optional
- `limit_to_item_ids` optional
- `discount_calculation_basis` optional: `subtotal_pre_tax`, `subtotal_post_tax`, `total_with_shipping`
- `metadata` optional
### Update
Mutable:
- `name`
- `coupon_code`
- `description`
- `max_uses`
- `expires_at`
- `status`
- `metadata`
- `minimum_purchase_amount`
- `is_stackable`
- `discount_calculation_basis`
- `percent_off`
- `amount_off_money`
- `clear_max_uses`
- `clear_expires_at`
- `clear_minimum_purchase_amount`
Clear strings with `""`:
- `coupon_code`
- `description`
### List filters
- `code`
- `query`
- `status`
- `limit_to_item_id`
- `page_size`
- `page_token`
- `sort_by`
- `sort_direction`
- `created_after`
- `created_before`
- `updated_after`
- `updated_before`
- `expires_after`
- `expires_before`
Sort options:
- `name`
- `created_at`
- `updated_at`
- `expires_at`
Statuses:
- `active`
- `disabled`
- `expired`
- `exhausted`
- `deleted`
## Payment Intents
### Methods
- `POST /v1/payment-intents`
- `GET /v1/payment-intents/{payment_intent_id}`
- `PATCH /v1/payment-intents/{payment_intent_id}`
- `POST /v1/payment-intents/{payment_intent_id}/confirm`
- `POST /v1/payment-intents/{payment_intent_id}/capture`
- `GET /v1/payment-intents`
- `POST /v1/payment-intents/{payment_intent_id}/cancel`
### Create
Body:
- `order_id` optional
- `amount_money` optional
- `payment_source_token` optional
- `payment_method_id` optional
- `customer_id` optional
- `auto_confirm` optional
- `payment_method_types` optional: `card`, `affirm`, `link`
- `digital_wallets` optional: `apple_pay`, `google_pay`
- `receipt_email` optional
- `tip_money` optional
- `external_reference_id` optional
- `metadata` optional
Rules:
- send `order_id` or `amount_money`
- `payment_source_token` and `payment_method_id` are mutually exclusive
- for order-linked payment intents, amount is usually derived from the order balance
### Update
Mutable fields vary by status. Public update body supports:
- `amount_money`
- `payment_source_token`
- `payment_method_id`
- `external_reference_id`
- `metadata`
- `customer_id`
- `tip_money`
- `receipt_email`
### Confirm
Body:
- `payment_source_token` optional
- `payment_method_id` optional
### Capture
Body:
- `amount_money` optional
### Cancel
Body:
- `cancellation_reason` optional
### List filters
- `status`
- `order_id`
- `customer_id`
- `source`
- `min_amount`
- `max_amount`
- `query`
- `page_size`
- `page_token`
- `sort_by`
- `sort_direction`
- `created_after`
- `created_before`
- `updated_after`
- `updated_before`
### Status state machine
```text
requires_payment_method -> requires_confirmation -> processing -> succeeded
                                                               -> requires_action
                                                               -> requires_capture
requires_capture -> succeeded
any non-terminal -> canceled | expired
```
Terminal:
- `succeeded`
- `canceled`
- `expired`
Response highlights:
- `payment_intent_id`
- `amount_money`
- `status`
- `next_actions`
- `processor_details.stripe.client_secret`
- `processor_details.stripe.account_id`
- `tip_money`
- `order_id`
- `customer_id`
- `receipt_email`
- `payment_method_last_four`
- `payment_method_brand`
- `cancellation_reason`
- `source`
## Payment Methods
### Methods
- `POST /v1/payment-methods`
- `GET /v1/payment-methods/{payment_method_id}`
- `GET /v1/payment-methods`
- `POST /v1/payment-methods/{payment_method_id}/remove`
- `POST /v1/payment-methods/{payment_method_id}/set-default`
### Create
Body:
- `customer_id` required
- `type` optional
Purpose:
- creates a setup intent
- returns a Stripe `client_secret`
- frontend should call `stripe.confirmCardSetup(clientSecret)`
### List query
- `customer_id` required
- `status`
- `page_size`
- `page_token`
Statuses:
- `active`
- `expired`
- `removed`
Response highlights after setup confirmation:
- `card.last4`
- `card.brand`
- `card.exp_month`
- `card.exp_year`
- `card.wallet`
## Refunds
### Methods
- `POST /v1/refunds`
- `GET /v1/refunds/{refund_id}`
- `GET /v1/refunds`
- `PATCH /v1/refunds/{refund_id}`
### Create
Body:
- `reason` required
- `order_id` optional
- `payment_intent_id` optional
- `amount_money` optional
- `reason_message` optional
- `refund_method` optional
- `metadata` optional
Rules:
- provide at least one of `order_id` or `payment_intent_id`
- if both are present, the payment intent must belong to that order
- omitting `amount_money` means full remaining refundable amount
- `refund_method` currently supports `original_payment`
### Update
Mutable:
- `metadata`
### List filters
- `order_id`
- `payment_intent_id`
- `customer_id`
- `status`
- `reason`
- `refund_method`
- `query`
- `page_size`
- `page_token`
- `sort_by`
- `sort_direction`
- `created_after`
- `created_before`
- `updated_after`
- `updated_before`
Response highlights:
- `refund_id`
- `amount_money`
- `status`
- `reason`
- `reason_message`
- `order_id`
- `payment_intent_id`
- `customer_id`
- `refund_method`
- `failure_reason`
- `next_actions`
## Invoices
### Methods
- `POST /v1/invoices`
- `GET /v1/invoices/{invoice_id}`
- `GET /v1/invoices`
- `PATCH /v1/invoices/{invoice_id}`
- `POST /v1/invoices/{invoice_id}/send`
- `POST /v1/invoices/{invoice_id}/void`
- `POST /v1/invoices/{invoice_id}/regenerate-public-link`
- `POST /v1/invoices/{invoice_id}/send-reminder`
- `GET /v1/invoices/{invoice_id}/events`
- `GET /v1/invoices/{invoice_id}/delivery-attempts`
- `POST /v1/invoices/{invoice_id}/manual-payments`
- `POST /v1/invoices/{invoice_id}/manual-payments/reverse`
- `POST /v1/invoices/{invoice_id}/checkout-session`
- `GET /v1/invoices/{invoice_id}/pdf`
### Create
Body:
- exactly one of `order_id` or `quick_pay`
- `recipient_email` optional
- `cc_emails` optional
- `due_at` optional
- `scheduled_send_at` optional
- `reference` optional
- `service_date` optional
- `memo` optional
- `footer` optional
- `metadata` optional
`quick_pay` supports:
- `line_items` optional
- `discounts` optional
- `tips` optional
- `customer_id` optional
- `buyer_note` optional
- `merchant_note` optional
Rules:
- `order_id` must reference an open unpaid order
- `quick_pay` creates the backing order internally
- `scheduled_send_at` must be a future RFC3339 timestamp
- scheduled invoices require `recipient_email`
- create returns `201 Created`
### Update
Mutable draft fields:
- `due_at`
- `scheduled_send_at`
- `recipient_email`
- `cc_emails`
- `reference`
- `service_date`
- `memo`
- `footer`
- `metadata`
Rules:
- only draft invoices are mutable
- invoice snapshots are rebuilt from the backing order while the invoice is still draft
### Send and reminder behavior
`POST /send`:
- issues the invoice and moves it from `draft` to `open`
- creates the public invoice link
- attempts the initial email delivery
- returns `invoice`, `public_url`, and `delivery_attempt`
- safe to retry with the same `Idempotency-Key`
`POST /send-reminder`:
- only for collectible `open` or `partially_paid` invoices
- requires an outstanding balance
- requires `recipient_email`
- returns `invoice` and `delivery_attempt`
- safe to retry with the same `Idempotency-Key`
`POST /regenerate-public-link`:
- only after the invoice has been sent
- revokes the prior public link and returns a new `public_url`
- safe to retry with the same `Idempotency-Key`
### Manual payments
`POST /manual-payments` body:
- `amount_money` required
- `received_at` optional
- `external_reference` optional
- `note` optional
Rules:
- only for collectible `open` or `partially_paid` invoices
- amount must be positive
- currency must match the invoice currency
- amount cannot exceed the collectible balance
`POST /manual-payments/reverse` body:
- `amount_money` required
- `received_at` optional
- `external_reference` optional
- `note` optional
Rules:
- only for sent invoices
- reversal cannot exceed the currently applied manual amount
Both endpoints are safe to retry with the same `Idempotency-Key`.
### Checkout and PDF
`POST /checkout-session`:
- only for collectible `open` or `partially_paid` invoices
- requires an outstanding balance
- returns `invoice`, `invoice_payment_attempt`, `checkout_session`, and `checkout_auth_token`
- reuses an existing open invoice-owned checkout session when possible
- do not create generic order checkout sessions or payment intents for invoiced orders
`invoice_payment_attempt` includes:
- `invoice_payment_attempt_id`
- `invoice_id`
- `rail`
- `status`
- `target_amount`
- `checkout_session_id`
- `payment_intent_id`
- `started_at`
- `expires_at`
- `settled_at`
- `failure_code`
- `failure_message`
`GET /pdf`:
- returns raw `application/pdf`
- response is a binary download, not a JSON envelope
- PDF content is rendered from the frozen invoice snapshot
### List filters
- `status`
- `customer_id`
- `order_id`
- `query`
- `page_size`
- `page_token`
### Statuses and webhooks
Invoice statuses:
- `draft`
- `open`
- `partially_paid`
- `paid`
- `void`
Refund statuses:
- `none`
- `partially_refunded`
- `refunded`
Invoice webhook events:
- `invoice.sent`
- `invoice.voided`
- `invoice.delivery_succeeded`
- `invoice.delivery_failed`
- `invoice.manual_payment_recorded`
- `invoice.manual_payment_reversed`
- `invoice.paid`
- `invoice.partially_paid`
- `invoice.refunded`
- `invoice.partially_refunded`
### Response highlights
Invoice objects include:
- `invoice_id`
- `invoice_number`
- `status`
- `refund_status`
- `order_id`
- `customer_id`
- `scheduled_send_at`
- `sent_at`
- `viewed_at`
- `paid_at`
- `voided_at`
- `recipient_email`
- `cc_emails`
- `snapshot`
- `collectible_balance`
- `paid_money`
- `refunded_money`
- `is_overdue`
- `metadata`
- `created_at`
- `updated_at`
`GET /events` items include:
- `invoice_event_id`
- `type`
- `description`
- `actor_type`
- `actor_id`
- `metadata`
- `occurred_at`
`GET /delivery-attempts` items include:
- `invoice_delivery_attempt_id`
- `kind`
- `channel`
- `to_email`
- `cc_emails`
- `status`
- `error_message`
- `created_at`
- `sent_at`
## Subscription Plans
### Methods
- `POST /v1/subscription-plans`
- `GET /v1/subscription-plans/{plan_id}`
- `GET /v1/subscription-plans`
- `PATCH /v1/subscription-plans/{plan_id}`
- `POST /v1/subscription-plans/{plan_id}/archive`
### Create
Body:
- `name` required
- `billing_interval` required: `daily`, `weekly`, `monthly`, `yearly`
- `billing_interval_count` required
- `currency` required
- `line_items` required
- `description` optional
- `setup_fee_money` optional
- `trial_period_days` optional
- `contract_term_months` optional
- `early_termination_fee_money` optional
- `image_url` optional
- `metadata` optional
`line_items[]` supports:
- catalog-linked: `item_id`, `quantity`
- ad hoc: `name`, optional `description`, `unit_price_money`, `quantity`
Rules:
- at least one line item is required
- all money fields must match `currency`
- `billing_interval_count` defaults to 1 when omitted or 0
- `early_termination_fee_money` requires `contract_term_months`
### Update
Mutable:
- same general surface as create
- `clear_trial_period_days`
- `clear_setup_fee_money`
- `clear_contract_term_months`
- `clear_early_termination_fee_money`
### List filters
- `status`
- `billing_interval`
- `query`
- `page_size`
- `page_token`
- `sort_by`
- `sort_direction`
- `created_after`
- `created_before`
- `updated_after`
- `updated_before`
## Subscriptions
### Methods
- `POST /v1/subscriptions`
- `GET /v1/subscriptions/{subscription_id}`
- `GET /v1/subscriptions`
- `PATCH /v1/subscriptions/{subscription_id}`
- `POST /v1/subscriptions/{subscription_id}/cancel`
- `POST /v1/subscriptions/{subscription_id}/pause`
- `POST /v1/subscriptions/{subscription_id}/resume`
### Create
Body:
- `plan_id` required
- `customer_id` required
- `payment_method_id` optional
- `billing_anchor_day` optional
- `metadata` optional
Rules:
- if `payment_method_id` is omitted, the customer must already have a default payment method
- Flint rejects duplicate non-canceled subscriptions for the same `customer_id` and `plan_id`
### Update
Mutable:
- `payment_method_id`
- `metadata`
### Cancel
Body:
- `cancel_immediately` optional
Response may include `contract_info`:
- `is_within_contract_term`
- `remaining_months`
- `early_termination_fee`
### Pause
Body:
- `pause_duration_cycles` optional
### Resume
No body required.
### List filters
- `customer_id`
- `plan_id`
- `status`
- `page_size`
- `page_token`
- `sort_by`
- `sort_direction`
- `created_after`
- `created_before`
- `updated_after`
- `updated_before`
Response highlights:
- `subscription_id`
- `plan_id`
- `customer_id`
- `payment_method_id`
- `status`
- `billing_anchor_day`
- `cancel_at_period_end`
- `current_period_start`
- `current_period_end`
- `next_billing_date`
- `trial_end`
- `contract_start_date`
- `contract_end_date`
- `paused_at`
- `canceled_at`
## Checkout Sessions
### Methods
- `POST /v1/checkout-sessions`
- `GET /v1/checkout-sessions/{checkout_session_id}`
- `GET /v1/checkout-sessions`
- `PATCH /v1/checkout-sessions/{checkout_session_id}`
- `POST /v1/checkout-sessions/{checkout_session_id}/close`
### Create
Exactly one of:
- `quick_pay`
- `order_id`
- `plan_id`
Body may also include:
- `theme`
- `payments`
- `tip`
- `customer`
- `tax`
- `redirects`
- `legal`
- `expiration`
- `custom_text`
- `coupon`
- `metadata`
`quick_pay`:
- `name`
- `amount_money`
`payments`:
- `allowed_digital_wallets`
- `allowed_payment_method_types`
- `payment_note`
- `external_reference_id`
`tip`:
- `is_enabled`
- `is_smart_tips_enabled`
- `smart_tip_amounts`
- `default_smart_tip_amount`
- `tip_percentages`
- `default_tip_percentage`
- `is_custom_tip_enabled`
`customer`:
- `customer_id`
- `external_reference_id`
- `prefilled_customer_info`
- `enable_address_autocomplete`
- `customer_note`
- `require_email`
- `require_phone`
- `require_shipping_address`
- `require_billing_address`
`redirects`:
- `success_redirect_url`
- `on_load_redirect_url`
- `cancel_redirect_url`
`legal`:
- `terms_of_service_url`
- `requires_terms_of_service`
- `refund_policy_url`
- `shipping_policy_url`
- `contract_url`
`expiration`:
- `expiration_seconds`
- `expiration_url`
`custom_text`:
- `shipping_address_label`
`coupon`:
- `is_enabled`
- `auto_apply_coupon_codes`
Notes:
- `plan_id` is for hosted subscription signup
- `order_id` must reference an open order
- the response includes hosted `url`
### Update
Mutable:
- `metadata`
### Close
Body:
- `reason` optional
### List filters
- `order_id`
- `payment_link_id`
- `status`
- `source`
- `query`
- `page_size`
- `page_token`
- `sort_by`
- `sort_direction`
- `created_after`
- `created_before`
- `updated_after`
- `updated_before`
Response highlights:
- `checkout_session_id`
- `status`
- `order_id`
- `payment_link_id`
- `url`
- `payment_intent_ids`
- `theme`
- `payments`
- `tip`
- `customer`
- `tax`
- `redirects`
- `legal`
- `expiration`
- `custom_text`
- `coupon`
## Payment Links
### Methods
- `POST /v1/payment-links`
- `GET /v1/payment-links/{payment_link_id}`
- `GET /v1/payment-links`
- `PATCH /v1/payment-links/{payment_link_id}`
- `POST /v1/payment-links/{payment_link_id}/line-items/replace`
- `POST /v1/payment-links/{payment_link_id}/custom-fields/replace`
- `POST /v1/payment-links/{payment_link_id}/activate`
- `POST /v1/payment-links/{payment_link_id}/deactivate`
### Create
Body:
- `name` required
- `line_items` optional
- `description` optional
- `plan_id` optional
- `theme` optional
- `payments` optional
- `tip` optional
- `customer` optional
- `tax` optional
- `redirects` optional
- `legal` optional
- `expiration` optional
- `custom_text` optional
- `coupon` optional
- `custom_fields` optional
- `mode` optional
- `donation_suggested_amounts` optional
- `donation_min_amount_money` optional
- `donation_max_amount_money` optional
- `event_config` optional
- `inactive_message` optional
- `max_completions` optional
- `image_url` optional
- `metadata` optional
Modes:
- `standard`
- `donation`
- `event`
Rules:
- `standard`: provide exactly one of `line_items` or `plan_id`
- `donation`: omit both `line_items` and `plan_id`
- `event`: `line_items` required and `plan_id` forbidden
Payment-link `customer` only supports:
- `require_email`
- `enable_address_autocomplete`
`line_items[]` fields:
- `key`
- `name`
- `description`
- `quantity`
- `amount_money`
- `item_id`
- `allow_quantity_adjustment`
- `min_quantity`
- `max_quantity`
- `allow_amount_adjustment`
- `min_amount_money`
- `max_amount_money`
- `suggested_amounts`
`custom_fields[]` fields:
- `key`
- `label`
- `type`
- `is_required`
- `placeholder`
- `options`
- `max_length`
- `sort_order`
Custom field types:
- `text`
- `textarea`
- `dropdown`
- `checkbox`
`event_config` fields:
- `event_date`
- `location`
- `max_total_quantity`
- `ticket_prefix`
- `send_ticket_emails`
### Update
Mutable:
- `name`
- `description`
- `theme`
- `payments`
- `tip`
- `customer`
- `tax`
- `redirects`
- `legal`
- `expiration`
- `custom_text`
- `coupon`
- `event_config`
- `inactive_message`
- `max_completions`
- `clear_max_completions`
- `image_url`
- `metadata`
Use separate replace endpoints for:
- `line_items`
- `custom_fields`
### Replace line items
Body:
- `line_items` required
### Replace custom fields
Body:
- `custom_fields` required
### Deactivate
Body:
- `inactive_message` optional
### List filters
- `status`
- `has_plan`
- `query`
- `mode`
- `page_size`
- `page_token`
- `sort_by`
- `sort_direction`
- `created_after`
- `created_before`
Response highlights:
- `payment_link_id`
- `name`
- `status`
- `completed_count`
- `url`
- `line_items`
- `plan_id`
- `theme`
- `payments`
- `tip`
- `customer`
- `tax`
- `redirects`
- `legal`
- `expiration`
- `custom_text`
- `coupon`
- `custom_fields`
- `mode`
- `event_config`
- `total_quantity_sold`
- `inactive_message`
- `max_completions`
- `image_url`
## Settings
### Methods
- `GET /v1/settings`
- `GET /v1/settings/effective`
- `PATCH /v1/settings`
### Sections
Top-level sections include:
- `tipping`
- `tax`
- `checkout`
- `receipts`
- `inventory`
- `branding`
- `legal`
- `subscriptions`
- `coupons`
- `point_of_sale`
- `fees`
- `payment_limits`
- `metadata`
### Effective settings query
- `location_id` optional
- `device_id` optional
### Update
Send section patches directly at the top level, not under a nested `settings` object.
Clear flags:
- `clear_tipping`
- `clear_tax`
- `clear_checkout`
- `clear_receipts`
- `clear_inventory`
- `clear_branding`
- `clear_legal`
- `clear_subscriptions`
- `clear_coupons`
- `clear_point_of_sale`
- `clear_fees`
- `clear_payment_limits`
Do not send a section patch and its matching `clear_*` flag together.
Common nested fields:
- `checkout.allowed_payment_method_types`
- `checkout.allowed_digital_wallets`
- `checkout.is_customer_email_required`
- `receipts.is_auto_email_enabled`
- `branding.primary_color`
- `legal.terms_of_service_url`
- `subscriptions.dunning_end_action`
- `coupons.max_coupons_per_order`
- `fees.rates.payment_link.percent`
- `payment_limits.max_amounts.payment_link`
## Webhooks
### Methods
- `POST /v1/webhooks`
- `GET /v1/webhooks/{webhook_endpoint_id}`
- `GET /v1/webhooks`
- `PATCH /v1/webhooks/{webhook_endpoint_id}`
- `DELETE /v1/webhooks/{webhook_endpoint_id}`
- `POST /v1/webhooks/{webhook_endpoint_id}/rotate-secret`
- `GET /v1/webhook-events`
### Create
Body:
- `url` required
- `subscribed_events` optional
- `description` optional
- `enabled` optional
Rules:
- non-local endpoints must use HTTPS
- omit or send empty `subscribed_events` to receive all supported events
- webhook signing `secret` is only returned on create and rotate
### Update
Mutable:
- `url`
- `subscribed_events`
- `description`
- `enabled`
### List webhook endpoints
Query:
- `page_size`
- `page_token`
### List webhook events
Query:
- `webhook_endpoint_id`
- `event_type`
- `page_size`
- `page_token`
### Signature verification
Webhook delivery requests include:
- `X-Flint-Signature`
- `X-Flint-Webhook-ID`
`X-Flint-Signature` format:
- `t=<unix_timestamp>`
- one or more `v1=<hex_hmac_sha256>` values
Verify signatures by:
- reading the raw request body bytes exactly as delivered
- building `{timestamp}.{raw_body_bytes}`
- computing HMAC-SHA256 using your webhook `secret`
- comparing against every `v1` value with constant-time comparison
- rejecting stale timestamps
Use `X-Flint-Webhook-ID` as your idempotency key because deliveries may be retried.
### Delivery payload
Delivered webhook JSON uses this envelope:
```json
{
  "webhook_event_id": "whevt_123",
  "event_type": "order.created",
  "merchant_id": "mer_123",
  "created_at": "2026-03-17T14:30:00Z",
  "data": {
    "order_id": "ord_123",
    "status": "open"
  }
}
```
Notes:
- `data` shape varies by `event_type`
- use the exact raw body for signature verification before parsing JSON
- `GET /v1/webhook-events` returns the delivered JSON as a string in `payload`
Webhook event responses include fields such as:
- `webhook_event_id`
- `webhook_endpoint_id`
- `event_type`
- `payload`
- `status`
- `attempt_count`
- `last_response_code`
- `last_error`
- `next_retry_at`
- `delivered_at`
Supported event types:
- `order.payment_succeeded`
- `order.refunded`
- `invoice.sent`
- `invoice.voided`
- `invoice.delivery_succeeded`
- `invoice.delivery_failed`
- `invoice.manual_payment_recorded`
- `invoice.manual_payment_reversed`
- `invoice.paid`
- `invoice.partially_paid`
- `invoice.refunded`
- `invoice.partially_refunded`
- `subscription.activated`
- `subscription.created`
- `subscription.payment_succeeded`
- `subscription.payment_failed`
- `subscription.past_due`
- `subscription.canceled`
- `subscription.paused`
- `subscription.resumed`
- `checkout_session.completed`
- `payment_intent.succeeded`
- `payment_intent.canceled`
- `payment_intent.requires_capture`
- `customer.created`
- `customer.updated`
- `payment_method.saved`
- `payment_method.removed`
- `refund.created`
- `refund.updated`
## Analytics
### Methods
- `GET /v1/analytics/overview`
- `GET /v1/analytics/payment-volume-timeseries`
- `GET /v1/analytics/subscriptions`
### Query
Overview and payment volume timeseries:
- `range` required: `today`, `last_7_days`, `last_30_days`
- `timezone` optional
- `include_previous_period` optional
Subscription analytics:
- `range` required: `today`, `last_7_days`, `last_30_days`
- `timezone` optional
### Overview response
Includes:
- `range`
- `timezone`
- `gross_volume`
- `net_volume`
- `refunds_total`
- `payments_count`
- `average_payment`
- `new_customers`
- `active_subscriptions` deprecated
- `has_multiple_currencies`
- `currencies`
Money metrics contain:
- `current_money`
- `previous_money`
- `change_percent`
Count metrics contain:
- `current_count`
- `previous_count`
- `change_percent`
### Payment volume timeseries
Returns:
- `range`
- `timezone`
- `buckets`
- `has_multiple_currencies`
- `currencies`
Bucket fields:
- `label`
- `period_start`
- `period_end`
- `volume_money`
- `payments_count`
- `previous_volume_money`
- `previous_payments_count`
### Subscription analytics
Returns:
- `range`
- `timezone`
- `window_metrics`
- `snapshot_metrics`
`window_metrics`:
- `new_subscriptions`
- `canceled_subscriptions`
- `subscription_collected_amount_by_currency`
`snapshot_metrics`:
- `mrr_by_currency`
- `arr_by_currency`
- `status_counts`
## Merchants
### Methods
- `GET /v1/merchants/{merchant_id}`
- `GET /v1/merchants`
- `PATCH /v1/merchants/{merchant_id}`
- `POST /v1/merchants/{merchant_id}/onboarding/begin`
- `POST /v1/merchants/{merchant_id}/onboarding/session`
- `GET /v1/merchants/{merchant_id}/onboarding/status`
For the public API, the path `merchant_id` must match the authenticated merchant for direct merchant reads and writes.
### Merchant fields
Common response fields:
- `merchant_id`
- `email`
- `onboarding_status`
- `status`
- `business_name`
- `processor_account`
- `charges_enabled`
- `payouts_enabled`
- `details_submitted`
- `has_past_due`
- `tier`
- `support_email`
- `support_phone`
- `support_url`
- `website_url`
- `metadata`
### Update
Mutable:
- `email`
- `image_url`
- `phone`
- `address`
- `support_email`
- `support_phone`
- `support_url`
- `website_url`
- `metadata`
- `clear_address`
### Onboarding begin body
- `email`
- `country`
- `capability_requests`
- `connected_account_config`
### Onboarding session body
- `mode`
- `include_future_requirements`
- `return_url`
- `refresh_url`
Response returns either a hosted onboarding `url` or an embedded `client_secret`.
## Users
### Methods
- `GET /v1/users/{user_id}`
- `GET /v1/users`
- `PATCH /v1/users/{user_id}`
- `POST /v1/users/{user_id}/deactivate`
`GET`, `PATCH`, and `POST /deactivate` are self-service in the public API. The path `user_id` must match the authenticated user.
### Update
Mutable:
- `first_name`
- `last_name`
### Deactivate
Body:
- `reason` optional
### List filters
- `status`
- `page_size`
- `page_token`
- `sort_by`
- `sort_direction`
- `created_after`
- `created_before`
- `updated_after`
- `updated_before`
## API Keys
### Methods
- `POST /v1/api-keys`
- `GET /v1/api-keys/{api_key_id}`
- `GET /v1/api-keys`
- `PATCH /v1/api-keys/{api_key_id}`
- `POST /v1/api-keys/{api_key_id}/revoke`
### Create
Body:
- `name` required
- `scopes` required
Response status is `201 Created`.
Response includes top-level `secret_key`, returned once.
### Update
Mutable:
- `name`
- `scopes`
Rules:
- only active keys can be updated
- `scopes` replaces the full scope list when present
### Revoke
Permanent and cannot be undone.
### List filters
- `status`
- `page_size`
- `page_token`
- `sort_by`
- `sort_direction`
- `created_after`
- `created_before`
- `updated_after`
- `updated_before`
Statuses:
- `active`
- `revoked`
Key prefixes:
- test keys start with `flint_test_`
- live keys start with `flint_live_`
Common scope families:
- `accounts.api_keys.*`
- `accounts.merchants.*`
- `accounts.users.*`
- `customers.customers.*`
- `commerce.orders.*`
- `commerce.items.*`
- `commerce.coupons.*`
- `commerce.refunds.*`
- `commerce.subscriptions.*`
- `commerce.subscription_plans.*`
- `payments.payment_intents.*`
- `payments.payment_methods.*`
- `checkouts.checkout_sessions.*`
- `checkouts.payment_links.*`
## Devices
### Methods
- `POST /v1/devices`
- `GET /v1/devices/{device_id}`
- `GET /v1/devices`
- `PATCH /v1/devices/{device_id}`
- `DELETE /v1/devices/{device_id}`
### Create
Body:
- `name` required
- `location_id` optional
- `hardware_fingerprint` optional
- `metadata` optional
If `hardware_fingerprint` matches an existing device, Flint returns the existing device with `already_existed: true`.
### Update
Mutable:
- `name`
- `location_id`
- `metadata`
- `clear_location_id`
Do not send both `location_id` and `clear_location_id`.
### List filters
- `location_id`
- `status`
- `query`
- `page_size`
- `page_token`
- `sort_by`
- `sort_direction`
## Quick Start Examples
### 1. Create a customer
```bash
curl -X POST https://api.staging.withflintpay.com/v1/customers \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: customer-jane-001" \
  -d '{"name":"Jane Doe","email":"jane@example.com"}'
```
### 2. Create an order
```bash
curl -X POST https://api.staging.withflintpay.com/v1/orders \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: order-jane-001" \
  -d '{
    "customer_id":"cus_xxx",
    "line_items":[
      {
        "name":"Premium Widget",
        "quantity":1,
        "unit_price_money":{"amount":2500,"currency":"USD"}
      }
    ]
  }'
```
### 3. Create a payment intent
```bash
curl -X POST https://api.staging.withflintpay.com/v1/payment-intents \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: pi-order-001" \
  -d '{"order_id":"ord_xxx"}'
```
Use `data.processor_details.stripe.client_secret` with Stripe.js on the frontend.
### 4. Create a hosted checkout session
```bash
curl -X POST https://api.staging.withflintpay.com/v1/checkout-sessions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: checkout-001" \
  -d '{
    "quick_pay":{
      "name":"One-time consultation",
      "amount_money":{"amount":15000,"currency":"USD"}
    },
    "customer":{"require_email":true},
    "redirects":{"success_redirect_url":"https://example.com/thanks"}
  }'
```
### 5. Create a refund
```bash
curl -X POST https://api.staging.withflintpay.com/v1/refunds \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: refund-001" \
  -d '{
    "order_id":"ord_xxx",
    "reason":"requested_by_customer"
  }'
```
