# Proof Agent Docs

## If Proof Looks Wrong

Use one tool call:

  POST /api/bridge/report_bug

If you already have a document slug, you can also call:

  POST /d/<slug>/bridge/report_bug

Send what you know. The server decides whether the report is complete, enriches it with server traces and document events, creates a GitHub issue when it has enough context, and returns a `fixerBrief` you can hand directly to another agent.

### Transient failure guidance

If a non-destructive read or action fails because of a timeout, slow load, or stale state, retry once. If the retry still fails, report it with raw evidence instead of a summary from memory.

### Detailed issue reporting guidance

If the failure looks transient:

- Retry once if the action was non-destructive and the failure looked like intermittent downtime, a timeout, a slow load, or a stale read.
- If the retry succeeds and the doc fully recovers, treat it as recovery noise unless you saw data loss, a stuck paused state, or the same failure twice on the same slug.
- If the retry still fails, report it with raw evidence instead of summarizing it from memory.
- Include whether the failure came from the homepage, library, shared doc HTML, `state`, `snapshot`, `collab-refresh`, `edit/v2`, comment routes, or another write route.

Report these issues right now:

- A doc stays paused after refresh/reopen and blocks normal reading or writing.
- `state`, shared doc HTML, or `snapshot` repeatedly times out or returns 5xx for the same slug.
- `edit/v2`, comment writes, or mark mutations fail after a fresh read and one safe retry.
- The UI shows blank, stale, duplicated, or rolled-back content after the server said a request succeeded.
- Comments, suggestions, or marks disappear, reappear, or disagree across refreshes or devices.
- Two surfaces disagree about document status, for example the page says paused but writes still apply, or `state` looks fresh while shared HTML is obviously stale.

Usually do not report these by themselves unless they repeat:

- A single slow homepage or library load that succeeds on retry.
- A one-off timeout with no document slug and no persistent symptoms.
- Expected `409` conflict responses caused by stale `baseToken` values, stale snapshots, or ambiguous anchors.
- Brief reconnect jitter where content, comments, and writes recover normally.
The most useful evidence is:
- Exact request URL, method, status, and `x-request-id`
- The document slug, if there is one
- Whether retry changed the outcome
- The raw response body or console/network error text

If the response says `status: "needs_more_info"`, ask the suggested questions and call the same endpoint again.

If you want reference code while debugging or writing up the report, you can optionally inspect the open-source repo:

  https://github.com/EveryInc/proof-sdk

## Which Editing Method Should I Use?

Proof has three editing approaches. **Pick one — don't mix them.**

| Goal | Method | Endpoint |
|------|--------|----------|
| **Add/replace/insert a few lines** (recommended) | Edit V2 (block-level) | `GET /snapshot` → `POST /edit/v2` |
| **Replace entire document** | Rewrite | `POST /ops` with `rewrite.apply` |
| **Add a comment** | Ops | `POST /ops` with `comment.add` |

**Start with Edit V2** for most tasks. It uses stable block refs, handles concurrent edits cleanly, and returns clean markdown without internal HTML annotations.

`suggestion.add` now matches against annotated documents correctly and preserves stable anchors, but `edit/v2` is still the better default for programmatic content changes.

`rewrite.apply` is still disruptive. Avoid it if anyone might have the document open: hosted environments block rewrites while live authenticated collaborators are connected, and `force` is ignored there.

## I Just Received A Proof Link

