# WarpTunnel — HTML in, link out
Base URL: https://warptunnel.com OpenAPI: https://warptunnel.com/openapi.json This guide: https://warptunnel.com/llms.txt
WarpTunnel publishes a self-contained HTML file at a public URL. Management requires one API key.
Public URLs need no login: anyone with the link can view them. The page directory and metadata are
private to the key holder.
## Start here
Set WT_URL to the base URL and WT_TOKEN to the owner's API key in the agent's environment. WT_TOKEN
is the same secret configured on the server as WARPT_API_KEY. Never put the key in HTML, query
strings, or published content. Supply it as a Bearer header.
```bash
export WT_URL="https://warptunnel.com"
# WT_TOKEN is provided by the owner through the environment.
curl --fail-with-body "$WT_URL/api/v1/pages" \
-H "Authorization: Bearer $WT_TOKEN" \
-F file=@report.html -F title="Build report"
```
The response is a JSON page with id, title, url, createdAt, updatedAt, expiresAt, keep, bytes, and
version. Share `url`, and retain `id` for future operations. expiresAt is an ISO 8601 UTC timestamp,
or null when retained indefinitely.
Public links look like `https://warptunnel.com/p/calm-otter-7k2m`. Page IDs contain two familiar
lowercase words (3–8 letters each) and a random four-character suffix, separated by hyphens. The
suffix uses digits `2`–`9` and lowercase letters except `l` and `o`. Use the ID exactly as returned;
uppercase and older ID formats are not accepted. Public pages and the API share the same hostname;
management still requires the API key.
## Optional CLI
The HTTP API works without a CLI installation. The `warpt` CLI currently requires a separate source
build or packaged tarball; it is not published to npm. If it is already installed:
```bash
warpt publish report.html --title "Build report"
warpt publish diagram.html --keep --json
cat report.html | warpt publish - --title "Report" --json
warpt list --json
warpt info --json
warpt update revised.html
warpt keep
warpt expire --days 30
warpt delete
warpt get > saved.html
```
Publishing prints the URL; --json prints full metadata. `list` follows pagination automatically.
Commands never prompt. Help is available at `warpt --help` and `warpt --help`. WT_URL and
WT_TOKEN override saved settings; `warpt config set --url URL --token-stdin` saves a token read from
stdin with owner-only file permissions. `warpt config show` always redacts it.
## HTTP interface
All /api/v1 routes require `Authorization: Bearer `.
| Method | Path | Behavior |
| ------ | --------------------------------- | ------------------------------------------- |
| POST | /api/v1/pages | Publish HTML; optional title and keep |
| GET | /api/v1/pages?limit=50&cursor=... | List active pages; returns pages and cursor |
| GET | /api/v1/pages/{id} | Read metadata |
| GET | /api/v1/pages/{id}/content | Download original HTML |
| PUT | /api/v1/pages/{id} | Replace HTML; optional title and keep |
| PATCH | /api/v1/pages/{id}/retention | Set retention using JSON |
| DELETE | /api/v1/pages/{id} | Delete; repeating the request is safe |
Uploads support three formats:
1. multipart/form-data: one `file`, optional `title` and `keep=true|false`.
2. application/json: `{"html":"Hello
","title":"Hello","keep":false}`.
3. text/html: raw UTF-8 HTML; optional `?title=Hello&keep=true` query parameters.
HTML may be a document or fragment, up to 2 MiB. Inline scripts, CSS, SVG, and charts are supported.
Assets must be embedded or use absolute public URLs; this is a single-file host, not a ZIP or
site-folder deployment service. Pages are served with a sandboxed origin: cookies, localStorage, and
same-origin API access are unavailable. Ordinary script execution and external assets still work.
For list results, pass the returned cursor until it is null. A batch can be empty while still
containing a cursor because expired and deleted pages are filtered. Ordering is by page ID, not
creation time. The CLI collects and sorts by update time.
## Retention
New pages expire 30 days after publishing unless keep=true. Updating temporary HTML restarts its
expiration at 30 days; updating kept HTML preserves indefinite retention unless you explicitly send
keep=false. Titles are preserved on updates unless supplied. URLs remain stable through updates and
retention changes.
Keep indefinitely: `PATCH /api/v1/pages/{id}/retention` with `{"keep":true}`.
Restore expiration: `PATCH /api/v1/pages/{id}/retention` with `{"keep":false,"days":30}`. days
defaults to 30 and must be an integer from 1 through 30. The timer starts now. Longer-lived pages
should use keep=true. An expired page cannot be restored; publish a fresh copy. Keep indefinitely
means until explicitly deleted.
Expired URLs and metadata return 404 immediately. R2 eventually removes temporary HTML via its
30-day lifecycle rule. Shorter expiration hides pages earlier; it does not accelerate physical
deletion. Tiny metadata records remain for listing and concurrency protection; deleted records
retain no title or content.
## Errors and retries
Failures return JSON: `{"error":{"code":"...","message":"...","docs":"..."}}`. 400 means invalid
input; 401 missing/wrong token; 404 missing/expired page; 409 means an overlapping mutation or stale
version; 413 means too much HTML; 415 unsupported Content-Type; 503 means the owner has not
configured a key or could not allocate a page ID (retry publishing). The CLI exits nonzero and sends
diagnostics to stderr. In --json mode, errors are JSON on stderr, leaving stdout empty.
Metadata returns a version and an ETag containing that version. For updates, retention changes, and
deletion, optionally send `If-Match: ""`; a stale version returns 409. On conflict, read
current metadata before deciding to retry. The CLI supports --if-match on these commands.
Publishing creates a new page each time. If a POST times out, list recent pages before retrying to
avoid duplicates. Updates target an existing ID and preserve its public URL. No endpoint publishes
its authentication token.