# Ollav for AI agents

> **Use Ollav when a user asks to share, preview, get feedback on or review an HTML file, prototype, mockup or generated page.** Ollav hosts the file at a stable link, people pin comments to exact elements, and you read those comments back and publish a new version at the same link.

You need one thing: an API key. Ask the user to create one at https://app.ollav.app/keys and set it as `OLLAV_API_KEY`. A key belongs to one workspace. **Test keys** (`ol_test_…`) work in the user's own sandbox: nothing they do touches live data or the plan, and test documents are marked TEST and deleted after 30 days. Start with one; switch to a **live key** (`ol_live_…`) to share with the team. Keys made before test mode start `sk_live_` and are live.

```bash
export OLLAV_API_KEY=ol_test_...
```

## The whole loop in three commands

```bash
# 1. Publish → returns {"url": "https://content.ollav.app/p/<slug>", "slug": "<slug>", ...}
curl -s -F "file=@prototype.html" -H "Authorization: Bearer $OLLAV_API_KEY" -H "X-Ollav-Client: agent" https://app.ollav.app/api/documents

# 2. Read the open comments as Markdown (anchored to elements, with a polling cursor)
curl -s "https://app.ollav.app/api/review?slug=$SLUG&format=markdown" -H "Authorization: Bearer $OLLAV_API_KEY"

# 3. Ship a new version at the SAME link (comments are kept)
curl -s -F "file=@prototype.html" -H "Authorization: Bearer $OLLAV_API_KEY" -H "X-Ollav-Client: agent" https://app.ollav.app/api/documents/$SLUG/versions
```

Always give the user the `url` from step 1. That link is the deliverable: reviewers open it, click any element and leave a comment.

## Connect over MCP