No browser automation is required. Use HTTP directly (for example, `curl` or your tool's `web_fetch`).

If you received a shared link like:

  https://www.proofeditor.ai/d/<slug>?token=<token>

You can discover the API and read the document in one step using **content negotiation** on that same URL.

Fetch JSON (recommended):

  curl -H "Accept: application/json" "https://www.proofeditor.ai/d/<slug>?token=<token>"

Fetch raw markdown:

  curl -H "Accept: text/markdown" "https://www.proofeditor.ai/d/<slug>?token=<token>"

The JSON response includes:
- `markdown` (document content)
- `_links` (state, ops, docs)
- `agent.auth` hints (how to use the token)

### Quick copy/paste flow (token already in the shared URL)

```bash
SHARE_URL='https://www.proofeditor.ai/d/<slug>?token=<token>'
TOKEN='<token>'
SLUG='<slug>'

curl -H "Accept: application/json" "$SHARE_URL"
curl -H "Accept: text/markdown" "$SHARE_URL"
curl -H "Authorization: Bearer $TOKEN" -H "X-Agent-Id: your-agent" "https://www.proofeditor.ai/api/agent/$SLUG/state"
```

## Read Comments And Thread State

Use:

  GET /api/agent/<slug>/state

The state response is the canonical read surface for document text plus mark metadata.

Comment bodies, replies, and resolved state live in `marks` on the state response. For comment threads, read `state.marks` and inspect comment marks like:

  {
    "kind": "comment",
    "text": "Comment body",
    "threadId": "comment-123",
    "thread": [{"by":"human:editor","text":"Reply","at":"..."}],
    "resolved": false
  }

Use `/snapshot` only for block refs and `edit/v2`. It is block-focused and does not include comment thread bodies.

Use the comment mark's `markId` as the reply target when calling `comment.reply`. `threadId` is part of the read surface, but the write route expects `markId`.
`comment.resolve` keeps the comment in the marks map and flips `resolved: true`; it does not delete the comment. The UI/native bridge has a separate delete action, but `comment.delete` is not part of the public `/ops` contract today.

Use `/events/pending` to notice low-level activity that may require a refresh. Do not treat it as the canonical source of comment thread text.
Use `/queue/pending` as the preferred summarized "what changed" feed for agents.

## Auth: Token From URL

If a URL contains `?token=`, treat it as an access token:

- Preferred: `Authorization: Bearer <token>`
- Also accepted: `x-share-token: <token>`

## Edit Via Ops (Comments, Suggestions, Rewrite)

Use:

  POST /api/agent/<slug>/ops

`by` controls authorship. Presence identity is separate.
- Preferred: send `X-Agent-Id: <your-agent-id>`.
- Also accepted on `/presence`: `agentId` or `agent.id` in the JSON body.
- `by` does not satisfy the presence identity requirement.
All public mutation routes require `baseToken` from `GET /api/agent/<slug>/state` or `GET /api/agent/<slug>/snapshot`.

Older clients may still use the legacy compatibility alias `POST /api/documents/<slug>/ops`, but new agent integrations should not depend on it.

Supported op types:
- `comment.add`
- `comment.reply`
- `comment.resolve`
- `comment.unresolve`
- `suggestion.add` with `kind: "insert" | "delete" | "replace"`
- `suggestion.accept`
- `suggestion.reject`
- `rewrite.apply`

`suggestion.resolve` is not supported. Use `suggestion.accept` or `suggestion.reject` instead.
For comments, the public API supports resolve/unresolve, not delete.

Add a comment:

  curl -X POST "https://www.proofeditor.ai/api/agent/<slug>/ops?token=<token>" \
    -H "Content-Type: application/json" \
    -H "X-Agent-Id: your-agent" \
    -d '{"type":"comment.add","by":"ai:your-agent","baseToken":"mt1:<token-from-state-or-snapshot>","quote":"text to anchor","text":"comment body"}'

Reply to an existing thread:

  curl -X POST "https://www.proofeditor.ai/api/agent/<slug>/ops?token=<token>" \
    -H "Content-Type: application/json" \
    -H "X-Agent-Id: your-agent" \
    -d '{"type":"comment.reply","by":"ai:your-agent","baseToken":"mt1:<token-from-state-or-snapshot>","markId":"comment-123","text":"Reply text"}'

Suggest a replace:

  curl -X POST "https://www.proofeditor.ai/api/agent/<slug>/ops?token=<token>" \
    -H "Content-Type: application/json" \
    -H "X-Agent-Id: your-agent" \
    -d '{"type":"suggestion.add","by":"ai:your-agent","baseToken":"mt1:<token-from-state-or-snapshot>","kind":"replace","quote":"old text","content":"new text"}'

Create and immediately apply a suggestion:

  curl -X POST "https://www.proofeditor.ai/api/agent/<slug>/ops?token=<token>" \
    -H "Content-Type: application/json" \
    -H "X-Agent-Id: your-agent" \
    -d '{"type":"suggestion.add","by":"ai:your-agent","baseToken":"mt1:<token-from-state-or-snapshot>","kind":"replace","quote":"old text","content":"new text","status":"accepted"}'

Accept a suggestion:

  curl -X POST "https://www.proofeditor.ai/api/agent/<slug>/ops?token=<token>" \
    -H "Content-Type: application/json" \
    -H "X-Agent-Id: your-agent" \
    -d '{"type":"suggestion.accept","by":"ai:your-agent","baseToken":"mt1:<token-from-state-or-snapshot>","markId":"mark-123"}'

Reject a suggestion:

  curl -X POST "https://www.proofeditor.ai/api/agent/<slug>/ops?token=<token>" \
    -H "Content-Type: application/json" \
    -H "X-Agent-Id: your-agent" \
    -d '{"type":"suggestion.reject","by":"ai:your-agent","baseToken":"mt1:<token-from-state-or-snapshot>","markId":"mark-123"}'

Rewrite the whole document:

  curl -X POST "https://www.proofeditor.ai/api/agent/<slug>/ops?token=<token>" \
    -H "Content-Type: application/json" \
    -H "X-Agent-Id: your-agent" \
    -d '{"type":"rewrite.apply","by":"ai:your-agent","baseToken":"mt1:<token-from-state-or-snapshot>","content":"# New markdown..."}'

## Legacy `/edit`

`POST /api/agent/<slug>/edit` has been removed.

If an older client still calls it, the server returns:
- `410 LEGACY_EDIT_REMOVED`
- `retryWithState: /api/agent/<slug>/state`
- `recommendedEndpoint: /api/agent/<slug>/edit/v2`

Migrate all surgical document changes to `/snapshot` + `/edit/v2`.

## Update Title Metadata

Use:

  PUT /api/documents/<slug>/title

Example:

  curl -X PUT "https://www.proofeditor.ai/api/documents/<slug>/title" \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer <token>" \
    -d '{"title":"Updated document title"}'

Discovery:
- `GET /api/agent/<slug>/state` includes `_links.title` and `agent.titleApi`.

## Edit V2 (Block IDs + Authoritative Base Locking)

Use v2 for top-level block edits with stable block IDs and authoritative optimistic locking. Send `mutationBase.token` from `/state` or `/snapshot` as `baseToken`.

Payload shape reminder:
- `/api/agent/<slug>/ops` uses a top-level `type` field like `comment.add` or `suggestion.accept`.
- `/api/agent/<slug>/edit/v2` uses a top-level `operations` array, and each entry uses `op` like `replace_block` or `insert_after`.
- Do not mix these request shapes.

### Get a snapshot

  GET /api/agent/<slug>/snapshot

Example:

  curl -H "Authorization: Bearer <token>" "https://www.proofeditor.ai/api/agent/<slug>/snapshot"

The response includes an ordered `blocks` array with deterministic refs (`b1`, `b2`, ...). When authoritative mutations are available it also includes `mutationBase.token`; use that token as your write precondition.

### Apply edits

  POST /api/agent/<slug>/edit/v2

Example:

  curl -X POST "https://www.proofeditor.ai/api/agent/<slug>/edit/v2" \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer <token>" \
    -H "Idempotency-Key: <uuid>" \
    -d '{
      "by": "ai:your-agent",
      "baseToken": "mt1:<token-from-state-or-snapshot>",
      "operations": [
        { "op": "replace_block", "ref": "b3", "block": { "markdown": "Updated paragraph." } },
        { "op": "insert_after", "ref": "b3", "blocks": [{ "markdown": "## New Section" }] }
      ]
    }'

