---
title: "Get recurring invoice stats"
method: GET
path: "/v1/companies/{company_id}/recurring-invoices/stats"
tags: ["CompanyRecurringInvoices"]
---

# Get recurring invoice stats

`GET /v1/companies/{company_id}/recurring-invoices/stats`

## Request

- Base URL: `https://app.beel.es/api`
- URL: `https://app.beel.es/api/v1/companies/{company_id}/recurring-invoices/stats`
- Auth: HTTP bearer

How many recurring schedules this company has alive, what they add up to per month, and
how much invoicing is stopped because the unattended generation broke.

**It is the whole company, and it takes no filters.** It is an anchor, not a summary of
whatever the list is showing: narrowing the list by customer or by status does not move
these figures. Pagination does not apply either — the numbers cover every schedule of the
company, not a page of them.

**The two amounts are never added together.** `active.monthly_amount` is a forecast of
what is going to be invoiced; `stopped.monthly_amount` is invoicing that should be
happening and is not. There is deliberately no grand total in the response.

The per-schedule figures behind them are the same ones
`GET /v1/companies/{company_id}/recurring-invoices` publishes as `amount`, so the rows
and this header cannot drift: adding the `amount` of every active row by hand gives
`active.monthly_amount` exactly, to the cent.

Filters of the list are **rejected**, not ignored: sending `status`, `customer_id` or any
other unknown parameter answers `400` naming it. Asking for a filtered header and getting
whole-company figures back with a `200` would be worse than being told no.

## Response `200`

Recurring invoice stats retrieved successfully

- object: The envelope every successful JSON response of the BeeL. API is wrapped in. There are no bare resources in v1 and none are planned: the payload always hangs off `data`. - A **single resource** is an object in `data`. - A **collection** hangs off a named key inside `data`, together with its `pagination`, also inside `data` — `data: {invoices: [...], pagination: {...}}`. - A collection carries `pagination` unless its operation declares itself a **closed catalogue**: a fixed, bounded list with nothing to page through. The declaration is explicit in the operation; a missing `pagination` is never something to infer. - `GET /v1/accounts` pages by cursor (`data: {accounts: [...], next_cursor}`). It is a documented variant of pagination, not another envelope. Putting the array straight into `data` with `pagination` as its sibling is the shape a v2 would adopt; v1 is not being flipped to it.
  - `success` boolean, required
  - `data` object, required: Live recurring schedules of this company and the money behind them, in two blocks that are **never added together**. `active` is a forecast: what is going to be invoiced if nothing breaks. `stopped` is the opposite — invoicing that should be happening and is not, because the unattended generation failed. They answer different questions, so there is no grand total here and you should not compute one. Schedules **a person paused**, and those parked by a plan downgrade, are in **neither** block: they are not a forecast (they will not generate) and not a breakage to fix (someone decided they should not invoice). Completed schedules are in neither block either.
    - `active` RecurringInvoiceStatsBlock, required: How many schedules fall in this block and what they are worth per month.
      - `count` integer, required: Schedules in this block, priced or not. It counts schedules, not money, so a schedule whose amount could not be computed is still counted here.
      - `monthly_amount` number, required: What the schedules in this block bring in per month. Each schedule contributes the per-schedule number the list publishes as `amount` (what its next invoice will charge), divided by the months its `frequency` covers: `MONTHLY` as is, `QUARTERLY` divided by 3, `YEARLY` divided by 12. The sum is rounded to the cent once, at the end. When every schedule is `MONTHLY`, adding the `amount` of the rows by hand gives exactly this figure. It covers `count − uncounted_count` schedules: the ones that could not be priced are **not** added as zero, they are left out and counted in `uncounted_count`. It is not an MRR: it carries taxes and withholding exactly as the invoices will. Always a number, never `null` — with no schedules at all it is `0`.
      - `uncounted_count` integer, required: Schedules of this block whose lines the engine could not price — the same ones the list returns with `amount: null` (no lines, or lines the engine rejects, including a VAT rate that is no longer accepted on the date of the next invoice). They are excluded from `monthly_amount` and counted here, so a partial total never passes for a complete one. `0` means the total covers every schedule in the block.
    - `stopped` RecurringInvoiceStatsBlock, required: How many schedules fall in this block and what they are worth per month.
      - `count` integer, required: Schedules in this block, priced or not. It counts schedules, not money, so a schedule whose amount could not be computed is still counted here.
      - `monthly_amount` number, required: What the schedules in this block bring in per month. Each schedule contributes the per-schedule number the list publishes as `amount` (what its next invoice will charge), divided by the months its `frequency` covers: `MONTHLY` as is, `QUARTERLY` divided by 3, `YEARLY` divided by 12. The sum is rounded to the cent once, at the end. When every schedule is `MONTHLY`, adding the `amount` of the rows by hand gives exactly this figure. It covers `count − uncounted_count` schedules: the ones that could not be priced are **not** added as zero, they are left out and counted in `uncounted_count`. It is not an MRR: it carries taxes and withholding exactly as the invoices will. Always a number, never `null` — with no schedules at all it is `0`.
      - `uncounted_count` integer, required: Schedules of this block whose lines the engine could not price — the same ones the list returns with `amount: null` (no lines, or lines the engine rejects, including a VAT rate that is no longer accepted on the date of the next invoice). They are excluded from `monthly_amount` and counted here, so a partial total never passes for a complete one. `0` means the total covers every schedule in the block.
  - `meta` ResponseMeta
    - `timestamp` string, date-time
    - `request_id` string

