Reports
Reports turn order, payment, Flint billing, balance, payout, and tax history into versioned CSV files. Generation is asynchronous: create a report, wait for it to finish, then download the file.
All report routes are API-key-only. Creating a report requires the reports.write scope; listing, reading, and downloading require reports.read. Reports are scoped to the environment your key belongs to, so a sandbox key generates sandbox reports.
For matching a single deposit to the transactions that funded it, see Reconciliation. For what each processing fee is, see Processing Fees.
Create a Report#
curl -X POST https://api.withflintpay.com/v1/reports \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 8f2b1c40-payments-2026-01" \
-d '{
"report_type": "payments_itemized_v1",
"interval_start_at": "2026-01-01T05:00:00Z",
"interval_end_at": "2026-02-01T05:00:00Z",
"timezone": "America/New_York",
"currency": "USD"
}'
| Field | Required | Notes |
|---|---|---|
report_type | Yes | One of the types listed under Report Types. |
interval_start_at | Yes | RFC 3339 instant, inclusive. |
interval_end_at | Yes | RFC 3339 instant, exclusive. Cannot be in the future. |
timezone | No | IANA name, defaults to UTC. Controls how timestamp columns are rendered. Payout reports also interpret the interval boundaries as local calendar dates in this timezone. |
currency | Yes | Three-letter ISO 4217 code. A report covers one currency; amounts are never converted. |
A new report comes back 201 with status: pending and a rep_ report ID.
Idempotency-Key is optional, and omitting it makes the call non-idempotent: each request creates a new report. When you do send one, replaying it with the same parameters returns the original report and sets Idempotency-Replayed: true; replaying it with different parameters returns 409 IDEMPOTENCY_KEY_REUSED. A replay is also a 201, but it carries whatever status the original report has now, so treat status as something to read rather than assume.
Wait for Completion#
Poll GET /v1/reports/{report_id}, or subscribe to report.succeeded and report.failed. The webhook data is the same report resource REST returns.
A report is in exactly one state:
pending: nodownload, nofailure_reason.succeeded:download.report_download_id,download.url, anddownload.expires_at.failed:failure_reasonofgeneration_failed,data_unavailable, orlimit_exceeded.
data_unavailable means Flint cannot prove that the interval is complete or a record's supporting evidence has not landed yet: provider balance or payout history has not reconciled through the interval, a Flint billing collection is still waiting on its outcome or lacks its balance transaction, a paid payout's allocations have not synced, an order lacks authority-level tax evidence, or a refund lacks tax lineage. Flint fails the report rather than emitting a file with gaps in it.
limit_exceeded means generation crossed the documented row, file-size, duration, or tax-summary grouping limit. Request a smaller interval.
Transient generation failures are retried before the report moves to a terminal state. Wait for report.succeeded or report.failed instead of creating a duplicate report.
Download the CSV#
curl https://api.withflintpay.com/v1/report-downloads/rdl_01J9Z2K7Q4M8XN3B5V6T0R1C2D \
-H "Authorization: Bearer YOUR_API_KEY" \
--location \
--output report.csv
The download is an authenticated Flint API route. After authorization, it redirects to a short-lived private file URL, so HTTP clients must follow redirects. Do not store or share the temporary redirect target. The Flint URL expires 24 hours after the report succeeds, at the exact instant in download.expires_at. After that the route returns 410 REPORT_DOWNLOAD_EXPIRED.
To get a fresh file for the same interval, create the report again with a new Idempotency-Key (or none at all). Replaying the original key returns the original report and its already-expired download.
List Your Reports#
curl "https://api.withflintpay.com/v1/reports?page_size=20" \
-H "Authorization: Bearer YOUR_API_KEY"
The response uses the standard data[] and opaque next_page_token envelope, ordered by created_at then report_id, newest first. The token is bound to the page_size, merchant, and environment it was issued for, so changing any of them mid-pagination returns INVALID_PAGE_TOKEN. Start a new pass instead.
Report Types#
| Report type | One row is | Interval matches |
|---|---|---|
orders_itemized_v1 | A closed order | Order close time |
payments_itemized_v1 | A succeeded payment | Capture time |
merchant_billing_itemized_v1 | One change to what you owe Flint or the credit you hold | Entry occurrence time |
balance_transactions_itemized_v1 | A balance transaction | Transaction time |
payouts_itemized_v1 | One balance transaction allocated to one payout | Payout arrival date |
tax_itemized_v1 | An order tax breakdown | Order close time |
tax_summarized_v1 | A jurisdiction and tax type | Order collection time for sales, refund success time for reversals |
Two report types over the same dates do not select matching activity. A payout that arrives on February 1 pays for balance transactions created in January, so payouts_itemized_v1 and balance_transactions_itemized_v1 cover different rows for the same interval by design. Reconcile a payout through its own entries, not by date arithmetic.
Reports use only the authoritative occurrence field for their type. A completed record without that field makes generation fail with data_unavailable; Flint does not move financial activity between periods using a mutable last-updated timestamp.
Type names are versioned. A future incompatible column change ships as a new version rather than editing a published shape.
payments_itemized_v1 and tax_summarized_v1 can cover at most 366 days. Other itemized reports can cover at most 90 days. Each merchant and environment can have up to five pending reports. Generation fails explicitly instead of truncating when a report exceeds 1,000,000 source or output rows, 500 MiB of uncompressed CSV, 25 minutes, or 10,000 tax authority and tax type groups.
Columns#
Money is two columns, an amount in minor units and a currency, matching the Money shape everywhere else in the API. See Money & Currency.
orders_itemized_v1
order_id, collection_completed_at, subtotal_money.*, discount_money.*, charge_money.*,
tax_money.*, additive_tax_money.*, inclusive_tax_money.*, requested_tip_money.*,
total_money.*
payments_itemized_v1
payment_intent_id, occurred_at, selected_payment_option, captured_money.*,
processing_fee_money.*, merchant_net_money.*, order_id
merchant_billing_itemized_v1
merchant_billing_entry_id, type, occurred_at, outstanding_effect_money.*,
available_credit_effect_money.*, net_billing_effect_money.*, description,
event_fee_type, payment_intent_id, dispute_id,
merchant_subscription_invoice_id, balance_transaction_id,
related_merchant_billing_entry_id
balance_transactions_itemized_v1
balance_transaction_id, type, status, occurred_at, available_at,
amount_money.*, fee_money.*, net_money.*, payment_intent_id, refund_id,
dispute_id, return_id, payout_id, related_balance_transaction_ids
payouts_itemized_v1
payout_entry_id, payout_id, arrival_date, balance_transaction_id, type,
occurred_at, amount_money.*
tax_itemized_v1
order_id, tax_breakdown_id, collection_completed_at, location_id, jurisdiction.code,
jurisdiction.country, jurisdiction.state, jurisdiction.level,
jurisdiction.name, tax_type, calculation_type, price_mode, percentage,
flat_money.*, taxable_money.*, tax_money.*
tax_summarized_v1
jurisdiction.code, jurisdiction.country, jurisdiction.state,
jurisdiction.level, jurisdiction.name, tax_type, taxable_sales_money.*,
nontaxable_sales_money.*, tax_collected_money.*, tax_refunded_money.*,
tax_payable_money.*
charge_money on the order report is the order's charges (delivery, service, handling), not a processing fee. Processing fees are on the payment and balance reports.
tax_money is the total tax for the order and equals additive_tax_money + inclusive_tax_money. Reconcile the charged order total with subtotal_money - discount_money + charge_money + additive_tax_money + requested_tip_money = total_money. Inclusive tax is already embedded in the entered prices, so adding it again would overstate the total.
merchant_billing_itemized_v1 is the line-by-line history behind what you owe Flint and the account credit you hold. Each row is one change to that position, and merchant_billing_entry_id (prefix mbe_) identifies the change, so the same row keeps the same ID every time you generate the report.
type names the change:
type | What the row is |
|---|---|
event_fee | A fee for one event on your account, such as a card dispute or a failed ACH payment. |
subscription_invoice | A Flint subscription invoice issued to you for a billing period. |
correction | A finalized correction to a fee Flint already charged you. |
credit_issued | Flint gave you account credit. |
credit_applied | Account credit was put toward what you owe. |
collection | Flint collected what you owe out of your Flint balance. |
collection_reversal | A collection came back, so the amount is owed again. |
write_off | Flint stopped collecting an amount you owed and cleared it. |
event_fee_type is set only on event_fee rows and is one of bank_verification, failed_ach_payment, ach_dispute, or card_dispute.
The two effect columns are signed: a positive outstanding_effect_money increases what you owe, a positive available_credit_effect_money increases the credit you can use, and net_billing_effect_money is outstanding_effect_money minus available_credit_effect_money. Collection and reversal rows carry the matching balance_transaction_id, and related_merchant_billing_entry_id points at the earlier row a credit application, collection, reversal, or write-off acts on. Event fees and finalized fee corrections belong here; the original processing fee stays on the payment and payments_itemized_v1. See Flint Billing.
The foreign-key columns on balance_transactions_itemized_v1 are sparse: a row populates the one that matches its source and leaves the rest empty. related_balance_transaction_ids is independent of that and can be set alongside another key, on a refund that points back at the payment it reverses. The value is a JSON array containing every related Flint balance transaction ID.
tax_itemized_v1 covers tax charged on orders. Successful refund executions increase tax_refunded_money and are netted into tax_payable_money on tax_summarized_v1, so use the summarized report when you need what is actually owed to a jurisdiction. Pending and failed refund executions do not change either amount. See Sales Tax.
Reconcile Fees and Payouts#
payments_itemized_v1 gives you the processing fee charged on each succeeded payment alongside merchant_net_money.
balance_transactions_itemized_v1 gives you every movement of your balance and preserves the /v1 sign convention: fee_money.amount is negative, and net_money = amount_money + fee_money.
For the transactions in one specific payout, GET /v1/payouts/{payout_id}/entries is authoritative and does not require generating a report. It needs the money_movement.payouts.read scope rather than reports.read, and it can return 503 PAYOUT_ENTRIES_UNAVAILABLE with a Retry-After header for a payout whose allocations have not synced yet. Retry rather than treating that as an empty payout.
Annual Payment Totals#
For a calendar-year gross payment total, generate payments_itemized_v1 for the full year in your reporting timezone and sum captured_money.amount. That figure is gross: it is not reduced by refunds, processing fees, credits, discounts, or payouts. Pull those from balance_transactions_itemized_v1 and subtract them yourself.
Flint does not expose tax forms through the API. Where Flint is required to file a US information return covering your payment volume, Flint prepares and delivers that form directly. Use these totals as an input to your own books, and confirm anything you file with your accountant.
Errors#
| Code | Status | Cause |
|---|---|---|
INVALID_REQUEST_BODY | 400 | The body is not valid JSON or does not match the schema. |
INVALID_REPORT_TYPE | 400 | report_type is not a supported type. |
INVALID_INTERVAL_START_AT | 400 | interval_start_at is missing or not an RFC 3339 instant. |
INVALID_INTERVAL_END_AT | 400 | interval_end_at is missing or not an RFC 3339 instant. |
INVALID_REPORT_INTERVAL | 400 | interval_end_at is not after interval_start_at. |
REPORT_INTERVAL_INCOMPLETE | 400 | interval_end_at is in the future. |
REPORT_INTERVAL_TOO_LARGE | 400 | The interval exceeds 90 days, or 366 days for payments_itemized_v1 and tax_summarized_v1. |
INVALID_TIMEZONE | 400 | timezone is not an IANA name. |
INVALID_CURRENCY | 400 | currency is not a three-letter ISO 4217 code. |
INVALID_PAGE_SIZE | 400 | page_size is outside the allowed range. |
INVALID_PAGE_TOKEN | 400 | The token is malformed, or page_size, merchant, or environment changed mid-pagination. |
INVALID_IDEMPOTENCY_KEY | 400 | The key is empty or malformed. |
IDEMPOTENCY_KEY_TOO_LONG | 400 | The key exceeds the maximum length. |
IDEMPOTENCY_KEY_REUSED | 409 | The key was already used with different parameters. |
REPORT_NOT_FOUND | 404 | No report or completed download with that ID in this environment. |
REPORT_DOWNLOAD_EXPIRED | 410 | Past download.expires_at. Create the report again with a new key. |
TOO_MANY_PENDING_REPORTS | 429 | Five reports are already pending in this environment. Wait for one to finish. |
REPORTING_UNAVAILABLE | 503 | Reporting could not serve the request. Retry. |
On GET /v1/payouts/{payout_id}/entries:
| Code | Status | Cause |
|---|---|---|
PAYOUT_NOT_FOUND | 404 | No payout with that ID in this environment. |
PAYOUT_ENTRIES_UNAVAILABLE | 503 | Allocations have not synced yet. Retry after Retry-After. |
Next Steps#
- Reconciliation: matching a single deposit to the transactions that funded it.
- Flint Billing: what you owe Flint, the credit you hold, and how Flint collects.
- Processing Fees: what each fee on the payment and balance reports is.
- Sales Tax: what the two tax reports are built from.
- Money movement API reference: balances, balance transactions, and payouts.