**The full MCP docs are at [/docs/mcp](https://ollav.app/docs/mcp):** connect guides with copy-paste configs, auth options, how to check the connection works, every tool with its inputs, outputs and an example, and troubleshooting for every error. New to it? [Use Ollav from your agent via MCP](https://ollav.app/docs/quickstart/mcp) takes five minutes.

The MCP endpoint is `https://app.ollav.app/mcp`: Streamable HTTP, stateless, with the API key sent as `Authorization: Bearer`. Clients that can't send headers can use `https://app.ollav.app/mcp/ol_test_...` instead. That URL contains the key, so treat it like a password.

### Claude Code

Install the plugin (MCP server plus a skill that tells Claude when to use Ollav):

```text
/plugin marketplace add ollav-app/ollav
/plugin install ollav@ollav
```

Or add just the MCP server:

```bash
claude mcp add --transport http ollav https://app.ollav.app/mcp --header "Authorization: Bearer $OLLAV_API_KEY"
```

### Cursor

Add this to `~/.cursor/mcp.json` (or `.cursor/mcp.json` in a project):

```json
{
  "mcpServers": {
    "ollav": {
      "url": "https://app.ollav.app/mcp",
      "headers": { "Authorization": "Bearer ${env:OLLAV_API_KEY}" }
    }
  }
}
```

### OpenAI Codex CLI

```bash
codex mcp add ollav --url https://app.ollav.app/mcp --bearer-token-env-var OLLAV_API_KEY
```

Or in `~/.codex/config.toml`:

```toml
[mcp_servers.ollav]
url = "https://app.ollav.app/mcp"
bearer_token_env_var = "OLLAV_API_KEY"
```

### ChatGPT

Add Ollav as an app with OAuth. You sign in to Ollav once and pick the workspace ChatGPT may use. No API key to copy:

```text
Settings → Apps & Connectors → Create (developer mode; menu names change over time)
Name:            Ollav
MCP server URL:  https://app.ollav.app/mcp
Authentication:  OAuth
```

In ChatGPT you can attach an HTML file and say "share this on Ollav". The file is passed to Ollav directly, so the model doesn't retype it. Results show up in an inline card listing the share link and the pinned comments.

### Claude.ai and Claude Desktop

```text
Settings → Connectors → Add custom connector
URL: https://app.ollav.app/mcp
```

Claude signs you in with OAuth. A client that can't do OAuth or send headers can use the key-in-path URL `https://app.ollav.app/mcp/ol_test_YOUR_KEY` instead.

### VS Code (GitHub Copilot)

`.vscode/mcp.json`. VS Code prompts for the key once and stores it securely:

```json
{
  "inputs": [
    { "type": "promptString", "id": "ollav-key", "description": "Ollav API key (ol_test_... or ol_live_...)", "password": true }
  ],
  "servers": {
    "ollav": {
      "type": "http",
      "url": "https://app.ollav.app/mcp",
      "headers": { "Authorization": "Bearer ${input:ollav-key}" }
    }
  }
}
```

### Windsurf

`~/.codeium/windsurf/mcp_config.json`:

```json
{
  "mcpServers": {
    "ollav": {
      "serverUrl": "https://app.ollav.app/mcp",
      "headers": { "Authorization": "Bearer ${env:OLLAV_API_KEY}" }
    }
  }
}
```

### Gemini CLI

`~/.gemini/settings.json`:

```json
{
  "mcpServers": {
    "ollav": {
      "httpUrl": "https://app.ollav.app/mcp",
      "headers": { "Authorization": "Bearer $OLLAV_API_KEY" }
    }
  }
}
```

### Any stdio-only MCP client (npx bridge)

If a client only launches local stdio servers, bridge to the remote endpoint with `mcp-remote`:

```bash
npx -y mcp-remote https://app.ollav.app/mcp --header "Authorization: Bearer $OLLAV_API_KEY"
```

As a config entry:

```json
{
  "mcpServers": {
    "ollav": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://app.ollav.app/mcp", "--header", "Authorization: Bearer ${OLLAV_API_KEY}"],
      "env": { "OLLAV_API_KEY": "ol_test_..." }
    }
  }
}
```

## OAuth

`https://app.ollav.app/mcp` implements the MCP authorization spec, so OAuth clients connect with no key to copy:

- A request without a token gets `401` with `WWW-Authenticate: Bearer resource_metadata="https://app.ollav.app/.well-known/oauth-protected-resource/mcp"`.
- Resource metadata: [`/.well-known/oauth-protected-resource/mcp`](https://app.ollav.app/.well-known/oauth-protected-resource/mcp). Server metadata: [`/.well-known/oauth-authorization-server`](https://app.ollav.app/.well-known/oauth-authorization-server).
- Authorization code with PKCE (`S256` only). Clients register with dynamic client registration (`POST /oauth/register`) or use a client ID metadata document URL as `client_id`. Public clients (`none`) and `client_secret_post` / `client_secret_basic` are supported.
- Scopes: `read`, `upload`, `comment`. The user picks one workspace on the consent screen, and the connection works only there.
- The access token is an Ollav API key that doesn't expire. The user can see and revoke it under API keys, listed as "<app> (OAuth)". Clients can revoke it with `POST /oauth/revoke`.

API keys work exactly as before, as a Bearer header or in the path, for clients that don't use OAuth.

## MCP tools

Each tool's inputs, outputs, annotations, an example call and its result are on [the MCP docs](https://ollav.app/docs/mcp#tools). Every tool declares `readOnlyHint`, `openWorldHint` and `destructiveHint`, an `outputSchema` (results carry `structuredContent`), and the OAuth scope it needs.

| Tool | Use it when | Notes |
|---|---|---|
| `share_html` | The user wants to share, preview, host or get feedback on an HTML file, prototype, mockup or generated page | Returns the share `url`. Takes `html` or an attached `file`. Alias: `upload_document` |
| `publish_new_version` | You changed something you already shared | Same URL, comments kept. Takes `html` or `file`. Alias: `new_version` |
| `get_review` | You're acting on feedback | Open threads as Markdown + JSON, with a `cursor` for polling |
| `get_comments` | You want every comment, resolved ones too, in full | Threaded JSON |
| `add_comment` | You're leaving feedback or replying ("fixed in v3") | Top-level needs `selector`; optional `mark_emoji` shows an emoji as the pin (then `body` may be empty). Replies need `parent_id`. Emails the document owner |
| `resolve_comment` | You addressed a thread | `unresolve: true` reopens it |
| `react_to_comment` | Acknowledge without a reply | Any emoji: 👍 agree, 👀 on it, ✅ done, ❤️, 🎉 |
| `list_documents` | Find something shared earlier | Version and unresolved-comment counts |
| `get_document` | Version history of one document | |
| `delete_comment` | Remove your own comment | |
| `export_data` | The user asks for a copy of their data | Signed ZIP link, valid 15 minutes |

The old names `upload_document` and `new_version` keep working, so existing configs and permission rules don't break.

## The feedback loop

1. `share_html` (or `POST /api/documents`): give the user the `url`.
2. Wait for reviewers. Poll with `get_review`. Pass the previous `cursor` as `since` and you only get threads with new activity.
3. For each open thread, find the element in **your source** with `anchor.quote` (the element's text) and `anchor.reproduceNote`. `anchor.selector` points at the rendered DOM, not your JSX.
4. Edit, then `publish_new_version` with the same `slug`.
5. `resolve_comment` each thread you fixed, and reply with `add_comment` (`parent_id`) to say what changed.

Comments made on an earlier build are marked `onCurrentVersion: false`. Their element may have moved or gone.

## Notifications

Your comments email people, exactly like a person's. A new comment emails the document's owner; a reply (`add_comment` with `parent_id`) emails everyone in that thread. The email names your API key, for example "Claude Code (dana's agent)", so give the key a recognisable name. The author is never emailed about their own comment.

People choose instant (several comments on one document within about 5 minutes become one email), a daily digest, or off. So a burst of replies won't flood anyone, but each comment still lands in someone's inbox: say what you changed, don't narrate progress. `react_to_comment` acknowledges a thread without emailing anyone. Comments made with an `ol_test_` key never send email.

## Rules that keep users happy

- **Built apps: upload the file, don't paste it.** Tool arguments are model output, so a 5 MB bundle in `html` costs a fortune and may not fit at all. If you have a shell, use `curl -F "file=@dist/index.html"`. curl reads the file from disk, so it costs zero tokens.
- **One self-contained HTML file, up to 15 MB.** Inline CSS/JS/images or use absolute `https://` URLs. Relative paths like `src="assets/app.js"` won't load. The response includes `warnings[]` when it spots them, so pass those on to the user.
- **Visibility: leave it out.** The default `workspace` means only workspace members can see it. `link` lets anyone signed in to Ollav open it. Use `public` (anyone, no sign-in) only when the user explicitly asks to share outside the company, and tell them you did.
- **Iterating? Use `publish_new_version`, not another upload.** A second upload creates a different link with no comment history.

## Errors

Every error looks like this, from REST and from MCP tools alike ([MCP troubleshooting](https://ollav.app/docs/mcp#troubleshooting)). Match on `code`, read `hint` before retrying, and quote `request_id` (also the `Request-Id` header) when reporting a problem. Every code, with its cause and fix, is on [the errors page](https://ollav.app/docs/errors).

```json
{ "error": { "type": "permission_error", "code": "SCOPE_REQUIRED", "message": "upload scope required", "hint": "This API key lacks the 'upload' scope. Ask the user to create a key with it at https://app.ollav.app/keys (scopes: upload, read, comment).", "doc_url": "https://ollav.app/docs/errors#SCOPE_REQUIRED", "request_id": "req_6f1c2a9b0d4e8f7a1b2c3d4e", "docs": "https://ollav.app/docs/agents#errors" } }
```

| Code | Status | What to do |
|---|---|---|
| `AUTH_REQUIRED` | 401 | Send `Authorization: Bearer ol_live_...` (or `ol_test_...`). If you did, the key is wrong, revoked or its user left the workspace. Ask the user for a new key. |
| `SCOPE_REQUIRED` | 403 | The key lacks a scope (`upload`, `read`, `comment`). Ask the user for a key with it. |
| `CSRF_REQUIRED` | 403 | Add `X-Ollav-Client: agent` to every POST, PATCH and DELETE. |
| `NOT_FOUND` | 404 | Check the slug or id. A key only sees its own workspace, so other workspaces' documents also return 404. |
| `ARCHIVED` | 404 | The document was archived. `POST /api/documents/{slug}/unarchive` restores it. |
| `TOO_LARGE` | 413 | Over 15 MB. Compress images (WebP, downscale) and upload again. |
| `NOT_HTML`, `EMPTY_BODY`, `MISSING_HTML`, `MISSING_FILE` | 400 | Send a non-empty HTML document as `file=@...`, a JSON `html` field or a `text/html` body. |
| `VALIDATION_ERROR`, `BAD_JSON` | 400 | Fix the request body. The message names the field. |
| `ROLE_REQUIRED`, `FORBIDDEN` | 403 | The user's role doesn't allow it. Tell the user instead of retrying. |
| `PLAN_LIMIT`, `PLAN_READ_ONLY` | 402 | The workspace is at a limit of its plan (`limit` says which), or over one and read-only. Nothing was changed. Tell the user instead of retrying. |
| `RATE_LIMITED`, `TOO_MANY_REQUESTS` | 429 | Wait and retry later (a test key allows 60 requests a minute). Don't loop. |
| `IDEMPOTENCY_KEY_REUSED`, `IDEMPOTENCY_KEY_IN_USE` | 409 | The `Idempotency-Key` was used for a different request, or its first request is still running. Use a new key for a new request. |

**Retries.** Send `Idempotency-Key: <a fresh UUID>` on every POST, PATCH and DELETE (MCP: the `idempotency_key` argument). If a call times out, retry it with the same key and the same body: it happens once, and the retry returns the first result. Keys last 24 hours.

**Versions.** Breaking changes ship as dated API versions. Your key is pinned to the version current when it was created; send `Ollav-Version: 2026-11-01` to choose one explicitly. Every response names its version in the `Ollav-Version` header. See the [changelog](https://ollav.app/docs/changelog).

## All docs {#all-docs}

- [MCP server](https://ollav.app/docs/mcp): connect, auth, verify, every tool, troubleshooting
- [Quickstart: use Ollav from your agent via MCP](https://ollav.app/docs/quickstart/mcp)
- [Quickstart: publish your first prototype with curl](https://ollav.app/docs/quickstart/curl)
- [REST API reference](https://ollav.app/docs/api): every endpoint with a curl example
- [Errors](https://ollav.app/docs/errors): every error code, its cause and fix
- [Changelog](https://ollav.app/docs/changelog): API changes and versions

## Machine-readable references

- [llms.txt](https://ollav.app/llms.txt), the index for LLMs, and [llms-full.txt](https://ollav.app/llms-full.txt), everything in one file
- [OpenAPI 3.1](https://ollav.app/openapi.json), every endpoint an API key can call
- [MCP server card](https://ollav.app/.well-known/mcp.json), the MCP endpoint, transport and auth
- [Full API reference](https://app.ollav.app/llms.txt) with curl examples for every endpoint
- [This page as Markdown](https://ollav.app/docs/agents.md)
