Read-only JSON API for listing Nukkit-family plugins and resolving installable jar URLs.
A read-only JSON API over the NukkitHub plugin index, shaped after the
Modrinth API v2. It exists so that tools —
server-panel installers, bots, package managers — can list Nukkit-family
plugins and fetch installable jar URLs. The /v2/ prefix deliberately
matches Modrinth's own: point a Modrinth client that accepts a custom base
URL at {api_base} and its /v2/… requests resolve here directly (see
Pointing Modrinth tooling at NukkitHub).
https://plugins.nukkit-mot.com/api ({api_base} below) —
same origin as the main sitemeta.json re-declares the canonical api_base — prefer it over hard-coded
URLs for long-term addressingAccess-Control-Allow-Origin: *)/v2/ is the first published version; changes within it are additive-only,
breaking changes will go to /v3/# 1. list installable plugins for one runtime
curl -s https://plugins.nukkit-mot.com/api/v2/search/nkmot.json \
| jq '.hits[] | {project_id, title, latest_version}'
# 2. resolve the jar URL for a chosen plugin
curl -s https://plugins.nukkit-mot.com/api/v2/project/SoBadFish/BedWar/latest.json \
| jq '.files[] | select(.primary) | .url'
For richer queries use dynamic search (/v2/search below); to work from a
full list, pull search.json (a few hundred KiB) and filter client-side.
Poll version.json for updates rather than latest.json
if you also want to see prereleases; poll at most every 15 minutes — the index
rebuilds hourly and deploys trail it.
| Endpoint | Description |
|---|---|
GET {api_base}/v2/search |
Dynamic search: query/facets/index/offset/limit |
GET {api_base}/v2/projects?ids=["owner/name",…] |
Batch project lookup, ≤ 20 ids; slugs and %2F-encoded ids accepted |
GET {api_base}/v2/project/{id} |
Project detail (bare object); {id} is owner/name, a unique single-segment slug, or %2F-encoded |
GET {api_base}/v2/project/{id}/version |
Version list (bare array); optional ?loaders= / ?game_versions= JSON-array filters |
GET {api_base}/v2/project/{id}/version/{version_number} |
Single version with files |
GET {api_base}/v2/version/{version_id} |
Single version by globally-unique version id ({project_id}@{version_number}) |
GET {api_base}/v2/versions?ids=["{version_id}",…] |
Batch version lookup, ≤ 20 ids; unresolvable ids are skipped |
GET {api_base}/v2/version_file/{hash} |
Version owning the file with this hash (lowercase hex; the index carries sha256) — 404 when unknown |
GET {api_base}/v2/project/{id}/latest |
Latest installable version: first non-prerelease, else newest (404 if the plugin has no releases) |
GET {api_base}/v2/tag/{name} |
Tags: loader / category / game_version |
GET {api_base}/v2/meta |
Index metadata, counts, canonical api_base |
GET {api_base}/v2/search.json |
Every plugin in one Modrinth-style search response (a parameterless /v2/search), sorted by date_modified descending |
GET {api_base}/v2/search/{loader}.json |
Same shape, pre-filtered by loader (nkmot, pnx, lumi, nkx) |
GET {api_base}/v2/project/{owner}/{name}.json |
Project detail with all metadata; versions lists version ids newest-first |
GET {api_base}/v2/project/{owner}/{name}/version.json |
Version list (bare array) |
GET {api_base}/v2/project/{owner}/{name}/version/{version_number}.json |
Single version with files |
GET {api_base}/v2/version_file/{hash}.json |
Static form of the hash lookup (one file per indexed sha256) |
GET {api_base}/v2/project/{owner}/{name}/latest.json |
Latest installable version |
GET {api_base}/v2/tag/{loader|category|game_version}.json |
Tag files |
GET {api_base}/v2/meta.json |
Index metadata, counts, canonical api_base |
{owner}/{name} is the GitHub-derived id (multi-module repositories use
owner/repo--module-suffix). Plugin ids with no indexed downloads (no GitHub
Release and no CI build) simply have an empty versions array and no
latest. Visiting {api_base}/ serves a small human-readable endpoint index
page.
Version numbers come from GitHub Release tags. Additionally, repositories that
build on motci.cn carry one snapshot version numbered
ci-{build} (version_type: "beta") whose files point at the Jenkins build
artifacts. latest still prefers a non-prerelease release when one exists.
Version numbers are only unique within a project, so the globally-unique
version id is the composite {project_id}@{version_number} — for example
SoBadFish/BedWar@v2.2.3. versions arrays carry these ids, and
/v2/version/{version_id} and /v2/versions?ids=[…] accept them (URL-encode
the / and the @ when an id goes into a path, e.g.
/v2/version/SoBadFish%2FBedWar%40v2.2.3).
GET {api_base}/v2/search takes the Modrinth v2 parameter syntax; called
with no parameters it returns exactly what search.json contains.
| Parameter | Default | Notes |
|---|---|---|
query |
(none) | Case-insensitive substring match over title + description (max 256 chars) |
facets |
(none) | JSON array of arrays — outer AND, inner OR. Keys: loaders, categories, versions (matches version ids), game_versions, project_type, license, author, title. Unknown keys match nothing (forward compatible). Max 16 groups x 16 terms. |
index |
relevance |
relevance (title prefix > title > description, then recency), updated, newest, stars, downloads, follows — the last two are approximated by stars until download telemetry exists |
offset |
0 |
|
limit |
20 |
Max 100 |
curl -s 'https://plugins.nukkit-mot.com/api/v2/search?facets=%5B%5B%22loaders%3Ankmot%22%5D%5D&query=economy&index=stars&limit=10'
Malformed facets returns 400 with an explanatory message. Responses carry
Access-Control-Allow-Origin: *, Cache-Control: public, max-age=300 and
X-Content-Type-Options: nosniff. GET and HEAD are supported; other methods
return 405.
Modrinth SDKs and clients that let you override the API base URL can talk to
NukkitHub directly: configure the base as https://plugins.nukkit-mot.com/api
so the client's /v2/… request paths land on our routes.
Works out of the box:
GET /v2/search with the full parameter syntax, including facetsGET /v2/projects?ids=[…] batch lookup (max 20 ids per call)GET /v2/project/{id} — owner/name ids, including %2F-encoded forms,
plus single-segment slugs (when the slug identifies exactly one plugin)GET /v2/project/{id}/version with ?loaders= / ?game_versions= filtersGET /v2/project/{id}/version/{version_number} and /latestGET /v2/version/{version_id} and GET /v2/versions?ids=[…] (batch, max 20
ids) — version ids are {project_id}@{version_number}GET /v2/version_file/{sha256} — reverse hash lookup, returns the version
whose files include this sha256GET /v2/tag/*Does not work, by design:
The field names and structure follow Modrinth v2. Quick orientation:
// search.json (top level)
{ "offset": 0, "limit": 359, "total_hits": 359, "hits": [ /* SearchHit */ ] }
// SearchHit (essentials only shown)
{
"project_id": "SoBadFish/BedWar", // "owner/name" — two segments, not base62
"project_type": "plugin",
"slug": "BedWar",
"author": "SoBadFish",
"title": "BedWar",
"description": "…",
"categories": ["game-mechanics"],
"loaders": ["nkx", "nkmot"], // runtime targets
"game_versions": ["1.0.11"], // server API versions
"versions": ["SoBadFish/BedWar@v2.2.3", "…"], // version ids ({project_id}@{version_number})
"latest_version": "v2.2.3",
"downloads": 0, // reserved, currently always 0
"icon_url": "…",
"date_created": "2024-08-27T04:01:38.000Z",
"date_modified": "2024-08-27T04:01:38.000Z",
"license": { "id": "MIT", "name": "MIT License", "url": "…" },
"stars": 12 // NukkitHub extension (GitHub stars)
}
// Version (element of version.json, or the /version/{n} and /latest bodies)
{
"id": "SoBadFish/BedWar@v2.2.3", // globally-unique version id
"project_id": "SoBadFish/BedWar",
"name": "2023/08/27 v2.2.3 更新",
"version_number": "v2.2.3",
"changelog": "…",
"version_type": "release", // "beta" for GitHub prereleases and ci-* snapshots
"date_published": "2024-08-27T04:01:38.000Z",
"downloads": 0,
"loaders": ["nkx", "nkmot"], // inherited from the project
"game_versions": ["1.0.11"],
"files": [
{
"url": "https://github.com/…/BedWar_v2.2.3.jar",
"filename": "BedWar_v2.2.3.jar",
"primary": true, // the jar a panel should install
"size": 437018,
"hashes": { // checksums by algorithm
"sha256": "9f86d0…" // {} when the source has no digest
}
}
],
"dependencies": [
{ "plugin_id": "EconomyAPI", "version_range": "", "dependency_type": "optional" }
]
}
Project detail objects additionally carry body (full README markdown),
gallery, authors, source_url, issues_url, wiki_url, discord_url,
and the NukkitHub extensions (stars, authors, homepage_url,
api_version, server_version, dependencies).
The full type contract lives in
src/types/api-v2.d.ts.
This is a shape-compatible subset with Modrinth-style addressing, not a drop-in replacement. What remains different:
| Modrinth capability | Here | Why |
|---|---|---|
GET /v2/search?query=&facets=&offset=&limit= |
/v2/search with the same syntax; facet keys and sort values are a subset (see the parameter table) |
The index only carries those dimensions |
GET /v2/projects?ids=[…] |
Supported (≤ 20 ids) | — |
GET /v2/versions?ids=[…], GET /v2/version/{id} |
Supported — version ids are {project_id}@{version_number} composites (version numbers alone are only unique per project) |
Identity is the GitHub release |
GET /v2/version_file/{hash} |
Supported for sha256 (the only hash the index carries); sha1/sha512-length hashes are accepted but always 404 |
Only GitHub exposes file digests, sha256 only |
featured flag / ?featured filter |
latest route instead |
No featured concept in the source data |
follows / followers |
Extension field stars instead |
No telemetry |
| 8-char base62 ids, single-segment slugs | owner/name two-segment ids; single-segment slugs resolve when unique |
Identity is the GitHub repository |
| Auth, teams, notifications, reports, payouts | Not present | Read-only public data, no user system |
| Rate-limit headers, mandatory User-Agent | None enforced | A descriptive User-Agent is still appreciated |
downloads is always 0 today (reserved placeholder).files[].hashes carries sha256 where the source provides a digest.
GitHub only exposes digests for release assets uploaded since mid-2025;
older assets, and ci-{build} CI artifacts, have "hashes": {}. The
meta.counts.files_with_hashes figure tracks the coverage.version_number is usable directly as the project-scoped
/project/{id}/version/{version_number} path segment; both parts of a
version id ({project_id}@{version_number}) are already path-safe —
filename-unsafe characters are replaced by the exporter.body and changelog are raw markdown from the repository README/release
notes. Sanitize before rendering (the NukkitHub site treats them the same way).