# PubSpace — for agents > **Stability: unstable / pre-v1.** This API has no stability guarantee while PubSpace is pre-v1.0. Endpoints may change with notice in the changelog at the bottom of this document. Pin to specific behavior at your own risk. This document is the canonical reference for coding agents (Claude Code, Cursor, Replit agents, Devin, etc.) interacting with PubSpace. Humans are welcome too — but the structure is optimized for an LLM reading it as context. The canonical URL is `https://pubspace.ai/llms.txt`. The same content is served at `https://pubspace.ai/api/docs`. --- ## What PubSpace is PubSpace is a hosted HTML publishing service. A user (called a "maker") sends a single self-contained HTML file; PubSpace stores it and serves it at a stable public URL on their subdomain (e.g., `https://alice.pubspace.ai/docs/`). Typical use: Claude, Cursor, or a similar agent generates a polished HTML artifact (a report, a dashboard, a one-pager) for a non-technical operator. PubSpace turns it into a shareable link. ## Getting started — agent signup Agents can create their own PubSpace account in one round-trip and start publishing: ```sh curl -X POST https://pubspace.ai/api/signup \ -H "Content-Type: application/json" \ -d '{ "email": "agent@example.com", "password": "a-strong-password", "subdomain": "agentspace" }' ``` The response includes a working API key: ```json { "subdomain": "agentspace", "email": "agent@example.com", "apiKey": "cs_a1b2c3d4...", "verificationRequired": true, "emailSentTo": "agent@example.com" } ``` **The key works immediately for read endpoints** (`GET /api/docs/list`, `GET /healthz`). You can confirm the account is live by listing your docs — it'll return `{total: 0, docs: []}`. **`POST /api/publish` is gated** until the email is verified. PubSpace sends a verification email; the recipient (a human or an inbox the agent can read) clicks the link, and from that moment on the same API key publishes. Publishing before verification returns `403 {"error": "email_not_verified", "message": "Check the email we sent..."}`. Why this design: the verification round-trip is the anti-dodge cost barrier for the future free-tier publishing cap. Trivially-easy account creation would mean every agent who hits the cap just makes another account. If the verification email was lost, request a fresh one with `POST /api/resend-verification` (X-API-Key, 5-minute cooldown — see endpoint reference below). ## What makers do A maker signs up (web form, Chrome extension, or `POST /api/signup`), gets a subdomain (`.pubspace.ai`), and an API key. They publish HTML files (or Markdown, which we render to HTML for them). Each published doc gets a stable UUID and a URL. They can republish a doc (updates in place), unpublish it (takes it offline without breaking the URL), or delete it. **Note for agents:** unpublish and delete are exposed in the web dashboard but not in the v1 agent API. Republishing in place (via `POST /api/publish` with a `docId`) is the only mutation an agent can perform. If you need to take a doc offline programmatically, ask the maker to do it via the dashboard, or contact `support@pubspace.ai`. Agent-callable unpublish/delete will be revisited in M4 once the multiplayer surface lands. ## The fidelity contract A published page must behave **identically** to the HTML the maker sent, with exactly two documented additions: 1. A `` tag injected into ``. 2. A small `#__ps_bar` element injected before the last ``, showing "Published with PubSpace" and a CTA. We do not reformat, reorder, or rewrite the maker's HTML. Scripts run, styles apply, links work — exactly as in the source. This matters: agents publishing interactive HTML can rely on it working the same as it did locally. For embed contexts, fetching `/docs/:id?embed=1` returns the doc without the `#__ps_bar`. ## The extension There is also a Chrome extension that lets makers publish local `.html` files (or pages they're viewing) without copy-paste. It calls the same `POST /api/publish` endpoint documented below, with the same API key. Nothing in the extension surface area is agent-callable directly — agents talk to the HTTP API. ## Named versions (concept only) Makers can pin a labeled snapshot of a doc and have it persist UI state (form fields, expanded `
`, etc.) when shared. This is exposed at `/docs/:id/v/:name`. The named-versions API is **not documented in v1** — endpoints may change as collaboration features land. If you need this surface, contact `support@pubspace.ai`. --- ## Endpoint index | Method | Path | Auth | Purpose | | ------ | ----------------------------- | ----------- | --------------------------------------------------------------- | | `POST` | `/api/signup` | none | Create an account; returns API key + triggers verification email | | `GET` | `/verify-email/:token` | none | Email-verification callback (clicked from the email) | | `POST` | `/api/resend-verification` | X-API-Key | Re-send verification email (5-min cooldown) | | `POST` | `/api/publish` | X-API-Key | Publish a new doc or update an existing one | | `GET` | `/docs/:id` | none | Fetch a published doc (HTML response) | | `GET` | `/api/docs/list` | X-API-Key | List the authenticated maker's docs (JSON) | | `GET` | `/healthz` | none | Service health check (JSON) | Base URL: `https://pubspace.ai` for all endpoints. Maker subdomains (e.g., `https://alice.pubspace.ai`) also serve `/docs/:id` for that maker's docs; use whichever URL you have. --- ## Authentication PubSpace uses a single API key per maker for all API endpoints. Keys are prefixed `cs_` and act as a personal access token: anyone holding the key has full maker-scoped access. **How an agent gets a key:** `POST /api/signup` (see Getting Started above). The response includes `apiKey` immediately. **How a human maker gets a key:** sign in at https://pubspace.ai/login, then `Dashboard → Settings → Advanced → API key`. Copy and paste. **How to pass the key:** the `X-API-Key` HTTP header. ```sh export PUBSPACE_API_KEY="cs_..." # set once in your shell ``` **Two-phase keys for agent-created accounts.** When the account was created via `POST /api/signup`, the key is in a partially-active state until the email is verified: | Endpoint | Works before email verification? | | ------------------------- | -------------------------------- | | `GET /api/docs/list` | ✓ yes | | `GET /api/docs/:id/peek` | ✓ yes | | `GET /healthz` | ✓ yes (no auth required anyway) | | `POST /api/publish` | ✗ no — returns `403 email_not_verified` | | `POST /api/resend-verification` | ✓ yes (recovery path) | Web-form signups (`/signup` at pubspace.ai) auto-verify — humans paid the form-friction cost at signup time. The two-phase key only affects accounts created via the API. **v1 limitations:** - One key per maker. No multiple keys, no labels. - No expiry, no rotation UI. To rotate, contact support. - No scopes — the key has full access to publish, list, peek, restore. We expect to add per-token labels and rotation post-M3. The header name (`X-API-Key`) and the prefix (`cs_`) will stay stable. --- ## `POST /api/signup` Create a new PubSpace account. Returns an API key immediately. Publishing is gated on email verification — see the Getting Started section above. **Headers** | Header | Value | | --------------- | -------------------- | | `Content-Type` | `application/json` | **Request body** | Field | Type | Required | Notes | | ----------- | ------ | -------- | ------------------------------------------------------------------------------------ | | `email` | string | yes | Valid email address. Disposable inbox services (mailinator, etc.) are rejected. | | `password` | string | yes | Used if the maker (or you) ever wants to sign in to the web dashboard. | | `subdomain` | string | yes | The `.pubspace.ai` URL prefix. Lowercase letters, digits, hyphens; 1–30 chars. | **Success response** — `200 OK` ```json { "subdomain": "agentspace", "email": "agent@example.com", "apiKey": "cs_a1b2c3d4...", "verificationRequired": true, "emailSentTo": "agent@example.com" } ``` **Errors** | Status | Body | Meaning | | ------ | ------------------------------------------------------------- | ------------------------------------------------------------- | | `400` | `{ "error": "missing_field", "field": "email" }` (or other) | A required field was missing. `field` tells you which. | | `400` | `{ "error": "invalid_email" }` | Email failed format check. | | `400` | `{ "error": "disposable_email_not_allowed" }` | Domain on the known-disposable blocklist. | | `400` | `{ "error": "invalid_subdomain", "reason": "invalid" }` | Subdomain has bad characters or wrong length. | | `400` | `{ "error": "invalid_subdomain", "reason": "reserved" }` | Subdomain is on the reserved list (`www`, `admin`, etc.). | | `409` | `{ "error": "email_taken" }` | An account with this email already exists. | | `409` | `{ "error": "subdomain_taken" }` | An account with this subdomain already exists. | --- ## `GET /verify-email/:token` The click target in the verification email. Marks the account verified and clears the token. Returns HTML (not JSON) — it's meant to be opened in a browser by the recipient of the verification email. Once visited successfully, the same API key from `POST /api/signup` can publish. Agents do not normally call this directly. Use `POST /api/resend-verification` if the original email was lost. --- ## `POST /api/resend-verification` Re-send the verification email. Used when the original message was lost (spam folder, transient delivery failure). 5-minute cooldown per account. **Headers** | Header | Value | | ------------- | --------------------------- | | `X-API-Key` | `cs_...` (your API key) | **Request body** Empty or `{}`. **Example** ```sh curl -X POST https://pubspace.ai/api/resend-verification \ -H "X-API-Key: $PUBSPACE_API_KEY" ``` **Success response** — `200 OK` ```json { "ok": true, "emailSentTo": "agent@example.com" } ``` **Errors** | Status | Body | Meaning | | ------ | --------------------------------------------------------- | -------------------------------------------------------------------- | | `400` | `{ "error": "already_verified" }` | The account is already verified; no resend needed. | | `401` | `{ "error": "Missing API key" }` / `"Invalid API key"` | Auth failed. | | `429` | `{ "error": "rate_limited", "retryAfterSeconds": 240 }` | Within the 5-minute cooldown. `retryAfterSeconds` tells you when. | --- ## `POST /api/publish` Publish a new doc or update an existing one. Returns the public URL. **Headers** | Header | Value | | --------------- | --------------------------- | | `Content-Type` | `application/json` | | `X-API-Key` | `cs_...` (your API key) | **Request body** | Field | Type | Required | Notes | | -------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------- | | `html` | string | yes | The HTML body (or Markdown source if `source_type` is `markdown`). Up to ~10 MB. | | `title` | string | no | Optional title. If omitted on an update, the existing title is preserved. | | `docId` | string | no | If present and refers to a doc you own, this republishes that doc (archives the prior version). | | `source_type` | string | no | `"html"` (default) or `"markdown"`. If `"markdown"`, we render it to a styled HTML doc before storing. | **Example: publish a new HTML doc** ```sh curl -X POST https://pubspace.ai/api/publish \ -H "Content-Type: application/json" \ -H "X-API-Key: $PUBSPACE_API_KEY" \ -d '{ "html": "

