Everything here is read-only
There is no payout request, no penalty settlement, no withdrawal method and no dispute on this
API. Each of those moves real money or commits banking details, and both belong to a person
signing in — not to a credential in a config file. There is no finance webhook either: a balance
is a position you poll, not an event.
What you need
A key with thefinance:read scope. Issuing one requires View Payouts and View
Transactions in the workspace — the strictest issuance ceiling on this API, because these are
the workspace’s own books.
An amount is always one object
Every figure is aMoney — a decimal string and its
currency, together:
value with a decimal type, not a float. 1250.10 has no exact IEEE-754 representation,
and a parser that rounds it costs a cent on every booking.
Currencies are never added together
A workspace has as many balances as it has settled currencies, and they are separate positions.GET /v1/finance/balance returns one partition per currency. A monthly summary returns one
bucket per period and currency — a September in which you settled in both EGP and USD is
two buckets, not one sum.
Nothing in this API converts between currencies, and there is no total across them anywhere. If
you want one headline number, convert the partitions yourself, with a rate you chose and can
explain.
A currency you have never traded in is absent, not zero. Absence means nothing has ever
happened there.
earned_on is the check-in date
An earning line is dated by the stay’s check-in date, not by the day the money moved.
June revenue means the stays that happened in June. It is the only bucketing that makes a
year-over-year comparison line up seasonally, and it is the convention the Maat host dashboard
uses — so the API and the app agree about the same month.
A guest who books in March for a June stay appears in June.
One line is one payment, not one booking
GET /v1/finance/earnings returns a row per payment. A booking that is later modified takes
its own additional charge, so it has a second row, with its own stay date.
gross on a line is what the platform booked as your income for that payment — the payout
plus any cancellation-insurance premium. It is already net of the platform’s commission and
service fee, so it is not the guest’s total. For what the guest paid, read amount_paid on the
booking.
refunded is what was reversed, as a positive amount, and net is gross - refunded.
platform_fee is Maat’s commission on that payment — the cut taken out of your rate, which is
why gross is smaller than what the stay was priced at. It is the commission and nothing else:
the guest’s service fee is charged on top of your rate rather than deducted from it, and card
processing is ours to carry.
It is published as a pair, like gross and refunded beside it. platform_fee is before
any reversal of it; refunded_platform_fee is how much came back when the payment was reversed.
What Maat kept is the difference. There is deliberately no single net-of-reversal figure: it
would be the one number on the line measured on a different basis from its neighbours, so
gross + platform_fee on a partly-refunded payment would add a pre-reversal amount to a
post-reversal one and quietly come out wrong.
Both figures are recorded when the payment is booked, on the same row and in the same database
transaction as the money itself. Neither is reconstructed at read time.
status says how far that payment’s money has got:
reserved is the one that is easy to miss, and leaving it out is why a balance would not add
up: available + reserved_for_payout + paid_out_total is everything that has cleared.
What you owe is not negative revenue
outstanding_penalties on a partition, and every row from GET /v1/finance/penalties, is a
liability. It is never subtracted from your earnings anywhere in this API.
A penalty is settled from released funds as stays check in, from your balance, or by card in the
Maat app. written_off is a separate answer from settled on purpose: nothing was paid,
somebody decided it would not be.
Windows and paging
from and to are inclusive at both ends and run over earned_on. A from after a to, or a
window wider than 731 days, is refused with 400 validation_failed — never truncated,
because a caller who believes they asked for five years and received two would reconcile against
a total that is missing three.
The summary requires from, to and granularity: it totals everything inside its window in
one pass, so an unbounded one would sweep your whole history on every call.
Lists page with page and page_size and answer has_more — there is no total. GET /v1/finance/payouts/upcoming is deliberately not paged: it is bounded by the platform (future
release dates on confirmed stays only), not by your history.
In the sandbox
A sandbox workspace answers all six for real, and every answer is empty. A sandbox never reaches a payment gateway — no card is ever charged there — so no money has moved, no payout has been requested and nothing is owed. You get{ "partitions": [] }, empty
pages and an empty series.
That is deliberate rather than a gap. The shapes are the production shapes, so you can build and
type your integration against them; what you cannot do is exercise a number. Read the balance of
a real workspace in the Maat app while you develop, and treat the sandbox as the contract check.
Against a production workspace every one of the six returns 501 not_enabled today.