## Other responses

- `400`: The request target could not be read as declared, so nothing was looked up. Three causes, all named in `error.details`: a path or query parameter whose value does not parse as its type (`VALIDATION_ERROR` — a UUID that is not a UUID, an unknown enum value, an empty element in a list such as `status=ISSUED,`), a required parameter that was not sent (`MISSING_PARAMETER`), or a header that is meant to carry an identifier and does not (`ACTIVE_COMPANY_HEADER_INVALID`). A list parameter sent entirely empty (`status=`) is not an error: it is the same as omitting it. The fault is in the target, not in the content — which is what `422` is defined over.
- `401`: Missing or invalid authentication. Like every other error, `message`/`detail` is localized according to `Accept-Language` (`es`, `en`, `ca`); defaults to English when the header is missing or asks for none of those.
- `403`: Your account does not own or manage this company, or does not hold the required access over it. A NIF that does not exist answers the same way. That case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.
- `429`: Rate limit exceeded. `RATE_LIMIT_EXCEEDED` when the request rate of your credential is over its limit; `TOO_MANY_REQUESTS` when an email the operation sends is held back because too many went out to that recipient in a short time. Either way, retry after the `Retry-After` seconds. `QUOTA_EXCEEDED` is the other `429`: the operation would go over a quota of your plan for the current period (issued invoices, for instance). Retrying does not help until the quota resets or the plan changes. `error.details` carries `feature_code`, `current_usage`, `quota_limit` and `reset_date`, and `RateLimit-Reset` the seconds until the reset when it is known.
- `500`: Internal server error
- `default`: Any status code the operation does not list above. Every operation declares it, so a generated client always has a branch to fall into and never loses the cause of a failure it did not anticipate. This is where the transport-level answers land — `405`, `406`, `415` and `429` — together with any status a future version of the API starts returning. All of them carry the same error envelope as the codes listed explicitly, so `error.code` is what tells them apart: switching on the status code alone is not enough. See «Transport-level errors» in the API description for when each one is produced. A `402` carrying `SUBSCRIPTION_REQUIRED` lands here when the account has no active subscription. `error.details` says why and what to do: `current_status` (absent if it never had one), `required_permission` and `action`. A `503` carrying `SERVICE_UNAVAILABLE` lands here too, with a `Retry-After` header: a dependency of BeeL itself (the database, the email provider) was momentarily unavailable and nothing was done. Retry after those seconds. A `502` carrying `EXTERNAL_SERVICE_ERROR` also lands here: an outbound integration the operation depends on failed or did not answer in time. It is a transient condition — retry with the same `Idempotency-Key` where the operation accepts one. One exception to the envelope: a failure of the network edge that never reaches the application (`502`, `503`, `504`, `524`) is generated by Cloudflare and its body is not BeeL's — it may not even be JSON. Treat those as "no answer", and retry.

---

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