Altay
HTTP API

Plugins API

Read the plugin index, releases, and download URLs over HTTP.

The hub exposes a read-only HTTP API over the same data the website renders. It exists so launchers, package managers, dashboards, and CI jobs can find plugins without scraping the pages.

Base URL:

https://hub.getaltay.org

No key, no registration, no Authorization header. Every endpoint on this page answers anonymous requests and sends Access-Control-Allow-Origin: *, so browser code can call it directly.

Only published plugins appear. Anything still waiting for review, or rejected, is invisible here.

Conventions

Responses are JSON with Content-Type: application/json. Field names are snake_case. Timestamps are ISO 8601 in UTC. Every URL in a response is absolute, so you can follow it without rebuilding it yourself.

Responses carry Cache-Control: public, max-age=60. Cache them. Polling this API in a tight loop gets you a minute-old answer anyway.

Errors use the HTTP status plus a body:

{ "error": "Unknown sort \"bogus\". Use updated, downloads, stars, name." }
StatusMeaning
200Fine
400A query parameter was malformed. The message says which
404No plugin by that name
503The index is temporarily unreachable. Retry with a backoff

List plugins

GET /api/hub/plugins

Query parameters

ParameterTypeDefaultNotes
qstringnoneMatches name, description, and keywords. Case-insensitive, 64 characters max
apistringnoneKeep only plugins whose newest release targets this API version, exactly MAJOR.MINOR.PATCH
sortstringupdatedOne of updated, downloads, stars, name
limitinteger30Clamped to 100
offsetinteger0For paging

Response

{
  "total": 8,
  "limit": 2,
  "offset": 0,
  "plugins": [
    {
      "name": "Customies",
      "slug": "customies",
      "description": "A PocketMine-MP plugin that implements support for custom blocks, items and entities.",
      "keywords": ["custom", "items", "blocks"],
      "author": {
        "login": "xRookieFight",
        "url": "https://hub.getaltay.org/u/xRookieFight"
      },
      "repository": {
        "full_name": "altayofficial/Customies",
        "url": "https://github.com/altayofficial/Customies"
      },
      "latest": {
        "version": "1.5.0",
        "api": ["5.44.0"],
        "download_url": "https://hub.getaltay.org/download/customies/1.5.0"
      },
      "stats": { "downloads": 3, "stars": 0 },
      "icon_url": "https://hub.getaltay.org/icon/customies?v=1789852301219",
      "url": "https://hub.getaltay.org/p/customies",
      "created_at": "2026-09-19T21:11:41.219Z",
      "updated_at": "2026-09-19T21:11:41.219Z"
    }
  ]
}

total counts everything matching the filter, not the page you got back. Page with offset until offset + limit >= total.

latest is null for a plugin whose releases were all withdrawn. Check it before reading latest.version.

slug is the lowercase name and the id used in every other URL.

Get one plugin

GET /api/hub/plugins/{slug}

Same object as the list, plus the full description and every published release, newest first.

{
  "name": "Customies",
  "slug": "customies",
  "long_description": "# Customies\r\n\r\nA PocketMine-MP plugin that...",
  "releases": [
    {
      "version": "1.5.0",
      "api": ["5.44.0"],
      "ref": "v1.5.0",
      "size_bytes": 105061,
      "sha256": "f5dfe0d6276ad5b905ab84eecd5b901aab5905dacde79d6ba988479b57bc26d9",
      "download_url": "https://hub.getaltay.org/download/customies/1.5.0",
      "published_at": "2026-09-19T21:11:41.219Z"
    }
  ]
}

long_description is Markdown, and may contain HTML, written by the plugin author. The hub sanitises it against an allowlist before rendering, but the API hands you the original. Treat it as untrusted: escape it, or run it through your own sanitiser before putting it in a page.

ref is the git tag or commit the phar was built from, in the repository named by repository.

Download a release

GET /download/{slug}/{version}

Returns the phar with Content-Type: application/octet-stream. This is a normal file download, not JSON, and it increments the plugin's download counter. Follow redirects.

Verify what you fetched against the sha256 from the plugin endpoint before loading it on a server:

curl -fLo Customies.phar https://hub.getaltay.org/download/customies/1.5.0
sha256sum Customies.phar

Plugin icon

GET /icon/{slug}

Returns the author's icon as a PNG, at most 256x256. Plugins without one get a generated placeholder instead, served as image/svg+xml, so branch on Content-Type rather than assuming PNG.

Icons live at a stable URL, so append the plugin's updated_at as a cache buster when you want the current one. That is exactly what icon_url in the API response already does.

Examples

# Everything that runs on API 5.0.0, most downloaded first
curl -s "https://hub.getaltay.org/api/hub/plugins?api=5.0.0&sort=downloads" \
  | jq -r '.plugins[] | "\(.name) \(.latest.version) - \(.stats.downloads) downloads"'

Paging through everything

offset=0
while :; do
  page=$(curl -s "https://hub.getaltay.org/api/hub/plugins?limit=100&offset=$offset")
  echo "$page" | jq -r '.plugins[].slug'
  total=$(echo "$page" | jq '.total')
  offset=$((offset + 100))
  [ "$offset" -ge "$total" ] && break
done

Checking for updates

Store the version you installed and compare it against latest.version. Do not compare updated_at: it also moves when an author edits a description or swaps an icon, which is not a new build.

installed="1.4.0"
latest=$(curl -s https://hub.getaltay.org/api/hub/plugins/customies | jq -r '.latest.version')

[ "$installed" != "$latest" ] && echo "Customies $latest is out"

Limits and etiquette

There is no per-key quota, because there are no keys. Instead:

  • Responses are cacheable for 60 seconds. Respect that rather than polling every second.
  • limit above 100 is silently clamped to 100.
  • Identify your tool in the User-Agent header. If something misbehaves, that header is what tells us who to contact instead of blocking a whole network.
  • Mirror the index if you are serving many users. One fetch per minute for your service beats one fetch per user.

What is not here

Publishing, editing, starring, and commenting all require a signed-in GitHub session and are not reachable with a token. There is no write API. Submit plugins through the hub.

Nobody audits plugin source on your behalf. The hub builds each phar from a public repository at a tag the author picked, and a moderator checks the submission, but a passing review is not a promise that the code is safe. Read the source before running it on a server you care about.

On this page