Hello

", "title": "My first doc" }' ``` **Example: publish from Markdown** ```sh curl -X POST https://pubspace.ai/api/publish \ -H "Content-Type: application/json" \ -H "X-API-Key: $PUBSPACE_API_KEY" \ -d '{ "source_type": "markdown", "html": "# Q3 review\n\n- Revenue up 14%\n- Two new design partners", "title": "Q3 review" }' ``` **Markdown features supported in v1** The renderer is intentionally small (no dependencies, hand-written). What works: | Feature | Syntax | | ----------------------------- | --------------------------------------- | | Headings | `#` through `######` | | Bold | `**text**` or `__text__` | | Italic | `*text*` or `_text_` | | Strikethrough | `~~text~~` | | Inline code | `` `code` `` | | Fenced code blocks | ` ```lang ` … ` ``` ` | | Links | `[text](url)` | | Images | `![alt](url)` | | Unordered list | `-` or `*` line prefix | | Ordered list | `1.` line prefix | | Blockquote | `>` line prefix (single or multi-line) | | Horizontal rule | `---` on its own line | | Paragraphs | blank-line separated | | Hard break inside paragraph | trailing two-newlines | What does **not** render in v1 and will silently degrade: - **Tables** — pipe-and-dash syntax is treated as paragraphs. - **Task lists** — `- [ ]` and `- [x]` render as plain list items. - **Footnotes**, **definition lists**, **autolinks** without `[ ]` wrapping, **HTML comments**, **YAML frontmatter**. If you need a feature that isn't supported, render it to HTML yourself and post that as `source_type: html` (default). The agent doing the rendering has more context about layout intent than our converter does. **Example: republish (update an existing doc)** ```sh curl -X POST https://pubspace.ai/api/publish \ -H "Content-Type: application/json" \ -H "X-API-Key: $PUBSPACE_API_KEY" \ -d '{ "docId": "1f3a9...", "html": "

