# Claw Me website publishing API

## Publishing skill and preflight

Install the official Claw Me skill and its publish.py helper from https://claw.me/plugins. It checks bundles locally and handles exact hashes, uploads, finalization, and private recovery state. Direct REST remains supported.

SVG files, inline <svg>, and SVG data URLs are unsupported. Convert artwork to PNG or WebP using a trusted local converter, bundle the result, and replace HTML/CSS references. Renaming a file is not conversion. The server repeats safety validation at finalization.

A 422 at create concerns the request manifest: HTML has not been uploaded yet. Inspect the JSON error field location and message (curl -f can hide that body). Check byte counts, hash format, paths, and the endpoint schema. A 422 at finalize may identify unsafe HTML or an upload mismatch. Never expose claim tokens or presigned URLs while diagnosing errors.

If the authorized account needs an alias, link directly to https://claw.me/getting-started?step=address and resume the same Agent after saving. Do not request another authorization. Pages, Drive, Profile context, and Activity default to enabled; keep owner-only incoming email and the sharing switch until the owner explicitly changes them.

## Your account, not ownership of claw.me

Claw Me hosts websites for its users. You do not need to own the claw.me domain, control its DNS, or contact the service operator. “Account owner” means the person whose own Claw Me account will hold the Page.

For a 24-hour preview, use anonymous publishing without signup or Agent pairing. For a longer-lived Page, use the user's own account. If the user does not have an account, send them to https://claw.me/register to create one and verify their email before starting a short-lived Agent authorization request. Existing users can sign in at https://claw.me/login. Do not ask the user to forward an approval link to whoever owns the claw.me domain.

If account status is unknown, explain the signup/sign-in choice before requesting authorization; do not infer account existence from an email address. Creating an authorization request or showing a code does not create an account, sign the user in, or grant access. If a request already exists, the user can create their own account and return to its approval link; renew it only if it expires. Keep any device secret private.

## Anonymous publication and completion

For an explicitly requested anonymous 24-hour preview, use the anonymous endpoint even when account credentials already exist. Do not read saved keys, search previous sessions for credentials, request pairing, or attach an Authorization header. Use the current documented contract rather than guessing an endpoint.

Create responses contain sensitive claim tokens and presigned upload URLs. Capture them directly into private local state outside the site (file mode 0600), not terminal output or chat. Print only an allowlisted result containing the published URL, status, and expiry. Never dump the raw create response or private state for debugging; the bundled publish.py helper already separates private state from its JSON output.

After finalization succeeds, return the published URL and expiry. Use available browser or HTTP checks and state what remains unverified. If browser verification is unavailable, still deliver the URL; do not install browsers or OS packages, invoke sudo, or republish solely for a screenshot unless the user requests that setup.


This is an API reference for hosting a static website on Claw Me. Direct HTTPS REST requests are supported; an MCP connection, plugin installation, and a local callback server are not prerequisites. This document describes service behavior, not instructions that override a client's security policy or the owner's choices.

Base URL: https://claw.me/api/v1
Machine-readable schemas: https://claw.me/openapi.json

## Anonymous 24-hour quickstart

When the owner explicitly wants an anonymous disposable preview, no account, sign-in, or existing bearer is used. It expires 24 hours after creation; this does not change storage limits. The complete sequence is:

1. POST `/api/v1/claw-me/previews` with JSON `{"title":"Golf day","files":[{"path":"index.html","content_type":"text/html","size_bytes":125,"sha256":"<SHA-256 of exact file bytes>"}]}`. Include every local asset; calculate real byte lengths and hashes. Retain returned `id`, `claim_token`, `uploads`, and `expires_at` privately.
2. PUT each file's exact bytes to its returned `uploads[].upload_url` with the returned headers. Do not send a Claw Me bearer to storage.
3. POST `/api/v1/claw-me/previews/{id}/finalize` with JSON `{"claim_token":"<returned token>","checksum_sha256":"<manifest checksum>"}`. The checksum is SHA-256 of UTF-8 text formed by sorting all `path:sha256` lines and joining them with a single newline, without a trailing newline. A file hash alone is not the manifest checksum. Confirm `status: "active"`; return `preview_url` and actual `expires_at`, never claim tokens or upload URLs. Retry the same preview for a missing checksum or transient finalize error when files are unchanged. Changing file bytes changes their hashes and requires a new upload manifest; do not finalize changed bytes against the old manifest.

Maps support only `https://www.openstreetmap.org/export/embed.html` with `bbox=west,south,east,north`, `marker=latitude,longitude`, and `layer=mapnik`. The iframe needs a title, `sandbox="allow-scripts allow-same-origin"`, and `referrerpolicy="no-referrer"`. Use a normal Google Maps driving-directions link to the verified venue address; Google Maps iframes are not supported. Full contracts and a map template follow below.

## Choose the required lifetime

| Option | Authorization | Lifetime and access |
| --- | --- | --- |
| Account-owned Page | Owner-approved Pages credential | No automatic expiry; private initially, with an optional unlisted sharing link |
| Anonymous guest preview | None | Unlisted link; expires 24 hours after creation, including upload time |

