The API reference is the single-page OpenAPI reference at /docs/openapi.
/docs/api (and /api, /api-reference) returned 404 because they were never
routes, and the /api redirect declared in next.config redirects() is dead:
docs.hanzo.ai deploys as a static export (out/) to Cloudflare Pages, which
ignores next.config redirects() and middleware (proven live: /api and
/tutorials both 404 despite being declared there).
Add public/_redirects -- the mechanism CF Pages actually honors, exactly like
the existing public/_headers -- as the single source of truth for redirects:
/docs/api, /docs/api/*, /api, /api/*, /api-reference, /api-reference/* -> /docs/openapi
/tutorials, /tutorials/* -> /docs/services (migrated from the dead config)
Remove the now-dead redirects() block from next.config so there is one
mechanism, not two.
Also fix two broken internal links in services/chat.mdx that pointed at
non-existent pages:
/docs/api/pricing -> /docs/services/pricing
/docs/models -> /docs/chat/models
Verified with `wrangler pages dev` against the exact _redirects file:
/docs/api -> 301 -> /docs/openapi (200); the real /docs/api-keys page is not
over-captured (passes through, no redirect).
console.hanzo.ai deep-links each product to docs.hanzo.ai/docs/<slug>; ~43 of
those slugs had no page and 404'd. Add real, accurate Fumadocs pages for every
one — Data (sql/vector/kv/search/datastore/docdb/storage/base/memory), Compute
(gpus/machines/edge/pipelines/registry/networks/blockchain), Observability
(metrics/logs/traces/sessions/dashboards), Evaluation (datasets/experiments/
scores/score-configs/annotation-queues), Security (iam/kms/authz/mpc/zero-trust),
Platform (api/gateway/console/integrations), Commerce (billing/referrals), and
AI (agents/prompts/embeddings/marketplace/ai-studio/crawl).
Each page: real /v1 examples with Bearer hk- auth, grounded in the actual
product repos + the console registry — no placeholder/coming-soon. Wired into
the sidebar nav (meta.json).
Move the docs and bot-docs RAG clients (search/chat/index) off the
residual /api/ prefix to top-level /v1/, matching the cloud-api route
rename and the openapi v1.0.0 lock-in (no /api/ prefix):
/api/search-docs -> /v1/search-docs
/api/chat-docs -> /v1/chat-docs
/api/index-docs -> /v1/index-docs
Touched: apps/{docs,bot-docs}/lib/hanzo/client.ts,
apps/docs/scripts/update-hanzo-index.ts, the .env.example (also
corrects the stale cloud-api.hanzo.ai host -> api.hanzo.ai front door),
and the two generate-search-index.py docstrings.
Server-side these are served at /v1/ by hanzoai/ai (bare search/chat/
index-docs); deeper product GETs by hanzoai/cloud productsvc. Hard
cutover, coordinated with cloud, universe cloud-api-v1 AUTH_PUBLIC_PATHS,
python-sdk, and the openapi cloud spec.
Co-authored-by: Hanzo Dev <[email protected]>
The cloud API now sits behind one front door (ingress -> gateway -> cloud).
api.cloud.hanzo.ai / cloud-api.hanzo.ai / functions.hanzo.ai are redundant
aliases routing to the same backend, so migrate clients to api.hanzo.ai.
- docs + bot-docs lib/hanzo/client.ts, scripts/update-hanzo-index.ts:
search/index/chat-docs default base cloud-api|api.cloud.hanzo.ai ->
api.hanzo.ai (RAG paths stay /api/* — served there per cloud AUTH_PUBLIC_PATHS)
- cloud landing page.tsx: curl base + request-flow label api.cloud.hanzo.ai ->
api.hanzo.ai
- functions.mdx (x2): collapse Endpoint+Gateway to one canonical
api.hanzo.ai/v1/functions/*
- hanzo-openapi.mdx: Cloud endpoint label api.cloud.hanzo.ai -> api.hanzo.ai
Non-breaking: the aliases still resolve to the same backend.
Co-authored-by: Hanzo Dev <[email protected]>
The Infisical KMS routes (/api/v1/auth/universal-auth/login,
/api/v3/secrets/raw) are decommissioned and 404. Switch every Pages
deploy workflow to the canonical Hanzo KMS API: machine-identity
login at /v1/kms/auth/login, then read CF Pages creds from
orgs/hanzo/secrets/deploy/{CLOUDFLARE_API_TOKEN,CLOUDFLARE_ACCOUNT_ID}?env=prod.
Identity is the KMS_CLIENT_ID/KMS_CLIENT_SECRET repo secrets.
Replace the nginx:alpine Dockerfile.static (platform rule violation) with a
hanzoai/static re-serve shim that wraps the existing flow-site:0.1.0 Next.js
export. Pin apps/flow/Dockerfile (from-source build) to static:0.4.1 and add
the explicit --root=/srv --spa --port=3000 entrypoint. flow.hanzo.ai was
serving ~20 bytes from the broken nginx pod; this serves the real 64 KB export.
Unblocks app-build-green for the content-docs apps (spec, gui-docs, zen-docs,
base-docs) and advances the docs registry/config app integration.
Root causes (upstream-merge artifacts from the 16.x restructure), with the
authoritative fix matched to upstream [email protected]:
- packages/{base-ui,radix-ui}/src/contexts/i18n.tsx: file was partially
overwritten with an older shape — it referenced `defaultTranslations`,
`use`, `renderTranslation`, `TranslationValue`, `TranslationObject`
without importing/defining any of them, and redeclared a stale local
`Translations`. Restored the upstream 16.9.3 context: import the i18n
primitives from `fumadocs-core/i18n` + `@/i18n`, use React `use`. This was
the `ReferenceError: defaultTranslations is not defined` prerender crash.
- packages/core/src/source/page-tree/builder.ts: item `$ref` was emitted as
an object `{ file: path }`, violating `PageTree.Item.$ref?: string` and
breaking the sort (`aT.localeCompare` on an object → `c.localeCompare is
not a function`). Restored upstream string `$ref: path`.
- packages/shared/registry.ts: `glob` was imported from `node:fs/promises`
(an async generator → `await glob()` is non-iterable → `{} is not
iterable`) and `Component` was double-imported. Restored upstream
`import { glob } from 'tinyglobby'`.
- apps/docs registry codegen aligned to the real `@hanzo/docs-cli/build`
(=`fuma-cli/compiler`) API: `components/registry/index.ts` split the
merged duplicate `export const registry` back into `compileOptions` +
`registry` (restoring the dropped `baseUi`/`sanity` registry imports);
`scripts/build-registry.ts` uses `compile({ root, ...compileOptions })` +
`writeRegistry` instead of a non-existent `RegistryCompiler`/
`combineRegistry`/`writeDocsRegistry` surface. Restored the dropped
`lib/inkeep/inkeep-qa-schema.ts` and declared the inkeep files in the
`ai/hanzo` component.
- apps/docs/next.config.mts: ESM `__dirname` shim via
`fileURLToPath(import.meta.url)`; removed orphaned `createNextStory()`
dead line; declared the `@hanzo/docs` workspace dep the config imports.
- apps/docs/package.json build:pre/post/lint run via `bun` (resolves `@/`
tsconfig aliases) to match every other app — `node` cannot resolve `@/`.
- apps/zen-docs/[[...slug]]/page.tsx: reuse the canonical `getMDXComponents()`
factory instead of an inline components map whose `next/dynamic` wrappers
produced an over-specific `ComponentType` incompatible with
`NestedMDXComponents`.
Verified `next build` GREEN (exit 0, full static gen): spec 245/245,
gui-docs 103/103, zen-docs 129/129, base-docs 19/19. 29/29 packages build
green (fumadocs-preview leaf excluded). The `docs` registry/demo app remains
blocked only on external `@hanzo/iam`/`@hanzo/logo` packages not present in
this tree (the in-flight subject of this branch).
Three phantom/path stragglers the prior normalization passes did not cover,
all blocking the package build chain (`turbo run build --filter=./packages/*`):
- packages/cli: `@hanzo/docs-cli/build` was imported by apps/docs +
shared/radix-ui/base-ui registries but the cli package never exposed it.
The real registry-build API is fuma-cli/compiler (cli already deps on
fuma-cli). Add src/build.ts re-export + ./build export + tsdown entry so
`@hanzo/docs-cli/build` resolves to the real symbols (compile, writeRegistry,
Registry, Component, Reference, SubComponentReference, CompileOptions).
- packages/hanzo-docs core/content/index.ts: barrel re-exported a bare
fumadocs-core/content subpath that exists in no version (upstream or fork).
Aggregate the real sibling modules (./github + ./toc) instead — no symbol
collision (getGithubLastEdit vs getTableOfContents).
- packages/openapi src/ui/client/full.tsx: import of ./components/codeblock
resolved one dir too shallow (file lives in src/ui/client/, codeblock in
src/ui/components/). Fix to ../components/codeblock, matching create-client.tsx.
Verified: full package chain builds green from cold cache (29/29, node 24.18).
Follow-up to 18f2af90: the package-name normalization covered JS/TS imports +
package.json but missed four other surfaces that name the same phantom packages,
relocating the breakage from 'pnpm install' to 'next/vite/astro build':
- H1: 46 CSS @import strings (@hanzo/docs-{base-ui,ui,openapi}/css/*) → real pkgs
(preserved legit @hanzo/docs-{twoslash,story} CSS)
- H2: examples/astro .astro imports (base-ui/mdx→fumadocs-ui/mdx, docs-core/toc→fumadocs-core/toc)
- L1: .changeset/config.json fixed-array lockstep names
- L2: removed apps/docs/next.config.mts dead phantom→phantom self-alias + dup keys
Found by adversarial red review of 18f2af90.
The fumadocs-fork rebrand left ~13 apps + examples referencing packages under
names that don't exist in the workspace (in BOTH directions: some kept
fumadocs-*, some became @hanzo/docs-*). pnpm install failed outright. Mapped
every reference (deps + imports, 607 source + 61 package.json) to the package
that actually exists, by suffix:
@hanzo/docs-{core,mdx,ui,base-ui} → fumadocs-{core,mdx,ui} / @fumadocs/base-ui
@fumadocs/{story,cli,mdx-remote,tailwind} → @hanzo/docs-{story,cli,...}
fumadocs-{docgen,epub,twoslash,typescript} → @hanzo/docs-{...}
drop phantom eslint-config-custom
Guarded @hanzo/docs-mdx vs -mdx-remote. Also fixes a pre-existing cli bug
(createConfigSchema referenced undefined isSrc) that blocked the package build.
Verified: pnpm install resolves clean (46.8s, was broken); package dist
regenerates phantom-free from the corrected source (hanzo-docs 430→0). Full
app build-green is blocked only by a node v26 toolchain bug in fumadocs-mdx's
config loader (repo targets node >=24.14; CI node builds it).
A dedicated standards site in the docs monorepo that SOURCES the HIPs from
hanzoai/hips at build time (scripts/sync-hips.ts, wired as build:pre) rather
than hand-copying them. Verified: sync pulls 240 HIPs → content/docs +
index + meta.json. Standards landing page, branding, .gitignore'd generated
content (single source = the hips repo).
NOTE: not build-verified locally — the docs monorepo does not install on this
box (mid @hanzo/docs-* → fumadocs-* rebrand; no sibling app has node_modules).
Deps + imports aligned to the real fumadocs-* workspace packages; build-green
is a CI concern once the monorepo's package scheme settles.
Remove the duplicated normative text. HIP-0504 (hanzoai/hips) is the single
source of truth; the gui-docs page is now a short orientation (the spine
diagram + the why) that links to it. One spec, one home.
One substrate (@hanzo/gui), one component library (@hanzo/ui), brands as
values (@luxfi/ui, @zooai/ui). Two orthogonal axes (substrate×components),
substrate-splits-by-domain-not-brand rule, dependency spine, invariants,
and the visual-regression-gated migration phases.
Renames the public doc-collection method across runtime, types, examples
and create-app templates so the API carries no upstream brand. The
internal helper and all call sites move together; fumadocs-core package
imports are untouched. Pairs with the .docs default outDir change.
BREAKING CHANGE: docs.toFumadocsSource() is now docs.toDocsSource().
Co-authored-by: Hanzo Dev <[email protected]>
The plugin default outDir was 'docs' while every template and example
tsconfig aliased collections/* -> ./.source/*, so a fresh fork could not
resolve `import { docs } from 'collections/server'` without manually
passing createMDX({ outDir }). Standardize on .docs everywhere:
- packages/mdx/src/core.ts: _Defaults.outDir 'docs' -> '.docs'
- 31 template/example tsconfig: collections/* -> ./.docs/*
- align generated-dir .gitignore entries to .docs
- changeset (patch)
Co-authored-by: Hanzo Dev <[email protected]>
Rewrite Authentication guide to the one canonical way: @hanzo/iam SDK,
/v1/iam/oauth/* endpoints, per-framework patterns (server, betterauth,
nextauth, react, browser, passport), app-registration rules, forbidden
anti-patterns, and the SPA catch-all gotcha. Point IAM SDK reference page
at @hanzo/iam for JS/TS. Per HIP-0111.
Replaces non-canonical scale-set / org-prefixed labels with the
existing labels every arcd host registers with. Matches evo for
amd64 and spark for arm64. No new labels added.
* fix types
* fix(openapi): newer scalar client
* Merge pull request #3318 from smakosh/feat/llmgateway-ai-provider
Docs: support LLMGateway for AI chat
* docs: document LLM_GATEWAY_MODEL for Ask AI (#3319)
* fix(core): improve source API utils & types
* feat(core): support `invalidate` & `revalidate` on dynamic loader
* fix(preview): fix invalid content type in `/img` endpoint
* chore: bump deps
* fix(mdx): respect `root` in Vite config
* docs: fix build
* Version Packages (#3324)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* feat(openapi): anchor links for request/response/parameter properties (#3327)
* feat(openapi): anchor links on schema properties
Each property rendered inline in a request body, response body, or parameter
now gets a stable id and a hover-revealed link icon, enabling deep links to
specific fields from guides. Ids follow:
- body-<prop> for request bodies
- response-<status>-<prop> for responses
- parameters-<location>-<name> for parameters
A new idPrefix option on SchemaUI opts other Schema renderings into the same
behavior. See fuma-nama/fumadocs#3322.
* feat(openapi): deep-link nested schema properties
Extend the property anchor scheme to support hashes that point inside a
popover or below an array's "Show array" collapsible. Schema-internal
segments are joined with `.` so `#body.user.address.street` resolves
unambiguously regardless of dashes in property names.
On hash match, the inline anchor scrolls into view, the matching popover
auto-opens with its breadcrumb pre-navigated, the array collapsible
expands, and oneOf/anyOf tabs switch to the variant containing the
targeted field. The deep target is consumed after the first auto-open,
so subsequent user clicks on the same trigger get the base view; a
different deep hash re-arms it.
The hover anchor is now a copy-link button (matches fumadocs heading
anchors) that writes the full URL to the clipboard and updates the
address bar via history.replaceState (no hashchange, no popover reset).
* address PR review feedback
- Clarified the changeset: split anchor ID rules by context (request/response property paths vs flat parameter anchors) so dots aren't implied to apply to parameters.
- Namespaced anchors by media type when an operation has multiple request-body or response content types: prefix becomes `<media-slug>-body` / `response-<status>-<media-slug>`, and the content-type SelectTabs auto-switches to the targeted tab when a deep link arrives. Single-content-type operations keep the simple `body` / `response-<status>` form.
- Made findUnionPropMatch validate the *full* remaining path (via new pathResolves helper) so two variants that share the first prop name no longer race; the wrong tab can't be selected for a deep hash anymore.
- Reset SchemaUIPopover breadcrumb state when initialPath changes so a fresh deep hash takes effect on an already-open popover.
- Replaced the lossy slugifyPropertyName with encodeURIComponent (plus % encoding for `.`) so property anchors stay collision-safe across case-only differences, punctuation-only differences, and non-ASCII keys, while `.` remains exclusively a path separator.
* address PR review feedback
- Fix potential hydration mismatch in useTabValueFromHash by initializing state with the fallback value and resolving from the URL hash inside useEffect; the deep-linked tab now corrects after hydration on the client.
* fix(openapi): move useTabValueFromHash into a client component
The previous hydration-mismatch fix put `useEffect`/`useState` directly in
`operation/index.tsx`, which is a Server Component — Next refused to compile
because RSC modules can't import client-only React APIs. Extract the
hash-aware tab wrapper into `operation/hash-tabs.tsx` (with `"use client"`)
and switch both body and response sites to `HashAwareSelectTabs`. The parent
tree stays server-side; only the tab wrapper hydrates on the client.
* address PR review feedback
- Wrap decodeURIComponent in try/catch in both deep-link hash parsers so a malformed percent-encoded fragment (e.g. `#body%`) is treated as "no target" instead of throwing URIError and breaking tab / popover resolution.
* address PR review feedback
- Match the URL hash against DOM ids using the raw fragment first, with the fully-decoded form as a fallback, in both DeepLinkProvider and hash-tabs. Property anchors are generated via slugifyPropertyName (which percent-encodes reserved chars like `.`), so DOM ids contain the encoded form; decoding the hash before lookup caused links like `#body.foo%2Ebar` to miss the matching element.
* redesign
* fix types
---------
Co-authored-by: Fuma Nama <[email protected]>
* feat(openapi): better Webhook UI
* Version Packages (#3331)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* docs: mention fumapress
---------
Co-authored-by: Ismail Ghallou <[email protected]>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Avner Matan <[email protected]>
The native 'hanzo' search + sync clients defaulted to dead endpoints (cloud-api.hanzo.ai/api/search-docs, api.cloud.hanzo.ai/api/index-docs). Repoint to the live hanzoai/ai gateway (verified vs routers/router.go): search -> https://api.hanzo.ai/v1/search, index -> https://api.hanzo.ai/v1/index.
Co-authored-by: Hanzo Dev <[email protected]>
* feat(openapi): anchor links on schema properties
Each property rendered inline in a request body, response body, or parameter
now gets a stable id and a hover-revealed link icon, enabling deep links to
specific fields from guides. Ids follow:
- body-<prop> for request bodies
- response-<status>-<prop> for responses
- parameters-<location>-<name> for parameters
A new idPrefix option on SchemaUI opts other Schema renderings into the same
behavior. See fuma-nama/fumadocs#3322.
* feat(openapi): deep-link nested schema properties
Extend the property anchor scheme to support hashes that point inside a
popover or below an array's "Show array" collapsible. Schema-internal
segments are joined with `.` so `#body.user.address.street` resolves
unambiguously regardless of dashes in property names.
On hash match, the inline anchor scrolls into view, the matching popover
auto-opens with its breadcrumb pre-navigated, the array collapsible
expands, and oneOf/anyOf tabs switch to the variant containing the
targeted field. The deep target is consumed after the first auto-open,
so subsequent user clicks on the same trigger get the base view; a
different deep hash re-arms it.
The hover anchor is now a copy-link button (matches fumadocs heading
anchors) that writes the full URL to the clipboard and updates the
address bar via history.replaceState (no hashchange, no popover reset).
* address PR review feedback
- Clarified the changeset: split anchor ID rules by context (request/response property paths vs flat parameter anchors) so dots aren't implied to apply to parameters.
- Namespaced anchors by media type when an operation has multiple request-body or response content types: prefix becomes `<media-slug>-body` / `response-<status>-<media-slug>`, and the content-type SelectTabs auto-switches to the targeted tab when a deep link arrives. Single-content-type operations keep the simple `body` / `response-<status>` form.
- Made findUnionPropMatch validate the *full* remaining path (via new pathResolves helper) so two variants that share the first prop name no longer race; the wrong tab can't be selected for a deep hash anymore.
- Reset SchemaUIPopover breadcrumb state when initialPath changes so a fresh deep hash takes effect on an already-open popover.
- Replaced the lossy slugifyPropertyName with encodeURIComponent (plus % encoding for `.`) so property anchors stay collision-safe across case-only differences, punctuation-only differences, and non-ASCII keys, while `.` remains exclusively a path separator.
* address PR review feedback
- Fix potential hydration mismatch in useTabValueFromHash by initializing state with the fallback value and resolving from the URL hash inside useEffect; the deep-linked tab now corrects after hydration on the client.
* fix(openapi): move useTabValueFromHash into a client component
The previous hydration-mismatch fix put `useEffect`/`useState` directly in
`operation/index.tsx`, which is a Server Component — Next refused to compile
because RSC modules can't import client-only React APIs. Extract the
hash-aware tab wrapper into `operation/hash-tabs.tsx` (with `"use client"`)
and switch both body and response sites to `HashAwareSelectTabs`. The parent
tree stays server-side; only the tab wrapper hydrates on the client.
* address PR review feedback
- Wrap decodeURIComponent in try/catch in both deep-link hash parsers so a malformed percent-encoded fragment (e.g. `#body%`) is treated as "no target" instead of throwing URIError and breaking tab / popover resolution.
* address PR review feedback
- Match the URL hash against DOM ids using the raw fragment first, with the fully-decoded form as a fallback, in both DeepLinkProvider and hash-tabs. Property anchors are generated via slugifyPropertyName (which percent-encodes reserved chars like `.`), so DOM ids contain the encoded form; decoding the hash before lookup caused links like `#body.foo%2Ebar` to miss the matching element.
* redesign
* fix types
---------
Co-authored-by: Fuma Nama <[email protected]>