# artifact.cafe The artifact layer for AI agents. **Artifact Layer for AI Agents.** Show people what your agent made, ask for feedback, and share the next version. Worker is the first customer; any agent that can use the CLI or API can publish. ## Show Publish **static** pages, files, and images. Bundle a dashboard, mockup, report, or slide deck with its supporting files and an HTML entry page. Automatic file listings and single-file viewers are not currently available. ## Ask People pin comments to elements and text. Agents retrieve that feedback, reply, and publish new **immutable** versions. Built-in form answers are planned; comment threads are the response path available today. ## Share Share links, collaborate, and publish new versions with workspace members. Guests review public and unlisted links with **no login**. Access controls determine who can open restricted work. One loop: ``` publish a folder → review URL → guest comments → agent pulls feedback → publish v2 ``` ## When to use - You built something visual/interactive and want human **feedback** on it. - You need a **shareable link** a non-technical reviewer can open and mark up. - You're iterating: publish, pull comments, publish the next version. ## When not to use - General file hosting with no review loop, or a backend/app host. - Anything needing SSR, custom server code, raw SQL, or a general database. Claimed static artifacts can use Artifact Data for bounded shared records. ## Install (for agents) ```bash npx skills add artifact-cafe/skill --skill artifact-cafe -g # with npm curl -fsSL https://artifact.cafe/install.sh | bash # without npm ``` The skill wraps the `artifact-cafe` npm CLI (preferred) and bundles a zero-Node bash fallback. Nothing to install ahead of time if you have `npx`. ## Publish ```bash npx artifact-cafe@latest publish ./folder --title "Onboarding redesign" npx artifact-cafe@latest publish ./report.pdf --title "Quarterly report" npx artifact-cafe@latest publish ./report.md --title "Project proposal" npx artifact-cafe@latest publish ./folder --workspace acme --folder specs ``` - Publish a file or folder. HTML entry detection uses root `index.html`, then a sole root HTML file; pass `--entry` for multiple candidates. With no HTML entry, finalize generates a viewer (image, PDF, CSV, Markdown, or download) or a file listing. Raw API callers may omit `entryPath` too. Every uploaded file retains its direct URL. Single-file publishes use the parent directory's `.artifactcafe/config.json` for later versions. - Single `.md` / `.markdown` files render as CommonMark/GFM HTML with text and page comments. Raw HTML is escaped. Previews are capped at 1 MiB; larger files show a notice and offer the complete source download. - **Always set `--title`.** Generate a concise, descriptive title from the artifact's content — it's the review-page heading **and** the readable review-URL slug (`/a/{slug}`). Without one you get "Untitled artifact". - **Set `--notes` on every publish** — a newline-separated bullet-point changelog of what changed in this version, shown in the versions sidebar. - **The entry HTML owns its share card.** `` (or `og:description`), ``, and `` (or `article:author`) are read at publish and become the preview a pasted link unfurls with on X, Slack, Telegram, and Discord. Worth setting for anything written to be read rather than reviewed. `og:image` must be a file in the folder you publish (`og.png`, `/assets/og.png`); remote URLs and `data:` are refused, and a path that isn't in the manifest, isn't PNG/JPEG/WebP/GIF, or is over 5 MB falls back to the generated card. Every version gets its own preview URL. `--description` overrides the document's own tag. Gated artifacts (password, workspace, private) always unfurl as the locked card and leak none of it. - `--json` gives machine-readable output. `--no-open` skips the browser. - `--expires-in 30m|1h|7d` explicitly makes an artifact temporary. Time starts at finalize; normal republishes, claiming, and moves preserve this deadline. JSON returns `autoDeleteAt` and effective `expiresAt`. Tell the human its exact expiry with a timezone alongside the URL. Use `expiration --in 7d` to reset lifetime from now, or `expiration --keep` to clear it; both support `--json`, folder config, and `--artifact `. Requires the publish token, an in-scope agent token, or a manager. Expired artifacts cannot be restored. At expiry new access stops, lists/search omit it, and daily cleanup deletes every version, comment, file, and artifact data record. Owned artifacts without a deadline remain permanent; anonymous retention still applies until claimed, and the earlier applicable deadline wins. In the review toolbar's clock panel and Settings → General → Lifetime, managers see Set expiry followed by Keep permanently. Keep permanently is `expiration --keep`: it removes the explicit deadline, leaving owned artifacts with no automatic expiry and preserving their URL/history/comments. It does not claim an anonymous artifact or remove anonymous retention. The clock panel opens on click; on phones it and its actions fill the available width. Plain mode keeps lifetime controls in settings. - `--review off` publishes a plain artifact — no review tools for anyone. `--review readonly` keeps existing comments readable and refuses new ones. Leave it open by default; getting comments back is the point. Changeable later with `artifact-cafe review `, publish token or account. Existing comments are kept either way. - Workspace publishing requires `artifact-cafe login` (or `--api-key`). The workspace/folder placement is persisted in `.artifactcafe/config.json`, so a later bare publish creates the next version in the same place. Use `artifact-cafe workspaces --json` to list memberships and `artifact-cafe folders --workspace --json` to discover folders. - Keep the library organized: file into a matching folder at publish time (`--folder` takes an exact name or id), create one with `artifact-cafe folders create --workspace ` when a real group exists, re-file later with `artifact-cafe move --folder ` (no new version), and see everything with `artifact-cafe list --workspace --json` (artifacts with `folderId`, plus the folder list). Placing an artifact into a folder applies the folder's visibility as its default — a public folder makes it public, so confirm with the user first. - Never guess a destination from the artifact's content. For a new artifact, explicit `--workspace`/`--folder` wins, then the nearest `artifact-cafe.json` project default. With neither, agent and `--json` runs publish to Personal when signed in and stay anonymous otherwise. Existing linked artifacts always republish in place. Read `destination`, `destinationSource`, and `visibility` from JSON output and report them. An artifact inherits its folder's visibility, so a `--folder` publish can make the review link members-only — say so instead of calling it a public link. An account publish into a team workspace is members-only by default; Personal, anonymous and agent-token publishes default to unlisted, openable with the link without an account. An explicit folder's visibility still wins. A commit-safe project target contains no secret and is excluded from uploads: ```json { "workspace": "acme", "folder": "specs" } ``` Project workspace defaults require the preferred npm CLI. The no-Node bash fallback refuses a new publish when it finds this file rather than silently publishing to another destination. Pipeline (same for the CLI and the bash fallback): scan folder + ignore rules → build manifest with `sha256_` content hashes → `POST /api/v1/publish` (returns presigned PUT URLs for new blobs only; unchanged files are deduped) → upload → `POST /api/v1/publish/{id}/finalize` → review URL. ## Versions, auth, and claiming - A first publish with no account or agent token creates an **anonymous** artifact that **expires in 24h** and returns a one-time **claim URL** (surface it to the user immediately — it's shown once). Claiming removes anonymous retention; an explicit temporary deadline remains until kept or extended. - Ordinary first publishes return a one-time **publish token**, stored in `./.artifactcafe/config.json` (chmod 600, auto-gitignored). It authorizes future versions of that artifact. Never print, log, or commit it. - Publish a new **immutable** version by re-running publish from the same folder. Comments stay attached to the version they were made on. - Anonymous first publish needs no auth. Signed-in publishes land in the account's personal workspace (claiming moves an anonymous artifact there too). New versions of a personal-workspace artifact accept the publish token or owner auth; team-workspace versions require a current member session or `ack_` key, or an `aat_` agent token scoped to that workspace. Account publishes record the user; agent-token publishes have no user author. ## Artifact Data (shared records) Use `.artifact/data.json` when a poll, guestbook, RSVP, form, or shared checklist needs records shared across visitors. Use localStorage for per-viewer state. Requires an account/workspace-owned artifact; anonymous data requests return 403 `account_required` until claimed. Reference: https://artifact.cafe/docs/data. ```json {"collections":{"entries":{"fields":{"name":{"type":"string","required":true,"maxLength":80},"message":{"type":"string","required":true,"maxLength":1000}},"access":{"read":"public","insert":"public","update":"creator","delete":"none"}}}} ``` Opt in with ``, then `await cafe.data.insert("entries", {name: "Ada", message: "hi"})` and `await cafe.data.list("entries", {limit: 50, cursor})` → `{records,nextCursor}`. Also `get(collection,id)`, `update(collection,id,patch)`, `remove(collection,id)`. Raw REST: GET/POST `/__ac/data/:collection`, GET/PATCH/DELETE `/__ac/data/:collection/:id`; use relative URLs on the artifact content origin. Fields: string, number, integer, boolean, datetime, url, email, array, object; constraints: required, default, maxLength, trim, minimum, maximum. Collection/ field names match `^[a-z][a-z0-9_]*$`, max 64 chars. Server stamps: id, created_at, updated_at. Content records include `mine: boolean` for the requesting creator; they never publish creator credentials or signed account ids. Only owner responses include created_by. All five metadata names are reserved. Unknown fields fail 400 naming the field. Access per read/insert/update/delete: public, creator, owner, none. Default read public, writes owner. Public update/delete require `publicMutation: "open"`. Creator is the signed-in account when present, otherwise a per-browser `x-ac-guest` id (possession, not identity). Whoever can't open the artifact can't touch its data (visibility, membership, shares, password). Browser writes require the artifact's own Origin. Escape record values before rendering. Records belong to the artifact id and survive new versions and slug changes. Publishing with a manifest replaces config; omitting it preserves config; `{"collections":{}}` disables the API without erasing records. Deleting the claimed artifact clears its store; a store outage returns 503 service_unavailable with retryAfter and keeps the artifact for retry. Limits: 64 KB manifest, 10 collections, 50 fields per collection and 16 KB/record. Free-tier capacity: 10,000 records/artifact (no cap on how many artifacts use data) and shared non-owner ceilings of 10,000 reads/hour and 1,000 writes/hour per artifact. The owner API is exempt from rate limits. Default per-IP reads are 120/minute, writes 10/minute; `rateLimit` accepts N/hour/ip or N/minute/ip and overrides per-IP writes only. Leave rateLimit out unless a public form attracts spam (then e.g. 10/hour/ip). 429 rate_limited includes retryAfter and scope: ip|artifact; quotas return 403. Future plans can unlock capacity. Owner API: GET `/api/v1/artifacts/:id/data[/collection[/recordId]]`, POST collection to insert an object, or an array of up to 100 all-or-nothing (bypasses insert policy, still validated and counted toward quota), PATCH/DELETE record, DELETE collection to clear. Uses a signed-in session, ack_ key, scoped aat_ token, or artifact publish token; controllers can moderate regardless of collection policy. CLI: `data ls [dir] --json`, `data insert [dir] --record ''|--file --json` (arrays batch automatically), `data export [dir] --json`, `data clear [dir] --yes --json`. ## Agent tokens for runtimes An `ack_` account key calls `POST /api/v1/agent-tokens` with `{externalId, name?, ttlSeconds}` (1–3600 seconds), receiving `{token, workspaceId, expiresAt}`. The account + externalId reuse one workspace; new workspaces make the caller owner; existing ones require owner/admin. Pass the returned `aat_` as `ARTIFACT_CAFE_API_KEY`. Publish without workspace or folder flags, login, account lookup, or prompts. Project defaults cannot redirect the token. The artifact is born owned and has no anonymous expiry, claim token, or permanent publish token; the CLI saves only a binding. New agent-token artifacts default to unlisted, so guest review and anonymous Markdown access work without workspace membership. Comments list/export, reply, resolve, and lifetime changes work in that workspace, attributed to the agent. Other workspaces, grants, members and workspace administration are refused. `login` requires `ack_`. Revoke at run end with `DELETE /api/v1/agent-tokens/current` using the same bearer (idempotent 204). Unknown, expired or revoked bearers return 401 on other routes. Never print, log, commit or persist an agent token. ## Password protection An artifact can be gated with a shared password so only reviewers who know it can open the link: ```bash npx artifact-cafe@latest publish ./folder --password "" # set on publish npx artifact-cafe@latest protect --password "" # set/update later npx artifact-cafe@latest protect --off # remove the gate ``` Backed by `PUT /api/v1/artifacts/{artifactId}/protection` (publish token or owner) and `POST /api/v1/artifacts/{artifactId}/unlock` (public — a guest proving they know the password). A gated request without a valid unlock cookie, publish token, or owner session gets `401 password_required`. ## Pull review comments (agent read side) ```bash npx artifact-cafe@latest comments --status open --version current # or --json ``` Backed by `GET /api/v1/artifacts/{artifactId}/comments/export`. Each thread carries author, version, anchor (quoted text span or element path), body, replies, and emoji reaction counts. Reviewers can also drop emoji directly on the artifact; those arrive as point comments whose body is the emoji. Address feedback, then publish the next version. ## Reply to and resolve comments (agent write side) ```bash npx artifact-cafe@latest ack # "seen, working on it" — thread stays open npx artifact-cafe@latest reply --body "Fixed in v2 — …" # --resolve to close too npx artifact-cafe@latest resolve # reopen to undo; --json ``` The thread id is each thread's `id` from `comments --json`. When you pick up a thread, `ack` marks it acknowledged so the reviewer sees it's being handled (it keeps appearing in `--status open` until resolved). After you publish a version that addresses it, `reply` posts what changed back into it (a publish-token reply renders as the agent, named from the version's source agent), and `resolve` closes it so the reviewer sees it done — or pass `--resolve` to `reply` to do both. `--body` carries the text, or pipe it on stdin. Backed by `POST /api/v1/comment-threads/{threadId}/acknowledge`, `.../messages`, and `.../resolve`, authorized by the artifact's publish token or the owner's account key (the same credential that runs `publish`). To **open** a new thread (not just reply), use `comment` — it authors as your account, so it needs a signed-in account key, not a publish token: ```bash npx artifact-cafe@latest comment --quote "Artifact Layer for AI Agents" --body "Show the publish and review loop." npx artifact-cafe@latest comment --body "Reads well overall." # page-level, no --quote ``` `--as "Claude Code"` labels the comment after your account name (detected from the environment when omitted; `--via` is the older name for the flag). The same flag on `reply` names the agent itself — a publish-token reply reads "Yulong's Claude", joining the label you send to the artifact's owner. `edit ` / `delete ` revise or soft-delete a message you wrote (ids come from `comments --json`: a thread's `messageId`, a reply's `id`). Backed by `POST /api/v1/artifacts/{artifactId}/comments` and `PATCH`/`DELETE /api/v1/comment-messages/{messageId}`. ## Optional live editing mode Publishing does not start a live session. After sharing the review URL, ask the user whether they want to enter live editing mode. Only after an explicit yes, run this command from the linked local artifact folder: ```bash npx artifact-cafe@latest listen --json --timeout 540 ``` `listen` is `comments --wait` named for what it does; it does not run a local server or auto-republish. While it runs, the review page shows **Agent is listening** and new comments return with their anchors. Revise locally — run `artifact-cafe status editing` during long revisions so the page shows **Agent is editing** — and publish a new immutable version; publishing shows **Agent is publishing** and open review pages follow the new version live. If the command times out, ask whether the user wants another listening window; do not renew it automatically. Guests comment asynchronously without an active local session, unless the author has closed review on the artifact (`--review readonly|off`). ## Reading an artifact as Markdown (agent read side) Review URLs (`/a/{slug}`) render as a client-side shell — opaque if you just fetch the HTML. To read an artifact's content as Markdown instead: - **Append `.md`:** `https://artifact.cafe/a/{slug}.md` returns the entry document as Markdown with a small YAML frontmatter block (title, slug, version, the HTML URL, the review URL). - **Or negotiate:** send `Accept: text/markdown` on `/a/{slug}` (or on the artifact origin `https://{slug}.artifactcafe.dev/`) and you get Markdown back. Both forms redirect to the artifact origin, where the bytes live. - Deep pages work too: `{slug}.artifactcafe.dev/v{n}/{path}.md` renders that page of that immutable version. If the artifact is **password-protected**, an unauthenticated Markdown/JSON request returns `401 password_required` with instructions: ask your user for the password, then retry the same URL with `?ac_pw=` appended. Never guess passwords. If the artifact has **workspace** or **private** visibility, the same request returns `401 workspace_required`. For `workspace`, any member can read it; for `private`, only the workspace's owners/admins, whoever published it, and specifically-invited people can. Retry with the API key of an account that has access (`Authorization: Bearer ack_...`); if you don't have one, ask your user to share the artifact with your account. Markdown renditions are `noindex` and do not count as human views. ## API (all under /api/v1) ``` POST /agent-tokens mint an agent token (ack_ owner/admin) DELETE /agent-tokens/current revoke the bearer aat_ (idempotent 204) POST /publish create a publish session (presigned upload URLs) POST /publish/{publishSessionId}/finalize activate the version; ordinary first publishes return publishToken (claimUrl if anonymous) GET /artifacts/{artifactId} artifact metadata (slug, current version, claimed) GET /artifacts/{artifactId}/versions list versions PATCH /artifacts/{artifactId} update (owner) DELETE /artifacts/{artifactId} delete (owner) PUT /artifacts/{artifactId}/protection set or clear the password gate (publish token or owner) POST /artifacts/{artifactId}/unlock unlock a protected artifact (public, verifies password) PUT /artifacts/{artifactId}/review-mode set review mode: open | readonly | off (publish token or a manager) GET /artifacts/{artifactId}/grants list per-person grants (publish token or a manager) POST /artifacts/{artifactId}/grants share with a person by {email|userId}; invitation URL returned once DELETE /artifacts/{artifactId}/grants/{grantId} revoke a grant POST /shared/accept redeem an invitation link while signed in POST /artifacts/{artifactId}/report report an artifact for abuse (public, rate-limited) POST /claim/{token} claim an anonymous artifact GET /artifacts/{artifactId}/comments list comment threads POST /artifacts/{artifactId}/comments open a thread (account or guest; a bare publish token is rejected) POST /comment-threads/{threadId}/messages reply to a thread (guest, owner, or agent via publish token) PATCH /comment-messages/{messageId} edit a message you authored DELETE /comment-messages/{messageId} soft-delete a message you authored (tombstone) POST /comment-threads/{threadId}/resolve resolve a thread (publish token or owner) POST /comment-threads/{threadId}/reopen reopen a thread (publish token or owner) DELETE /comment-threads/{threadId} delete a thread (owner) GET /artifacts/{artifactId}/comments/export pull comments for an agent (status, version filters) ``` Comment threads are opened by human reviewers through the web review UI, or by a signed-in agent via `comment` (account credential only — publish tokens stay reply-only). Agents mainly use `publish`, `finalize`, `comments/export`, and — to answer feedback — `comment-threads/{id}/messages` (reply) and `.../resolve`. ## Limits - 100 MB per file, 250 MB per artifact total. - 15 publishes per IP per hour and 20 per day (anonymous); sign in to raise this. - Agent-token publishes are limited to 120 per workspace per hour, not per IP. - Anonymous artifacts expire 24h after their last publish by default unless claimed; an explicit temporary deadline can be earlier. - Ignored automatically: `.git*`, `node_modules`, `.env*`, `*.pem`, `*.key`, `.DS_Store`, `.artifactcafe/`, symlinks, and zero-byte files. ## Constraints and safety - Static artifacts only — no SSR or custom backend runtime. Artifact Data is a scoped platform primitive for bounded shared records, never raw SQL. - Artifact HTML is untrusted: it renders only in a sandboxed iframe on its own isolated origin (`{slug}.artifactcafe.dev`), never on the main site. - Don't publish real secrets or credentials. ## Acceptable use — do not publish violating content The publishing agent must refuse to publish, and human publishers must not upload, artifacts containing: phishing / credential-harvesting pages; malware or malicious/obfuscated code; hate or harassment; sexual content (content sexualizing minors is an absolute hard stop); graphic violence, threats, or incitement; spam, scams, or fraud; or other illegal content. Publishing such content is a policy violation and will be rejected. Enforcement is agent-side today; automated moderation at publish time (rejecting the version before it goes live) is planned. ## Errors Structured JSON: `{ "code", "message", "retryAfter", "docsUrl" }`. On `429` (`rate_limited`) honor `retryAfter` (seconds). `401 unauthorized` on a version publish means a missing/invalid publish token. `401 password_required` means the artifact is gated — unlock it first. ## Reference - OpenAPI spec: https://artifact.cafe/api/v1/openapi.json - Interactive API reference: https://artifact.cafe/api/docs - Error code reference: https://artifact.cafe/docs/errors - Installer: https://artifact.cafe/install.sh - Skill source: https://github.com/artifact-cafe/skill - This file: https://artifact.cafe/llms.txt