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/publish

Publish 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

CodeErrorCause
401Invalid or missing API keyNo Authorization header, or key is invalid
403Missing required scopeAPI key does not have releases:write scope
400Missing required fieldsproductSlug, version, channel, or releases is missing
400Invalid channelChannel is not STABLE, BETA, or ALPHA
400Invalid release entryMissing platform, arch, or downloadUrl in a release entry
400Invalid platform/archPlatform or architecture value not recognized
400Invalid download URLURL is malformed or does not use HTTPS
409Invalid version formatVersion string is not valid semver
404Product not foundNo 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);
}