Retrieve a newsletter's headline numbers

Add MCP server to your AI tool

Allow AI tools and LLMs to interact with the API documentation portal through MCP.

MCP server URL

https://api-reference.usecommune.dev/mcp

Standard setup for AI tools providing an mcp.json file

mcp.json
{
  "REST API MCP server": {
    "url": "https://api-reference.usecommune.dev/mcp"
  }
}

Close
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.

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 Required

    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.

    Values are 7d, 14d, 30d, 90d, 6m, 1y, or all. Default value is 30d.

  • 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.

    Maximum length is 40.

  • until string

    End of the window, exclusive, as a date or an RFC 3339 timestamp. Defaults to now. Only meaningful alongside since.

    Maximum length is 40.

Responses

  • 200 application/json

    The newsletter's numbers for the resolved window.

    Hide response attributes Show response attributes object
    • object string Required

      Always newsletter_stats.

      Value is newsletter_stats.

    • newsletter object | null Required

      The newsletter these numbers describe. A Ref unless newsletter is named in ?expand=.

      One of:
    • period_start string(date-time) Required

      Start of the resolved window, inclusive. Echoed because period, since and until can each decide it.

    • period_end string(date-time) Required

      End of the resolved window, exclusive.

    • audience object Required

      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.

      Additional properties are NOT allowed.

      Hide audience attributes Show audience attributes object
      • known_subscribers integer Required

        How many subscribers Commune has a record of at period_end. Not the newsletter's subscriber count. For a newsletter connected to an outside provider, Commune's records are a partial cache of that provider's list, filled in by imports and by people who joined through Commune, so this is a floor and not a total. Ask the provider for the real number.

        Minimum value is 0.

      • net_change integer Required

        Subscribers gained minus subscribers lost inside the window, against the same partial record. Negative when the list shrank.

      • by_status object Required

        How the known subscribers split across the subscription states at period_end. The keys are the SubscriberStatus values.

        Additional properties are NOT allowed.

        Hide by_status attributes Show by_status attributes object
        • subscribed integer Required

          Mailable.

          Minimum value is 0.

        • unsubscribed integer Required

          Opted out, and kept so the history survives.

          Minimum value is 0.

        • bounced integer Required

          Undeliverable, and suppressed by the send pipeline.

          Minimum value is 0.

        • complained integer Required

          Reported the email as spam, and suppressed.

          Minimum value is 0.

        • pending integer Required

          Recorded but not yet confirmed as mailable.

          Minimum value is 0.

    • publishing object Required

      Cadence. These are counts of issues, never counts of emails.

      Additional properties are NOT allowed.

      Hide publishing attributes Show publishing attributes object
      • sent integer Required

        Issues that went out inside the window and that Commune has a record of. Commune's article table is filtered rather than exhaustive, so this must never be presented as the number of emails the newsletter sent. That number lives on a send.

        Minimum value is 0.

      • scheduled integer Required

        Issues queued to go out after period_end. A forward looking count, so it does not move when the window does.

        Minimum value is 0.

    • community object Required

      What happened in the newsletter's community inside the window. All four are Commune side counts with no email equivalent.

      Additional properties are NOT allowed.

      Hide community attributes Show community attributes object
      • threads integer Required

        Conversations started, including the ones Commune opens under a published article.

        Minimum value is 0.

      • messages integer Required

        Replies posted inside those conversations. An article's comments are replies in its thread, so they are counted here.

        Minimum value is 0.

      • highlights integer Required

        Passages readers marked inside an article.

        Minimum value is 0.

      • reactions integer Required

        Emoji reactions added to a message.

        Minimum value is 0.

    • delivery object | null Required

      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.

      Additional properties are NOT allowed.

      Hide delivery attributes Show delivery attributes object | null
      • open_rate number | null Required

        Opens over deliveries, as a fraction between 0 and 1. null when nothing was delivered in the window, which is not the same as a rate of zero. Open tracking is unreliable by nature: a mail client that prefetches images inflates it and one that blocks them suppresses it.

        Minimum value is 0, maximum value is 1.

      • click_rate number | null Required

        Recipients who clicked at least one link, over deliveries, as a fraction between 0 and 1. null when nothing was delivered in the window.

        Minimum value is 0, maximum value is 1.

  • 400 application/json

    The request was malformed: an unknown query parameter, an unparseable cursor, an unknown expand path, or an unrecognised Commune-Version.

    Hide response attribute Show response attribute object
    • error object Required

      Additional properties are NOT allowed.

      Hide error attributes Show error attributes object
      • code string Required

        The stable, machine readable reason a request failed. New codes may be added in a minor version, so treat an unrecognised code as a generic failure of its HTTP status class.

        Values are bad_request, invalid_version, unauthorized, forbidden, insufficient_scope, payment_required, not_found, conflict, unprocessable, rate_limited, or internal_error.

      • message string Required

        A human readable sentence describing what went wrong. Written for a developer reading a log, not for an end user. Do not branch on it, branch on code.

      • param string

        The query, path or body parameter the error is attributed to, when the error is attributable to exactly one. Absent otherwise.

      • allowed_values array[string]

        Everything param would have accepted, when what it accepts is a finite set. Absent when it is not: a cursor, an identifier or a numeric range has nothing to enumerate, and an empty array would read as "nothing is allowed".

        It is deliberately redundant with message, which names the same values in prose. The array is what a program branches on and the sentence is what a model reads, and a caller correcting a request has to be able to do it from this one response without opening a reference page.

        On an unknown parameter name rather than an unknown value, this carries the parameter names the operation does accept, since that is the set the caller has to pick from.

        On an insufficient_scope failure there is no parameter at fault and param is absent, and this carries the scopes that would have been accepted instead.

      • request_id string

        Identifier for this request, echoed in the Commune-Request-Id response header. Quote it in support requests.

      • docs_url string(uri)

        Link to the reference page for this error code.

  • 401 application/json

    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.

    Hide headers attribute Show headers attribute
    • 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.

    Hide response attribute Show response attribute object
    • error object Required

      Additional properties are NOT allowed.

      Hide error attributes Show error attributes object
      • code string Required

        The stable, machine readable reason a request failed. New codes may be added in a minor version, so treat an unrecognised code as a generic failure of its HTTP status class.

        Values are bad_request, invalid_version, unauthorized, forbidden, insufficient_scope, payment_required, not_found, conflict, unprocessable, rate_limited, or internal_error.

      • message string Required

        A human readable sentence describing what went wrong. Written for a developer reading a log, not for an end user. Do not branch on it, branch on code.

      • param string

        The query, path or body parameter the error is attributed to, when the error is attributable to exactly one. Absent otherwise.

      • allowed_values array[string]

        Everything param would have accepted, when what it accepts is a finite set. Absent when it is not: a cursor, an identifier or a numeric range has nothing to enumerate, and an empty array would read as "nothing is allowed".

        It is deliberately redundant with message, which names the same values in prose. The array is what a program branches on and the sentence is what a model reads, and a caller correcting a request has to be able to do it from this one response without opening a reference page.

        On an unknown parameter name rather than an unknown value, this carries the parameter names the operation does accept, since that is the set the caller has to pick from.

        On an insufficient_scope failure there is no parameter at fault and param is absent, and this carries the scopes that would have been accepted instead.

      • request_id string

        Identifier for this request, echoed in the Commune-Request-Id response header. Quote it in support requests.

      • docs_url string(uri)

        Link to the reference page for this error code.

  • 403 application/json

    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.

    Hide response attribute Show response attribute object
    • error object Required

      Additional properties are NOT allowed.

      Hide error attributes Show error attributes object
      • code string Required

        The stable, machine readable reason a request failed. New codes may be added in a minor version, so treat an unrecognised code as a generic failure of its HTTP status class.

        Values are bad_request, invalid_version, unauthorized, forbidden, insufficient_scope, payment_required, not_found, conflict, unprocessable, rate_limited, or internal_error.

      • message string Required

        A human readable sentence describing what went wrong. Written for a developer reading a log, not for an end user. Do not branch on it, branch on code.

      • param string

        The query, path or body parameter the error is attributed to, when the error is attributable to exactly one. Absent otherwise.

      • allowed_values array[string]

        Everything param would have accepted, when what it accepts is a finite set. Absent when it is not: a cursor, an identifier or a numeric range has nothing to enumerate, and an empty array would read as "nothing is allowed".

        It is deliberately redundant with message, which names the same values in prose. The array is what a program branches on and the sentence is what a model reads, and a caller correcting a request has to be able to do it from this one response without opening a reference page.

        On an unknown parameter name rather than an unknown value, this carries the parameter names the operation does accept, since that is the set the caller has to pick from.

        On an insufficient_scope failure there is no parameter at fault and param is absent, and this carries the scopes that would have been accepted instead.

      • request_id string

        Identifier for this request, echoed in the Commune-Request-Id response header. Quote it in support requests.

      • docs_url string(uri)

        Link to the reference page for this error code.

  • 404 application/json

    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.

    Hide response attribute Show response attribute object
    • error object Required

      Additional properties are NOT allowed.

      Hide error attributes Show error attributes object
      • code string Required

        The stable, machine readable reason a request failed. New codes may be added in a minor version, so treat an unrecognised code as a generic failure of its HTTP status class.

        Values are bad_request, invalid_version, unauthorized, forbidden, insufficient_scope, payment_required, not_found, conflict, unprocessable, rate_limited, or internal_error.

      • message string Required

        A human readable sentence describing what went wrong. Written for a developer reading a log, not for an end user. Do not branch on it, branch on code.

      • param string

        The query, path or body parameter the error is attributed to, when the error is attributable to exactly one. Absent otherwise.

      • allowed_values array[string]

        Everything param would have accepted, when what it accepts is a finite set. Absent when it is not: a cursor, an identifier or a numeric range has nothing to enumerate, and an empty array would read as "nothing is allowed".

        It is deliberately redundant with message, which names the same values in prose. The array is what a program branches on and the sentence is what a model reads, and a caller correcting a request has to be able to do it from this one response without opening a reference page.

        On an unknown parameter name rather than an unknown value, this carries the parameter names the operation does accept, since that is the set the caller has to pick from.

        On an insufficient_scope failure there is no parameter at fault and param is absent, and this carries the scopes that would have been accepted instead.

      • request_id string

        Identifier for this request, echoed in the Commune-Request-Id response header. Quote it in support requests.

      • docs_url string(uri)

        Link to the reference page for this error code.

  • 429 application/json

    Too many requests. Back off and retry after the interval named by the Retry-After response header.

    Hide headers attribute Show headers attribute
    • Retry-After integer

      Seconds to wait before retrying.

      Minimum value is 1.

    Hide response attribute Show response attribute object
    • error object Required

      Additional properties are NOT allowed.

      Hide error attributes Show error attributes object
      • code string Required

        The stable, machine readable reason a request failed. New codes may be added in a minor version, so treat an unrecognised code as a generic failure of its HTTP status class.

        Values are bad_request, invalid_version, unauthorized, forbidden, insufficient_scope, payment_required, not_found, conflict, unprocessable, rate_limited, or internal_error.

      • message string Required

        A human readable sentence describing what went wrong. Written for a developer reading a log, not for an end user. Do not branch on it, branch on code.

      • param string

        The query, path or body parameter the error is attributed to, when the error is attributable to exactly one. Absent otherwise.

      • allowed_values array[string]

        Everything param would have accepted, when what it accepts is a finite set. Absent when it is not: a cursor, an identifier or a numeric range has nothing to enumerate, and an empty array would read as "nothing is allowed".

        It is deliberately redundant with message, which names the same values in prose. The array is what a program branches on and the sentence is what a model reads, and a caller correcting a request has to be able to do it from this one response without opening a reference page.

        On an unknown parameter name rather than an unknown value, this carries the parameter names the operation does accept, since that is the set the caller has to pick from.

        On an insufficient_scope failure there is no parameter at fault and param is absent, and this carries the scopes that would have been accepted instead.

      • request_id string

        Identifier for this request, echoed in the Commune-Request-Id response header. Quote it in support requests.

      • docs_url string(uri)

        Link to the reference page for this error code.

  • 500 application/json

    Something failed inside Commune. The request may be retried.

    Hide response attribute Show response attribute object
    • error object Required

      Additional properties are NOT allowed.

      Hide error attributes Show error attributes object
      • code string Required

        The stable, machine readable reason a request failed. New codes may be added in a minor version, so treat an unrecognised code as a generic failure of its HTTP status class.

        Values are bad_request, invalid_version, unauthorized, forbidden, insufficient_scope, payment_required, not_found, conflict, unprocessable, rate_limited, or internal_error.

      • message string Required

        A human readable sentence describing what went wrong. Written for a developer reading a log, not for an end user. Do not branch on it, branch on code.

      • param string

        The query, path or body parameter the error is attributed to, when the error is attributable to exactly one. Absent otherwise.

      • allowed_values array[string]

        Everything param would have accepted, when what it accepts is a finite set. Absent when it is not: a cursor, an identifier or a numeric range has nothing to enumerate, and an empty array would read as "nothing is allowed".

        It is deliberately redundant with message, which names the same values in prose. The array is what a program branches on and the sentence is what a model reads, and a caller correcting a request has to be able to do it from this one response without opening a reference page.

        On an unknown parameter name rather than an unknown value, this carries the parameter names the operation does accept, since that is the set the caller has to pick from.

        On an insufficient_scope failure there is no parameter at fault and param is absent, and this carries the scopes that would have been accepted instead.

      • request_id string

        Identifier for this request, echoed in the Commune-Request-Id response header. Quote it in support requests.

      • docs_url string(uri)

        Link to the reference page for this error code.

