ZZD public AI gateway · zzd_public_ai_gateway_v3 Resource: 03 · Scoped REST deployment API / 部署接口 Canonical map: https://zzd.show/docs/ai/map.json Full handbook: https://zzd.show/llms-full.txt REST capabilities: https://zzd.show/api/v1/capabilities/ # ZZD scoped REST v1 deployment API This is the supported platform-shaped control plane for an external Codex, Claude Code, or other authorized coding agent. It is HTTPS REST/JSON, not MCP JSON-RPC. - API base: `https://zzd.show/api/v1/` - Public discovery: `GET https://zzd.show/api/v1/capabilities/` - Authentication: `Authorization: Bearer ` - Bearer project-route JSON success: `{"ok":true,"data":...}` - Bearer project-route JSON error: `{"ok":false,"error":{"code":"stable_code", "message":"...","details":{...}}}` - Binary exception: the authenticated thumbnail-content route streams image bytes with `Cache-Control: private, no-store`; its errors use the normal JSON error envelope. - Mutation retries: send a stable `Idempotency-Key` of 8–128 safe characters. Keys are recommended for POST, PUT, and PATCH and are replayable for the window reported by capabilities. Always fetch capabilities at the start of a task. This handbook explains adoption; the live capabilities response is authoritative if routes, limits, or feature availability evolve. ## Canonical-host boundary The developer API and credential control plane accept only the exact canonical `https://zzd.show` Host and return a featureless 404 on an alternate host. Do not call them through `www.zzd.show`, `code.zzd.show`, the server IP, an alternate port, a custom domain, or a proxy-selected Host. Send the bearer only to canonical `https://zzd.show/api/v1/...` endpoints and never forward it across a redirect. `code.zzd.show` is for the editor/preview URLs, not bearer API requests. ## 1. Obtain and store a developer credential Credential creation is a signed-in user control-plane action, not a bearer API bootstrap. The user can use the Credentials UI or these same-origin session-authenticated routes: - `GET /api/v1/developer-credentials/` — list lifecycle metadata and the current scope/expiry catalog; - `POST /api/v1/developer-credentials/create/` — create after CSRF validation from the signed-in browser; - `POST /api/v1/developer-credentials/{credential_id}/revoke/` — revoke an owned credential after CSRF validation. The user chooses a label, finite expiry, and least-privilege scopes, then confirms creation. The raw value is returned exactly once; ZZD stores only its hash and lifecycle metadata. Never ask the user to paste that response into chat. A browser-assisted agent may complete the same-origin UI flow without reading cookies or echoing the token. Store the value in the OS keychain where possible. A fallback file must live outside the project, be excluded from source control, and be readable only by the user, for example a profile under `~/.config/zzd/` with mode `0600`. Keep only a credential profile name in automation configuration. Never place the raw value in a prompt, transcript, source file, `.env` committed to Git, screenshot, URL, browser Show code, asset, log, or final answer. Available scopes are discovered live. REST v1 currently uses: - `identity:read` — verify account identity with `/me/`; - `shows:read` — list and inspect owned Shows; - `shows:write` — create/update draft metadata and replace Pieces; - `shows:publish` — publish or unpublish after confirmation; - `assets:write` — upload and bind safe asset bytes; - `baas:write` — provision the bounded runtime-identity BaaS profile; - `collections:write` — create a collection and attach owned Shows. Other advertised scopes may support separate surfaces. Do not request a broad scope merely because it exists. A BaaS app/service runtime credential and a legacy CLI secret are different credentials and must never be used as developer bearer tokens. Credentials expire, are revocable, and do not have a permanent mode. The signed-in credential UI/API is authoritative for current expiry choices, active status, last use, and revocation. ## 2. Discover and verify ```http GET /api/v1/capabilities/ HTTP/1.1 Host: zzd.show Accept: application/json ``` Then verify the selected local credential without printing it: ```http GET /api/v1/me/ HTTP/1.1 Host: zzd.show Authorization: Bearer Accept: application/json ``` Confirm that the returned account ID is the intended ZZD user and that returned scopes cover the planned operations. Stop on a mismatch. ## 3. Create a safe Show draft Develop and test in the user's local workspace first. Keep a local `.zzd/project.json`-style manifest containing non-secret information such as the canonical origin, Show ID, Piece names, source hashes, asset mount paths, and last validation notes. Never put the bearer credential in that manifest. ```http POST /api/v1/shows/ HTTP/1.1 Authorization: Bearer Content-Type: application/json Idempotency-Key: tilt-garden-create-20260801 { "title": "Tilt Garden", "desc": "An IMU-controlled visual study.", "prompt": "Preserve the three live Pieces, keep pointer and keyboard fallbacks, validate orientation permission on mobile, test blank/edit/embed states, and rerun the full matrix after dependency or motion changes.", "context": "tilt-garden", "category": "imu", "tags": ["imu", "interactive"], "cdns": ["https://cdn.jsdelivr.net/npm/three@0.179.1/build/three.module.js"], "esm": ["https://cdn.jsdelivr.net/npm/three@0.179.1/build/three.module.js"] } ``` The server ignores any attempt to choose public state and creates an owned `private=true`, `hidden=true` draft. Unknown/server-controlled fields are rejected. `context` must be unique for the owner when set; `category` is a bounded lowercase slug; dependency URLs must be bounded public HTTPS URLs without credentials or private/local hosts. Save the returned `data.id` and `data.links`. Use `GET /api/v1/shows/` to list owned Shows, `GET /api/v1/shows/{show_id}/` to retrieve one with Pieces/assets/BaaS/ collections, and `PATCH /api/v1/shows/{show_id}/` to update supported metadata. A PATCH returns a published Show to private/hidden draft. ## 4. Replace authoritative Pieces ```http PUT /api/v1/shows/{show_id}/pieces/ HTTP/1.1 Authorization: Bearer Content-Type: application/json Idempotency-Key: tilt-garden-pieces-a17c5e92 { "pieces": [ {"generic":"html","name":"index.html","raw":"

