ODocs.co Agent Integration Guide ================================ Base URL: https://api.odocs.co (Port 443 — no port number needed) No auth required. Access is controlled by document UUID. Working with shared URLs ------------------------ When someone shares an ODocs.co link, the doc ID is in the URL fragment: https://odocs.co/#/doc/550e8400-e29b-41d4-a716-446655440000 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This is the doc ID To access this doc via the API, extract the UUID after "#/doc/" and use it: GET https://api.odocs.co/api/docs/550e8400-e29b-41d4-a716-446655440000 (MCP: pass that UUID as the id to get-document.) The browser URL is a client-side route (SPA). Do NOT fetch the HTML page — it's just a JavaScript app shell. Always use the REST API or MCP to read/write docs. Quickstart (curl) ----------------- # 1. Create a document curl -X POST https://api.odocs.co/api/docs -H Content-Type: application/json -d @- << 'EOF' { "title": "My Doc", "content": "Hello", "author": "MyAgent" } EOF # Returns: { "id": "550e8400-..." } — SAVE THE ID # 2. Read the document (note the version) curl https://api.odocs.co/api/docs/550e8400-... # DEFAULTS TO PLAINTEXT — a .txt-style view with a YAML frontmatter carrying the # metadata, then the document body: # --- # id: "550e8400-..." # title: "My Doc" # version: 2 # commentCount: 0 # openCommentCount: 0 # --- # Hello # Want JSON instead? Use the /json sibling or an Accept header (or ?format=json): # curl https://api.odocs.co/api/docs/550e8400-.../json # curl -H 'Accept: application/json' https://api.odocs.co/api/docs/550e8400-... # → { "id":"...", "title":"...", "content":"...", "version":2, # "commentCount":0, "openCommentCount":0 } # If openCommentCount > 0, humans left feedback — read it with GET .../comments # (or add ?include=comments to this call to inline the threads in the body). # 3. Insert text (use expectedVersion to avoid overwriting human edits) curl -X PATCH https://api.odocs.co/api/docs/550e8400-... -H Content-Type: application/json -d @- << 'EOF' { "operations": [{ "type": "insert", "position": 7, "text": " world" }], "author": "MyAgent", "expectedVersion": 2 } EOF # Returns: { "ok": true, "version": 3 } Use Cases --------- Create an odoc and share its URL to: - Get a human to comment on and approve a spec before you start work. - Share an engineering design with a product manager for comments. - Hand off context to another agent. Endpoints --------- POST /api/docs Create doc. Body: {title?, content?, author?} GET /api/docs/:id Get doc. DEFAULTS TO PLAINTEXT (frontmatter + body). Metadata in the frontmatter: id, title, version, commentCount (all threads), openCommentCount (unresolved). If openCommentCount > 0, read the comments (below). Add ?include=comments to wrap each commented span inline (see Comments). For JSON use the /json path or Accept: application/json (or ?format=json) → {id,title,content,version, commentCount,openCommentCount[,comments]}. GET /api/docs/:id/json Same as above but ALWAYS JSON. Honors ?include=comments. PATCH /api/docs/:id Edit doc. Body: {operations, author?, expectedVersion?} GET /api/docs/:id/versions/json List snapshots (JSON). [{index, timestamp, contributors}]. (/versions also works, JSON.) GET /api/docs/:id/versions/:index Get snapshot. DEFAULTS TO PLAINTEXT (frontmatter: timestamp, title; then content). JSON via .../:index/json or Accept: application/json. GET /api/docs/:id/comments Read comment threads humans left. DEFAULTS TO PLAINTEXT (a readable list). For JSON use .../comments/json or Accept: application/json → [{id, quote, contextBefore, contextAfter, approximateLineNumber, resolved, comments:[{author, kind, body, createdAt}]}]. approximateLineNumber is a 1-based "roughly where" hint (null if the quoted text was deleted); always act on the QUOTE, not the line number. Add ?resolved=false for open threads only. POST /api/docs/:id/comments/:threadId/replies Reply to an EXISTING thread. Body: {body, author?}. You CANNOT create threads or resolve them — both are human-only. Media types (the read endpoints) -------------------------------- GET /api/docs/:id, /comments, and /versions/:index DEFAULT to plaintext — a .txt-style view that's easier to read than JSON. To get JSON, either: • use the explicit /json path (…/json), or • send Accept: application/json, or • add ?format=json With ?include=comments, the plaintext doc wraps each commented passage inline: the quoted text User 1 (human): please expand this Claude (agent): done The quoted text inside the tag is verbatim — use it as your edit anchor. Threads whose quote no longer exists are listed at the end (orphaned="true"). The plaintext views start with a YAML frontmatter carrying the metadata (id, title, version, comment counts, etc.). To get just the body without it, add ?frontmatter=false (plaintext only; default is true). Comments (reading human feedback) --------------------------------- Humans highlight a passage and leave a comment on it. You discover there ARE comments from a plain doc read: GET /api/docs/:id returns openCommentCount — if it's > 0, go read them. Each thread gives you the QUOTED TEXT it refers to (act on that text; do NOT try to compute positions; approximateLineNumber is only a rough orientation hint). Typical loop: 1. GET /api/docs/:id → see openCommentCount > 0 2. GET /api/docs/:id/comments?resolved=false → find what to fix (or GET /api/docs/:id?include=comments to do steps 1+2 in one call) 3. Edit the doc with PATCH (text-anchored ops on the quote) 4. POST .../comments/:threadId/replies {body:"done — ..."} → tell the human You can reply to existing threads but you cannot create new comment threads or mark a thread resolved (the human does that after reviewing your reply). Operation types (PATCH body) ---------------------------- Two styles. Mix freely in one PATCH; ops are applied sequentially and each op resolves against the doc state AFTER the previous ones in the same call. Text-anchored (PREFERRED — LLMs are bad at character counting) { "type": "insert-after", "anchor": "...", "text": "...", "occurrence": N? } { "type": "insert-before", "anchor": "...", "text": "...", "occurrence": N? } { "type": "replace-text", "find": "...", "replace": "...", "occurrence": N? } { "type": "delete-text", "find": "...", "occurrence": N? } Exact-match (no regex, no whitespace normalization). If anchor/find appears multiple times: omit "occurrence" → 400 with match count; or pass 0-based "occurrence" to pick a specific one. Position-based (use only if you already know exact character offsets) { "type": "insert", "position": N, "text": "..." } { "type": "delete", "position": N, "length": N } { "type": "replace", "position": N, "length": N, "text": "..." } Position semantics: N is a UTF-16 code unit offset (same as JavaScript String.length / .indexOf). ASCII chars = 1 unit each; em-dash and most accented Latin = 1 unit; emoji like 🎉 = 2 units (surrogate pair). If your text has non-BMP chars (emoji etc.), compute positions programmatically rather than estimating, or use the text-anchored ops above which sidestep position arithmetic entirely. Examples -------- # Fix a missing list item — anchor lets you skip counting characters curl -X PATCH https://api.odocs.co/api/docs/$ID \ -H Content-Type:application/json \ -d '{ "operations": [ { "type": "insert-after", "anchor": "Item 1\n", "text": "Item 2\n" } ], "author": "MyAgent" }' # Rename a term throughout (unique match → no occurrence needed) curl -X PATCH https://api.odocs.co/api/docs/$ID \ -H Content-Type:application/json \ -d '{ "operations": [ { "type": "replace-text", "find": "good", "replace": "excellent" } ] }' # Multiple matches → disambiguate with 0-based occurrence curl -X PATCH https://api.odocs.co/api/docs/$ID \ -H Content-Type:application/json \ -d '{ "operations": [ { "type": "replace-text", "find": "foo", "replace": "bar", "occurrence": 1 } ] }' Error codes ----------- 400 Bad request — missing fields, unknown op type, position out of bounds 404 Document not found 409 Version mismatch — re-read and retry 429 Rate limited — back off and retry Rate limits ----------- POST /api/docs: 10/min per IP All other endpoints: 60/min per IP Conflict detection ------------------ Always use expectedVersion when editing existing docs. Workflow: 1. GET /api/docs/:id → get version N 2. Compute your operations 3. PATCH with expectedVersion: N 4. If 409: re-read, recompute, retry Doc lifecycle ------------- Docs are EPHEMERAL — RAM only. By design (draw.io-style: no account, no save button). Typical lifetime: ~24 hours after the last edit or last live viewer disconnects, whichever is later. Docs may also be lost on server restart. If you need to keep something, download a markdown export. MCP Server ---------- Agents that support the MCP (Model Context Protocol) can connect directly. KNOWN ISSUE (claude.ai only, current): claude.ai MCP connectors may show "connected" but expose no usable tools. This is a claude.ai-side bug, not an ODocs problem. If you are on claude.ai and the tools do not appear, fall back to the REST API above instead. Claude Code is NOT affected — use REST or MCP there. Endpoint: https://api.odocs.co/mcp Auth: None (rate limited by IP, same as REST API) Protocol: MCP over Streamable HTTP (not stdio) Your MCP client performs the connection handshake automatically — just point it at the endpoint above. No manual initialize/session steps needed. Tools available: create-document Create doc. Args: title(required), author?, content? Returns: {id, url, title} get-document Read a doc. Args: id(required), format?("text"|"json", default text), frontmatter?(bool, default true), includeComments?(bool). DEFAULT is a readable plaintext view (frontmatter with id/title/version/commentCount/ openCommentCount, then the body). format:"json" → {id, title, content, version, commentCount, openCommentCount}. The version (for patch conflict detection) is in the frontmatter, or use format:"json". If openCommentCount > 0, humans left feedback — pass includeComments:true to inline it (or call list-comments). patch-document Edit doc. Args: id, operations(required), author?, expectedVersion? Supports text-anchored ops (insert-after, insert-before, replace-text, delete-text) and position-based ops (insert, delete, replace) — see PATCH op section above. Returns: {ok, version} list-versions Version history. Args: id(required) Returns: {versions:[{index,timestamp}]} get-version Get content of a specific version snapshot. Args: id(required), index(required), format?("text"|"json", default text), frontmatter?(bool, default true). Call list-versions first to find the index you want. DEFAULT plaintext (frontmatter: timestamp, title; then content); format:"json" → {timestamp, title, content}. get-instructions Returns this agents.txt content (the full integration guide). Useful for MCP-only clients that landed without visiting the frontend. Args: none. list-comments Read the comment threads humans left. Args: id(required), includeResolved?(bool), format?("text"|"json", default text). DEFAULT plaintext list; format:"json" → array of threads. Each thread gives the QUOTED TEXT it refers to + its replies + approximateLineNumber (a rough "where in the doc" hint) — use the quote to find feedback to act on, not the line number. reply-to-comment Reply to an EXISTING thread. Args: id, threadId, body, author?. You CANNOT create new threads or resolve them (human-only). NOTE: no list-documents tool — doc UUIDs are capability tokens. Knowing the UUID = access. No enumeration endpoint by design. Errors: JSON-RPC errors in {error:{code,message}}. Tool errors: isError flag in result content. Workflows --------- Create and share (REST or MCP): 1. POST /api/docs {title:"Spec"} → {id:"..."} 2. Return the URL: https://odocs.co/#/doc/ 3. Agent or human edits at that URL — all changes sync in real time Read-then-patch safely (avoid overwriting human edits): 1. GET /api/docs/:id → {version: N} 2. Compute your operations 3. PATCH /api/docs/:id {operations:[...], expectedVersion: N} 4. If 409: re-read (get new version), recompute, retry Read-then-patch in MCP: 1. tools/call get-document → note version in response 2. tools/call patch-document with expectedVersion from step 1 3. If error includes "409" or "version mismatch": retry from step 1 Version history: 1. GET /api/docs/:id/versions → list of snapshots 2. GET /api/docs/:id/versions/:index → content at that snapshot (MCP: use list-versions, then get-version) Business account domain allowlisting ------------------------------------ If you are running in a Claude.ai business account with domain allowlisting enabled, your administrator may need to add the following URLs to the allowlist: https://odocs.co/ https://odocs.co/agents.txt https://api.odocs.co/ https://api.odocs.co/api/docs/ https://api.odocs.co/mcp (These are the only endpoints ODocs.co uses. The last one is only needed if you connect over MCP rather than REST.) Agent docs URL -------------- https://odocs.co/agents.txt (this file — plain text, curl-friendly)