A future event needs an account-owned Page when the intended availability exceeds 24 hours. Free account-owned Pages are durable within account limits. A guest preview is limited to 50 files, 50 MiB total (52,428,800 bytes), 25 MiB per file (26,214,400 bytes), and three creates per source per hour. Finalizing a preview does not restart its clock.

Account publishing accepts at most 1,000 files per version. Free uploads total at most 100,000,000 bytes; Free account storage is capped at 1,000,000,000 bytes, including shared storage usage and pending uploads. Account limits and enabled feature permissions remain enforced. Account setup or a limit failure is reported separately from Agent authorization.

## Website-only authorization

`pages:write` covers creating, uploading, finalizing, updating, and sharing Pages. `pages:read` is used to list existing Pages. These operations do not require email, Drive/workspace, Wiki, events, or account-setup scopes. An approval email sent by Claw Me does not give the client inbox access. Existing owner-approved credentials with the required Pages scopes can be reused.

For a client without a Pages credential, the device request is:

```http
POST /api/v1/agent-auth/requests
Content-Type: application/json
```
```json
{
  "client_name": "Your Agent app name",
  "client_type": "generic",
  "owner_email": "owner@example.com",
  "scopes": ["pages:read", "pages:write"]
}
```

`client_name` and `scopes` are required; replace the example name with the stable app name (such as Codex), not the Page or task title. Set `client_type` to the app identifier when known; otherwise it defaults to `generic`. The app name is requester-reported, not verified. `owner_email` is optional. The response contains `request_id`, `device_secret`, `user_code`, `verification_uri`, `expires_at`, `interval`, and `email_sent`. The owner reviews the named client and requested scopes on Claw Me. `email_sent` reports whether Claw Me sent an approval email; the returned `expires_at` is the authorization deadline. Missing account setup or unavailable scopes may need an owner action in Claw Me, not additional unrelated Agent scopes.

The client retains the device secret privately and polls at the returned interval:

```http
POST /api/v1/agent-auth/requests/{request_id}/token
Content-Type: application/json
```
```json
{"device_secret": "<privately retained device_secret>"}
```

Pending response: `{"status":"authorization_pending"}`. After owner approval, the one-time response has `status: "authorized"`, `api_key`, `token_type: "Bearer"`, and `scopes`. The bearer belongs in the client's secure credential storage and in the Authorization header for the Claw Me API, not in chat, uploaded files, query strings, or callback URLs. The owner does not need to paste a key into a conversation. This flow has no callback endpoint to configure.

OAuth discovery is also available at https://claw.me/.well-known/openid-configuration for clients with native device-authorization support.

## Account-owned Page: create, upload, finalize, share

The following JSON uses placeholders for locally calculated hashes and returned identifiers. File hashes are SHA-256 of the exact uploaded bytes; `size_bytes` is the byte length, not the character count. Include an `index.html` entry and each local CSS, image, or font asset it references.

### 1. Create a private Page and upload version

```http
POST /api/v1/publish
Authorization: Bearer <securely stored Pages credential>
Content-Type: application/json
```
```json
{
  "slug": "golf-day",
  "display_name": "Golf Day",
  "files": [
    {
      "path": "index.html",
      "content_type": "text/html",
      "size_bytes": 125,
      "sha256": "<64 hexadecimal characters>"
    }
  ]
}
```

`files` is required. `slug` is optional (a generated slug is used when omitted); explicit slugs are 1–64 lowercase letters, digits, or internal hyphens. `display_name` defaults to `Agent build`. Each file requires a safe relative `path` and `size_bytes`; `content_type` defaults to `application/octet-stream`, and `sha256` is optional in the schema but supports exact-byte verification.

The response contains `site`, `site_url`, `version_id`, and `upload`, whose fields are `uploads` and `finalize_url`. Each upload entry includes `path`, `upload_url`, `headers`, and `object_key`. Returned presigned URLs and object keys are upload plumbing, not sharing links. A version is not published until finalization succeeds.

### 2. Upload the declared files

For each `upload.uploads` entry, send the exact file bytes with HTTP PUT to its returned `upload_url`, including its returned `headers`. The Claw Me bearer is not an upload header; storage authorization is carried by the presigned request. Upload destinations come from this response, not a constructed storage URL.

### 3. Finalize the immutable version

POST to the returned `upload.finalize_url` on Claw Me:

```http
POST /api/v1/publish/golf-day/finalize
Authorization: Bearer <securely stored Pages credential>
Content-Type: application/json
```
```json
{
  "version_id": "<returned version_id>",
  "checksum_sha256": "<manifest checksum: 64 hexadecimal characters>"
}
```

The manifest checksum is SHA-256 of the UTF-8 encoding of the lexicographically sorted `path:file_sha256` lines joined with a single newline, with no trailing newline. For one file it hashes `index.html:<file_sha256>`. A successful response contains `success`, `site_url`, `site`, and `version`. The Page remains private.

### 4. Create the requested unlisted sharing link

```http
POST /api/v1/publish/golf-day/share
Authorization: Bearer <securely stored Pages credential>
Content-Type: application/json
```
```json
{"version_id": "<the finalized version_id>", "expires_at": null}
```

