Docs
Search docs

Create a release

POST/api/repos/{owner}/{repo}/releases

createRepositoryRelease

Creates a release for a git tag that already exists in the repository. The tag is peeled to the commit it names and that commit is recorded; a tag that does not point at a commit is rejected rather than stored. A tag already released is 409, as is claiming the rolling slot when another release holds it. Callable with a CI job token of this repository from a trusted run (the release then records the workflow run rather than an account); a job token of a fork pull request run is 403.

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

curl

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

fetch

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

Path parameters

ownerrequired

Repository owner or organization slug.

string

reporequired

Repository name.

string

Request bodyrequired

application/json

CreateReleaseRequest

object

Body of `POST /api/repos/{owner}/{repo}/releases`. The tag must already exist in the repository and must peel to a commit; a tag that does not is refused here rather than stored.

body

string | null

Release notes, markdown.

draft

boolean

A draft is invisible to anyone without repo Write and has no publication instant until it is published.

name

string | null

prerelease

boolean

Excluded from `latest`.

rolling

boolean

Claim the repository's single rolling-release slot. Cannot be combined with `draft`, and never resolves as `latest`.

tagrequired

string

example"v1.2.0"

Responses

201Release created

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