List a thread's messages Run in API Explorer

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 /threads/{thread}/messages

Every reply in the thread, oldest first, flattened. A reply to a reply is in the same page as its parent and points at it through parent, so a client rebuilds the two level tree without a second request.

Deleted messages are omitted rather than tombstoned.

Headers

  • Commune-Version string

    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.

    Minimum length is 1.

Path parameters

  • thread string Required

    The thread's id (a UUID) or its short_id. A thread that Commune opened under an article has no short_id, because it is addressed on the web through the article's own permalink, so use its id.

Query parameters

  • cursor string

    The pagination.next_cursor value from the previous page. Omit it to read the first page. A cursor is opaque, is only valid for the same operation with the same filters, and is not a durable identifier.

    Maximum length is 512.

  • limit integer

    How many items to return in this page. This is a page size, not an offset. Fewer items than requested may come back and that does not mean the collection is exhausted, only an absent next_cursor does.

    Minimum value is 1, maximum value is 100. Default value is 20.

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

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

Responses

  • 200 application/json

    A page of messages, oldest first.

    Hide response attributes Show response attributes object
    • object string Required

      Always list, so a response is self describing.

      Value is list.

    • pagination object Required

      Cursor pagination state. Commune never exposes an offset or a page number: a collection is a moving window, and an offset silently skips or repeats items when the window shifts between two requests.

      Additional properties are NOT allowed.

      Hide pagination attributes Show pagination attributes object
      • has_more boolean Required

        Whether another page exists. When false, next_cursor is null.

      • next_cursor string | null Required

        Pass this back as ?cursor= to read the next page. null on the last page. Opaque, and valid only for the same operation with the same filters.

    • data array[object]

      Every reply in this thread, oldest first and flattened rather than nested: a reply to a reply sits in the same page as its parent and points at it through parent, so a client rebuilds the two level tree without a second request and depth says which level an entry is on. Deleted replies are omitted rather than tombstoned. Every entry inherits the thread's own visibility and none carries one of its own.

      Hide data attributes Show data attributes object

      A reply inside a thread. Commune allows two levels: a reply to the thread, and a reply to that reply. A deleted message is omitted from every read rather than returned as a tombstone.

      • object string Required

        Always message.

        Value is message.

      • id string(uuid) Required

        Stable identifier.

      • short_id string | null

        Eight character base62 identifier used by the message's permalink. Null for a message old enough that none was assigned.

      • thread object | null Required

        The thread this reply belongs to. A Ref unless thread is named in ?expand=.

        One of:
      • newsletter object | null Required

        The community the thread lives in, denormalised so a client does not have to walk up to it. A Ref unless newsletter is named in ?expand=.

        One of:
      • author object | null

        Who wrote the reply. A Ref unless author is named in ?expand=.

        Any of:
      • parent object | null

        The message this one replies to, or null when it replies to the thread itself. A Ref unless parent is named in ?expand=.

        One of:

        A reply inside a thread. Commune allows two levels: a reply to the thread, and a reply to that reply. A deleted message is omitted from every read rather than returned as a tombstone.

        Additional properties are NOT allowed.

      • quoted object | null

        The message this one quotes, when the author quoted rather than replied. null otherwise. A Ref unless quoted is named in ?expand=.

        One of:

        A reply inside a thread. Commune allows two levels: a reply to the thread, and a reply to that reply. A deleted message is omitted from every read rather than returned as a tombstone.

        Additional properties are NOT allowed.

      • depth integer Required

        1 for a reply to the thread, 2 for a reply to a reply. Commune does not nest deeper, so a client can render the tree with a fixed two level layout.

        Minimum value is 1, maximum value is 2.

      • content string Required

        The message body as HTML. Treat it as untrusted markup and render it in a sandboxed context.

      • media array[object]

        Attachments on the message.

        Hide media attributes Show media attributes object

        An image or file attached to a thread or a message.

        • url string(uri) Required

          Where the attachment is served from.

        • type string | null

          The attachment's media type when Commune recorded one, for example image/png. Null for an attachment old enough that none was recorded.

        • thumbnail string(uri) | null

          A smaller rendition, when one was generated.

      • highlight object | null

        The passage of an article this reply is anchored to, when the reader wrote it from a highlight. null otherwise. A Ref unless highlight is named in ?expand=.

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

        When the reply was written.

      • updated_at string(date-time)

        When the row last changed for any reason.

      • edited_at string(date-time) | null

        When the author last edited the text or attachments. Not touched by reactions or other side effects, so it is a faithful edited marker.

      • reactions array[object]

        The emoji reactions on this message, one entry per distinct emoji, most used first. Empty when there are none. Each entry carries users, who left it, only when reactions is named in ?expand=.

        Hide reactions attributes Show reactions attributes object

        One emoji on a message, and how many people left it.

        • emoji string Required

          The emoji itself, as the character rather than a shortcode.

        • count integer Required

          How many people left this emoji on the message.

          Minimum value is 1.

        • users array[object]

          Who left it, in the order they did. Present only when reactions is named in ?expand=.

          Hide users attributes Show users attributes object | null

          An unexpanded relationship. Ask for the relationship in ?expand= to get the full object in its place.

          • object string Required

            The type of the referenced resource.

          • id string Required

            The referenced resource's id, in whatever form that resource's own schema declares. Most are UUIDs; a Ref whose object is user carries an account identifier, which is an opaque string and not a UUID. Compare it for equality and pass it back; do not parse it.

  • 400 application/json

    The request was malformed, and the same request will fail the same way until it is changed. param names the parameter or header at fault when there is exactly one, and allowed_values lists what it accepts when that is a finite set. The code is bad_request for every case below except the last.

    • A query parameter: one the operation does not have, a value outside its set, range or format (an unparseable cursor, an unknown expand path or fields name, an identifier that is not a UUID), or a required one left out, such as q on a search or newsletter when the credential reaches more than one.
    • The request body: not JSON, not the shape the operation reads, a property it does not write, or a value of the wrong type, length or format. param is absent here, since the body is not a parameter, and the message names the property.
    • The Idempotency-Key header, on an operation that changes something: missing, or a value this API will not store.
    • An unrecognised Commune-Version, which answers with its own code, invalid_version, because it is never fixed by changing the body.
    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.

        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.

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

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

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

  • 401 application/json

    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.

    Hide headers attribute Show headers attribute
    • WWW-Authenticate string

      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.

    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.

        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.

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

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

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

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

        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.

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

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

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

  • 429 application/json

    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.

    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.

        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.

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

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

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

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

        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.

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

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

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