Tilt Garden

"}, {"generic":"css","name":"style.css","raw":"#app { min-height: 100vh; }"}, {"generic":"js","name":"app.js","raw":"document.querySelector('#app').dataset.ready = 'true';"} ] } ``` This is an atomic whole-tree replacement, not a merge. Read the current Show first during maintenance, include every Piece that must remain, and keep a local/known-good copy. At least one HTML Piece is required. Names are unique safe basenames of at most 30 characters; generic is exactly `html`, `css`, or `js`. Compiled/unplug/runtime fields and unsafe wrapper/dynamic execution patterns are rejected. Current size limits are returned by capabilities (currently up to 32 Pieces, 512 KiB each, 2 MiB combined). ## 5. Upload and bind safe assets Send exactly one `multipart/form-data` file per request: ```text POST /api/v1/shows/{show_id}/assets/ Authorization: Bearer Idempotency-Key: tilt-garden-asset-hero-v1 file= mount_path=images/hero.webp role=attachment context=Hero image for the ready state ``` The endpoint accepts allowlisted non-executable project asset bytes, creates an owned Upload and StaticFileBinding, and returns its stable `/assets/show/{show_id}/{mount_path}` runtime URL. While the Show is draft, the bytes stay in opaque private storage outside public media. The stable URL is available only through the normal Show-access/ signed-in owner browser check and is streamed with private/no-store; it is not a bearer asset-download API. There is no raw public-media object or public private-storage route to guess. Explicit publication promotes a public presentation copy while retaining the private source. Unpublish or a later Show content, Piece, asset, thumbnail, or BaaS mutation returns the Show to draft and removes promoted public copies after commit. The stable mount URL does not change across those states. The upload route does not fetch remote URLs or accept HTML, JavaScript, credentials, archives, or server code. Filenames and relative mount paths are validated; `..`, absolute paths, duplicate active mount paths, and ownership violations fail closed. Current limits are 50 MiB per asset and 1 GiB aggregate per account, subject to live capabilities. REST v1 does not currently replace or delete an existing asset binding. Use a new versioned mount path or the signed-in editor after exact impact review; do not invent a hidden deletion endpoint. ## 6. Provision bounded BaaS only when needed REST v1 currently provisions one isolated, private runtime-identity profile for an owned Show. It is not arbitrary Space/database access. ```http POST /api/v1/shows/{show_id}/baas/runtime-identity/ HTTP/1.1 Authorization: Bearer Content-Type: application/json Idempotency-Key: tilt-garden-runtime-state-v1 { "namespaces": ["app-state"], "guest_write": false, "confirm_guest_write": false, "guest_ttl_days": 30 } ``` Namespace names are bounded lowercase slugs. Anonymous guest writes are temporarily disabled in REST v1: `guest_write` must remain `false`. Use authenticated runtime identity for writes until ZZD publishes per-app, per-owner, and per-subject runtime quotas. Provisioning or changing BaaS returns the Show to draft. If a Show already has a backend outside this v1-managed isolated profile, the API returns a conflict instead of taking it over. Use the signed-in BaaS UI for broader supported designs. ## 7. Create and attach a collection when requested ```http POST /api/v1/collections/ HTTP/1.1 Authorization: Bearer Content-Type: application/json Idempotency-Key: imu-collection-create-20260801 {"title":"IMU Experiments","desc":"Motion and orientation Shows."} ``` Collection titles are currently at most 20 characters and unique per owner. Attach an owned Show with an empty JSON object: ```http PUT /api/v1/collections/{collection_id}/shows/{show_id}/ HTTP/1.1 Authorization: Bearer Content-Type: application/json Idempotency-Key: imu-collection-attach-show-42 {} ``` Both collection and Show must belong to the credential owner. REST v1 does not expose collection deletion or detach; use the signed-in UI with explicit target review when those actions are needed. ## 8. Validate, upload the thumbnail, and publish Open the returned blank, edit, embed, and detail links in a browser the user controls. Test desktop/mobile and every meaningful state as specified by `quality` and `handoff`. Server publication preflight checks metadata/source structure; it does not execute the browser experience or prove quality. REST v1 has no browser-validation operation. After the real browser matrix passes, capture a fresh final usable state and upload exactly one verified cover image: ```text POST /api/v1/shows/{show_id}/thumbnail/ Authorization: Bearer Idempotency-Key: tilt-garden-thumbnail-final-v1 file= ``` The endpoint accepts a non-animated JPEG, PNG, or WebP whose filename extension matches the decoded format. Current limits are 8 MiB, 320x180 minimum, and 20 megapixels maximum. It uses the account asset quota, replaces the previous developer-managed thumbnail, returns the Show to draft, and stores the bytes privately until publication. Verify the owned draft thumbnail without exposing a storage name: ```http GET /api/v1/shows/{show_id}/thumbnail/content/ HTTP/1.1 Host: zzd.show Authorization: Bearer ``` That response is private/no-store image bytes. It is not public evidence and another owner receives not found. Before publication the Show must have a nonempty description, category, at least one tag, at least one HTML Piece, a procedural prompt of at least 80 characters, and a verified thumbnail, and must pass source preflight. Only after successful validation and explicit user confirmation: ```http POST /api/v1/shows/{show_id}/publication/ HTTP/1.1 Authorization: Bearer Content-Type: application/json Idempotency-Key: tilt-garden-publish-confirmed-v1 {"action":"publish","confirm":true} ``` This is the only REST v1 route that can set public, visible state. A failed preflight returns HTTP 409 with structured failures; fix them rather than bypassing the check. ## 9. Maintain, recover, and unpublish For every update: GET the current detail, record a known-good source and asset manifest, PATCH only intended metadata, PUT the complete intended Piece tree, add versioned assets if needed, rerun the same browser matrix, POST a replacement verified thumbnail when visible output changed, and ask again before republishing. Draft-producing Show mutations set both `private=true` and `hidden=true`, retain private source bytes, and remove promoted public presentation copies; this intentionally stops public access during API maintenance. To withdraw access after explicit confirmation: ```json {"action":"unpublish","confirm":true} ``` Send that body to the same publication endpoint. It returns the Show to private/hidden state. REST v1 intentionally exposes no Show, asset, collection, BaaS-data, credential, or Sale deletion. Use the signed-in control plane and exact impact review for unsupported or destructive actions. Remote MCP JSON-RPC is not available. An MCP-capable client may read this public resource graph, then use the documented REST control plane as ordinary HTTPS. Do not fabricate MCP tool calls or translate REST into a claim that a remote MCP server exists.