# Retrieve a newsletter's acquisition breakdown **GET /newsletters/{newsletter}/growth** Where a newsletter's new subscribers came from over a window: the split by acquisition source, how many of them arrived through Commune itself, and how the invite funnel performed. A subscriber's source is frozen when the row is first written, so migrating a newsletter between providers later never relabels the history. The counts here are arrivals Commune recorded inside the window and are not the newsletter's audience size. For a newsletter connected to an outside provider they also miss anyone who joined at the provider between two imports. `invites` describes Commune's own invitations to the people on the list who have no Commune account yet. Opting out of those is deliberately separate from unsubscribing from the newsletter, so `opted_out` here says nothing about whether those people still receive the email. ## 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 acquisition breakdown for the resolved window. #### Body: application/json (object) - **object** (string) Always `newsletter_growth`. - **newsletter** (object | null) The newsletter that grew. A `Ref` unless `newsletter` is named in `?expand=`. - **period_start** (string(date-time)) Start of the resolved window, inclusive. - **period_end** (string(date-time)) End of the resolved window, exclusive. - **by_source** (array[object]) One entry per acquisition source that produced at least one subscriber inside the window, largest first. A source that produced none is omitted rather than returned as a zero. - **invites** (object) Commune's invitations to the people on the list who do not have a Commune account yet, asking them to join the conversation around the newsletter they already read. This funnel is ring fenced from the subscription. Declining an invitation leaves the newsletter subscription untouched, so nothing here is an unsubscribe signal. ### 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)