Put a thing on the internet.
It'll be lovely.
Two products, one machine. Free puts a file online in seconds — it lives for seven days. Managed builds a durable app with a private database. Pick one; the docs never mix them.
What you can publish
Everything is one self-contained file. Paste it in, get a URL back, share it.
Web apps & pages
A single HTML file becomes a live page: a landing, a tool or a game.
Markdown documents
Write in Markdown, ship a typeset page. Tables and code render for you.
Mermaid diagrams
A flowchart or architecture in plain text, published as a zoomable visual.
Data explorers
JSON or CSV in, instant explorer out: search, filter, sort and chart.
Set a password and visitors are asked for it before the page
opens. Set one_time: true and the page deletes itself after the
first visit.
Quickstart
Make something
One self-contained file.
Publish it
The machine, one curl, or your agent.
Share the URL
sites.hostan.app/<slug> — works immediately.
Forget about it
Self-deletes after seven days.
curl -X POST https://api.hostan.app/api/v1/deploy \
-H "Content-Type: application/json" \
-d '{"type": "markdown", "content": "# Notes\n\nEverything I remember."}'
# → 201 Created
{ "slug": "notes", "url": "https://sites.hostan.app/notes",
"expires_at": "…seven days later…" }
Publish via AI agent
Add the MCP server once — Claude Code, Cursor or VS Code — and
your agent gains deploy_asset: content in, live URL back, plus
read_docs — the full agent instructions in-band. Free, no
credential.
{
"mcpServers": {
"hostan": { "url": "https://api.hostan.app/mcp" }
}
}
Limits
Deliberately small: fast to publish, easy to forget.
| Surface | Rate limit |
|---|---|
| POST /api/v1/deploy | 30 req/min per IP, burst 15 |
| POST /mcp | 30 req/min per IP, burst 15 |
| POST /api/v1/auth/<slug> | 10 attempts/min per IP |
| /<slug>/__visit | 60 beacons/min per IP |
| POST /api/v1/reports | 10 reports/min per IP |
| Static content | No rate limit |
Over a limit: 429 with Retry-After: 60 —
back off and retry. Platform full: 503 with a reason. Slugs are
lowercase, hyphenated, 3–63 chars; a few names are reserved.
The model
Managed is one durable envelope: a project holds your pages and a private database, and everything shares its clock. Who gets what:
no token
Managed is self-serve: sign in and a personal workspace is created for you on the spot — free, with 1 project. Pro (€5/month, tax included) raises the cap to 5 projects. A workspace credential from the operator remains the path for machine callers.
Workspaces & the dashboard
A workspace is the ownership boundary: it holds your projects and bills your plan. A credential authenticates a user, and that user's membership in a workspace carries the role they act with there — one account can belong to several workspaces, each with its own role.
| Role | What it grants |
|---|---|
| owner | Everything: projects, members, billing and deletion. A workspace always keeps at least one. |
| admin | Projects and members below owner — it cannot grant or demote owner. |
| editor | Create and change projects, their frontends and backends. |
| viewer | Read-only. |
Every account gets a personal workspace at sign-in (they own it); owners add teammates there or to a shared workspace. Authority is read live on each request, so removing a member takes effect immediately — no re-login and no token to re-mint.
Plans
A workspace holds exactly one plan, and the plan is what caps its projects. Upgrades are a payment, not a setting: a paid plan can never be asserted — it is granted when the payment provider confirms the subscription, and never before.
| Plan | Projects | Price |
|---|---|---|
| free | 1 | — |
| pro | 5 | €5.00 / month, tax included |
# owner floor: returns a hosted checkout URL for a human to open
POST /api/v1/workspaces/{id}/billing/checkout → { "url": "https://checkout.stripe.com/…" }
# the plan moves when the provider confirms the subscription — not before
GET /api/v1/workspaces/{id} → "plan": "pro"
# asserting a paid plan is refused, on purpose
PATCH /api/v1/workspaces/{id} { "plan": "pro" } → 403, naming the price
# the way out: the provider's own portal, where the owner cancels
POST /api/v1/workspaces/{id}/billing/portal → { "url": "https://billing.stripe.com/…" }
Cancel or let a subscription lapse and the workspace returns to free — your projects are never deleted. The plan is a limit, not a deletion: the workspace just cannot create more projects than the free allowance until it upgrades again. Both ends are dashboard buttons: Upgrade on a free card opens checkout, and Manage billing on a paid one opens the provider's portal — where the owner changes the card or cancels. Cancelling starts at the provider on purpose: the subscription ends there, and the workspace returns to free when the provider reports it.
manage_workspace { "action": "list" } # the workspaces you belong to, with your role
manage_workspace { "action": "get", "workspace": "acme" }
manage_workspace { "action": "add_member", "workspace": "acme",
"user_id": "usr_…", "role": "editor" }
manage_workspace { "action": "set_role", "workspace": "acme",
"user_id": "usr_…", "role": "viewer" }
# or over REST: GET /api/v1/workspaces (only your memberships)
Members and roles
Owners and admins invite people into a workspace and set each one's role; the last owner can never be removed, and no one may grant a role above their own.
The dashboard
auth.hostan.app/dashboard is the human console: sign in through the platform's own authorization server to manage your workspaces (plan usage, billing, members, API keys), open a project to see its frontends, visits and database at a glance, and create projects without touching a terminal. Server-rendered with a session cookie — no token ever reaches the browser.
Agents that connect over OAuth meet a consent page first: it names the client, the requested scope and the workspaces the client could act in. Deny cancels the connection (error=access_denied) and grants nothing. The token identifies the user, so a workspace created later is reachable without re-authorizing, and a revoked membership stops working on the very next request.
Build an app in three calls
Create the project
the envelope
Attach the backend
the database
Publish the pages
wired automatically
# once: the credential on the hostan server entry
"hostan": { "url": "https://api.hostan.app/mcp",
"headers": { "Authorization": "Bearer hstn_…" } }
manage_project { "action": "create", "name": "Tasks" }
manage_backend { "action": "create", "project_id": "0193…",
"ddl": "CREATE TABLE todos (id INTEGER PRIMARY KEY, title TEXT NOT NULL);" }
manage_frontend { "action": "create", "project_id": "0193…",
"type": "html", "content": "<h1>Hi</h1>", "requested_slug": "tasks-ui" }
Pages published after the backend exists get the
hostan.db runtime automatically; earlier ones need one
manage_frontend update. Evolve later with
manage_backend update — pages pick up changes on their next call.
For agent-managed edits, prefer the revision loop: create_revision
publishes a change as an unlisted preview (your live page is untouched) and
promote releases it — so nothing ships to the canonical URL until
you explicitly say so, and a broken release rolls back with rollback.
hostan.db — actions, not SQL
Visitors never send SQL and never see a credential. Pages call named actions you define; raw SQL stays owner-only.
const res = await hostan.db.op("add_task", { title });
// res.rows[0].title — rows come back as objects
Tokenless for visitors
No URLs, IDs or keys in the page — connectivity is bound by the project.
Versioned actions
Checksummed, one gated statement each, exact-param contract. Business rules live in the SQL and constraints.
Owner-only raw SQL
/api/query and /api/schema answer only to your
workspace credential — never to a visitor.
Visitor sign-in
Frontends can sign visitors in with Google or GitHub — run
by the platform, no per-app OAuth setup, no session code. Pages read the
visitor through the injected hostan.auth runtime, and each app
owner picks which methods its frontends offer.
const user = hostan.auth.user(); // null, or { id, email?, name?, picture?, provider?, locale?, expiresAt? }
hostan.auth.login(); // redirect into sign-in, back to this page
hostan.auth.login('github'); // or force a specific method
hostan.auth.providers(); // the app's allowed sign-in methods
await hostan.auth.logout(); // revoke, then reload
| Field | What it is |
|---|---|
| id | Stable platform user id (usr_…) — store it as the ownership column. |
| email · name | Present when Google verified or asserted them. |
| picture | The provider avatar URL — drop it straight into an <img>. |
| provider | The authenticating provider, e.g. google. |
| locale | The visitor's language tag, when the provider asserts one. |
| expiresAt | The session's hard end (RFC3339 UTC); expired sessions resolve back to null. |
Sign-in finishes on the platform's identity origin
(auth.hostan.app) and returns the visitor to the origin
they started from — the app's own subdomain, not a shared one. Sessions are
per-frontend and project-scoped — signing in on one
frontend never signs the visitor in on a sibling. Actions can bind the reserved
:hostan_user_id / :hostan_user_email: the platform injects
the signed-in visitor's identity, so rows are scoped per user with zero session
code.
Real-time rooms
A backend with realtime: true gets one
project-scoped WebSocket relay and an injected hostan.ws runtime:
chat, presence, shared cursors, live dashboards and lightweight games — no
external service.
const room = hostan.ws.connect('game-abc');
room.on('open', async () => reloadStateFromDb());
room.on('message', (env) => { // env = { v, type, data, sender, room, ts }
if (env.type === 'move') renderMove(env.data);
});
room.on('presence', (p) => updateCount(p.members));
room.on('close', () => showReconnecting());
room.send('move', { row: 1, column: 2 });
room.close();
Enable it at create (manage_backend with
realtime: true) or flip it later with a standalone
update {"realtime": true|false}. Frontends published after the
capability was enabled carry the runtime; older pages need one republish.
Rooms are strictly scoped: same backend + same room, never across backends,
and the hostan:* event family belongs to the platform.
The relay is deliberately ephemeral: no history, no replay,
no delivery receipts, no offline inbox. A restart ends all sockets — the
runtime reconnects with capped backoff, and your app reloads authoritative
state from hostan.db on open. Realtime is public
(it is not authentication): keep the source of truth in the database, use
:hostan_user_id actions for identity, and broadcast only
transient state. Limits: 16 KiB frames, ~10 messages/second per socket,
100 sockets and 100 rooms per backend, 20 sockets per visitor IP.
Managed payments
Frontends can collect one-time payments through the platform's managed Stripe integration — hosted checkout, verified webhooks, and the entitlement verdict, all operated by the platform. No per-app keys, no webhook code, no amounts in the browser.
hostan.payments.checkout('price_…'); // redirect into hosted checkout
const pays = await hostan.payments.status();
// [{ price, status: 'paid', amount, currency, user_id?, email?, matched_by, created_at }]
Define prices with
POST /api/v1/projects/{id}/payments/catalog
({product_name, label, amount, currency, recurring?} — amount in
minor units) or the manage_payments MCP tool
(add_price, catalog, deactivate_price,
sessions). The returned price_… id is the only
thing a page ever handles: the visitor picks a price, never an amount.
A price flagged recurring bills as a monthly subscription;
otherwise it is a one-time payment. The entitlement is granted on the
first verified success webhook either way, and the platform enforces
the lifecycle both ways — see below.
Checkout creates one hosted Stripe session and redirects the visitor; the
verdict comes only from a signature-verified, deduplicated webhook that marks
the session paid and materializes a row into the project's database. Reads
through status() are authoritative platform state — the success
redirect lands on a platform confirmation page at
/<slug>/payments/success (canceled when the visitor
abandons) and is UX, not proof. For signed-in visitors, payments match the
platform user; an anonymous visitor is recognized only through this
browser's checkout cookie (30 days) — a payment made without signing
in is invisible from another browser or device. Each entry also carries
matched_by ('user_id', 'email' or
'cookie', strongest wins) so an app can tell a
platform-identity match from the email convenience: the email match is
what lets a later sign-in claim an anonymous checkout, and since
addresses get recycled, two different accounts sharing a
provider-verified email both resolve paid on it — require
matched_by === 'user_id' to trust platform identity only.
The lifecycle is
enforced both ways: a full refund or a terminated subscription
revokes the entitlement (it disappears from status()),
while a failed renewal is dunning — access continues while Stripe
retries.
The platform also writes a reserved
hostan_payments table into your database for JOINs and
composability — a derived view you can query in your own actions, but treat
status() as the hardened gate. The hostan_ table
prefix is reserved platform-wide: your DDL may not create
hostan_* tables. Where the platform has payments unconfigured,
checkout answers 503 — the feature fails closed, it never half-exists.
Limits
| What | Rule |
|---|---|
| Project lifetime | You choose: a TTL or permanent (default). Members inherit — no per-page TTL. |
| Backend size | 5 MB, enforced by SQLite itself — an over-cap write fails atomically with 413. |
| Actions API | 120 req/min per IP (burst 30) + 300 req/min per deployment. |
| Projects per plan | 1 on free, 5 on pro (€5.00/month, tax included) — creating beyond the limit is a 403 naming the plan. |
| Changing plan | pro is granted by a subscription, never asserted (a plan update to it is a 403). Cancel in the provider's billing portal; the workspace returns to free with its projects intact. |
| Schema changes | Additive only — no DROP, no type changes, no destructive ALTER. |
Full technical reference
Every tool call, endpoint, parameter, schema and error code — for both products — lives in the reference your agents use.
Reliability
99.9% monthly availability target — under 44 minutes of downtime per month. Static-first architecture on Cloudflare and Hetzner; a target, not a contractual guarantee.
FAQ
Seven days, then it removes itself. Managed projects live as long as you chose — permanent by default.
Not for Free: no signup, no API key. Managed is self-serve now: sign in and a personal workspace is created for you on the spot — free, with 1 project.
Yes, set a password when you publish and visitors are asked for it before they can open the page.
Yes. One-time deployments remove themselves after the first visit — handy for sharing something sensitive once.
Add api.hostan.app/mcp as an MCP server in Claude Code, Cursor
or VS Code, and your agent can publish for you.
Head to the interactive reference — endpoints, schemas, the Projects contract and every error code.