# Retrieve a chat room **GET /rooms/{room}** Read one chat room by `id`, including whether its creator has hidden it (`hidden`). Needs `audience: read`, on a newsletter the credential reaches. Live rooms only, which is where this differs from `GET /tags/{tag}`: that operation resolves a retired tag on purpose, because a client rendering the audience of an article sent last year holds the id and needs the name. A room that was removed, or whose tag was retired, is not a room, and answers `404` here. Read it as a tag instead. A hidden room does resolve, carrying `hidden: true`. ## Servers - Production. There is no separate sandbox host. : https://api.usecommune.com (Production. There is no separate sandbox host. ) ## Authentication methods - Api key - Oauth2 ## Parameters ### 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. ### Path parameters - **room** (string(uuid)) The room's `id`, which is the `id` of the tag the room is. A room has no short id. ### 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`. - **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 The room. #### Body: application/json (object) - **object** (string) Always `room`. - **id** (string(uuid)) Stable identifier, and the same value as the room's `tag.id`: they are one row. - **newsletter** (object | null) The newsletter the room belongs to. A room never spans newsletters. A `Ref` unless `newsletter` is named in `?expand=`. - **name** (string) What the room is called, which is the tag's name. The people in the room read this, which is the whole reason a tag has to be marked as a room before a conversation can be scoped to it: a tag is otherwise internal ("Churned", "Refunded"). - **slug** (string) The room's name as a URL carries it, which is what `?room=` holds in Commune's chat: `founding-members`. Derived from the name when the room's tag is created, and **not derived again when the room is renamed**, so a link to a room survives it being renamed and a slug can therefore disagree with the current `name`. Two names that reduce to the same slug get a numeric suffix (`founding-members-2`). Unique among the newsletter's live tags, not across Commune: two newsletters can both have `founding-members`. Resolve a room by its `id`, which is what every operation here takes; this is for building a link a person will read. **Added on `2026-10-14`, after this contract was released.** See Removals in the API description, and this schema's own description. - **description** (string | null) What the room is for, in the creator's own words, or `null` when they have not written one. A room's `name` has to fit on a chip, so it is two or three words ("Founding members"). This is the sentence or two that says who the room is for and what it is for, and the room's members read it in the room header in Commune's chat, beside the name. It is published to exactly the people `name` is published to, so it is a label and not a permission: who may read a conversation in a room is decided by the tag the room is, and nothing consults this field. Creator-written free text, capped at 280 characters, with no markup of any kind: Commune renders it as a text node, and a client should do the same rather than running it through a Markdown or HTML renderer. `null` is the only way "no description" is spelled. An empty string is not a value this field takes, in a response or in Commune's own write path. Read only here. Descriptions are written in Commune, in Dashboard > Chat. **Added on `2026-10-14`, after this contract was released.** See Removals in the API description, and this schema's own description. - **known_member_count** (integer) How many currently subscribed people Commune knows of who are in this room AND have a Commune account. Not the same count as the tag's `known_subscriber_count`, and the difference is the point: a tag is a slice of the audience and what is sent to it reaches an address, while a room is a place people talk and opening one requires an account. A subscriber Commune knows only as an email address (an import from another provider, a CSV) can hold the room's tag, receives what is sent to it, and can read nothing in the room until they sign up. So this is the number of people who can actually open the room, and it is at most the tag's `known_subscriber_count`. Counted at read time from Commune's own record of the audience, which for a newsletter connected to an outside provider is a partial cache of that provider's list. Named `known_` for that reason: a floor, never the room's true size. **Narrowed on `2026-10-15`.** The value used to count every subscribed holder, which described rooms with members who could never open them. The field's name, type and presence are unchanged, so this is not a schema change; a client that stored the number will read a smaller one. - **hidden** (boolean) `true` when the creator has withdrawn the room. A hidden room is not a removed one. While it is hidden the tag grants visibility to nobody: the room's conversations, **and any article already sent to the same tag**, are readable by the newsletter's team and by nobody else, and the room is not browsable in chat. Every member still holds the tag and nothing is deleted, so unhiding restores all of it. The article half is the surprising one and it is intended: one audience rule gates everything a tag is the audience of, and a flag that withdrew conversations while leaving sent issues readable would be two answers to one question. Read only here, like the rest of this resource. Hiding a room is done in Commune. - **tag** (object | null) - **created_at** (string(date-time)) When the tag behind the room was created, which is not necessarily when it became a room: a creator can turn a segment they already had into a room, and that is the common way to make one. ### 400 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. #### Body: application/json (object) - **error** (object) ### 401 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** (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. #### Body: application/json (object) - **error** (object) ### 403 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. #### Body: application/json (object) - **error** (object) ### 404 No such resource, or the key is not allowed to know that it exists. Commune answers `404` rather than `403` where distinguishing the two would leak the existence of private content. #### Body: application/json (object) - **error** (object) ### 429 Too many requests. Back off and retry after the interval named by the `Retry-After` response header. 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** (integer) Seconds to wait before retrying. #### Body: application/json (object) - **error** (object) ### 500 Something failed inside Commune. The request may be retried. #### Body: application/json (object) - **error** (object) [Powered by Bump.sh](https://bump.sh)