# Retrieve an API key **GET /api-keys/{key}** One of this newsletter's API keys, by `id`, revoked or not. Needs `settings: write`. Carries no secret: Commune stores none to return. A key belonging to another newsletter answers `404`, whichever newsletter the caller names. ## 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 - **key** (string(uuid)) The API key's `id`. Never the secret itself: Commune does not store one and could not look a key up by one, and a credential that travelled in a URL would end up in access logs and browser history. ### 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 key. #### Body: application/json (object) - **object** (string) Always `api_key`. - **id** (string(uuid)) Stable identifier. This is what addresses the key in a path; the secret never appears in a URL and never will. - **newsletter** (object | null) The newsletter this projection describes: the one whose grant `permissions` was read from. A key may hold several, so this is not "the key's newsletter" but the one it is being listed under. A `Ref` unless `newsletter` is named in `?expand=`. - **name** (string) What the creator called it when they minted it. Not unique: two keys called `staging` are a creator's problem and not an error. - **key_prefix** (string) The leading fifteen characters of the secret, which is all of it that Commune keeps. Enough to recognise which key an integration is configured with, and short enough that it is not itself usable. - **permissions** (object) What this key was granted **on the newsletter above**. Six families, each `none`, `read` or `write`. A key can be granted more than one newsletter and can carry different permissions on each, so this is the grant for the newsletter this row is being served under and not a property of the key on its own. An operation this key does not hold the family for answers `403` naming the family and the level it needed. - **pinned_version** (string(date)) The contract version a request made with this key resolves to when it sends no `Commune-Version` header. Stamped when the key was minted, so a newer contract shipping does not move an existing integration. - **live** (boolean) Whether a request made with this key right now would be authenticated. False once it has been revoked, and false once it has expired. Computed against Commune's own clock with the same test the authentication path applies, so it is a more reliable answer than comparing `expires_at` to a client's clock. - **revoked** (boolean) Whether somebody turned this key off. A revoked key never becomes live again: nothing in this API can revive one. - **revoked_at** (string(date-time) | null) When it was turned off, or null while it is not. A second revocation does not move it. - **expires_at** (string(date-time) | null) When the key stops working on its own, or null for one that never does. Expiry and revocation are separate: an expired key has not been revoked and reports `revoked` false. - **last_used_at** (string(date-time) | null) The last time a request was authenticated with this key, or null if none ever has been. Written at most once a minute, so it is accurate to the minute rather than to the request, which is the resolution the question behind it needs: is anything still calling with this, and can it be revoked. - **self** (boolean) True for the one key the current request was made with, and false for every other. A caller holds a secret rather than an id, so this is the only way it can tell which of these rows is itself, which is what it needs before revoking any of them. False on every row for a request made with an OAuth access token, since no key is that credential. - **created_by** (object | null) The team member who minted it, or null if that account has since been removed. The key belongs to the newsletter rather than to the person, so it keeps working either way. A `Ref` unless `created_by` is named in `?expand=`. - **created_at** (string(date-time)) When the key was minted. ### 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)