Commune Users API

A person with a Commune account: the readers who join a community and the writers who are credited on an article. Looked up by identifier or by username, and only ever as a public profile: never an email address. The one account read from the inside is the credential's own. `GET /me` is the same person as the profile above plus the address and verification state that one withholds, and it sits here rather than under a heading of its own because it is the private view of exactly what this tag already documents. It needs no permission at all.

Operations 2

GET /me Retrieve the authenticated account #
GET /users/{user} Retrieve a user #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/usecommune:usecommune-users-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

usecommune-users-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Usecommune Users API
  version: '2026-08-26'
  contact:
    name: Commune
    url: https://usecommune.com
    email: support@usecommune.com
  description: 'Operations tagged Users across 2 of this provider''s published API definitions: usecommune-openapi.json, usecommune-openapi.yml. Each path carries the servers of the definition it was published in.'
servers:
- url: https://api.usecommune.com
  description: 'Production. There is no separate sandbox host.

    '
security:
- apiKey: []
tags:
- name: Users
  description: 'A person with a Commune account: the readers who join a community and the

    writers who are credited on an article.'
paths:
  /me:
    parameters:
    - $ref: '#/components/parameters/CommuneVersion'
    get:
      operationId: getMe
      summary: Retrieve the authenticated account
      description: 'The account this credential belongs to: the public profile

        `GET /users/{user}` returns, plus the email address and verification

        state that profile withholds.


        **Any credential this API accepts can call it**, an API key included,

        and no permission is required. `GET /memberships`,

        `GET /subscriptions`, `GET /saved-articles` and `GET /liked-articles`

        are different and do need `account: read`.'
      tags:
      - Users
      security:
      - apiKey: []
      - oauth2: []
      parameters:
      - $ref: '#/components/parameters/Fields'
      responses:
        '200':
          description: The authenticated account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Me'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
    servers:
    - url: https://api.usecommune.com
      description: 'Production. There is no separate sandbox host.

        '
  /users/{user}:
    parameters:
    - $ref: '#/components/parameters/CommuneVersion'
    - $ref: '#/components/parameters/UserPath'
    get:
      operationId: getUser
      summary: Retrieve a user
      description: 'Read one public profile by `id` or by `username`. This is the whole

        public shape of a person in Commune.'
      tags:
      - Users
      security:
      - apiKey: []
      - oauth2: []
      parameters:
      - $ref: '#/components/parameters/Fields'
      responses:
        '200':
          description: The user.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
    servers:
    - url: https://api.usecommune.com
      description: 'Production. There is no separate sandbox host.

        '
