Docs
Search docs

Update a release

PATCH/api/repos/{owner}/{repo}/releases/{tag}

updateRepositoryRelease

Patches a release. An omitted field is unchanged; an explicit null clears `name` or `body`. The tag and the target commit are not patchable. Publishing a draft stamps its publication time once, and it is never moved afterwards. Claiming the rolling slot while another release holds it is 409, as is returning a published release to draft, or patching a release another writer published in the meantime. Callable with a CI job token of this repository from a trusted run.

Requires authentication using a bearer token or a session cookie — see tokens and scopes.

curl

curl -X PATCH 'https://neosource.dev/api/repos/OWNER/REPO/releases/TAG' \
  -H 'Authorization: Bearer $NEOSOURCE_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{}'

fetch

fetch("https://neosource.dev/api/repos/OWNER/REPO/releases/TAG", {
  method: "PATCH",
  headers: {
    Authorization: "Bearer $NEOSOURCE_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({}),
});

Path parameters

ownerrequired

Repository owner or organization slug.

string

reporequired

Repository name.

string

tagrequired

The release's git tag. Tags containing `/` must be percent-encoded (`release%2Fv1`). `latest` and `rolling` are reserved: they are resolver segments, so no release can carry either as its tag.

string

ownerrequired

string

reporequired

string

tagrequired

string

Request bodyrequired

application/json

UpdateReleaseRequest

object

Body of `PATCH /api/repos/{owner}/{repo}/releases/{tag}`. Every field is optional and an absent field means "no change". `name` and `body` are doubly optional on purpose: absent leaves the value alone, an explicit `null` clears it — a single optional could not express both, and release notes must be clearable. The tag and the target commit are not patchable: a release names a fixed point in history, and moving it would silently restate what shipped.

body

string | null

Absent = unchanged, `null` = cleared.

draft

boolean | null

Clearing this publishes the release. Setting it on an already-published release is a 409: un-publishing is a later, designed transition, not something this patch can do.

name

string | null

Absent = unchanged, `null` = cleared.

prerelease

boolean | null

rolling

boolean | null

Responses

200Release updated

application/json

ReleaseResponse

object

One release. `assets` is always present: every uploaded asset of the release, in filename order, each with its size and SHA-256 (empty when none has been uploaded).

assetsrequired

array

items

ReleaseAssetResponse

object

One downloadable file attached to a release.

asset_idrequired

string

content_sha256required

string

Lower-case hex SHA-256 of the file's bytes.

content_typerequired

string

download_url

string | null

Where to fetch the bytes. **Always `null` in Phase 1 and 2** — the public download surface, and the signed URL it mints, land in Phase 3.

filenamerequired

string

The name the file downloads as. Never a path: separators, a leading dot and control bytes are refused at creation.

example"neo-linux-x86_64.tar.gz"

size_bytesrequired

integerint64

uploaded_atrequired

integerint64

Unix millis.

uploaded_by_jobrequired

boolean

True when a CI job token published the asset rather than a person.

body

string | null

Release notes, markdown. Rendered by the client.

created_atrequired

integerint64

Unix millis.

created_by

string | null

Account that created the release, or `null` when a service token did.

draftrequired

boolean

A draft is visible only to a caller with repo Write.

name

string | null

prereleaserequired

boolean

published_at

integer | nullint64

Unix millis; `null` while the release is a draft. This is what `latest` orders by, and it is set once and never moved.

release_idrequired

string

rollingrequired

boolean

A mutable pointer — at most one per repository, and it never resolves as `latest`.

tagrequired

string

The git tag this release names, case-preserved (`v1.0-RC1` is not `v1.0-rc1`).

example"v1.2.0"

target_commitrequired

string

40-char hex of the commit the tag peeled to when the release was created. Fixed at creation: a later retag does not move it.

updated_atrequired

integerint64

Unix millis.

workflow_run_id

string | null

The CI run that published it, when a job did (Phase 2).

Standard errors

Bodies documented once for the whole API — see standard errors.

  • 400Bad Request — one of: invalid_input
  • 401Authentication required
  • 403Forbidden — one of: forbidden
  • 404Not Found — one of: not_found
  • 409Conflict — one of: already_exists, conflict, non_fast_forward
  • 423Locked — one of: busy
  • 429Rate limited — retry after the `Retry-After` header
  • 500Internal server error
  • 503Service temporarily unavailable / at capacity — retry after the `Retry-After` header
  • 504Gateway timeout — the request exceeded the server's handling budget