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)