Data semantics
The reference tells you the shape of every response. This page tells you what the values mean. Everything here is a place where a technically successful request can still give you a number you did not intend to use, so it is worth reading once before you write the integration rather than after.
1. Books do not start at the beginning of time
Section titled “1. Books do not start at the beginning of time”Every record from GET /business_entities carries two dates that bound what you can ask
for and what you can trust:
books_start_date— the earliest date its books cover.nullwhere the entity has no books yet (reporting_available: false).books_closed_through— the most recent date a bookkeeper has finalized them through.nulluntil the first month is closed, which includes a live entity whose books simply have not been closed yet — read it as “nothing is settled”, not as “no books”. §3 covers what it is for.
Requested ranges are clamped to books_start_date. If you ask for a trailing twelve
months and the entity’s books begin four months ago, you get four months of data, and the
response tells you so: start_date and end_date in the payload are the effective
range we reported on, not the range you asked for.
This is not an edge case. An entity typically arrives on Flychain part-way through its
financial year, and its books begin when we start keeping them, so a short window is the
normal early state of a new entity rather than a fault. The failure to design against is
treating those months as missing or as zero revenue: a partial year of books is “the books
start here”, and a per-month figure of zero for a month before books_start_date would be
a fabrication, not a fact.
Two consequences worth building in:
- Compare the returned range against the range you asked for, and record which one the figure covers. Any downstream calculation that assumes twelve months because it asked for twelve months will be wrong for months at a time.
- A range entirely before
books_start_dateis a400, not an empty statement. We would rather fail your request than hand you a zero that looks like a real measurement. The same applies to a range entirely in the future.
end_date is also capped at today, which is the ordinary case for a job that asks for the
current month every day.
The balance sheet clamps the other way, and on purpose. An as_of_date earlier than
books_start_date is raised to it rather than refused, and the response’s as_of_date
tells you which date you got. The difference is what the two reports are: a range with no
books behind it is not a period, so there is nothing to return but a fabricated zero — but
a balance sheet at the date an entity’s books begin is its real opening position. Compare
as_of_date against the date you asked for, exactly as you would compare a range.
The monthly balance sheet is clamped the same way, by column count rather than by date.
Its optional months parameter (default 12, maximum 36) counts the as_of column, so
months=12 asks for eleven prior month-ends plus the as-of — and an entity with four
months of books returns four columns however many you ask for. periods is empty when
the as-of date falls in the entity’s first month of books: there are no complete prior
months to report, and as_of is the only column. An empty periods is that state, not a
failure.
2. is_closed and is_complete are different questions
Section titled “2. is_closed and is_complete are different questions”Both flags appear on every period of a monthly response — income statement, balance sheet
and cash flow report alike. is_closed appears on its own wherever a response covers one
point or range rather than a series: the single-range and single-date endpoints, and
whole_period / as_of inside a monthly one.
On a monthly balance sheet, is_complete is always true — its periods are month-end
columns, and the one column that can be mid-month is as_of. It is returned anyway so a
period reads the same on every report.
| Flag | Question it answers |
|---|---|
is_complete |
Has this period’s end date passed? false for a period ending today or later — in practice, the month in progress. |
is_closed |
Has a Flychain bookkeeper finalized the books through this period’s end date? |
is_complete is a calendar fact. is_closed is an accounting fact, and it is the one that
tells you whether a figure can still move.
is_closed is computed from the entity’s books_closed_through date, returned on
every record from GET /business_entities: it is true for any period ending on or before
that date, and false for every period when that field is null. The two are the same fact
in two forms, which is useful — one discovery call tells you which periods across your
whole roster are settled, without pulling a report per entity to find out.
A note on is_complete: it is about the period, not the calendar month. If your
requested range starts or ends mid-month, the first and last periods are clipped to your
range (see §5), and a clipped period reads is_complete: true once its end date has
passed even though the calendar month it is labelled with is not fully covered.
3. Trust is_closed, not the calendar
Section titled “3. Trust is_closed, not the calendar”Our bookkeepers aim to close each month within the first couple of weeks of the following month. Treat that as a target, not a guarantee — a close can land later, and a process that assumes a fixed date will occasionally read provisional figures as final. We are telling you this rather than promising a date we might miss, because you are invoicing on these numbers.
is_closed is the authoritative signal, and it is why the flag exists. Before a close,
figures are provisional: categorization is largely automated, so revenue is usually close
to final well before the books close, but as in any accounting system the item most likely
to move a figure is a large non-revenue deposit — a loan, say — that has not yet been
reconciled. That can make revenue read high and then settle lower.
The pattern that handles this deterministically:
- Re-pull trailing months on a schedule (the monthly endpoints make this one request).
- Treat every period with
is_closed: trueas settled, and stop re-pulling it. - Treat every period with
is_closed: falseas provisional, and expect it to change. - If a figure you have already acted on changes after close, true it up from the closed value.
If you would rather only ever act on final figures, wait for is_closed: true on the
period before using it. That is a choice on your side; nothing changes on ours.
4. Accounting basis belongs to the entity
Section titled “4. Accounting basis belongs to the entity”Each entity’s basis — CASH or ACCRUAL — is recorded against its books and is the basis
its financials are prepared and reviewed on. We return accounting_basis on every entity
record and every report, so a figure is never ambiguous about which basis it is on.
There is no per-request basis switch, and adding one would be misleading rather than helpful: a report is produced on the basis the underlying books are kept on, and re-expressing cash-basis books on an accrual basis is a bookkeeping exercise, not a query parameter. Where an entity needs to change basis, that is a decision made with them and the field will reflect it — it will not change without notice.
Practically: an entity’s basis is stable, but do not assume every entity in your relationship shares one. Store the basis alongside any figure you keep.
5. Dates, ranges and periods
Section titled “5. Dates, ranges and periods”- All dates are
YYYY-MM-DD, zero-padded.2026-7-1is rejected, as is a date that does not exist (2026-02-31). Both are400 INVALID_REQUEST. - Ranges are inclusive of both endpoints.
start_date=2026-07-01&end_date=2026-07-31is the whole of July. - Not every report takes a range. The income statement and the cash flow report do —
start_dateandend_date, both required. The balance sheet takes a single requiredas_of_date, because a balance sheet is a position on a date rather than activity over a window; balances accrue from the entity’s inception, so there is nothing for them to accumulate over. Its monthly variants are sized bymonthsrather than by a range (see §1), which is why their periods carry anend_dateand nostart_date. - Report windows are evaluated in UTC. Do not send a local-timezone-adjusted date; a shifted window can pull a transaction from the neighbouring day into the wrong period.
generated_atis ISO 8601 UTC. Reports are computed on request, so it is also the as-of time of the figures. There is no caching delay to work around: an on-demand pull reflects the current state of the books.- Monthly periods are clipped, not extended. A range from the 15th to the 14th yields a
first period starting on the 15th and a last period ending on the 14th. The
label(“July 2026”) is derived from the period’s start date, so use the dates, not the label, to know what a period covers. Align your ranges to month boundaries if you want whole calendar months. whole_periodis computed independently, over the full range, rather than summed from the per-month figures. The two agree; a discrepancy would indicate an upstream inconsistency rather than rounding. On a cash flow report, agree means the three flow figures sum across periods while the two balances do not —starting_cash_balance_centsis the first period’s andending_cash_balance_centsthe last period’s, because a balance is a position rather than an amount that accumulated.- A clipped period’s cash balances follow the clip. On a monthly cash flow report, a
first period starting on the 15th has the balance on the 15th as its
starting_cash_balance_cents, not the balance on the 1st.
6. Entities that are not reportable, and entities that are gone
Section titled “6. Entities that are not reportable, and entities that are gone”Two distinct situations, reported differently on purpose.
Not yet reportable, or no longer reportable. GET /business_entities lists the entity
with reporting_available: false. This covers both directions: an entity still being
onboarded, and one that has been deactivated. A report request for it returns
409 BOOKS_NOT_AVAILABLE. Skip these rather than recording a zero — an entity mid-onboarding
would otherwise read as a business with no revenue. This is the same for both kinds of key.
Outside your key’s reach, and here the two audiences are answered differently — on purpose, because they need different things.
With a partner key: the entity or provider stops appearing in the discovery endpoints,
and a report request returns 403, with a code (PROVIDER_NOT_IN_PARTNER_SCOPE or
BUSINESS_ENTITY_NOT_IN_PARTNER_SCOPE) distinct from the 404 you get for an id that
never existed. That distinction is deliberate: a provider leaving the relationship is a
thing your platform should be able to detect and flag, rather than discovering it as a
silent absence.
So for a partner: PROVIDER_NOT_IN_PARTNER_SCOPE and
BUSINESS_ENTITY_NOT_IN_PARTNER_SCOPE mean “this existed and is no longer yours — stop
querying it and tell someone”. 404 means “this id is wrong”.
With a provider key: an id that is not yours returns 404 — PROVIDER_NOT_FOUND or
BUSINESS_ENTITY_NOT_FOUND, the identical answer an id that never existed gets. There is
no 403 scope code on this side, and no way to tell the two apart. You hold exactly one
valid provider_id and GET /business_entities already lists your entities, so there is
nothing here you need to distinguish — and confirming that some other provider’s id is
real is not something we will do.
Match on the code, not on the status. Two other things arrive as 403 and mean something
else entirely:
PARTNER_API_NOT_ENABLED(partner keys) andPROVIDER_NOT_ACTIVE/PROVIDER_API_NOT_ENABLED(provider keys) are about your own account, not about a resource. Every id you hold is still valid and nothing has left your reach. Treating one of these as a departure would flag your whole roster as churned.ENDPOINT_NOT_AVAILABLE(partner keys) is about the endpoint, not the resource: the balance sheet and cash flow families are provider-key only. The entity is fine and your URL is fine — see which endpoints each key reaches.
7. Amounts
Section titled “7. Amounts”Every monetary value is a signed integer in cents, USD. We hold monetary values in cents internally to avoid floating-point rounding in financial calculations, and expose them the same way so no precision is lost in transit. Expense categories are positive magnitudes, as they appear on a statement.
In the full statement, read total_amount_cents for a category or account total — it
includes every account beneath that node. amount_cents is only what is posted directly at
that node, which for a category root is usually zero but is not guaranteed to be. Reading
amount_cents on a category is the single easiest way to under-report a figure.
The cash flow report is the exception, and it fails quietly. Its nodes are cash accounts
rather than statement categories, and on a ledger total_amount_cents is always 0 —
the figure lives in total_net_cash_flow_cents. A reader that applies the rule above to a
cash flow ledger gets a zero rather than an error. One level down, on the individual
transactions beneath a ledger, amount_cents is the amount that moved.
And on the cash flow report, reading one account and summing every account are different
fields. ledgers is a flat list: where one account rolls up into another, both appear in
it as their own entries. So total_net_cash_flow_cents — which includes everything beneath
an account — is what to read for a single account and double-counts under a sum.
To aggregate, add up each account’s total_inflow_cents and total_outflow_cents. Those
are the per-account figures the report totals are built from, so they reconcile exactly.
net_cash_flow_cents is that account’s own net, useful for reading one row next to its
rolled-up figure, but it reaches us as a separate value rather than as the sum of the two
above — so use the pair when the arithmetic has to hold.
That extra level is itself worth knowing about: the cash flow full variants descend to
individual transactions — line_id, datetime and the description as it reaches us —
where the income statement and balance sheet stop at account level. It is why those payloads
grow with an entity’s transaction volume, and why the summary variant is the one to point
a recurring pull at.
Cash outflows carry their sign. total_outflow_cents is negative (or 0 where nothing
left in the range), so total_inflow_cents + total_outflow_cents = net_cash_flow_cents —
unlike an income statement’s expense categories, which are positive magnitudes.