On success, the response includes the new `revision`, a `snapshot` payload, and a `collab` status.
If your `baseToken` is stale, you'll receive `STALE_BASE` plus the latest state or snapshot for retry.

Supported `/edit/v2` operation kinds:
- `replace_block`
- `insert_before`
- `insert_after`
- `delete_block`
- `replace_range`
  `replace_range` uses `fromRef` and `toRef`.
- `find_replace_in_block`
  `occurrence` may be `first` or `all` and defaults to `first`.

v2 convergence fields:
- `collab.status` remains compatibility status (`confirmed|pending`) and is fragment-authoritative.
- `collab.fragmentStatus` and `collab.markdownStatus` expose render-vs-projection split directly.
- `202` is only expected when fragment convergence is pending.

Precondition contract for v2:
- `baseToken` is required.
- Read it from `mutationBase.token` on `/state` or `/snapshot`.
- `baseRevision` and `baseUpdatedAt` are not accepted on `/edit/v2`.

Idempotency guidance:
- Send `Idempotency-Key` for mutation requests (`X-Idempotency-Key` is also accepted for compatibility).
- `/edit/v2` examples include this header because block-level retries are common in automation.

Mutation contract discovery:
- Read `contract.mutationStage` from `GET /api/agent/<slug>/state` to detect Stage A/B/C rollout.
- `contract.idempotencyRequired` and `contract.preconditionMode` summarize current requirements.

Common mutation contract error codes:
- `IDEMPOTENCY_KEY_REQUIRED`: mutation request omitted idempotency key in required stage.
- `IDEMPOTENCY_KEY_REUSED`: same key reused with a different payload hash.
- `BASE_TOKEN_REQUIRED`: route requires `baseToken`.
- `INVALID_BASE_TOKEN`: `baseToken` is malformed and must be an `mt1:` token.
- `STALE_BASE`: document changed since the provided `baseToken`.
- `LIVE_CLIENTS_PRESENT`: rewrite blocked because active authenticated collab clients are connected.
  Use `retryWithState` to refresh state, confirm `connectedClients === 0`, and if `forceIgnored=true` do not retry with `force` in hosted environments.
  This response is retryable and includes `reason` + `nextSteps`.
- `REWRITE_BARRIER_FAILED`: rewrite safety barrier failed before mutation; no rewrite was applied.
  This response is retryable and includes `reason` + `nextSteps`; retry with bounded exponential backoff and jitter.

