---
title: "Count invoices by collection status, and total the money"
method: GET
path: "/v1/invoices/summary"
tags: ["Invoices"]
---

# Count invoices by collection status, and total the money

`GET /v1/invoices/summary`

## Request

- Base URL: `https://api.getfinero.com/functions/v1/api`
- URL: `https://api.getfinero.com/functions/v1/api/v1/invoices/summary`
- Auth: HTTP bearer

Answers 'how many, and how much' in ONE request. Returns counts per collection_status, outstanding and collected money per currency, and the customers owed the most (`by_customer`, capped by `customer_limit`), over the filters listInvoices accepts (all but `invoice_number`). Use this instead of paging every invoice and adding it up: collection_status is derived from Finero's own payment record and is not something a caller can compute from the fields it receives, and money must never be summed or ranked across currencies, so `by_customer` ranks within each. Exact at any workspace size: the counts come from an indexed column, so nothing is capped and nothing is sampled. Every filter listInvoices accepts except `invoice_number` works here too — pair `due_after` with `due_before` to select whole invoices by their earliest collectible due date. For installment-level aging use the Aging operations; an invoice can span multiple aging buckets.

Available to **admin and readonly** API keys (no side effects).

## Query parameters

- `collection_status` 'paid' | 'open' | 'inactive'
- `currency` string
- `connection_id` string, uuid
- `updated_since` string, date-time
- `customer_id` string, uuid
- `erp_push_state` 'held' | 'pending' | 'processing' | 'posted' | 'failed'
- `has_active_payment_link` boolean
- `due_before` string, date
- `due_after` string, date
- `customer_limit` integer

## Response `200`

Success

