# Releases and reverting > Every publish is a numbered release, and reverting one drafts an ordinary change set that restores the state before it. A release is a [change set](/concepts/change-sets) that has been published. There is no separate resource: publishing assigns the change set a sequential `releaseNumber` within the project and that is what makes it a release. The change set already recorded what shipped, who shipped it, when, and — through each item's `baseRevisionId` — the state every resource was based on. That last field is what makes reverting possible. ```bash $ myna releases list # TITLE PUBLISHED BY ITEMS STATE 12 Autumn pricing 2026-07-24T09:12:04Z Deploy agent 3 live 11 Fix launch post typo 2026-07-23T16:40:11Z Ana Duarte 1 reverted by #12 ``` Release numbers are per project, assigned in publish order, and never reused. A change set that is closed instead of published never becomes a release. ## Reading releases - `GET /v1/projects/:project/releases` lists releases newest first, paginated on the release number. - `GET /v1/projects/:project/releases/:release` reads one. `:release` accepts either the release number (`12`) or the id of the change set that produced it (`chs_01...`), so a number copied out of a changelog works directly. Each release carries `itemCount`, a resolved `publishedBy` identity, and both directions of any revert relationship: `revertsReleaseNumber` when the release was itself a revert, and `revertedBy` when something has been drafted or published to undo it. To see field-by-field what a release changed, run `myna releases diff `, or read the [diff](/concepts/change-sets) of its change set directly — the release id *is* the change set id. Releases are also addressable from the Content API. `?at=` reads a collection as that release served it, which is what lets a repository [pin its content](/concepts/pinned-content) and build reproducibly. ## Reverting `POST /v1/projects/:project/releases/:release/revert` **does not publish anything.** It creates a new open change set that stages the inverse of every item in the release, and returns it. From there it is an ordinary change set: it validates, previews, accepts comments, runs [checks](/concepts/change-sets), and honors the project's approval and check gates exactly like a change written by hand. ```bash $ myna releases revert 11 Created change set chs_01J9... — Revert release #11 OP RESOURCE RESTORES update posts/launch rev_01J8... Not published. Review, then: myna changes publish chs_01J9... --confirm-publish ``` That is deliberate. A revert is usually authored under time pressure, which is exactly when the review path matters most — and it is why the endpoint requires `content:write` rather than `content:publish`. Whoever holds `content:publish` still decides whether the rollback ships. ### What the inverse of each operation is | The release did | The revert stages | Effect on publish | |---|---|---| | `create` (nothing was published before) | `unpublish` | Clears the published pointer; the entry and its revisions stay | | `update` | `update` with the pre-release data copied into a **new** revision | The old content returns as the current revision | | `unpublish` | `create` or `update` restoring `baseRevisionId` | The entry is published again | | `delete` | `create` or `update` restoring `baseRevisionId` | The entry is un-deleted and published again | | asset `delete` | asset restore | The asset returns to `published` | Restoring data copies it forward into a new [revision](/concepts/revisions) rather than repointing at the old one. Revisions are immutable, and history should show that someone reverted and when — not that an old revision quietly became current again. The new revision is stamped with the collection's **current** schema version, so if the schema has moved on in a way the old data no longer satisfies, the change set's validation and `schema` check say so before it can publish. ### What cannot be reverted The response separates what will be put back (`reverted`) from what was deliberately left alone (`skipped`), each skip carrying a `reason`: | Reason | Meaning | |---|---| | `superseded` | A later release changed this resource, so restoring it would undo that later work too | | `no_base_state` | The release recorded no revision preceding its operation, so there is nothing to restore | | `asset_not_revertible` | Assets carry no revision history, so publishing one cannot be undone — delete it instead | | `missing` | The resource no longer exists | `superseded` is the one that stops the whole call. If any resource in the release has moved on, the revert is refused with `CONFLICT` and the full list in the error details, rather than reverting the rest and silently skipping the interesting part. Pass `{"force": true}` (or `--force`) to take those resources back anyway, discarding the later work for them; the plan is then rebased on what is published right now. A revert is also refused when a revert of that release is already open or already published (`CONFLICT`, with the existing change set id). It is *not* refused because an entry is mid-edit elsewhere: an entry may be staged on several open change sets, so both proposals stand and whichever publishes second [rebases](/concepts/change-sets). ### Reverting a revert A revert is a normal release once it publishes, so it gets its own number and can itself be reverted. That is how you roll forward again after deciding the rollback was the mistake. ## Events and attribution Publishing emits `change_set.published`, which now carries `releaseNumber`. There is no `release.published` event, because that would be the same event twice. Publishing a change set that reverts a release additionally emits `release.reverted`, carrying the release that was undone and the release that undid it. See the [event reference](/webhooks/events). Audit records the revert's creation as `release.revert_created` and its publish as `release.reverted` instead of `change_set.published`, so the [activity log](/management-api/overview) distinguishes a rollback from an ordinary ship. ## Why this matters for agents Granting an agent `content:publish` used to be an irreversible decision: publish had no inverse, so the only recovery was to reconstruct the old content by hand, entry by entry. With a one-call revert that produces a reviewable change set, letting an agent ship is a decision you can take back — which is a very different risk to accept.