Agents generate HTML constantly — reports, dashboards, prototypes, mockups — and then have nowhere to put it. Screenshots of markup are sad. ship.page is the missing last mile: one anonymous POST and the thing is on the public internet, on its own subdomain, with a link the agent can hand to its human.
Updated August 2026: the MCP endpoint now exposes nine tools (claim, restore and append joined the original set), and anonymous deploys return a one-time claim token. Both are covered below.
One POST, one link
There is nothing to set up. No account, no API key, no project, no region picker. The agent streams HTML at the endpoint and reads url out of the JSON response:
deploy.sh
curl -X POST https://ship.page/deploy \ -H "Content-Type: text/html" \ --data-binary @report.html→ { "url": "https://vast-juice-c2dse.shipped.run/", "claim_token": "spc_…", … }
Multi-file sites go up as a JSON files map ({"files": {...}}, base64 for binaries) or as a zip of the whole build folder. Every drop mounts at the root of its own unguessable subdomain, so absolute paths just work, and the URL itself is the access control — viewers never log in. Anonymous drops live 30 days; ?ttl= shortens that.
That claim_token in the response is new: a one-time spc_… credential, shown exactly once, that can later attach the drop to an account. An agent that keeps it can hand its human a way to rescue the drop from the 30-day clock — more on that in the lifecycle section.
Errors that talk to the human
The interesting part for agents isn't the happy path — it's what happens when they hit a wall. Every error is now a structured envelope: error and a machine-stable code, plus, when it helps, a hint, an upgrade_url and a docs_url.
The hint is written for the human, not the model. When an agent gets a 402 back from a subscriber-gated feature, the correct move is to relay the hint verbatim: sign in at ship.page, subscribe in the dashboard, mint an sp_ key, hand it back. The agent can't check out — but it can read the instructions aloud. Rate limits are equally explicit: a 429 always carries Retry-After: 60.
Parse code, not prose
error text can change; code is the contract. Switch on the code, relay the hint, respect the Retry-After. That's the whole error-handling strategy.
MCP, if you'd rather have tools
For agents that speak the Model Context Protocol, there's a stateless endpoint at POST https://ship.page/mcp (JSON-RPC 2.0, per the 2026-07-28 spec) exposing nine tools. Three publish — deploy_html, deploy_files, append_files — and six manage what got published: list_drops, claim_drop, restore_drop, delete_drop, get_limits and get_account. Auth is optional pass-through — an Authorization: Bearer sp_… header makes the tools act as that account; no header means anonymous deploys.
{
"mcpServers": {
"ship.page": { "url": "https://ship.page/mcp" }
}
}
The server is listed on the official MCP registry as page.ship/ship-page, plus Smithery, mcp.so and Glama — so most clients can find it on their own. Copy-paste configs for every major client live in the agent guides: Claude Code, Cursor, Claude Desktop, Windsurf and VS Code.
A call is ordinary JSON-RPC. Ask for a deploy, get a URL and a claim token back:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "deploy_html",
"arguments": { "html": "<h1>Q3 churn report</h1>…" }
}
}
The whole API is also published as an OpenAPI 3.1 spec at /openapi.json, and everything above — quickstart, limits, error contract, the upgrade hand-off — lives on one page written for machine readers: /docs/agents.
The security model: unguessable, unlisted, isolated
"Anyone with the link can view it" sounds alarming until you look at what the link is. Slugs carry roughly 47 bits of entropy — no public index, no directory, no sequential IDs to walk — so possession of the URL is the credential, and guessing one is a years-long spray of full HTTPS requests. The actual arithmetic is in 47 bits: why sharing a link can be the access control.
Three more properties matter for agent workloads specifically:
- Drops are unlisted, not public. Every drop serves with
X-Robots-Tag: noindex, nofollow, and we never enumerate drops anywhere. The only people who find a drop are the ones the link was handed to. - Files serve verbatim on Pro. What the agent uploads is byte-for-byte what the human's browser downloads on a Pro account. Other drops only add a small dismissible badge in browsers; stored files and non-browser fetches are never modified.
- Origin isolation. User content serves exclusively on
*.shipped.runsubdomains, never on the origin that holds anyone's session.
The honest boundary: a link is a bearer token. For reports, prototypes and review artifacts that's exactly right; for regulated data, put the page behind your SSO instead. The 47-bits post is blunt about where the model stops.
The lifecycle: thirty days, a claim token, and a way back
Anonymous drops expire after 30 days (?ttl= shortens it; nothing lengthens it anonymously). But expiry isn't a cliff edge:
- Past the deadline, an anonymous drop answers
410 Gonebut remains privately claimable for 30 days. Whoever holds itsspc_…claim token can claim it — dashboard,POST /drops/:slug/claim, or theclaim_dropMCP tool — and it comes back at the free-account lifetime of 90 days. - Owned drops answer
410 Goneat expiry, but their files are retained. Any owner can restore through 90 days: free accounts get a new 90-day lifetime, and a restored subscriber drop never expires again. Past that window, the files are gone for good.
The full matrix — lifetimes per plan, warning emails, restore paths — is in Drop expiry & restore. The practical upshot for agents: persist the claim token alongside the URL, and "my link died" becomes a recoverable error instead of a lost artifact.
API, MCP, or the GitHub Action?
Three surfaces, three shapes of work:
- An agent producing a one-off artifact — a report, a mockup, a dashboard — should call the REST API or the MCP tools. One call, one URL, zero setup.
- A human who wants their agent to deploy habitually should wire the MCP server into their client once (see the guides) and forget about it.
- Anything that runs in CI on every push belongs in the GitHub Action: test runs publish their HTML reports to a stable named URL per PR, with an auto-updating comment carrying the link. Recipes per tool — Playwright, coverage, Storybook, Lighthouse — live in CI reports and the use-case pages, and Your Playwright report shouldn't be a zip makes the case in full.
What a workflow looks like
A coding agent finishes a refactor and renders a before/after metrics report as HTML. It POSTs the file, gets https://warm-bison-x3k91pq2.shipped.run/, and drops the link in its reply. Thirty seconds of work, zero credentials, and the human is looking at a real page instead of a wall of markdown in a terminal.
Two weeks later the same agent tries ?name=daily-report and gets a 402. The body tells it exactly what to say: "this needs a paid plan — Pro is $4/mo, sign in, subscribe, mint a key, give it to me." The user does the two-minute human part, the agent retries with the key in the Authorization header, and the named drop goes through.
That's the shape we wanted: the machine-readable path is the default, and the moment a human decision is required — money, an account — the API says so in plain language the agent can pass along untouched.