# Publish your first prototype with curl

Five commands, about five minutes. You publish an HTML file, get a link people can comment on, read their comments, ship a fix at the same link and close the thread. Everything here runs with a **test key**, so nothing touches your team's live documents or your plan.

## 1. Get a test key {#key}

Open https://app.ollav.app/keys, choose **Test**, keep the scopes `upload`, `read` and `comment`, and create the key. It is shown once.

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

Check it works:

```bash
curl -s https://app.ollav.app/api/me -H "Authorization: Bearer $OLLAV_API_KEY"
```

The response says `"livemode": false`: this key works in your own sandbox. Test documents carry a TEST mark and are deleted after 30 days, and a test key may make 60 requests a minute.

## 2. Publish a file {#publish}

Any self-contained HTML file up to 15 MB works. No file handy? Make one:

```bash
cat > prototype.html <<'HTML'
<!DOCTYPE html>
<html><head><title>Pricing page</title></head>
<body><h1>Pricing</h1><p>Pro: €12 a month.</p><button id="buy">Buy now</button></body></html>
HTML
```

Upload it. curl reads the file from disk, which is also the cheapest way for an agent with a shell to publish:

```bash
curl -s -F "file=@prototype.html" \
  -H "Authorization: Bearer $OLLAV_API_KEY" \
  -H "X-Ollav-Client: agent" \
  -H "Idempotency-Key: $(uuidgen)" \
  https://app.ollav.app/api/documents
```

```json
{ "url": "https://content.ollav.app/p/qkjgjQCQGz", "slug": "qkjgjQCQGz", "versionId": "...", "deduped": false, "versioned": false, "warnings": [], "livemode": false }
```

Open the `url`: that link is the deliverable. Keep the `slug`:

```bash
export SLUG=qkjgjQCQGz
```

Every POST, PATCH and DELETE needs `X-Ollav-Client: agent`. `Idempotency-Key` is optional: retry with the same key and the upload happens once.

## 3. Leave a comment {#comment}

Normally a reviewer opens the link and clicks an element. To try the loop alone, pin one yourself:

```bash
curl -s -X POST https://app.ollav.app/api/comments \
  -H "Authorization: Bearer $OLLAV_API_KEY" \
  -H "X-Ollav-Client: agent" \
  -H "Content-Type: application/json" \
  -d "{\"slug\":\"$SLUG\",\"body\":\"Make the button say Start free trial\",\"selector\":\"#buy\",\"rect\":{\"x\":0,\"y\":0,\"w\":1,\"h\":1},\"elementText\":\"Buy now\"}"
```

## 4. Read the feedback {#review}

`/api/review` returns the open threads. With `format=markdown` they come as a summary an agent can act on directly:

```bash
curl -s "https://app.ollav.app/api/review?slug=$SLUG&format=markdown" -H "Authorization: Bearer $OLLAV_API_KEY"
```

Without `format`, the JSON has each thread's `id`, `comment` and `anchor.quote` (the element's text, for finding it in your source), plus a `cursor`: pass it as `since` next time to get only new activity.

Keep the first thread's id (this uses `jq`):

```bash
export COMMENT_ID=$(curl -s "https://app.ollav.app/api/review?slug=$SLUG" -H "Authorization: Bearer $OLLAV_API_KEY" | jq -r '.threads[0].id')
```

## 5. Ship the fix at the same link, and resolve {#version}

Edit the file, then publish a new version. The URL stays the same and every comment is kept:

```bash
sed -i.bak 's/Buy now/Start free trial/' prototype.html
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
```

Close the thread:

```bash
curl -s -X POST https://app.ollav.app/api/comments/$COMMENT_ID/resolve \
  -H "Authorization: Bearer $OLLAV_API_KEY" \
  -H "X-Ollav-Client: agent"
```

## When something fails {#errors}

Errors are JSON with a stable `code`, a `hint` saying what to do and a `doc_url`. The common ones here:

| Code | Why | Fix |
|---|---|---|
| `AUTH_REQUIRED` | The key is missing, mistyped or revoked | Check `$OLLAV_API_KEY` is exported in this shell |
| `CSRF_REQUIRED` | A POST without `X-Ollav-Client` | Add `-H "X-Ollav-Client: agent"` |
| `NOT_FOUND` | Wrong slug or id, or a live document with a test key | Use the slug from step 2 with the same key |
| `TOO_LARGE` | Over 15 MB | Compress inlined images |
| `RATE_LIMITED` | Over 60 requests a minute with a test key | Wait a minute |

Every code: [errors](https://ollav.app/docs/errors).

## Next {#next}

- Switch to a live key (`ol_live_...`) to share with your team. Live documents count towards your plan.
- Let your agent do this: [Use Ollav from your agent via MCP](https://ollav.app/docs/quickstart/mcp).
- Every endpoint: [REST API reference](https://ollav.app/docs/api).
