DocsUpdated 2026-10-27View as Markdown

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

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

export OLLAV_API_KEY=ol_test_...

Check it works:

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

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

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:

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
{ "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:

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

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

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

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

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):

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

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

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:

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 are JSON with a stable code, a hint saying what to do and a doc_url. The common ones here:

CodeWhyFix
AUTH_REQUIREDThe key is missing, mistyped or revokedCheck $OLLAV_API_KEY is exported in this shell
CSRF_REQUIREDA POST without X-Ollav-ClientAdd -H "X-Ollav-Client: agent"
NOT_FOUNDWrong slug or id, or a live document with a test keyUse the slug from step 2 with the same key
TOO_LARGEOver 15 MBCompress inlined images
RATE_LIMITEDOver 60 requests a minute with a test keyWait a minute

Every code: errors.

Next