components:
  schemas:
    Error:
      type: object
      title: Error
      description: 'The error envelope. Every non `2xx` response from every operation has

        this shape, so a client can branch on `error.code` without knowing which

        operation produced it.

        '
      additionalProperties: false
      required:
      - error
      properties:
        error:
          type: object
          additionalProperties: false
          required:
          - code
          - message
          properties:
            code:
              $ref: '#/components/schemas/ErrorCode'
            message:
              type: string
              description: '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`.

                '
              examples:
              - Newsletter not found.
            param:
              type: string
              description: 'The query, path or body parameter the error is attributed to,

                when the error is attributable to exactly one. Absent otherwise.

                '
              examples:
              - cursor
            allowed_values:
              type: array
              description: '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 repeats what `message` says in prose, so a caller can correct

                a request from this one response: the array is what a program

                branches on, the sentence is what a person or a model reads.


                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 usually no

                parameter at fault and `param` is absent, and this carries the

                one permission that was needed, written the way the permission

                table writes it, such as `content: read`. The exception is a

                credential that may call the operation but not with one value

                of a parameter, such as `?expand=subscriber` on

                `listNewsletterInsights` without `audience: read`: then `param`

                names the parameter and this carries the values this credential

                may send instead.

                '
              items:
                type: string
              examples:
              - - subscribed
                - unsubscribed
                - bounced
                - complained
                - pending
            request_id:
              type: string
              description: 'Identifier for this request, echoed in the `Commune-Request-Id`

                response header. Quote it in support requests.

                '
              examples:
              - req_01j9c8h1q7m3n4p5r6s7t8u9v0
            docs_url:
              type: string
              format: uri
              description: 'Link to the documentation for this error code: always

                `https://usecommune.dev/errors/` followed by the code, a page

                on what the code means, what usually causes it and how to fix

                it.

                '
              examples:
              - https://usecommune.dev/errors/not_found
    User:
      type: object
      title: User
      description: 'A person''s public profile, and the whole of what this API returns about

        anybody other than the credential''s own owner. Email address, theme,

        notification preferences, push subscriptions, read state and saved

        articles are never carried.

        '
      additionalProperties: false
      required:
      - object
      - id
      properties:
        object:
          type: string
          const: user
          description: Always `user`.
        id:
          type: string
          description: Stable identifier.
        username:
          type:
          - string
          - 'null'
          description: 'The unique handle the profile resolves on at `/@{username}`. Null

            for an account that has not finished signing up.

            '
        display_name:
          type:
          - string
          - 'null'
          description: The name shown next to their messages and bylines.
        avatar:
          type:
          - string
          - 'null'
          format: uri
          description: 'Profile picture. Commune falls back to a generated avatar when the

            person never set one, so this is rarely null in practice.

            '
    Me:
      type: object
      title: Me
      description: 'The account behind the credential that asked.


        Everything `User` carries, plus the two properties a public profile

        withholds. A separate schema rather than `User` with optional fields, so

        a public profile cannot carry an email address at all. A client routing

        on `object` gets `me` here and `user` there, so an absent email is never

        ambiguous between "not served" and "not set".


        The account''s own edges are not properties of it. Which teams it is on,

        what it subscribes to, what it saved and what it liked are four

        collections of their own: `GET /memberships`, `GET /subscriptions`,

        `GET /saved-articles` and `GET /liked-articles`.


        Not carried: theme and contrast, notification preferences, push

        subscriptions and read state.

        '
      additionalProperties: false
      required:
      - object
      - id
      properties:
        object:
          type: string
          const: me
          description: Always `me`.
        id:
          type: string
          description: 'Stable identifier. The same value `User.id` carries, so a client

            can match itself against an author or a message it has already

            read.

            '
        username:
          type:
          - string
          - 'null'
          description: 'The unique handle the public profile resolves on at

            `/@{username}`. Null for an account that has not finished signing

            up.

            '
        display_name:
          type:
          - string
          - 'null'
          description: 'The name shown next to their messages and bylines. Null when it was

            never set; a blank name is reported as null rather than as an empty

            string.

            '
        avatar:
          type:
          - string
          - 'null'
          format: uri
          description: 'Profile picture. Commune falls back to a generated avatar when the

            person never set one, so this is rarely null in practice.

            '
        email:
          type:
          - string
          - 'null'
          format: email
          description: 'The address Commune sends this account''s own mail to. Only ever

            this account''s own, and never returned for anybody else.

            '
        email_verified:
          type: boolean
          description: 'Whether the address above has been confirmed.

            '
        created_at:
          type:
          - string
          - 'null'
          format: date-time
          description: When the account was created.
    ErrorCode:
      type: string
      title: ErrorCode
      description: '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.


        Two of these share a status with a neighbour and exist because what a

        caller does next is different. `invalid_version` is a `400` that is

        never fixed by changing the request body. `not_commune_newsletter` is a

        `422` that is never fixed by changing the request at all: it says the

        newsletter''s articles are published somewhere else and mirrored into

        Commune afterwards, so Commune cannot write one. Its page at

        `https://usecommune.dev/errors/not_commune_newsletter`, like every

        code''s, is its `docs_url`, and it covers moving a newsletter onto

        Commune''s own publishing, which is the only thing that resolves it.

        '
      enum:
      - bad_request
      - invalid_version
      - unauthorized
      - forbidden
      - insufficient_scope
      - payment_required
      - not_found
      - conflict
      - unprocessable
      - not_commune_newsletter
      - rate_limited
      - internal_error
      - service_unavailable
  responses:
    Unauthorized:
      description: 'No credential was presented, or it is malformed, unknown, revoked or

        expired, or it is an access token minted for a different audience.


        Every one of these answers identically, down to the wording and the

        headers, so a refusal never confirms that a string was once real.

        '
      headers:
        WWW-Authenticate:
          description: 'The authentication scheme this API accepts, and where to find out

            how to get a credential for it. Always

            `Bearer realm="Commune API", resource_metadata="https://api.usecommune.com/.well-known/oauth-protected-resource"`.


            `resource_metadata` is the RFC 9728 pointer to this API''s protected

            resource metadata, which names the authorization server an OAuth

            client should send its user to. A client holding an API key can

            ignore it. The header carries no `error` parameter, not even

            `error="invalid_token"`, because it describes what this API accepts

            rather than what was wrong with the credential sent, and the

            reasons above are deliberately indistinguishable.


            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.

            '
          schema:
            type: string
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    NotFound:
      description: '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.

        '
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    RateLimited:
      description: 'Too many requests. Back off and retry after the interval named by the

        `Retry-After` response header.


        One of the budgets in `RateLimit-Policy` ran out, and the

        `RateLimit-*` headers on this response say which and when it resets.

        '
      headers:
        Retry-After:
          description: Seconds to wait before retrying.
          schema:
            type: integer
            minimum: 1
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Forbidden:
      description: 'The credential is valid but is not allowed to do this. Two codes answer

        with this status, and `error.code` says which.


        **`insufficient_scope`: it does not hold the permission.** The

        operation needs, say, `audience: read` on the newsletter addressed, and

        this credential holds less than that there. `allowed_values` carries

        the permission that was needed, and the message says what the

        credential does hold on that newsletter, because a credential granted

        the wrong family and a credential belonging to somebody whose standing

        on the team has narrowed look identical without it. The answer can

        differ per newsletter: the same credential may be allowed here and

        refused on the next one it reaches.


        The same code answers an operation that needs the **account

        permission** from a credential that does not carry it. That permission

        is about the person a credential belongs to rather than about any

        newsletter, so nothing granted on a newsletter adds up to it. It is

        granted on the credential itself, when a key is minted or when an

        authorization asks for `account:read`.


        And it answers a parameter the credential may send, but not with the

        value it sent: a filter a credential holding only `read` permissions

        may not use, or an `expand` path whose rows need a permission the

        operation does not. `param` names the parameter, and `allowed_values`

        carries what this credential may send instead, or is absent when it may

        send nothing there at all.


        **`forbidden`: it may not act here at all.** Either the credential does

        not reach the newsletter addressed, because it was never granted it or

        because the person it belongs to can no longer act on it, or it reaches

        no newsletter at all; `param` is `newsletter`, and `GET /newsletters`

        lists the ones it does reach. Or, on `DELETE /api-keys/{key}`, the

        credential named belongs to somebody else. Neither carries

        `allowed_values`, because there is no value to send instead.

        '
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    InternalError:
      description: Something failed inside Commune. The request may be retried.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    BadRequest:
      description: "The request was malformed, and the same request will fail the same way\nuntil it is changed. `param` names the parameter or header at fault\nwhen there is exactly one, and `allowed_values` lists what it accepts\nwhen that is a finite set. The code is `bad_request` for every case\nbelow except the last.\n\n* **A query parameter**: one the operation does not have, a value\n  outside its set, range or format (an unparseable cursor, an unknown\n  `expand` path or `fields` name, an identifier that is not a UUID),\n  or a required one left out, such as `q` on a search or `newsletter`\n  when the credential reaches more than one.\n* **The request body**: not JSON, not the shape the operation reads,\n  a property it does not write, or a value of the wrong type, length\n  or format. `param` is absent here, since the body is not a\n  parameter, and the message names the property.\n* **The `Idempotency-Key` header**, on an operation that changes\n  something: missing, or a value this API will not store.\n* **An unrecognised `Commune-Version`**, which answers with its own\n  code, `invalid_version`, because it is never fixed by changing the\n  body.\n"
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  parameters:
    CommuneVersion:
      name: Commune-Version
      in: header
      required: false
      description: 'The contract version this request is written against. Every version

        published so far is a release date (`YYYY-MM-DD`), which is why the

        examples look like one, but the value is an opaque identifier: match it

        against the versions this API publishes rather than parsing it, because

        a future one may not be only a date. An unknown value answers `400`

        with `invalid_version`.


        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.

        '
      schema:
        type: string
        minLength: 1
        examples:
        - '2026-08-26'
    Fields:
      name: fields
      in: query
      required: false
      description: '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`.

        '
      schema:
        type: string
      examples:
        trimmed:
          summary: Only the fields a link list needs
          value: title,slug,posted_at
    UserPath:
      name: user
      in: path
      required: true
      description: The user's `id` or their `username`, with or without a leading `@`.
      schema:
        type: string
      examples:
        byUsername:
          summary: By username
          value: '@ada'
  securitySchemes:
    oauth2:
      type: oauth2
      description: 'An OAuth access token, sent as `Authorization: Bearer <token>`. The

        walkthrough of the whole flow is at

        [usecommune.dev/use-cases/build-an-integration](https://usecommune.dev/use-cases/build-an-integration):

        discovery, registration, PKCE, the consent screen, the exchange, refresh

        and revocation.


        Ask for a family scope and the person picks which newsletter

        the token reaches; ask for `account:read` alone and it reaches no

        newsletter and reads only the account it belongs to.


        Each operation lists the scopes a token must carry to call it. An

        operation that lists none takes any token.


        Discover the URLs under `flows` at runtime from

        `GET /.well-known/oauth-authorization-server` rather than hardcoding

        them.

        '
      flows:
        authorizationCode:
          authorizationUrl: https://usecommune.com/api/oauth/authorize
          tokenUrl: https://usecommune.com/api/oauth/token
          refreshUrl: https://usecommune.com/api/oauth/token
          scopes:
            content:read: Read articles, threads and the rest of what a newsletter publishes.
            content:write: Create, edit and delete that content.
            audience:read: Read subscribers, tags and segments, including email addresses.
            audience:write: Add, tag and remove subscribers.
            insights:read: Read engagement, delivery and growth figures.
            insights:write: Write back an insight the newsletter owns.
            sending:read: Read sends, schedules and delivery outcomes.
            sending:write: Send an article, schedule one, and cancel a schedule.
            settings:read: Read a newsletter's configuration, senders and domains.
            settings:write: Change that configuration.
            webhooks:read: Read event destinations and their delivery history.
            webhooks:write: Create and remove event destinations.
            account:read: Read the person the credential belongs to, and nothing about any newsletter.
    apiKey:
      type: http
      scheme: bearer
      bearerFormat: Commune API key
      description: 'A Commune API key, sent as `Authorization: Bearer <key>`. A key is

        granted one or more newsletters and carries six permission families on

        each, every one of them `none`, `read` or `write`. An operation names

        the family and the level it needs.


        A key is minted by a creator in Commune''s settings: no flow, no consent

        screen, no expiry. That is the whole difference from `oauth2`. An

        operation that declares both accepts either credential, and what each

        may do is what it was granted.

        '
x-refined-from:
- usecommune-openapi.json
- usecommune-openapi.yml
x-deferred:
- resource: user_newsletters
  scope: partner
  reason: The newsletters a person owns or is on the team of. Public one profile at a time, but served in bulk it maps the network. A Partner API candidate.
- resource: user_subscriptions
  scope: partner
  reason: The newsletters a person subscribes to. The reader side of the same network graph, so it waits for a Partner API with it.
- resource: user_activity
  scope: public
  reason: The threads, highlights and articles sub resources of a public profile. Each filters a collection that has its own operation.
- resource: user_settings
  scope: reader
  reason: Theme, contrast and the rest of a person's account preferences. Personal, and of no use to an integration.
- resource: notification_preferences
  scope: reader
  reason: Personal account settings.
- resource: push_subscriptions
  scope: reader
  reason: Per device push endpoints. Credential shaped, and personal.
- resource: newsletter_settings
  scope: creator
  reason: Chat permissions, physical address and editor defaults. Split from the core object so the public schema stays frozen, and deferred with the write surface it exists to serve.
- resource: invitations
  scope: creator
  reason: Carries invitee email addresses and single use tokens, and is write shaped. This version of the API is reads only.
- resource: esp_connections
  scope: creator
  reason: Holds provider OAuth tokens and API keys. The connection becomes readable without them; the credentials never do.
- resource: esp_imports
  scope: creator
  reason: Import and migration runs are long running writes against an outside provider. This version of the API is reads only.
- resource: esp_share_audiences
  scope: creator
  reason: The provider side allow list that decides what Commune ingests. Import configuration, not a resource an integration reads.
- resource: rss_authors
  scope: creator
  reason: The feed author to team member mapping. Import configuration, wired to one provider path.
- resource: newsletter_exports
  scope: never
  reason: An admin only operation, not part of the creator catalog.
- resource: article_drafts
  scope: creator
  reason: Commune's editor stores its own document format, and pinning it in a public contract would stop the editor evolving.
- resource: article_preview
  scope: creator
  reason: Renders an article to final email HTML. Worth exposing, and it would pin the merge tag engine and the block system while both are still moving.
- resource: article_compliance
  scope: creator
  reason: The pre send gate as a readable resource, answering "would this send?" without sending. Its blocker vocabulary is still growing, and freezing it now would freeze the gate; the send and schedule operations report the same refusals when they refuse.
- resource: article_move
  scope: creator
  reason: Moving a draft from one newsletter to another. A credential reads one newsletter, so both ends of the move cannot be named by one of them.
- resource: article_thread
  scope: public
  reason: An article's discussion, reachable as a sub resource. It is a thread and has an operation already; a second path to it is navigation.
- resource: article_comments
  scope: public
  reason: Dead table. An article's discussion is its chat thread, so the count is on `article.stats.comments` and the comments themselves are that thread's messages.
- resource: article_saved_event
  scope: creator
  reason: 'A topic for an article being saved or unsaved. Built alongside `article.liked` and `article.read` and then withdrawn before it shipped, on the ground that it is not the same kind of change they are.


    Those two ride a disclosure that already exists. A credential holding `insights: read` reads `GET /newsletters/{newsletter}/events` today, which names which subscriber viewed or liked which article, so a topic carrying the same facts tells a creator nothing they could not already fetch. A save has no counterpart anywhere: no entry in `EngagementEventType`, no tally on `Article.stats`, nothing in the product that shows a creator who saved what, and a row only its owner can read. The topic would therefore have been the first thing ever to tell a creator anything about saves, and the thing it told them would be who.


    That is a decision about what readers are told is private, not a gap in the catalog, and it is deferred until that decision is made rather than shipped as a side effect of building its two neighbours. `article_saves` itself is untouched: `GET /saved-articles` still returns a person their own list.'
- resource: article_shared_event
  scope: creator
  reason: 'A topic for an article being shared. Refused rather than queued, because Commune does not observe a share and cannot: the product hands the reader to the operating system''s own share sheet, which reports nothing back, so the only shares that could ever be counted are the ones that begin with a button inside Commune, and even those end somewhere Commune cannot see.


    Read `share` in `EngagementEventType` as the record of an earlier attempt rather than as a signal that exists. The value is declared, the insight scores weight it, and the collection at `GET /newsletters/{newsletter}/events` will return one if it ever finds one. None of that makes a share observable, and a `share` row is not something any newsletter has.


    Publishing a topic for it would put a channel in this catalog that can never carry a message, which is worse than an absence: an absence is visible, and a silent channel reads as a quiet week.'
- resource: article_read_state
  scope: reader
  reason: 'Per reader read and unread state, as a resource a client reads back and writes. Written on every read in the product, so exposing it invites the polling loop `article_views` is deferred for, on the same hot path. The `article.read` topic is not this resource arriving early: it is pushed rather than polled, which is the whole of what the objection was about, it reports one crossing per reader per article rather than a state a client can re-read, and it cannot be written.'
- resource: article_views
  scope: creator
  reason: 'A write on every read in the product. Exposing it as a readable counter invites polling loops against a hot path. Still deferred after `article.read` landed, and not made redundant by it: that topic deliberately reports neither anonymous reads nor repeat visits, so it is not the counter and a consumer cannot build the counter out of it.'
- resource: thread_demotion
  scope: creator
  reason: Taking a thread back off the global feed. Promoting one is an operation; the reverse has no topic and no considered answer to what a consumer already told about it should do.
- resource: article_schedule_cancelled_event
  scope: creator
  reason: A topic for a cancelled schedule. Cancelling is an operation; the event is not, for the reason directly above, and the article's own status is the authority until there is an answer.
- resource: thread_read_state
  scope: reader
  reason: Per user last read timestamps and mutes. A user token could hold it; a row names a thread, and handing one back would let an app walk into a private thread whose other participants consented to nothing.
- resource: thread_participants
  scope: public
  reason: Who spoke in a thread. Derivable from the thread's messages, which have an operation of their own.
- resource: moderation
  scope: creator
  reason: No moderation queue exists yet. An auditable log is worth having before write access rather than after it.
- resource: posts
  scope: public
  reason: Retired. Posts were folded into newsletter scoped chat threads, so the resource is `threads`, and modelling `posts` would put a dead stack into a contract with outside consumers.
- resource: post_replies
  scope: public
  reason: Retired with posts. A reply is a `message` in a thread.
- resource: reposts
  scope: public
  reason: 'Retired with posts, and never wired up: the internal surface returns a hardcoded zero.'
- resource: community_member
  scope: public
  reason: One person's place in a community, addressable on its own. The person has an operation and the place carries nothing but a date, so a second path to it is navigation rather than a resource.
- resource: suppressions
  scope: creator
  reason: Bounces, complaints and unsubscribes as one list. The data is spread across two tables and there is no single surface to freeze yet.
- resource: audience_count
  scope: creator
  reason: Commune's subscriber records are a partial cache of an outside provider's list, so any total derived from them would misstate the audience. Ask the provider.
- resource: article_deliveries
  scope: creator
  reason: Per recipient send results, including bounces. Deferred until the send pipeline's own shape is stable enough to freeze.
- resource: article_send_stats
  scope: creator
  reason: Opens and clicks come from the sending provider on the provider's schedule, so a number read here would be stale in a way the contract could not describe.
- resource: send_links
  scope: creator
  reason: Click breakdown per destination URL. Clicks are recorded as events and never aggregated by destination, so the rollup does not exist.
- resource: deliverability
  scope: creator
  reason: Rolling bounce and complaint health against thresholds. Derivable, and nothing computes it today.
- resource: delivery_retries
  scope: never
  reason: Re-sending a send's failed recipients. Commune retries transient failures itself; what still fails is followed up by its team, because some of it may already have been delivered.


# --- truncated at 32 KB (37 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/usecommune/refs/heads/main/openapi/usecommune-users-api-openapi.yml