Registry
The central catalog of coding capabilities — skills, agents, MCP servers, plugins, tools and models — at api.misar.io/coder/registry/*.
The registry is the one place coding capabilities are published, versioned, discovered and installed from. Misar Code, Misar.dev's Skill Center, and any other product read the same catalog over the same REST surface — there is no shared package to install and no database to connect to.
Base URL: https://api.misar.io/coder/registry
Auth: reads are anonymous. Writes need a credential (see Publishing).
Kinds
Every item has exactly one kind, from a closed set:
| Kind | What it is |
|---|---|
skill | A reusable instruction set the assistant can invoke |
agent | A specialised assistant with its own triggers and tools |
mcp_server | A Model Context Protocol server that adds tools |
plugin | A bundle that extends the runtime, including lifecycle hooks |
tool | A single callable function |
model | A model definition available for routing |
Ignore kinds you do not recognise
The set is closed, but it can grow. A client must render the kinds it supports and
skip the rest — a new kind should never break an older client. Do not switch on
kind without a default branch.
Lifecycle hooks publish as kind: "plugin" carrying a hook:<name> capability, rather
than as a kind of their own, so clients that predate them still handle them sensibly.
Reading the catalog
Reads are cacheable (Cache-Control: public, max-age=60, stale-while-revalidate=600) —
the catalog changes when someone publishes, not per request.
/itemsList public items, filtered and paginated.
kindstringqueryRestrict to one kind. Unknown values are ignored rather than erroring.
qstringqueryFree-text search across name, summary and description.
tagstringqueryRestrict to items carrying this tag.
productstringqueryRestrict to items installable in a given product (dev, coder, ai, …).
pageintegerquery1-based page number. Default 1.
limitintegerqueryItems per page. Default 24, clamped to a server maximum.
sortstringquerypopular (default), recent, or name.
Response
{
"data": [
{
"slug": "git-worktree",
"kind": "skill",
"name": "Git Worktree",
"summary": "Work on several branches at once without stashing.",
"description": "…",
"version": "1.2.0",
"publisher": { "id": "misar", "name": "Misar", "verified": true },
"tags": ["git", "vcs"],
"capabilities": ["worktree"],
"products": ["dev", "coder"],
"license": "MIT",
"sourceUrl": "https://git.misar.io/misaradmin/MisarCoder",
"icon": null,
"installs": 128,
"createdAt": "2026-08-01T10:00:00Z",
"updatedAt": "2026-08-02T09:30:00Z"
}
],
"pagination": { "total": 42, "page": 1, "limit": 24, "totalPages": 2 }
}pagination.total is the number of matching items, not the number on this page.
Visibility
Only public items appear in list results. An unlisted item is fetchable by its exact
slug but never listed. A private item requires its publisher's credentials — to anyone
else it returns 404, not 403, so the endpoint cannot be used to discover that a
private slug exists.
/items/{slug}One item, with its release history and the manifest of its latest version.
Response — the item shape above, plus:
{
"versions": [
{ "version": "1.2.0", "changelog": "…", "yanked": false, "createdAt": "…" },
{ "version": "1.0.0", "changelog": null, "yanked": true, "createdAt": "…" }
],
"manifest": { "…": "kind-specific" }
}Yanked versions remain listed — a client that pinned one needs to know it was withdrawn — but must not be offered as an install target.
/items/{slug}/manifestThe raw installable manifest.
versionstringqueryA specific semver. Defaults to the latest non-yanked version.
/kindsCounts per kind, for filter UIs: [{ "kind": "skill", "count": 8 }, …]
/tagsCounts per tag: [{ "tag": "git", "count": 3 }, …]
Publishing
Writes accept either credential:
x-registry-service-key: <key>— server-to-server, with an explicitpublisherIdin the body.Authorization: Bearer <api key>— a MisarCoder API key.
Manifests must never contain secret values
A registry row is world-readable by design. Runtime configuration carries environment variable names, never their values — the server strips values at the storage boundary, including from nested objects, and consumers prompt for them at install time. A manifest that ships a key has published that key.
/itemsCreate or update an item. Matching is by slug, so re-posting updates in place.
slugstringbodyrequiredLowercase, hyphen-separated. Stable forever — it is the item's identity.
kindstringbodyrequiredOne of the six kinds above.
namestringbodyrequiredDisplay name.
summarystringbodyrequiredOne line, 160 characters or fewer.
descriptionstringbodyLong form, markdown. Consumers render it as text, not HTML.
visibilitystringbodypublic (default), unlisted, or private.
tagsstring[]bodycapabilitiesstring[]bodyproductsstring[]bodyWhich products may install it. Defaults to all.
licensestringbodysourceUrlstringbodyiconstringbodyverified cannot be self-asserted — a bearer caller requesting it gets false.
/items/{slug}/versionsPublish a version.
versionstringbodyrequiredSemver. Ordered numerically, so 1.10.0 is newer than 1.2.0.
manifestobjectbodyrequiredThe installable payload. Validated against the schema for the item's kind.
runtimeobjectbodyTransport, command, args and environment variable names.
changelogstringbodyA version is immutable once published
Re-publishing the same (slug, version) returns 409. Fix a bad release with a new
version or a yank — never by overwriting. Someone who installed 1.2.0 yesterday and
someone who installs it today must get the same thing.
/items/{slug}/versions/{version}Yank a version. The row is kept and stays visible in the release history; it is removed
from install targets, and version falls back to the highest remaining non-yanked release.
Nothing is ever hard-deleted.
/items/{slug}/installRecord an install. This is what makes the popular sort meaningful, so a client that
installs without recording is under-reporting.
versionstringbodyrequiredproductstringbodyrequiredWhich product installed it (dev, coder, …).
This call must never block or fail an install. A registry-side fault returns
200 {"recorded": false} rather than an error, so a caller can fire it and move on. An
unknown slug is still a 404, because that is a bug in the caller rather than an outage.
Errors
Errors carry a real status code and a body of { "error": string, "code"?: string } —
never 200 with an error inside.
| Status | Meaning |
|---|---|
404 | Unknown slug, or a private item you cannot see |
409 | That (slug, version) already exists |
422 | The manifest failed its kind's schema |
503 | The registry's storage is unavailable |
503 means unavailable, not empty
When storage is down the registry returns 503. It never falls back to a local plugin
list, because presenting one node's loaded plugins as "the catalog" is worse than an
honest error. Clients should show "unavailable" rather than an empty catalog — an empty
grid reads as there is nothing here, which is a different and wrong statement.
SDKs
Both SDKs expose the same surface under registry, with the same names and arguments as
the REST endpoints.
import { MisarCoder } from "@misar/coder";
const coder = new MisarCoder({ apiKey: process.env.MISARCODER_API_KEY });
const { data, pagination } = await coder.registry.list({ kind: "skill", q: "git" });
const item = await coder.registry.get("git-worktree");
const manifest = await coder.registry.manifest("git-worktree", "1.2.0");
await coder.registry.recordInstall("git-worktree", {
version: "1.2.0",
product: "dev",
});from misarcoder import MisarCoder
client = MisarCoder(api_key=os.environ["MISARCODER_API_KEY"])
items = client.registry.list(kind="skill", q="git")
item = client.registry.get("git-worktree")
manifest = client.registry.manifest("git-worktree", version="1.2.0")