GET /newsletters/{newsletter}/stats
curl "https://api.usecommune.com/newsletters/the-weekly/stats?period=90d" \
  -H "Authorization: Bearer $COMMUNE_API_KEY" \
  -H "Commune-Version: 2026-08-26"
curl \
 --request GET 'https://api.usecommune.com/newsletters/9a4c1c6e-0f2b-4f47-9d3f-6d1b1a2c3d4e/stats' \
 --header "Authorization: Bearer $ACCESS_TOKEN" \
 --header "Commune-Version: 2026-08-26"
Response examples (200)
{
  "object": "newsletter_stats",
  "newsletter": {
    "object": "newsletter",
    "id": "9a4c1c6e-0f2b-4f47-9d3f-6d1b1a2c3d4e"
  },
  "period_start": "2026-05-04T09:42:00Z",
  "period_end": "2026-05-04T09:42:00Z",
  "audience": {
    "known_subscribers": 42,
    "net_change": 42,
    "by_status": {
      "subscribed": 42,
      "unsubscribed": 42,
      "bounced": 42,
      "complained": 42,
      "pending": 42
    }
  },
  "publishing": {
    "sent": 42,
    "scheduled": 42
  },
  "community": {
    "threads": 42,
    "messages": 42,
    "highlights": 42,
    "reactions": 42
  },
  "delivery": {
    "open_rate": 42.0,
    "click_rate": 42.0
  }
}
Response examples (400)
{
  "error": {
    "code": "bad_request",
    "message": "Newsletter not found.",
    "param": "cursor",
    "allowed_values": [
      "subscribed",
      "unsubscribed",
      "bounced",
      "complained",
      "pending"
    ],
    "request_id": "req_01j9c8h1q7m3n4p5r6s7t8u9v0",
    "docs_url": "https://usecommune.com/docs/api/errors#not_found"
  }
}
Response examples (401)
# Headers
WWW-Authenticate: string