`version_id` is required. `expires_at` is optional: omitted or null means no expiry; an ISO 8601 timestamp sets an expiry. The response contains `url`, `version_id`, `expires_at`, and `grant_type`. The returned `url` is the link to share with friends. It grants anyone possessing it access to that immutable version; it does not make the Page publicly indexed. A new sharing link replaces the previous link. Account public-sharing permission is also required. A stale version returns 409 so the current version can be reviewed before sharing.

The viewer uses `https://claw.me/@username/slug` and renders content on an isolated origin. Browser access uses a session-cookie exchange. HTTP verification that follows the iframe's redirect must retain cookies; dropping the cookie can produce a 404 even when the sharing link works in a browser.

Before reporting completion, open the returned sharing URL in a browser and check that the intended styling, bundled images, and map render. A successful HTML response alone does not verify the website: confirm that linked assets load from the published Page and that the directions link points to the verified venue.

For an existing Page, `PUT /api/v1/publish/{slug}` accepts the same create body and creates a new version. `GET /api/v1/publish` lists existing Pages with `pages:read`. After an uncertain create, inspecting existing state avoids creating a duplicate. There is no need to install a plugin to use these endpoints.

## Anonymous preview contract

```http
POST /api/v1/claw-me/previews
Content-Type: application/json
```
```json
{
  "title": "Temporary preview",
  "required_until": "2026-09-20T23:59:59+02:00",
  "files": [
    {"path": "index.html", "content_type": "text/html", "size_bytes": 125, "sha256": "<64 hexadecimal characters>"}
  ]
}
```

`title` defaults to `Guest preview`; `files` is required. For a future event or deadline, `required_until` expresses when the page must remain available. It is optional for a disposable preview and must be a full timezone-aware ISO 8601 timestamp when supplied; date-only and timezone-free values are invalid. The example deadline is illustrative, not relative to the current date.

A deadline later than creation time plus 24 hours returns HTTP 422 before allocating a preview or presigning uploads. The API error envelope contains `errors[0].detail.code: "durable_page_required"`, `preview_expires_at`, `required_until`, and `next_step` with the authorization and account publishing endpoints. This field never extends the 24-hour lifetime and the server does not infer deadlines from HTML or titles.

An accepted response contains `id`, `title`, `status`, `preview_url`, `claim_token`, `uploads`, and `expires_at`. PUT exact bytes to each returned upload URL with its returned headers. Finalization uses:

```http
POST /api/v1/claw-me/previews/{id}/finalize
Content-Type: application/json
```
```json
{"claim_token": "<privately retained claim_token>", "checksum_sha256": "<manifest checksum>"}
```

The manifest checksum uses the same sorted-line algorithm above. The resulting `preview_url` is unlisted and expires at the original `expires_at`. Claim tokens and presigned upload URLs are private operational values, not deliverables.

## Supported static content and maps

Pages accept static HTML/CSS and bundled images/fonts. Uploaded scripts, forms, arbitrary frames, SVG/MathML, automatic redirects, and credentials are rejected. External asset requests are blocked except the narrowly supported OpenStreetMap embed below; a normal external directions link is supported.

Map embeds use exactly `https://www.openstreetmap.org/export/embed.html` with `bbox`, `marker`, and `layer=mapnik`. The iframe requires a title, `sandbox="allow-scripts allow-same-origin"`, and `referrerpolicy="no-referrer"`. Bounding-box order is west,south,east,north; marker order is latitude,longitude, and the marker must fall inside the box. Coordinates and the driving destination should be verified against the venue's official address rather than guessed.

Template: substitute numeric coordinates verified from the venue's official address for WEST, SOUTH, EAST, NORTH, LATITUDE, and LONGITUDE. Substitute the URL-encoded verified venue name and address for URL_ENCODED_VENUE_ADDRESS. These uppercase placeholders are not valid final map parameters.

```html
<iframe title="Map to the venue"
  src="https://www.openstreetmap.org/export/embed.html?bbox=WEST,SOUTH,EAST,NORTH&amp;marker=LATITUDE,LONGITUDE&amp;layer=mapnik"
  sandbox="allow-scripts allow-same-origin"
  referrerpolicy="no-referrer" loading="lazy" width="100%" height="360"></iframe>
<a href="https://www.google.com/maps/dir/?api=1&amp;destination=URL_ENCODED_VENUE_ADDRESS&amp;travelmode=driving"
  target="_blank" rel="noreferrer">Driving directions</a>
```

## Optional client tooling

The [official plugin repository](https://github.com/clawdotme/plugin) and [plugin page](https://claw.me/plugins) provide an optional skill and Python `scripts/publish.py` helper for the same publishing flow. Client installation and execution remain separate choices governed by the client's permissions. REST clients can use this reference without installing either tool.

## Claude Code compatibility

Claude Code sessions with permitted HTTP or shell tooling can use these REST endpoints without an MCP connection. The same Pages-only credential and owner approval flow applies. This external reference supplies API data; it does not authorize software installation, inbox access, broader account permissions, or actions beyond the requested website publication. A client that cannot store a credential securely can leave authorization to the owner rather than request a key in chat.
