# Retrieve a newsletter's headline numbers **GET /newsletters/{newsletter}/stats** One snapshot of a newsletter over a window: how the audience moved, what was published, how much the community talked, and how the email performed. It answers the question a dashboard opens with, in one call rather than six. Every number is scoped to the window. Pick the window with `period`, or state it exactly with `since` and `until`. Read `audience` and `publishing` carefully before charting them. Commune's record of a newsletter's subscribers is complete only for a newsletter Commune sends natively. For one connected to an outside provider it is a partial cache of that provider's list, which is why the field is called `known_subscribers` and not `subscriber_count`. Do not present it as the newsletter's audience size, and ask the provider for that number instead. `publishing.sent` counts the issues Commune has a record of and is never a count of emails delivered, which lives in `delivery`. ## Servers - Production. There is no separate sandbox host. : https://api.usecommune.com (Production. There is no separate sandbox host. ) ## Authentication methods - Api key ## Parameters ### Headers - **Commune-Version** (string(date)) The contract version this request is written against, as a release date (`YYYY-MM-DD`). Omitting the header pins the request to the version that was current when the API key was issued, so an integration keeps working when a newer version ships. An unknown value answers `400` with `invalid_version`. ### Path parameters - **newsletter** (string) The newsletter's `id` (a UUID) or its `handle`. A handle is unique across Commune and is the identifier its public web profile uses, so it is the one to hardcode in an integration. ### Query parameters - **expand** (string) Comma separated list of relationship paths to inline in the response. Unexpanded relationships are returned as a reference object carrying only `id` and `object`. Each operation documents the paths it accepts, and an unknown path answers `400`. Nested paths use a dot, for example `article.newsletter`. One accepted path is not a relationship. `?expand=content` on `GET /articles/{article}` adds the Markdown rendition of the body beside the HTML one. It is the same trade the parameter always offers, a fuller response for a larger one, over a property that has more than one representation rather than over a reference. - **fields** (string) Comma separated allow list of top level properties to return on each object, so a client can trim a response it does not need in full. `id` and `object` are always returned. An unknown property name answers `400`. Properties omitted by an operation, such as `content` on any article list, cannot be brought back with `fields`. A trimmed body is a subset of the schema this operation declares, and a property that schema marks required is absent when it was not asked for. That is the point of the parameter, so a client that validates responses against the schema either sends no `fields` or relaxes `required`. - **period** (string) A named window, counted back from today in UTC. Defaults to `30d`. Ignored when `since` is given, so a request never has to reconcile two conflicting windows. - **since** (string) Start of the window, inclusive, as a date or an RFC 3339 timestamp. Giving this overrides `period`. A `since` later than `until` answers `400`. Anything that is neither of those two shapes answers `400` as well, rather than being guessed at: a lenient parser would read `8/1/2026` as a date and give two callers different windows for the same string. - **until** (string) End of the window, exclusive, as a date or an RFC 3339 timestamp. Defaults to now. Only meaningful alongside `since`. ## Responses ### 200 The newsletter's numbers for the resolved window. #### Body: application/json (object) - **object** (string) Always `newsletter_stats`. - **newsletter** (object | null) The newsletter these numbers describe. A `Ref` unless `newsletter` is named in `?expand=`. - **period_start** (string(date-time)) Start of the resolved window, inclusive. Echoed because `period`, `since` and `until` can each decide it. - **period_end** (string(date-time)) End of the resolved window, exclusive. - **audience** (object) How the list moved. Every count here comes from Commune's own subscriber records, which are the source of truth only for a newsletter Commune sends natively. - **publishing** (object) Cadence. These are counts of issues, never counts of emails. - **community** (object) What happened in the newsletter's community inside the window. All four are Commune side counts with no email equivalent. - **delivery** (object | null) How the email performed across the issues sent inside the window. `null` for a newsletter Commune does not send, because the provider that sent the mail holds those numbers and does not hand them over per issue. ### 400 The request was malformed: an unknown query parameter, an unparseable cursor, an unknown `expand` path, or an unrecognised `Commune-Version`. #### Body: application/json (object) - **error** (object) ### 401 No API key was presented, or the key is unknown, revoked or expired. All four answer identically, down to the wording. Saying that a key was revoked rather than never issued confirms to whoever is holding the string that it was once real, which a legitimate caller does not need and a thief should not get. #### Headers - **WWW-Authenticate** (string) The authentication scheme this API accepts. Always `Bearer realm="Commune API"`; there is no second scheme and no query-parameter fallback, because a credential that can travel in a URL ends up in access logs and referer headers. #### Body: application/json (object) - **error** (object) ### 403 The key is valid but is not allowed to read this. Either it carries public scope and the operation needs creator scope, or it is bound to a different newsletter than the one addressed. #### Body: application/json (object) - **error** (object) ### 404 No such resource, or the key is not allowed to know that it exists. Commune answers `404` rather than `403` where distinguishing the two would leak the existence of private content. #### Body: application/json (object) - **error** (object) ### 429 Too many requests. Back off and retry after the interval named by the `Retry-After` response header. #### Headers - **Retry-After** (integer) Seconds to wait before retrying. #### Body: application/json (object) - **error** (object) ### 500 Something failed inside Commune. The request may be retried. #### Body: application/json (object) - **error** (object) [Powered by Bump.sh](https://bump.sh)