## Presence And Event Polling

Poll for changes:

  GET /api/agent/<slug>/events/pending?after=<cursor>&limit=100

Poll summarized document change queue items (preferred for agent workflows):

  GET /api/agent/<slug>/queue/pending?after=<cursor>&limit=100

Ack processed events (editor/owner):

  POST /api/agent/<slug>/events/ack
  Body: {"upToId": <cursor>, "by": "ai:your-agent"}

Queue contract notes:
- Queue items are summaries and refresh hints only; they are not canonical content.
- Use `changeKinds` and `refreshHints` to decide whether to call `state` (document text/title/share state) and/or inspect `state.marks` (comments/suggestions).
- Keep your own queue cursor and poll idempotently; the same cursor can be used safely after restarts.

Presence identity:
- `POST /api/agent/<slug>/presence` requires explicit agent identity.
- Preferred: `X-Agent-Id: <your-agent-id>`.
- Also accepted: `agentId` or `agent.id` in the JSON body.
- `by` is not used for presence identity.

## Local Bridge

For the local Proof app bridge (localhost:9847) and deeper workflows, see:

  https://www.proofeditor.ai/agent-setup

## Projection Guardrails And QA

Operational metrics:
- `projection_guard_block_total{reason,source}`
- `projection_drift_total{reason,source}`
- `projection_chars_bucket{source,le}`

Staging soak (live browser viewers + repeated `/edit/v2`):

  SHARE_BASE_URL=https://proof-web-staging.up.railway.app \
  SOAK_DURATION_MS=300000 \
  npx tsx scripts/staging-collab-projection-soak.ts

## Create A New Shared Doc

If you need to create a share from scratch, use:

  POST /share/markdown

This is the canonical public create route.
Legacy create routes like `/api/documents` are internal/legacy and may be warned or disabled on hosted environments.

## Recommended Workflow: Adding Content To An Existing Doc

This is the most reliable way to add a line, row, or section to an existing document:

### Step 1: Get the snapshot

  curl -H "Authorization: Bearer <token>" "https://www.proofeditor.ai/api/agent/<slug>/snapshot"

This returns clean markdown per block (no internal HTML tags) plus stable `ref` identifiers. Use `mutationBase.token` as your write precondition.
### Step 2: Find the right block

Look through the `blocks` array for the block you want to edit or insert near. Each block has:
- `ref`: stable identifier (e.g., `b3`)
- `markdown`: the clean markdown content of that block
- `type`: block type (e.g., `paragraph`, `heading`, `table`)

### Step 3: Apply your edit

  curl -X POST "https://www.proofeditor.ai/api/agent/<slug>/edit/v2" \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer <token>" \
    -H "Idempotency-Key: <uuid>" \
    -d '{
      "by": "ai:your-agent",
      "baseToken": "mt1:<token-from-state-or-snapshot>",
      "operations": [
        { "op": "insert_after", "ref": "b3", "blocks": [{ "markdown": "New content here." }] }
      ]
    }'

### Step 4: Handle conflicts

If you get `STALE_BASE`, the response includes the latest state or snapshot. Re-read the blocks and retry with the fresh `mutationBase.token`.

## Troubleshooting

### `web_fetch` / `WebFetch` cannot access `proofeditor.ai`

If your tool reports a blocked or disallowed domain, ask the user to allow `proofeditor.ai` in the assistant's **Capabilities** settings (Web access allowed domains), then retry the same request.

### Legacy callers still hitting `/edit`

That route now returns `410 LEGACY_EDIT_REMOVED`. Re-read `/state` or `/snapshot`, then retry with `/edit/v2`.

### `LIVE_CLIENTS_PRESENT` on `rewrite.apply`

`rewrite.apply` is blocked when authenticated collaborators are connected. Outside hosted environments you can pass `"force": true`, but on hosted environments `force` is ignored. If you still prefer the safer path:
1. Use `/edit/v2` instead (it works with live clients).
2. Wait for clients to disconnect (poll `/state` and check `connectedClients`).

### Suggestion anchors not matching

`suggestion.add` now resolves quotes against clean text even when the stored markdown contains internal `<span data-proof="authored">` annotations. If you still get `ANCHOR_NOT_FOUND`, re-read state and verify the quote text genuinely exists.

### Document content looks corrupted after suggestion reject cycles

Repeated suggest/reject cycles on annotated documents now preserve stable suggestion anchors so the document text should remain unchanged. If you still see unexpected content drift, re-read `Accept: text/markdown` and report the exact request/response pair.

### `COLLAB_SYNC_FAILED` errors

Edits via the API can fail when a browser has the document open with an active Yjs collab session. `/edit/v2` handles this gracefully, but `rewrite.apply` does not. If you hit this, retry after a short delay or use `/edit/v2` instead.