GET /threads/{thread}/messages
curl https://api.usecommune.com/threads/b3Xn8kTw/messages \
  -H "Authorization: Bearer $COMMUNE_API_KEY" \
  -H "Commune-Version: 2026-08-26"
Response examples (200)
{
  "object": "list",
  "data": [
    {
      "object": "message",
      "id": "c2d3e4f5-0617-4829-93a4-b5c6d7e8f90a",
      "short_id": "p7w2rd",
      "thread": {
        "object": "thread",
        "id": "b1c2d3e4-f506-4718-8293-a4b5c6d7e8f9"
      },
      "newsletter": {
        "object": "newsletter",
        "id": "7d3f1c02-58a1-4a4e-9a0b-2f6d1c9e4411"
      },
      "author": {
        "object": "user",
        "id": "usr_9Lp3Zr7tYb"
      },
      "parent": null,
      "quoted": null,
      "depth": 1,
      "content": "Same here. We ended up capping thread depth for that reason.",
      "media": [],
      "reactions": [
        {
          "emoji": "👍",
          "count": 3
        },
        {
          "emoji": "🎉",
          "count": 1
        }
      ],
      "highlight": null,
      "created_at": "2026-08-26T15:14:02Z",
      "updated_at": "2026-08-26T15:14:02Z",
      "edited_at": null
    },
    {
      "object": "message",
      "id": "d3e4f506-1728-493a-a4b5-c6d7e8f9001b",
      "short_id": "r2k9vt",
      "thread": {
        "object": "thread",
        "id": "b1c2d3e4-f506-4718-8293-a4b5c6d7e8f9"
      },
      "newsletter": {
        "object": "newsletter",
        "id": "7d3f1c02-58a1-4a4e-9a0b-2f6d1c9e4411"
      },
      "author": {
        "object": "user",
        "id": "usr_2Nf8Kq1pWc"
      },
      "parent": {
        "object": "message",
        "id": "c2d3e4f5-0617-4829-93a4-b5c6d7e8f90a"
      },
      "quoted": null,
      "depth": 2,
      "content": "What depth did you settle on? We are still arguing about three.",
      "media": [],
      "reactions": [],
      "highlight": null,
      "created_at": "2026-08-26T15:31:48Z",
      "updated_at": "2026-08-26T15:31:48Z",
      "edited_at": null
    }
  ],
  "pagination": {
    "has_more": false,
    "next_cursor": null
  }
}
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.dev/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.dev/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.dev/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.dev/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.dev/errors/not_found"
  }
}