Stacktree

by Unknown

Not rated
Website

About

Publish HTML to a private, unguessable URL from any MCP client. Gate by password or email domain; replace in place.

Details

Author
Unknown
Categories
Cloud Service, File Management, Other, Security
Stacktree docs ](https://stacktr.ee/) Agents Docs Use cases Pricing Blog Dashboard Search docs⌘K Getting started - Connect an agent CLI installer - Claude.ai - Claude Code · Codex - MCP config file - Slack Reference - HTTP API Auth - POST /sites - PUT /sites/:id - PATCH /sites/:id - List · raw · delete - Share tokens - API keys - MCP server - Agent payments - Custom domains - OAuth - Limits Plan errors Machine-readable - llms.txt - pricing.md - auth.md - x402.md Changelog · Blog Dashboard · Pricing

The publish primitive for agent-made HTML.

private by default MCP-native replace-in-place

Handing this to a coding agent? Give it the OpenAPI spec and go:api.stacktr.ee/openapi.jsonfor the HTTP API, oragents.stacktr.ee/openapi.jsonfor the pay-per-publish front-door. MCP server:https://api.stacktr.ee/mcp. Machine-readable index:stacktr.ee/llms.txt.

For a guided walkthrough with your actual API key inlined into snippets, openapp.stacktr.ee/connect.

If you have an agent open, give it this and it does the rest — installs, verifies, and learns the tool surface:

`Fetch and follow the setup instructions at https://stacktr.ee/prompt.md

Works in any agent that can fetch a URL. The instructions are plain Markdown atstacktr.ee/prompt.md— read them before you run them if you like.

One command wires every agent at once — Claude Code, Cursor, Codex, OpenCode, Amp — and drops thestacktree-publishskill in for Claude:

It signs you in via a one-time code atapp.stacktr.ee/connect/cliand mints an API key automatically; pass an existing key as an argument (npx stacktree-install stk_live_…) to skip sign-in.

One-line install. Identical syntax between the two:

claude mcp add stacktree -- npx -y stacktree-mcp codex mcp add stacktree -- npx -y stacktree-mcp

Both expose the same 18 tools (seeMCP server). SetSTACKTREE_API_KEYin your shell — generate one atapp.stacktr.ee/api-keys.

Prefer a skill? InstallsSKILL.md+ helper script into your agent's skills directory:

npx skills@latest add stevysmith/stacktree-skill export STACKTREE_API_KEY=stk_live_...

Source:github.com/stevysmith/stacktree-skill· the whole collection atstacktr.ee/skills

MCP config file — Cursor / Claude Desktop / Windsurf / Zed

{ "mcpServers": { "stacktree": { "command": "npx", "args": ["-y", "stacktree-mcp"], "env": { "STACKTREE_API_KEY": "stk_live_..." } } } }

Drop into~/.cursor/mcp.json,~/Library/Application Support/Claude/claude_desktop_config.json,~/.codeium/windsurf/mcp_config.json, or thecontext_serverskey in Zed's settings.

Add the Slack app(one approval; installing mints the workspace its own free identity, no Stacktree account needed). Then ⋮ → Host on Stacktree on any message with an.htmlor.mdfile — channels or DMs — posts a private link back into the conversation. Re-uploading the same filename republishes to the same URL./stacktree linkmigrates the workspace's sites into a dashboard account. Details:stacktr.ee/slack.

Three methods, all resolve to the same user context:

Authorization: Bearer stk_live_…API key Create at app.stacktr.ee/api-keys — or let an agent buy its own over x402 or MPP. Authorization: Bearer <clerk-session-jwt>session Clerk session token, for dashboard-originated calls. Authorization: Bearer <oauth-jwt>OAuth Access token from /oauth/token, used by custom connectors.

POST/sitesUpload a single HTML/markdown file or a zip.multipart/form-data. Anonymous uploads work — no auth header — and live 24 hours.

filefilerequired .html / .htm / .md / .zip public_slugstring Opt-in public subdomain; authed only. passwordstring Passcode gate on serve. Paid plans only for account publishes; a free account gets 402 plan_password_not_available. expires_in_hoursnumber | "never" Default: 24h anonymous, no expiry on a paid plan. Free is capped at 7 days: a longer value, or "never", is clamped down to the ceiling rather than rejected. Omit it over MCP and the plan's own default applies, same as the raw API. burn_after_read"true" Delete after first view. agentation"true" Inject feedback toolbar on serve. csp_strict"false" Disable strict CSP (default on). The strict policy allows Google Fonts and Loom / YouTube / Vimeo / Wistia / Descript / Calendly embeds, and blocks remote scripts and remote images. A publish that contains something the policy would block still succeeds and returns a warnings array saying what will not render. e2e"true" Treat upload as ciphertext; the decryption key lives in the URL fragment and is never sent to Stacktree. pii_checkoff | warn | block Default warn (MCP layer overrides to block).
curl -F file=@page.html \ -F password=hunter2 \ -F expires_in_hours=72 \ -H "Authorization: Bearer stk_live_..." \ https://api.stacktr.ee/sites
{ "id": "…", "url": "https://stacktr.ee/p/abc123…/", "visibility": "unlisted", "expires_at": 1781234567, "file_count": 1, "size_bytes": 1234, "has_password": true, "agentation": false }

PUT/sites/:idOrSlugReplace a site's files in place — the URL never changes. Same multipart fields as POST. Authed only; 401 if not the owner. E2E-encrypted sites must be replaced withe2e=trueuploads (no silent downgrade to plaintext).

curl -X PUT -F file=@new.html \ -H "Authorization: Bearer stk_live_..." \ https://api.stacktr.ee/sites/my-deck

PATCH/sites/:idOrSlugUpdate settings without re-uploading files. JSON body.

passwordstring | null Set or remove the passcode gate. Setting one needs a paid plan; removing one always works, including on a page that carries a grandfathered passcode. expires_in_hoursnumber | null null cancels expiry on a paid plan. On Free it is clamped to the 7-day ceiling. allowed_email_domainstring | null Viewers verify an email on that domain before the page renders. Paid plans only; Free gets 402 plan_viewer_gate_not_available. public_slugstring | null Claim or release a public subdomain. agentation · burn_after_read · csp_strictboolean Toggle the serve-time behaviors documented under POST.
curl -X PATCH \ -H "Authorization: Bearer stk_live_..." \ -H "Content-Type: application/json" \ -d '{"agentation": true, "expires_in_hours": null}' \ https://api.stacktr.ee/sites/my-deck
GET/sites List your sites. GET/sites/:idOrSlug One site, with file manifest + absolute preview_url. GET/raw/:token Page HTML stripped of head/scripts — clean text for re-feeding into an agent. Honors password gates. DELETE/sites/:idOrSlug Hard delete: removes R2 objects and metadata. On Free this does not give a page slot back, because the cap counts publishes, not live pages. Viewer numbers are plan-gated. GET /sites and GET /sites/:idOrSlug return metrics_locked: true, with view_count, unique_viewers and last_viewed_at as null, when the plan has no view metrics (anonymous and Free). A boolean opened comes through on every plan, so you can tell someone read the page without seeing how many did. The redaction is server-side, so an agent reading this JSON sees exactly what the dashboard does.
POST https://api.stacktr.ee/sites/:idOrSlug/share-tokens { "label": "alice", "max_uses": 5, "expires_in_hours": 168 } GET https://api.stacktr.ee/sites/:idOrSlug/share-tokens DELETE https://api.stacktr.ee/share-tokens/:tokenId

Returns a URL with?t=…appended. Bypasses the password gate when valid; revocable per-token; optional max-use counter and expiry.

GET https://api.stacktr.ee/sites/:idOrSlug/feedback POST https://api.stacktr.ee/feedback/:id/resolve { "note": "fixed the header spacing" } DELETE https://api.stacktr.ee/feedback/:id

Viewer annotations left via the on-page Agentation toolbar (enable withagentationon upload, PATCH, orset_agentation). Each item carries the comment plus the annotated element, selected text, intent, and severity. Unresolved first. The loop: read feedback → fix the page withupdate_site(same URL) → resolve.

GET https://api.stacktr.ee/sites/:idOrSlug/reactions { counts, total, reactors, messages } GET https://api.stacktr.ee/sites/:idOrSlug/engagement { sessions, median_active_seconds, avg_scroll, read_to_end_pct, buckets }

How a page landed, read by the owner. Reactions: turn on the on-page reaction bar per site (dashboard Settings) and viewers react with an emoji or leave a short private note, no account. Engagement (Studio and up): aggregate, non-PII read metrics: typical time on page, scroll depth, read-to-end rate, and a 10-bucket attention heatmap of dwell by page depth. No recording, no session replay. Both feed the dashboard activity bell and the optional daily email digest.

POST https://api.stacktr.ee/api-keys { "label": "claude desktop" } GET https://api.stacktr.ee/api-keys DELETE https://api.stacktr.ee/api-keys/:id

Streamable HTTP MCP server athttps://api.stacktr.ee/mcp(spec 2025-11-25). Connect from any MCP host that supports OAuth-based remote servers — seeConnect an agent.

publish_html→ { url, id, expires_at, … } One call, one private URL. update_site→ { url, file_count, size_bytes } Replace in place; the URL survives every revision. get_site→ { url, settings, … } Read a published page back. set_password→ { ok: true } Passcode-gate a site. Paid plans only. set_expiry→ { ok: true } Schedule or cancel auto-delete. Cancelling needs a paid plan; on Free the value is clamped to 7 days. set_email_gate→ { ok: true } Restrict viewers to an email domain. Paid plans only. set_agentation→ { ok: true } Toggle the on-page feedback toolbar. list_sites→ { sites: [...] } Everything the key owns. delete_site→ { ok: true } Hard delete. link_wallet→ { ok, adopted } Link the wallet an agent pays from, to claim and manage the pages it published. list_feedback→ { feedback: [...] } Read viewer annotations left via the toolbar; unresolved first. resolve_feedback→ { ok: true } Mark an item addressed after fixing the page, with an optional note. set_client→ { ok: true } File an existing page under a client space, by name or slug. The space is created if it doesn't exist; pass null to detach the page again. Filing is free on every plan. list_client_spaces→ { spaces: [...] } Every client space on the account: slug, name, page count, last activity, the bound hostname if there is one, and whether its client portal is on. create_client_space→ { ok, space } Create a space up front. Rarely needed — publishing with client already creates one. update_client_space→ { ok: true } Rename, archive or restore a space, or set the space-wide viewer gate (a passcode or an email domain) that every page filed under it inherits. delete_client_space→ { ok, detached_pages } Remove a space. Its pages detach and keep their URLs — deleting a client's space never deletes the client's work. get_design_guide→ { guide } The house design guide. Read it before generating or restyling a page so the result arrives looking finished rather than default-styled.
Agents act autonomously without a human reviewing every flag. The MCP layer applies tighter defaults than the raw API: - Plan-aware expiry. Omitting expires_in_hours takes the plan default — 24h anonymous, 7 days on Free, no expiry on a paid plan. Pass "never" for permanence, which a paid plan honours and Free clamps back to 7 days. - Safety scan in block mode (raw API: warn) — blocks accidental publication of personal data or secrets. The scan protects your content; nothing is collected or stored. - Unlisted token URL, strict CSP (Google Fonts + named video embeds allowed; remote scripts and images blocked), X-Robots-Tag: noai — same defaults as the raw API.

The dashboard registers the same verbs ondocument.modelContextwhere the browser supports it (Chrome origin trial), so an in-browser agent helping a signed-in human can call them without an API key.How and why.

Want the same pattern in your own app? The palette and WebMCP registration are built onagentk, our open-source cmdk extension: define tools once as JSON Schema, humans get generated forms, agents get the schemas.

An agent pays with no human and no account, two ways: per publish at the front-door, or with a persistent key bought once. Both acceptx402(USDC on Base or Solana) andMPP(USDC.e on Tempo), so the agent pays with whatever it holds, gasless for the payer. Machine-readable version:x402.md.

The simplest path, when there is no key and no human to make one. POST your HTML toagents.stacktr.ee/api/publish, get a 402, pay $0.50 over x402 (Base or Solana) or MPP (Tempo), and the page publishes to a private link with the URL in the response. No provisioning step. The endpoint is listed on x402scan and mppscan; its spec is atagents.stacktr.ee/openapi.json.

Persistent key: provision once, then pay as you go

GET/provision Lists the accepted payment rails. POST/provision 402 → pay $1.00 over x402 or MPP → persistent stk_live_ key, no account. The key carries free-tier limits: 3 pages in total, each expiring after 7 days, no passcodes. Lift them with an unlock below. GET/unlock The à-la-carte catalog: make-permanent $5 per page, custom domain $5/30d, higher limits $25/30d. POST/unlock 402 → pay → feature entitlement on your key. POST/pay/sessions No wallet? Returns a pay link + terminal QR; a human pays by card in two taps. Poll GET /pay/sessions/:code/poll.

Paying above the price in a pay session (up to $20) leaves a prepaid balance on the key that later paid actions draw from silently. Balances never expire and are refundable on request.

The wallet that pays at the front-door is recorded with each page, so it doubles as a claim ticket. A human can link that wallet from thedashboard(generate a code, the agent signs it), or the agent can self-link with thelink_walletMCP tool. Every page the wallet published then becomes owned and manageable.

Paid plans (Solo 1 domain, Studio 10, Firm 25), or thecustom_domainx402 unlock. Bring your own hostname (docs.acme.comet al), point a CNAME at our Cloudflare for SaaS fallback origin, prove ownership via a TXT record, and traffic to that hostname serves your site over HTTPS.

curl -X POST https://api.stacktr.ee/custom-domains \ -H "Authorization: Bearer stk_live_..." \ -H "Content-Type: application/json" \ -d '{"hostname":"docs.acme.com","site_id":"abc123"}'

Response includes averify_tokenand the two DNS records you need to add:

{ "hostname": "docs.acme.com", "site_id": "abc123", "verified": false, "instructions": { "cname": { "name": "docs.acme.com", "value": "proxy.stacktr.ee", "type": "CNAME" }, "txt": { "name": "_stacktree-verify.docs.acme.com", "value": "verify_", "type": "TXT" } } }

POST/custom-domains/:hostname/verifyAfter adding the DNS records, call verify. We DNS-lookup the TXT record; on match we register the hostname with CF for SaaS and SSL provisioning begins (~60 s).

curl -X POST https://api.stacktr.ee/custom-domains/docs.acme.com/verify \ -H "Authorization: Bearer stk_live_..."
Gotcha — DNS-only CNAME. If your DNS is on Cloudflare, the CNAME must be set to DNS only (grey cloud), not Proxied (orange). A proxied CNAME makes Cloudflare claim the hostname for your own zone and Stacktree's SaaS routing never sees the SNI.
PATCH https://api.stacktr.ee/custom-domains/:hostname # { "site_id": "..." } — re-bind DELETE https://api.stacktr.ee/custom-domains/:hostname # unregister + drop row

List your domains withGET https://api.stacktr.ee/custom-domains. Unverified rows are auto-pruned after 7 days.

For MCP host implementers — if you're using a maintained client (Claude.ai, Cursor, etc.) skip this section.

GET https://api.stacktr.ee/.well-known/oauth-authorization-server GET https://api.stacktr.ee/.well-known/oauth-protected-resource

Both return standard RFC 8414 / RFC 9728 metadata documents.

OAuth 2.1 with PKCE (S256 required) and Dynamic Client Registration (RFC 7591). Endpoints:

POST/oauth/register DCR — rate-limited to 10/IP/hour. GET/oauth/authorize Bounces to Clerk-gated consent page on app.stacktr.ee. POST/oauth/token Code → access token (HS256 JWT, 30-day TTL). POST/oauth/revoke RFC 7009 revocation.

Callback for hosted Claude surfaces:https://claude.ai/api/mcp/auth_callback.

Every number below is enforced server-side from one table.GET /mereturns the caller's ownlimitsobject; read it from there rather than hard-coding a cap in a client.

LimitAnonymousFreeSolo $19Studio $79Firm $249 Pages—3 in total25 activeunlimitedunlimited Page lifetime24h7 days, alwayspermanentpermanentpermanent Publishes / 24h20 per IP501,0001,000unlimited Per-site size10 MB25 MB250 MB250 MB1 GB Files / archive1,0001,0001,0001,0001,000 Passcodes · email gatespasscode only—✓✓✓ Viewer numbers——opens, views, last opened+ full engagement+ full engagement Custom slug—✓✓✓✓ Custom domains——11025 Client spaces——110unlimited Stacktree badgestaysstaysremovedremovedremoved

The client-spaces row counts ACTIVATED spaces, meaning ones with a hostname bound or a portal enabled. Filing pages under a client is free on every plan, Free included: the paid unit is the address, not the label. Archiving a space frees the slot, and a space hostname does not consume a custom-domain slot.

Free counts publishes, not live pages. The 3 islifetime_publishes, a counter that only goes up: deleting a page or letting it expire does not give the slot back. Solo's 25 is the other model: active pages, freed by deleting. Every free page expires 7 days after it is published, and passingexpires_in_hours: "never"clamps to that ceiling rather than failing.

DCR rate limit: 10 client registrations / IP / hour. Sites auto-purge from R2 + D1 within 1 hour of expiry; the URL then serves a page saying the link expired.

Enterprise is custom and annual (self-hosting, DPA, SLA, data residency). Ask atgm@stacktr.ee. Accounts on the older Pro and Agent plans keep the limits they signed up on; neither is sold any more.

No plan? Thehigher_limitsunlock ($25 / 30 days overx402) lifts a free identity to fleet limits: 1 GB per site, unlimited daily publishes, no page cap, and pages that do not expire.make_permanent($5, one page) cancels the expiry on a single page instead.

Each carries a stableerrorcode, the caller'splan, a humanmessage, and where relevant alimit. Surface them as an upgrade prompt, not a raw string:

CodeStatusMeans plan_lifetime_limit_exceeded402All 3 free pages used. Deleting one does not help. plan_site_limit_exceeded402Active-page cap reached (Solo). Delete one, or move up. plan_password_not_available402Passcodes are not on this plan. plan_password_limit_exceeded402Passcode-protected page cap reached. plan_viewer_gate_not_available402Email gates are not on this plan. plan_viewer_gate_limit_exceeded402Email-gated page cap reached. plan_domain_not_available402Custom domains are not on this plan. plan_domain_limit_exceeded402Custom-domain cap reached. plan_space_not_available402Activating a client space (address or portal) is not on this plan. Filing pages under a client still works. plan_space_limit_exceeded402Activated client-space cap reached. Archive or deactivate one, or move up. plan_upload_limit_exceeded429Daily publish cap reached; resets on a rolling 24h window.

Expiry is the exception: it is clamped, not refused. A page lifetime longer than the plan ceiling comes back shortened inexpires_at`, with no error.

Full markdown summary of the Stacktree marketing surface:https://stacktr.ee/llms-full.txt

Publish static sites from your AI chat with one tool call — no account.

Securely retrieve credentials from 1Password for use by Agentic AI.

MCP server for BulkRender — generate bulk DOCX and PDF documents from Claude, Cursor, Windsurf, and any MCP-compatible AI assistant

No reviews yet — be the first

Sign in to leave a review

Use Google, GitHub, or an email account so ratings stay tied to real people.

Email sign in

No reviews posted yet.