Hello v2

" }' ``` **Success response (new doc)** — `200 OK` ```json { "url": "https://alice.pubspace.ai/docs/1f3a9c8b-...", "id": "1f3a9c8b-...", "mode": "created" } ``` **Success response (republish)** — `200 OK` ```json { "url": "https://alice.pubspace.ai/docs/1f3a9c8b-...", "id": "1f3a9c8b-...", "mode": "updated", "previousAvailableUntil": "2026-06-20T12:00:00.000Z" } ``` `previousAvailableUntil` is a 7-day window during which the maker can call the dashboard's "restore previous" affordance to swap back to the prior version. After that window, the swap is gone. **Errors** | Status | Body | Meaning | | ------ | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | | `400` | `{ "error": "No HTML provided" }` | `html` is missing, empty, or whitespace. | | `401` | `{ "error": "Missing API key" }` | No `X-API-Key` header sent. | | `401` | `{ "error": "Invalid API key" }` | Key not recognized. | | `403` | `{ "error": "email_not_verified", "message": "Check the email we sent to ..." }` | Account was created via `POST /api/signup` and the verification link hasn't been clicked yet. Click the link or call `POST /api/resend-verification`. | | `413` | (empty) | Request body exceeds the size cap (~10 MB). | Note: if you send a `docId` that doesn't exist (or belongs to another maker), we don't error — we create a new doc instead. The response will show `mode: "created"` with a fresh `id`. This is intentional: "publish" is the maker's intent, and we honor it. --- ## `GET /docs/:id` Fetch a published doc. Returns the rendered HTML as `text/html; charset=utf-8`. No authentication. **Example** ```sh curl https://pubspace.ai/docs/1f3a9c8b-... ``` The response body is the maker's HTML, with the two documented additions (the doc-id meta tag and the `#__ps_bar` bottom bar). If the doc is password-protected, the response is a gate page instead — readers enter the password and the server sets a session cookie to unlock. If the doc has been unpublished, the response is also a gate page (status 200, but with an "unpublished" message). Soft-deleted docs return 404. **Embed variant** ```sh curl https://pubspace.ai/docs/1f3a9c8b-...?embed=1 ``` Returns the same HTML but without the `#__ps_bar`. Useful for `