# Payload
{
  "error": {
    "code": "bad_request",
    "message": "Newsletter not found.",
    "param": "cursor",
    "allowed_values": [
      "subscribed",
      "unsubscribed",
      "bounced",
      "complained",
      "pending"
    ],
    "request_id": "req_01j9c8h1q7m3n4p5r6s7t8u9v0",
    "docs_url": "https://usecommune.com/docs/api/errors#not_found"
  }
}
Response examples (403)
{
  "error": {
    "code": "bad_request",
    "message": "Newsletter not found.",
    "param": "cursor",
    "allowed_values": [
      "subscribed",
      "unsubscribed",
      "bounced",
      "complained",
      "pending"
    ],
    "request_id": "req_01j9c8h1q7m3n4p5r6s7t8u9v0",
    "docs_url": "https://usecommune.com/docs/api/errors#not_found"
  }
}
Response examples (404)
{
  "error": {
    "code": "bad_request",
    "message": "Newsletter not found.",
    "param": "cursor",
    "allowed_values": [
      "subscribed",
      "unsubscribed",
      "bounced",
      "complained",
      "pending"
    ],
    "request_id": "req_01j9c8h1q7m3n4p5r6s7t8u9v0",
    "docs_url": "https://usecommune.com/docs/api/errors#not_found"
  }
}
Response examples (429)
# Headers
Retry-After: 42

# Payload
{
  "error": {
    "code": "bad_request",
    "message": "Newsletter not found.",
    "param": "cursor",
    "allowed_values": [
      "subscribed",
      "unsubscribed",
      "bounced",
      "complained",
      "pending"
    ],
    "request_id": "req_01j9c8h1q7m3n4p5r6s7t8u9v0",
    "docs_url": "https://usecommune.com/docs/api/errors#not_found"
  }
}
Response examples (500)
{
  "error": {
    "code": "bad_request",
    "message": "Newsletter not found.",
    "param": "cursor",
    "allowed_values": [
      "subscribed",
      "unsubscribed",
      "bounced",
      "complained",
      "pending"
    ],
    "request_id": "req_01j9c8h1q7m3n4p5r6s7t8u9v0",
    "docs_url": "https://usecommune.com/docs/api/errors#not_found"
  }
}