# nerdstack > Agent-native static hosting on *.nerdstack.top. POST files with a bearer key, get a live URL. Built to be driven by AI agents; works from curl. Base URL: https://stack.nerdlabz.com Auth: every /api route except /api/health, /api/register, /api/register/resend, /api/verify requires Authorization: Bearer $NERDSTACK_DEPLOY_KEY Keep the key in a dotfile (.nerdstack-env), same pattern as .netlify/token or flyctl. No key yet? That's a human step: your operator registers at https://stack.nerdlabz.com/register (email + invite code) and hands you the key. You cannot register on their behalf. ## POST /api/deploy Create or atomically replace a site you own. Idempotent: same name overwrites. Three request shapes: 1) JSON, inline text (classic): {"subdomain": "my-app", "files": {"index.html": "...", "style.css": "..."}} 2) JSON, mixing inline text and binary refs from POST /api/uploads: {"files": {"index.html": "...", "img/logo.png": {"r2": ""}}} 3) multipart/form-data — the one-curl way to ship text + binaries together: curl -X POST $BASE/api/deploy -H "Authorization: Bearer $KEY" -F "index.html=@index.html" -F "assets/app.js=@app.js" -F "img/logo.png=@logo.png" Field name = site path. Small text parts stay inline; binary parts are stored as blobs automatically and referenced for you. Optional -F "subdomain=my-app". subdomain is optional — omit it and you get a random nerdy name (adjective + computing pioneer, e.g. "quantum-lovelace"). Validation: - subdomain: optional; omitted = random name, response carries "generated": true. If given: 3-63 chars, a-z0-9 and hyphens, must start and end alphanumeric; many names reserved - files: 1 to 16 entries, paths are relative and may nest (assets/app.js); no leading slash, no .. - inline text: UTF-8 strings, max 256 KB per file, 256 KB total per site - binary: max 10 MB per blob upload, 40 MB total blobs per site, 100 MB account-wide across all your live sites (content-addressed; identical files counted once — reuse is free) - sites: 3 live sites per account - rate: 100 deploys per day per account (UTC day; failed deploys don't count; 429 when reached) - expires_in (optional): site TTL in seconds, 3600 (1 hour) to 7776000 (90 days). Omitted = permanent. Demo/preview/throwaway deploys are the use case. 7-day and 1-day warning emails; expired sites 410 and stay restorable for 14 days, then hard delete. Redeploy with expires_in resets the clock; redeploy without it makes the site permanent. - index.html at the root is required - blocked file types (415): executables, installers, packages, disk images, and script launchers — by extension (exe, dll, msi, so, dylib, deb, rpm, apk, jar, dmg, pkg, iso, img, bat, cmd, vbs, ps1, sh, hta, and friends) and by content signature (Windows PE "MZ", ELF, Mach-O, Java class, shebang), so renaming a file does not help. nerdstack hosts websites, not software. - first deploy claims the subdomain for your account; later deploys require the same account (403 otherwise); names already serving platform sites return 403, not a takeover Success 200: {"ok": true, "subdomain": "my-app", "generated": false, "url": "https://my-app.nerdstack.top", "files": 2, "bytes": 431, "binary_bytes": 52210, "blobs": 1, "expires_at": "2026-09-21T00:00:00.000Z" | null, "created": true, "updated_at": "..."} Errors: 401 bad key, 403 not your subdomain, 413 over blob quota (per-file, per-site, or account-wide), 422 validation failed (error + hint name the exact problem). ## POST /api/sites/{subdomain}/extend Expiring site: add days (max(now, current expiry) + days, capped at 90d out) or make it permanent. {"days": 30} -> extends 30 days from now {"days": null} -> permanent; will never expire Also one-click from warning emails (signed link, no key needed). ## POST /api/sites/{subdomain}/restore Expired within the last 14 days? Bring it back. Returns with a 7-day TTL; extend with days:null to re-permanent it. {"ok": true, "restored": true, "expires_at": "...", "hint": "..."} ## POST /api/preview Anonymous, no key. Text-only site on a generated name with a TTL — the try-before-signup tier. curl -X POST $BASE/api/preview -H "content-type: application/json" -d '{ "files": {"index.html": "

hello from a preview

