api.mzizi.dev: the Mzizi registry API as a Hono Cloudflare Worker, served from registry data bundled at build time. No origin, no database, no Supabase.
Repository: mzizi-dev/mzizi-api (formerly mzizi-api-gateway) | Package and Worker: mzizi-api-gateway (private) | Live API: api.mzizi.dev | Docs: docs.mzizi.dev
The rename. The repository is mzizi-dev/mzizi-api, formerly mzizi-api-gateway.
The Worker name in wrangler.jsonc, the package name and the X-Mzizi-Source header still
say mzizi-api-gateway. Changing them changes the deployed Worker's name and the header that
readers use to find the registry commit, so it is a separate decision and is not part of the
repository rename. The owner has renamed the repository, and GitHub redirects the old
name.
The API Worker for api.mzizi.dev is Hono, running its own API. Nothing
touches Supabase except the Mzizi console.
That replaces this repository's earlier "pure-Rust workers-rs" position, for
this repository. The Rust proxy that stood here (a strangler fig forwarding to
the registry's Next.js handlers) is retired. This Worker implements the whole
public /v1 API itself, from files.
Live on api.mzizi.dev since 2026-09-29.
The owner moved the custom domain from the mzizi-registry Worker (the
registry's Next.js app on OpenNext) to this Worker on 2026-09-29, and
wrangler.jsonc now declares it, so every production deploy keeps it. See
Cutover for how it was done, and Rollback for undoing
a bad deploy (the move itself can no longer be reversed).
You can tell which Worker answered: every response from this one carries
X-Mzizi-Source: mzizi-api-gateway; registry=<commit>.
The same contract api.mzizi.dev serves today: the same paths, methods, query
parameters, status codes, response bodies, CORS headers and cache headers. Both
base paths answer: /v1/... (canonical) and /api/v1/....
| Route | Data |
|---|---|
GET /v1 · /api/v1 |
Discovery document |
GET /v1/health |
This Worker's liveness |
GET /v1/ui (node, owner, collection, type, limit, offset) |
registry.json joined to the files on disk |
GET /v1/ui/{name}: the shadcn install endpoint |
Item plus its source file |
GET /v1/ui/{name}/docs |
The item's meta block in registry.json |
GET /v1/rs/{name} |
The Rust (.rs) source, where one exists |
GET /v1/astro |
Every name with an Astro implementation |
GET /v1/astro/{name} |
The pure .astro (or framework-free .ts) document mzizi add --target astro installs |
GET /v1/search (q, node, category; layer is a deprecated alias of node) |
registry.json |
GET /v1/brand |
lib/tokens: 21 colour families, and the rest |
GET /v1/architecture · /v1/architecture/nodes/{n} |
The DNA double helix: 8 nodes, 4 rungs, 6 strands |
GET /v1/data-layer · /ecosystem · /pipeline · /sovereignty · /ubuntu/pillars · /ubuntu/principles |
content/doctrine/** |
GET /v1/ai/instructions · /v1/ai/instructions/{name} |
content/doctrine/** |
GET /v1/changelog · /v1/changelog/{version} |
lib/changelog.generated.ts |
GET /v1/skills · /v1/skills/summary · /v1/skills/{name} |
lib/skills.generated.ts |
GET /v1/samples · /v1/samples/{type} |
lib/samples/data.ts |
GET /v1/stats |
Zeroed usage figures plus real per-node counts |
GET /openapi · /api/openapi |
lib/openapi.generated.ts |
GET /.well-known/security.txt |
Contact security@nyuchi.com |
Also answered, as they are today:
308for renamed components.nyuchi-*→mzizi-*on/v1/ui,/v1/rs(and their/apiforms), keeping the sub-path and query. The map is the registry'slib/component-renames.json.410 Gonefor retired routes:/v1/docs,/v1/docs/{slug},/v1/architecture/axes,/v1/architecture/frontend/{axes,layers},/v1/architecture/layers/{n}.503for/v1/ui/{name}/versions, whose body says why: version history is console-owned data, and this API has no database. See Not served from files.308 /mcp→https://mcp.mzizi.dev/mcp, and a308that strips a trailing slash.- Read-only. A path with a
GEThandler answersOPTIONSwith204andAllow: GET, HEAD, OPTIONS, and every other method with an empty405.
The differences from the registry Worker this replaced were all on purpose, and
were listed in EXPECTED in scripts/parity.mjs at the
cutover (mzizi-api-gateway#13 and before). Since then the baseline is
api.mzizi.dev itself, so EXPECTED lists only what the current pin move
changes. The cutover differences were:
- The bare
/v1serves the discovery document (the registry Worker answered it with the website's HTML 404). - An unknown path gets a small JSON 404 instead of that 93 KB HTML page.
security.txthas its new contact./v1/ui/{name}/docs,/v1/searchand/v1/ai/instructions/{name}serve their data from files, where the registry Worker answered503 {"error":"Database not configured"}. See File-backed routes./v1/ui/{name}/versionsis still503, with a body that gives the real reason instead ofDatabase not configured.
There is no database behind the registry. Components, doctrine, brand,
changelog, skills and samples are files in
mzizi-dev/mzizi-registry, and
that's where this Worker reads them. It reads them at build time, not at request
time.
npm run build:data (scripts/build-data.mjs):
- Checks out
mzizi-registryat the commit pinned inscripts/registry-ref.json(shallow, cached under.registry/). - Bundles
scripts/extract.tsagainst that checkout with esbuild. The extractor calls the registry's own readers:lib/registry,lib/registry-source,lib/db(file-only since the registry dropped Supabase),lib/doctrine, and the generated changelog, skills, tokens, samples and OpenAPI modules. So the shapes are the ones the registry serves, not a reimplementation.@supabase/supabase-jsstays replaced by a stub that throws, so a reader that ever reached for a database again would fail the build. - Writes
src/data/*.json(gitignored). The Worker imports it as JSON modules.
Wrangler runs this as its custom build (build.command in wrangler.jsonc), so
wrangler dev, wrangler deploy and Workers Builds all build the same bundle
from the same pin.
Size: 4.6 MB raw, ~961 KiB gzipped, under the 3 MiB Workers limit with
room to spare. So the data is bundled into the Worker. R2 would add a network
hop and a second deploy artefact for no benefit at this size. Revisit it only if
wrangler deploy --dry-run approaches the limit.
Three routes used to answer 503 {"error":"Database not configured"} even though
their data was in files: the registry handlers gated them on Supabase
credentials that were never set on the registry Worker. This Worker started
serving them from files at the cutover, porting the handlers from
0b1819e.
The registry's own handlers now serve them from files too
(mzizi-registry#373),
so this Worker ports the handlers at the pin:
| Route | Reader (mzizi-registry lib/db) |
Answer |
|---|---|---|
/v1/ui/{name}/docs |
getComponentWithDocs: the docs row and demo flag from the item's meta block |
name, description, node, nodeLabel, owner, collection, docs, demo; 404 for an unknown name |
/v1/search |
searchComponents, then node and categories filters |
{ data, meta: { total, query, node, category } }, each hit name, type, title, description, categories, node, nodeLabel; 400 with no parameter |
/v1/ai/instructions/{name} |
getAiInstruction, then getAiInstructionByTarget |
The whole doctrine row, by name or by target; 404 for neither |
build-data stores what the first and third need (src/data/component-docs.json,
src/data/ai-instruction-index.json). Search is computed per request, so it runs
src/search.ts over components.json, which is the output of
readComponents(). The per-route projections for docs, AI instructions, the
versions 503, the discovery document and /v1/rs/{name} are in
src/projections.ts.
Both are checked against the registry's route handlers at build time.
The registry removed its Next.js app, and with it app/api/v1/**/route.ts, in
mzizi-registry#389, so
the six handlers this check needs are ported from its last commit with them,
270af9f,
into scripts/registry-handlers/, unchanged but
for formatting. They still call the pinned registry's own lib/ readers.
scripts/extract.ts runs them in-process, with next/server replaced by
scripts/next-server-stub.mjs (just
NextResponse.json). It compares their answers with this Worker's: search on
about 1,300 queries (status, body and the Deprecation header), docs for every
component and for unknown names, every AI instruction key, the versions 503,
the discovery document, and /v1/rs/{name} for every component (the .rs
source, its path and the crate that compiles it) and for unknown names. A
disagreement fails the build.
Search filters on node and categories, combined (AND), as the registry
handler does since mzizi-registry#373. ?layer= still works as a deprecated
alias of ?node=: the answer carries meta.deprecation and a
Deprecation: true header, and node wins when both are given. Before that
the handler filtered on layer and category, the retired database row's
field names, so those filters matched nothing and a hit had only name and
description.
The discovery document says what serves it. Its data block is
{ source: "files", repository, components }, with the live count, and its
description names Mzizi as the operator of the registry and this API, and the
Bundu Foundation as the project's home. It used to carry
database: { status: "not_configured", components: 0 }.
Genuinely dynamic data belongs to the Mzizi console, the one place that talks to Supabase. This Worker doesn't implement it:
| Data | Where it lives | What this Worker does |
|---|---|---|
Component version history (component_versions) |
The console's Supabase, written by releases | /v1/ui/{name}/versions → 503, saying so |
Usage telemetry (usage_events) |
Supabase, written by request tracking | /v1/stats reports zeroed totals, as today; per-node counts are real |
| Fundi issues, self-healing log, observability, chaos | Supabase | /api/health/{name}, /api/chaos/{name} not served (both 503 today) |
| Accounts, users, keys, anything per-user | The console | Not part of this API |
/v1/ui/{name}/versions keeps its 503 status and headers. Its body now reads:
{
"error": "Version history is not served by this API",
"message": "Component version history is machine-written data owned by the Mzizi console (app.mzizi.dev), not part of the registry's files. api.mzizi.dev serves those files and has no database. Release history is at https://api.mzizi.dev/v1/changelog."
}The pin is deliberate: a registry change reaches api.mzizi.dev only through a
commit to this repository that CI has checked. Owner decision, 2026-11-04:
no bot moves it. The pin (ref in scripts/registry-ref.json) is moved by
hand, in a pull request into staging:
- Set
refto the mzizi-registry commit to serve. - Run
npm run build:data,npm run typecheck,npm run checkandnpm test, thennpm run parityagainst production. - Record each intended difference in
EXPECTEDinscripts/parity.mjs, with its reason, and paste the parity summary into the pull request.
The pin reaches production with the owner's next release pull request, from
staging to main. CONTRIBUTING.md, "Moving the registry
pin", has the detail.
Direction, not shipped. Mzizi's own branded components are being converted to Rust as Mzizi Roots: UI and server components built for the agentic web. React/TSX components keep working, but they're deprioritised: they aren't the lead and aren't where new work goes. Where a Rust implementation exists, it comes first.
What that means for this API:
- Today,
/v1/rs/{name}serves the Rust source for every item that has one (55 at the current pin), and/v1/ui/{name}serves the React source that the shadcn CLI installs. Both come from the same pinned registry files. Neither changes in this repository before the registry changes. crateon/v1/rs/{name}names the crate that compiles the component, read from the registry'smzizi-rs/crate-for-node.jsonat build time:mzizi-uifor the N2 primitives,mzizi-brandfor the N3 brand components (Roots batch 1, mzizi-registry#372),mzizi-shell,mzizi-assurance,mzizi-fundi,mzizi-docs,mzizi-discoveryandmzizi-tokensfor the rest. It also carriesgit, the install that resolves before and after a crates.io release.scripts/extract.tschecks every/v1/rs/{name}answer against the registry's last handler for it (scripts/registry-handlers/rs.ts, ported from 270af9f).- Renderer (decided, not built here). Mzizi components are built in-house, not borrowed from
another framework (owner, 2026-11-04: Dioxus is out, across every Mzizi repository,
mzizi-registry#635). This API names no
Rust rendering target: the
/v1/rs/{name}envelope carries notargetfield, only thecrateand the source. What replaces the old renderer is the registry's decision to make. - The direction is for Rust to be the first answer: Roots components listed and described ahead of their React counterparts, and new component endpoints designed around the Rust implementation. The registry has had no route handlers since mzizi-registry#389, so such a change lands here, with a CHANGELOG entry and parity recording the difference.
- The design is the Roots RFC in mzizi-registry (status: proposed). Roots-specific routes follow it, and land in the registry first; don't build them here ahead of it.
npm ci
npm run build:data # clone the pinned registry ref, generate src/data/
npm run dev # wrangler dev on http://localhost:8787
npm test # offline route tests (vitest)
npm run typecheck
npm run parity # live api.mzizi.dev vs the local WorkerREGISTRY_DIR=/path/to/mzizi-registry reuses an existing checkout, but it must
be at the pinned commit.
scripts/parity.mjs is the acceptance test. It sends
read-only requests to a baseline and a candidate: every route, every
component slug on /v1/ui/{name} and /v1/rs/{name}, the renamed names, the
query filters, the error paths, the /api/v1 spellings and the method handling.
It diffs status, Location, content type, the CORS, cache and security headers,
and the body (JSON deep-equal). The only values it normalises are the health
timestamp and security.txt's Expires. Intentional differences are listed in
the script with their reasons. Anything else fails the run.
node scripts/parity.mjs --baseline https://api.mzizi.dev --candidate http://localhost:8787The Parity GitHub workflow does the same, with the report as the job
summary. It runs by hand against any two URLs, and against production on every
pull request into main that changes scripts/registry-ref.json,
scripts/parity.mjs, scripts/parity-body.mjs or parity.yml. There it passes
--strict unless the pull request edits scripts/parity.mjs: --strict
ignores EXPECTED, so every difference counts as unexplained (see
Moving the registry pin).
Done 2026-09-29. Parity before the move, against the deployed Worker on
workers.dev: 1359 requests, 0 unexplained differences. The steps are kept
here as the record.
- Deploy. Merge to
main. Workers Builds builds and deploys this Worker toworkers.dev(see the Workers Builds settings in the pull request that introduced this README). - Verify on workers.dev. Run the Parity workflow with baseline
https://api.mzizi.devand candidatehttps://mzizi-api-gateway.nyuchi.workers.dev. Expect 0 unexplained differences. - Move the domain. In the Cloudflare dashboard: Workers & Pages →
mzizi-registry→ Settings → Domains & Routes, removeapi.mzizi.dev. Thenmzizi-api-gateway→ Settings → Domains & Routes → Add → Custom domain, addapi.mzizi.dev. Then declare it inwrangler.jsonc(done), so the next deploy keeps it. - Re-run parity with baseline
https://mzizi-registry.nyuchi.workers.devand candidatehttps://api.mzizi.dev, and check thatX-Mzizi-Sourceis present onhttps://api.mzizi.dev/v1/health. - Roll back to the registry Worker: no longer possible. It served the registry's Next.js app, which mzizi-registry removed on 2026-10-02, and the Worker is being deleted. See Rollback.
There is no older Worker to move api.mzizi.dev back to. To undo a bad
change, revert it in a pull request; the merge redeploys. To undo a bad deploy
faster than that, roll this Worker back to its previous deployment
(wrangler rollback, or Workers & Pages → mzizi-api-gateway → Deployments
in the Cloudflare dashboard), then revert on main so the next deploy does not
bring the change back.
The Bundu ecosystem has four API gateways. Each has its own repository and host. This repository is the first one, the registry API.
| Gateway | Repository | Host | Today | Decided, not built |
|---|---|---|---|---|
| Mzizi API (this repository; the registry API, public and free) | mzizi-dev/mzizi-api (formerly mzizi-api-gateway) |
api.mzizi.dev |
A Hono (TypeScript) Worker | Stays a Hono Worker for now |
Nyuchi API (B2B, scoped nyk_ keys) |
nyuchi/nyuchi-api (formerly nyuchi/api-gateway, private) |
api.nyuchi.com |
FastAPI on Fly | Built in Rust, including its async-writes data gateway |
| Mukoko API (consumer app surfaces) | mukoko-dev/mukoko-api (private, unchanged) |
api.mukoko.com (no DNS yet) |
Being built on Workers | Built in Rust |
| Nyuchi Servers API (internal only) | nyuchi/nyuchi-servers-api (formerly nyuchi/data-gateway, private) |
api.nyuchiservers.com (no public IP) |
Not built | Built in Rust; internal-only Fly origin behind a private Cloudflare Tunnel |
These are decisions, not shipped code. Decided, not built: the Nyuchi API (including its async-writes data gateway), the Nyuchi Servers API and the Mukoko API are built in Rust; the Mzizi API stays a Hono Worker for now. Only the Mzizi API is built, and it is the only one this repository describes.
Build targets (decided, not built). The Mzizi build-out covers Cloudflare Workers,
containers (a standard OCI image, built with Docker by default and run by any OCI runtime, such
as on Fly machines) and bare metal (a native Linux binary). The language's RFC-0016
(mzizi-dev/mzizi#123) designs them; the owner approved it as design only on 2026-10-21, and
every build wave waits for the Phase 0 gate. This repository is a Cloudflare Worker today, and no
decision moves the Mzizi API to another target.
Fly regions (decided, not built). Stateless gateway origins are decided to run in jnb
(Johannesburg), cdg (Paris, with mad Madrid second) and sin (Singapore). The intended
coverage is a routing rationale: jnb for Southern and East Africa, cdg for North and West
Africa and Europe, and sin for Asia-Pacific. Stateful data services stay primary in jnb
until a replication design exists. No latency figures are claimed for any region.
| Repository | What it is | Address |
|---|---|---|
mzizi |
The language: Rust compiler research, Phase 0 | — |
mzizi-registry |
The component registry, brand and architecture | mzizi.dev |
mzizi-console |
The console, run under Nyuchi; reads this API | app.mzizi.dev |
mzizi-site |
Mzizi's front door | mzizi.dev |
mzizi-api (formerly mzizi-api-gateway) |
This repository | api.mzizi.dev |
CONTRIBUTING.md covers changing a route, bumping the
registry pin, and the wrangler.jsonc rules that only fail on production.
AGENTS.md has the same in agent-facing form, plus the merge
convention.
SECURITY.md · CODE_OF_CONDUCT.md
Licensed under the Apache License 2.0.
Mzizi is an independent open-architecture project that owns, operates and develops its framework, design system, registry, docs and this API. The Mzizi console and everything revenue-generating are run under Nyuchi.