Report on finances
Requires: A secret key with
READ_FINANCE; addREAD_FINANCE_PIIfor buyer identity.
These reads give a community its own books: every sale, the commission it earned on members' sales, refunds, donations, and what was transferred to it. They are for accounting exports, dashboards and reconciliation.
GET /api/v1/communities/:communityTag/finance/entries
GET /api/v1/communities/:communityTag/finance/payouts
GET /api/v1/communities/:communityTag/finance/summary
GET /api/v1/communities/:communityTag/finance/deposits/:depositId/statementSecret key only, with READ_FINANCE. Add READ_FINANCE_PII only if you
need buyer email and name. A publishable key answers 403 SECRET_KEY_REQUIRED.
Every amount is an integer in the smallest currency unit. Reference:
Finance.
Entries
let cursor: string | undefined;
do {
const page = await cobuntu.finance.entries({ from: "2026-10-01", to: "2026-10-31", limit: 500, cursor });
for (const e of page.data) save(e);
cursor = page.nextCursor ?? undefined;
} while (cursor);Oldest first. limit is clamped to 500 rather than refused. Pass back
nextCursor, or an entry's own cursor to resume after it. Never build a
cursor from id: a malformed cursor is 400 INVALID_CURSOR, never a silent
restart. Bad dates are 400 INVALID_INPUT.
Read role before net
{
"id": "fe_1", "cursor": "...", "type": "COMMISSION", "stream": "DIGITAL",
"role": "COMMISSION_RECIPIENT", "currency": "EUR",
"gross": 5000, "net": 1000,
"fees": { "platform": -250, "stripe": -95, "stripeTax": 0, "memberSeller": -3655 },
"settlement": { "status": "ESCROW", "payoutId": null, "paidAt": null },
"buyerRef": "b_8f2c..."
}role: "SELLER": the community sold its own thing;netis its earnings.role: "COMMISSION_RECIPIENT": a member sold and the community keeps only its commission.grosshere is the member's sale value. Summinggrossacross entries gives a number that looks like revenue and is not. Sumnet.
Refunds and settlement
A refunded sale carries refund: { refundedAt, amount, status }, and a full
refund already reports net: 0. Do not subtract the refund again.
settlement.status says where the money is:
| Status | Means |
|---|---|
ESCROW | Inside the refund window. |
HOLD | Releasable, below the transfer threshold. |
ELIGIBLE | Waiting for the next transfer run. |
PAID | Transferred; payoutId joins to /finance/payouts. |
FAILED | A transfer was attempted and did not succeed. |
BLOCKED | Not a sanction: the sale will never pay out because it was fully refunded. Render it as refunded. |
Buyers
Without READ_FINANCE_PII, entries carry only buyerRef: opaque, stable per
buyer, null for guests and after the buyer exercises erasure. With it, buyer
adds email and name.
Payouts
const { data } = await cobuntu.finance.payouts({ from: "2026-10-01" });
// [{ id: "po_1", status: "PAID", currency: "EUR", amount: 120000, salesCount: 14, paidAt, stripeTransferId }]These are transfers into the community's Stripe balance, not its bank statement. Stripe then pays out to the bank on its own schedule, often batching several of these into one bank credit.
Totals
const { data, window } = await cobuntu.finance.summary({ from: "2026-01-01", to: "2026-12-31" });
// [{ currency: "EUR", net: 845000, count: 212, byStream: { EVENT: { count, net } }, byType: { SALE: { count, net } } }]One bucket per currency, never summed across currencies. Computed from the
same entries /finance/entries serves, so the two agree.
One bank credit
The payout.deposited webhook carries a depositId. The statement lists the
payouts, and their invoice numbers, behind it:
const { data } = await cobuntu.finance.depositStatement(depositId);
// { deposit: { amount: 240000, ... }, lines: [...], totals: { covered: 240000, deposit: 240000, unaccounted: 0 } }unaccounted is normally 0; non-zero means the deposit also carried money
that did not come through Cobuntu. It is a statement, not an invoice: each
payout in it was already invoiced.
The client returns JSON only. For the PDF, fetch the same path yourself:
const res = await fetch(
`https://api.cobuntu.com/api/v1/communities/your-community/finance/deposits/${depositId}/statement?format=pdf`,
{ headers: { "X-API-Key": process.env.COBUNTU_API_KEY! } },
);
const pdf = await res.arrayBuffer();Keeping a copy in sync
Entries are append-only, but figures can be restated in place when Stripe
settles a fee differently from its estimate. Follow the cursor for new
entries, and periodically re-pull a recent window (for example the last 60
days) and upsert by id.