"}, "expires_in": 86400 }' Rules: inline text files only (binaries need an account), 128 KB total, max 8 files, index.html required, generated names only (you cannot pick one), TTL 1h–7d (default 24h), 3 live previews per visitor, no ownership, no restore. Previews are marked (x-nerdstack-preview: 1 header) and abuse-reported at /report. Good for: throwaway demos, agent experiments, sharing a quick page. ## POST /api/report Anyone. Flag a *.nerdstack.top site for a human review. {"subdomain": "bad-actor", "reason": "phishing"} ## GET /api/blobs Your uploaded blobs: sha, size, type, uploaded_at, referenced_by (your sites), orphan flag. curl $BASE/api/blobs -H "Authorization: Bearer $KEY" Response fields: count, total_bytes (everything you've uploaded), quota_bytes (referenced blobs only — this is what counts against your 100 MB account quota), truncated (R2 list paging past 1000). Orphan blobs (uploaded, referenced by nothing) don't count against the quota. ## POST /api/uploads Store one binary file in the blob pool; reference it from any deploy, any site, any number of times. curl -X POST $BASE/api/uploads -H "Authorization: Bearer $KEY" -H "content-type: image/png" --data-binary @logo.png {"ok": true, "sha256": "<64-hex>", "size": 52210, "deduplicated": false, "ref": {"r2": ""}} Then deploy with "img/logo.png": {"r2": ""} in files, or skip this endpoint entirely and POST multipart straight to /api/deploy. Identical bytes dedupe for free (deduplicated: true means it was already stored). Max 10 MB per file, 60 uploads/min per account. Executables, installers, disk images, and script launchers are rejected with 415 — by magic bytes, not just extension, so renames do not help. Rate-limited 429 on abuse. Body must be raw bytes (JSON or multipart here is a 422 with a pointer to the right endpoint). ## GET /api/sites List sites owned by the key's account. {"ok": true, "count": 1, "sites": ["my-app"]} ## GET /api/sites/{subdomain} Full record including file contents, if your account owns it. 404 otherwise. ## DELETE /api/sites/{subdomain} Undeploy immediately, if your account owns it. {"ok": true, "deleted": "my-app"} ## POST /api/sites/{subdomain}/rename Rename a site you own. Files and created_at move with it; the old URL 404s within a minute. The intended flow: deploy with no subdomain, iterate, then rename once you settle on a permanent name. body: {"to": "permanent-name"} {"ok": true, "subdomain": "permanent-name", "old_subdomain": "quantum-lovelace", "url": "https://permanent-name.nerdstack.top"} Errors: 422 target invalid/reserved/taken, 404 no such site under your account. ## POST /api/keys Mint an additional labeled key (one per agent is the pattern: claude-code, codex, hermes). body: {"label": "claude-code"} {"ok": true, "api_key": "nrk_...", "label": "claude-code"} The key is shown once. Lost it? Mint a new one, revoke the old. ## GET /api/keys List your keys (prefix only, never the full key). DELETE /api/keys/{id} revokes one. Revoking your LAST active key requires an email code: request it first (below), then send {"code":"123456"} with the DELETE. ## POST /api/keys/challenge Request an email confirmation code for a destructive key action. body: {"purpose": "rotate"} // or "revoke" (only needed for last-key revocation) {"ok": true, "sent": true} Code goes to the account email. 10-minute expiry, 5 wrong attempts kills it. ## POST /api/keys/rotate Nuclear reset: revokes EVERY key on the account (including the one making this call) and returns one fresh key. body: {"code": "123456"} // from POST /api/keys/challenge {"purpose":"rotate"} {"ok": true, "rotated": true, "api_key": "nrk_..."} This is the leaked-key response. Sites stay live; only keys die. ## POST /api/recover + POST /api/recover/confirm No valid key left? The account email is the trust root. No auth needed. POST /api/recover {"email": "you@example.com"} -> sends a code (always {ok:true}; no account-state leak) POST /api/recover/confirm {"email": "...", "code": "123456"} -> revokes ALL existing keys, returns one fresh key. Human-friendly page: /recover ## GET /api/health No auth. {"ok": true, "service": "nerdstack-control", "time": "..."} ## Serving behavior - https://{subdomain}.nerdstack.top serves files by path, correct content types - binary assets are content-addressed and served with a strong ETag: revalidation is a free 304, caching is immutable - paths without a file extension fall back to index.html, so client-side routers work - deploys are atomic replaces; HTML is live immediately, a fresh deploy can take up to a minute to reach every edge node - an unclaimed subdomain returns a 404 page explaining how to claim it - names are claimable later: deploy unnamed, then POST /api/sites/{sub}/rename to lock in one you like ## Minimal working example curl -X POST https://stack.nerdlabz.com/api/deploy \ -H "Authorization: Bearer $NERDS..._KEY" \ -H "content-type: application/json" \ -d '{"files":{"index.html":"

hello

"}}' Response includes the generated subdomain and live URL. Rename later with POST /api/sites/{sub}/rename. ## Roadmap custom domains with automatic SSL, dynamic server-side workers, deploy logs, blob garbage collection (orphaned blobs from deleted sites). Run by NerdLabz. Fleet status: https://ns.nerdlabz.com