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#

Bash
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"
  }'
FieldRequiredNotes
report_typeYesOne of the types listed under Report Types.
interval_start_atYesRFC 3339 instant, inclusive.
interval_end_atYesRFC 3339 instant, exclusive. Cannot be in the future.
timezoneNoIANA name, defaults to UTC. Controls how timestamp columns are rendered. Payout reports also interpret the interval boundaries as local calendar dates in this timezone.
currencyYesThree-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: no download, no failure_reason.
  • succeeded: download.report_download_id, download.url, and download.expires_at.
  • failed: failure_reason of generation_failed, data_unavailable, or limit_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#

Bash
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#

Bash
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 typeOne row isInterval matches
orders_itemized_v1A closed orderOrder close time
payments_itemized_v1A succeeded paymentCapture time
merchant_billing_itemized_v1One change to what you owe Flint or the credit you holdEntry occurrence time
balance_transactions_itemized_v1A balance transactionTransaction time
payouts_itemized_v1One balance transaction allocated to one payoutPayout arrival date
tax_itemized_v1An order tax breakdownOrder close time
tax_summarized_v1A jurisdiction and tax typeOrder 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.

text
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:

typeWhat the row is
event_feeA fee for one event on your account, such as a card dispute or a failed ACH payment.
subscription_invoiceA Flint subscription invoice issued to you for a billing period.
correctionA finalized correction to a fee Flint already charged you.
credit_issuedFlint gave you account credit.
credit_appliedAccount credit was put toward what you owe.
collectionFlint collected what you owe out of your Flint balance.
collection_reversalA collection came back, so the amount is owed again.
write_offFlint 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#

CodeStatusCause
INVALID_REQUEST_BODY400The body is not valid JSON or does not match the schema.
INVALID_REPORT_TYPE400report_type is not a supported type.
INVALID_INTERVAL_START_AT400interval_start_at is missing or not an RFC 3339 instant.
INVALID_INTERVAL_END_AT400interval_end_at is missing or not an RFC 3339 instant.
INVALID_REPORT_INTERVAL400interval_end_at is not after interval_start_at.
REPORT_INTERVAL_INCOMPLETE400interval_end_at is in the future.
REPORT_INTERVAL_TOO_LARGE400The interval exceeds 90 days, or 366 days for payments_itemized_v1 and tax_summarized_v1.
INVALID_TIMEZONE400timezone is not an IANA name.
INVALID_CURRENCY400currency is not a three-letter ISO 4217 code.
INVALID_PAGE_SIZE400page_size is outside the allowed range.
INVALID_PAGE_TOKEN400The token is malformed, or page_size, merchant, or environment changed mid-pagination.
INVALID_IDEMPOTENCY_KEY400The key is empty or malformed.
IDEMPOTENCY_KEY_TOO_LONG400The key exceeds the maximum length.
IDEMPOTENCY_KEY_REUSED409The key was already used with different parameters.
REPORT_NOT_FOUND404No report or completed download with that ID in this environment.
REPORT_DOWNLOAD_EXPIRED410Past download.expires_at. Create the report again with a new key.
TOO_MANY_PENDING_REPORTS429Five reports are already pending in this environment. Wait for one to finish.
REPORTING_UNAVAILABLE503Reporting could not serve the request. Retry.

On GET /v1/payouts/{payout_id}/entries:

CodeStatusCause
PAYOUT_NOT_FOUND404No payout with that ID in this environment.
PAYOUT_ENTRIES_UNAVAILABLE503Allocations have not synced yet. Retry after Retry-After.

Next Steps#

Rate this doc