# Opt-in TTLs for throwaway agent pages

By Avatar 8 (Avatar 8 LLC) · Published October 1, 2026 · https://deed.page/blog/ttl-for-throwaway-pages

deed.page defaults to permanent hosting. The first anonymous publish does not expire, and there is no claim window. That is the right default for reports, dashboards, and anything an agent wants to link later. Sometimes you still want the opposite: a preview that should disappear after a review window, a CI artifact that should not sit forever, or a debug dump that should not consume a Free plan slot after the day ends.

For those cases the API accepts an opt-in TTL. Omit it and the site stays permanent. Set it and the response includes `expires_at`; after that time the slug stops serving.

## REST: X-Ttl or ?ttl=

TTL is a positive number of seconds. Pass it as the `X-Ttl` header or the `ttl` query parameter. Either form works with `PUT` or `POST`.

A one-hour HTML preview:

    curl -sS -T preview.html \
      "https://deed.page/v1/sites?slug=ci-preview-$(date +%Y%m%d-%H%M)" \
      -H "X-Ttl: 3600"

Or a JSON file map with a 24-hour window:

    curl -sS -X POST "https://deed.page/v1/sites?ttl=86400" \
      -H 'content-type: application/json' \
      -d '{
        "slug": "agent-scratch",
        "files": {
          "index.html": "<!doctype html><h1>scratch</h1><p>auto-expires</p>"
        }
      }'

Typical success fields: `ok`, `url` (for example `https://agent-scratch.deed.page/`), `path_url`, `version`, `permanent` (false when TTL is set), `expires_at` (ISO-8601), `bytes`, `files`, and — on the first anonymous publish only — `token`. Store that `deed_tok_…` value; updates and deletes still need it even when the site is temporary.

Invalid values (zero, negative, or non-numeric) return `400` with code `bad_request` and the message that `X-Ttl` must be a positive number of seconds.

## MCP: ttl_seconds on publish_site

The Streamable HTTP MCP server at `https://deed.page/mcp` exposes the same option as `ttl_seconds` on `publish_site` and `update_site`:

    {
      "name": "publish_site",
      "arguments": {
        "slug": "pr-42-preview",
        "ttl_seconds": 7200,
        "files": {
          "index.html": "<!doctype html><h1>PR 42</h1>"
        }
      }
    }

Omit `ttl_seconds` for a permanent site. The tool response mirrors REST: `permanent`, `expires_at`, and the one-time token when you published anonymously.

## What happens when it expires

After `expires_at`, GET on the public URL and `get_site` behave as if the slug is gone (`404` / `not_found`). Listing with your Bearer token skips expired sites. The slug leaves the owner index, so it no longer counts toward the plan site cap. You can delete early with `DELETE /v1/sites/{slug}` (or MCP `delete_site`) if you no longer need the URL.

A later publish to the same slug with your token can set a new TTL, clear expiry by omitting TTL (the site becomes permanent again), or replace the files. Treat TTL like any other publish field: send what you want the slug to be after this request.

## Patterns that fit agents

- **CI previews:** Use a slug that includes the PR number or run id, set `X-Ttl` to cover the review window (for example `172800` for 48 hours), and post the returned URL in the PR comment. No inbox or claim step.
- **Cron scratchpads:** Nightly jobs can publish a status page with a TTL slightly longer than the schedule interval so stale runs disappear if the next job fails before overwrite.
- **Debug dumps:** Agents that materialize HTML traces can publish with a short TTL (minutes to a few hours) so the Free plan's site count is not filled with forgotten experiments.
- **Keep the token either way:** Temporary does not mean public write access. Strangers still get `409 slug_taken`. Only the Bearer token updates or deletes the slug before expiry.

## What TTL is not

- It is not the default. Permanent remains the product default; TTL is opt-in every time you want expiry.
- It is not a plan feature gate. Free, Hobby, and Pro can all set `X-Ttl` / `ttl_seconds`.
- It does not replace plan quotas. Expired sites stop serving, but you should still prefer unique slugs or deletes so your principal's site list stays tidy.
- It is not related to the ~4 MB per-request body cap. Large temporary sites still use `X-Merge: 1` the same way permanent ones do.

## Related

- Why permanent is the default: [/blog/publish-without-a-human](/blog/publish-without-a-human)
- MCP walkthrough: [/blog/mcp-publish-walkthrough](/blog/mcp-publish-walkthrough)
- Large sites with merge: [/blog/merge-publish-large-sites](/blog/merge-publish-large-sites)
- Docs: [/docs](/docs)
- OpenAPI: [/openapi.json](/openapi.json)
- Skill for agents: [/skill.md](/skill.md)

Questions or corrections: ops@avatar33.com.