- object
  - `total` integer, required: Invoices matching the filters. An exact count, never an estimate — and the number to trust for 'how many are there'.
  - `by_collection_status` object, required: Count per collection status, over every invoice matching the filters. The three are exhaustive and sum to `total`. `paid` counts invoices with nothing left to collect that Finero collected on — including one settled by MIXED paths, part in your ERP and part through Finero. The full rule is on the invoice's own `collection_status`; it is not restated here so the two cannot disagree.
    - `paid` integer, required: Invoices with nothing left to collect that Finero collected on. See `collection_status` on the invoice for the full rule.
    - `open` integer, required: Invoices with an invoice-level or installment balance Finero can collect. See `collection_status` on the invoice for the full rule.
    - `inactive` integer, required: Invoices Finero is not collecting and did not collect — settled entirely outside Finero, cancelled, or with nothing collectible left. NOT the same as paid, and NOT the same as unpaid.
  - `inactive_nothing_to_collect` integer, required: The subset of `inactive` with nothing left to collect — settled outside Finero, cancelled, a draft, empty, a credit, or no longer present in your ERP. `inactive` minus this is the count with money STUCK on it: owed, and not collectible right now. This is Finero's own answer (its `collection_outstanding` at or below zero, which is also the case for an invoice not finalised in your ERP), not the ERP's balance, so it stays correct while the ERP lags.
  - `by_currency` object[], required: Money per ISO 4217 currency, over every invoice matching the filters. NEVER summed across currencies — a single total over a mixed-currency workspace is a wrong number, not a shorter one. An invoice no longer present in your ERP, or not finalised in it (a draft, awaiting approval, voided), owes nothing here (money already collected on it still counts), as in Aging; it still counts in `total` and `by_status`.
    - `currency` string, nullable, required: Uppercased. NULL when the connector supplied no currency: that money is still owed and still counted, but nothing can be collected against it until the ERP supplies a code. Never merge that bucket into another. ISO 4217 alphabetic code, e.g. "USD".
    - `collection_outstanding` string, required: What is still outstanding after Finero's own collections, in this currency. Exact decimal string at that currency's own scale — USD reads 2989.00, JPY 1560817, BHD 2.010. CAUTION: this is what is OWED, not what is collectible — it includes invoices Finero is blocked from collecting (see an installment's block_reasons). Filtering to collection_status=open is the right total of what Finero can act on. It can still include an OPEN invoice's blocked installment — when one specific invoice matters, its detail says per installment what is collectible; that is a per-invoice check, never a workspace walk.
    - `amount_collected` string, required: What Finero has collected, in this currency. Exact decimal string at that currency's scale. IT INCLUDES TEST-MODE MONEY, exactly as the invoice's own field does, so the two cannot disagree — but test money is NOT revenue. Real cash is this figure MINUS `amount_collected_test_mode` beside it; when they are equal, nothing real has been collected and reporting this as income would be wrong.
    - `amount_collected_test_mode` string, required: How much of `amount_collected` came from TEST-mode payments, in this currency. Exact decimal string at that currency's scale. Subtract for real cash. The only aggregate that separates the two — `list_payments` has `mode` per payment, but paging it is what this call avoids.
  - `by_customer` object[], required: WHO OWES THE MOST: the customers with money outstanding, over the same filters as everything else here. One row per (customer, currency) — a customer billed in two currencies is two rows, because money is never summed across currencies and, for the same reason, never ranked across them: `collection_outstanding` ranks WITHIN a currency, grouped by currency and largest first inside each. Narrow with `currency` to rank one book. Capped by `customer_limit` (default 25, max 100), drawn evenly across currencies so a short list never omits one; `by_customer_total` says how many rows exist, so a capped answer never reads as complete. This is the aggregate that replaces paging every invoice and grouping by `customer.id` client-side — there is deliberately no customer endpoint.
    - `customer_id` string, uuid, nullable, required: The stable `customer.id` carried on each invoice. NULL for invoices the connector attached to no customer: that money is still owed and is one honest row, not dropped.
    - `customer_name` string, nullable, required: The customer's display name as the ERP supplied it; null when it supplied none.
    - `currency` string, nullable, required: Uppercased; NULL when the connector supplied no currency. ISO 4217 alphabetic code, e.g. "USD".
    - `collection_outstanding` string, required: What this customer still owes in this currency after Finero's collections, as an exact decimal string at that currency's scale — the same figure and format as `by_currency`, so the rows for one currency sum to that bucket.
    - `amount_collected` string, required: What Finero has collected from this customer in this currency, as an exact decimal string at that currency's scale. TEST-MODE MONEY IS INCLUDED, as on the invoice, and is not split out per customer: before reporting it as revenue, check the currency's `amount_collected_test_mode` in `by_currency` (or `mode` per payment in list_payments).
    - `invoice_count` integer, required: How many of this customer's invoices in this currency carry money (outstanding or collected). An invoice collected in full still counts here — the aging report's `invoice_count` counts only what is still owed, so it can be lower for the same customer.
  - `by_customer_total` integer, required: How many (customer, currency) rows matched in total. When it exceeds the length of `by_customer`, the list was capped by `customer_limit` — raise it, or narrow the filters.

## Other responses

- `400`: `validation_failed` — The request failed schema validation. See error.details for field messages.
- `401`: `invalid_credentials` — The credential is malformed or does not match an active key. The response never reveals whether a key identifier exists. `missing_credentials` — No Authorization: Bearer header was sent. `revoked_credentials` — The presented key was revoked. Create a new key in Settings → API.
- `403`: `feature_unavailable` — The tenant's tier does not currently include API access. Feature rollout flags cannot grant this entitlement. `permission_denied` — The key's permission does not allow this operation (readonly keys cannot call admin-only operations).
- `404`: `not_found` — No such resource in YOUR tenant. Ids belonging to another tenant are indistinguishable from missing ones.
- `429`: `rate_limit_exceeded` — Too many requests. Honor the Retry-After header (seconds) before retrying — waiting that long is enough. The RateLimit-* fields on every response say how much headroom is left before this happens; pace against them instead of retrying into a refusal.
- `500`: `internal_error` — Unexpected server error. Safe to retry with the same Idempotency-Key.

---

[API](https://skmtc.dev/getfinero/apis/finero-public-api.md) · [All operations](https://skmtc.dev/getfinero/apis/finero-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/getfinero/apis/finero-public-api/revisions/3d456e222775?raw)
