Releases API
The Releases API allows CI pipelines to register new releases in Abydonian. This endpoint requires an API key with the releases:write scope.
POST/api/releases/publishPublish one or more platform-specific release artifacts for a product version. If a release for the same product/version/platform/arch already exists, it will be updated.
Authentication
This endpoint requires an API key with the releases:write scope. Pass it in the Authorization header:
Authorization: Bearer <your-api-key>
API keys can be created from the admin dashboard under Admin → API Keys.
Request Body
{
"productSlug": "nexadeck",
"version": "1.2.0",
"channel": "STABLE",
"releaseNotes": "## What's New\n- Feature X\n- Bug fix Y",
"releases": [
{
"platform": "MACOS",
"arch": "ARM64",
"downloadUrl": "https://releases.example.com/nexadeck/1.2.0/nexadeck-macos-arm64.tar.gz",
"signature": "dGhlIHNpZ25hdHVyZQ==",
"fileSize": 52428800
},
{
"platform": "WINDOWS",
"arch": "X64",
"downloadUrl": "https://releases.example.com/nexadeck/1.2.0/nexadeck-windows-x64.msi",
"signature": "dGhlIHNpZ25hdHVyZQ==",
"fileSize": 48234567
}
]
}TypeScript Interface
interface PublishRequest {
productSlug: string; // Required. Product identifier.
version: string; // Required. Semver version (e.g. "1.2.0"). Leading "v" is stripped.
channel: Channel; // Required. Release channel.
releaseNotes?: string; // Optional. Markdown release notes.
releases: ReleaseEntry[]; // Required. Non-empty array of platform releases.
}
interface ReleaseEntry {
platform: "WINDOWS" | "MACOS" | "LINUX"; // Required. Target platform.
arch: "X64" | "ARM64"; // Required. CPU architecture.
downloadUrl: string; // Required. HTTPS download URL.
signature?: string; // Optional. Signature for update verification.
fileSize?: number; // Optional. File size in bytes.
}
type Channel = "STABLE" | "BETA" | "ALPHA";Success Response (200)
{
"product": "NexaDeck",
"version": "1.2.0",
"channel": "STABLE",
"releases": [
{ "platform": "MACOS", "arch": "ARM64", "status": "created" },
{ "platform": "WINDOWS", "arch": "X64", "status": "updated" }
]
}Each release entry reports a status of "created" (new) or "updated" (replaced existing).
Error Responses
| Code | Error | Cause |
|---|---|---|
| 401 | Invalid or missing API key | No Authorization header, or key is invalid |
| 403 | Missing required scope | API key does not have releases:write scope |
| 400 | Missing required fields | productSlug, version, channel, or releases is missing |
| 400 | Invalid channel | Channel is not STABLE, BETA, or ALPHA |
| 400 | Invalid release entry | Missing platform, arch, or downloadUrl in a release entry |
| 400 | Invalid platform/arch | Platform or architecture value not recognized |
| 400 | Invalid download URL | URL is malformed or does not use HTTPS |
| 409 | Invalid version format | Version string is not valid semver |
| 404 | Product not found | No product matches the given slug |
Example (curl)
curl -X POST https://your-domain.com/api/releases/publish \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <your-api-key>" \
-d '{
"productSlug": "nexadeck",
"version": "1.2.0",
"channel": "STABLE",
"releaseNotes": "Bug fixes and performance improvements",
"releases": [
{
"platform": "MACOS",
"arch": "ARM64",
"downloadUrl": "https://releases.example.com/nexadeck-1.2.0-macos-arm64.tar.gz",
"signature": "dGhlIHNpZ25hdHVyZQ=="
}
]
}'Example (GitHub Actions)
# .github/workflows/release.yml
- name: Publish to Abydonian
run: |
curl -X POST https://your-domain.com/api/releases/publish \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${{ secrets.ABYDONIAN_API_KEY }}" \
-d '{
"productSlug": "nexadeck",
"version": "'${{ github.ref_name }}'",
"channel": "STABLE",
"releaseNotes": "Release ${{ github.ref_name }}",
"releases": [
{
"platform": "MACOS",
"arch": "ARM64",
"downloadUrl": "https://releases.example.com/nexadeck-'${{ github.ref_name }}'-macos-arm64.tar.gz",
"signature": "'${{ steps.sign.outputs.signature }}'"
}
]
}'Example (TypeScript)
const response = await fetch("https://your-domain.com/api/releases/publish", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer <your-api-key>",
},
body: JSON.stringify({
productSlug: "nexadeck",
version: "1.2.0",
channel: "STABLE",
releaseNotes: "Bug fixes and performance improvements",
releases: [
{
platform: "MACOS",
arch: "ARM64",
downloadUrl: "https://releases.example.com/nexadeck-1.2.0-macos-arm64.tar.gz",
signature: signatureBase64,
},
],
}),
});
const data = await response.json();
if (response.ok) {
console.log("Published:", data.version);
data.releases.forEach((r) =>
console.log(` ${r.platform}/${r.arch}: ${r.status}`)
);
} else {
console.error("Publish failed:", data.error);
}Example (Rust)
use reqwest::Client;
use serde::Deserialize;
#[derive(Deserialize)]
struct PublishResponse {
product: String,
version: String,
channel: String,
releases: Vec<ReleaseResult>,
}
#[derive(Deserialize)]
struct ReleaseResult {
platform: String,
arch: String,
status: String,
}
let client = Client::new();
let res = client
.post("https://your-domain.com/api/releases/publish")
.header("Authorization", "Bearer <your-api-key>")
.json(&serde_json::json!({
"productSlug": "nexadeck",
"version": "1.2.0",
"channel": "STABLE",
"releaseNotes": "Bug fixes and improvements",
"releases": [{
"platform": "MACOS",
"arch": "ARM64",
"downloadUrl": "https://releases.example.com/nexadeck-1.2.0-macos-arm64.tar.gz",
"signature": "<base64-signature>"
}]
}))
.send()
.await?;
if res.status().is_success() {
let data: PublishResponse = res.json().await?;
println!("Published: {}", data.version);
for r in &data.releases {
println!(" {}/{}: {}", r.platform, r.arch, r.status);
}
} else {
let body = res.text().await?;
eprintln!("Publish failed: {}", body);
}