REST API
The Server AI Toolkit provides REST API endpoints for AI agent tools.
Backward compatibility
Previous API names such as schemaAwarenessData, getSchemaAwarenessData,
/v3/ai/toolkit/tools, and /v3/ai/toolkit/schema-awareness-prompt are still available for
backward compatibility, but they are deprecated. Use editorContext, getEditorContext, and
/v3/ai/toolkit/tools for new integrations.
Postman collection
Browse the Postman collection for the Server AI Toolkit REST API.
Authentication
All requests must include:
Authorization: Bearer <JWT>X-App-Id: <APP_ID>
Generate the JWT server-side with your AI secret. Get your App ID and secret key on the Server AI Toolkit settings page.
import jwt from 'jsonwebtoken'
const JWT_TOKEN = jwt.sign(
{
experimental_document_server_id: 'your-document-server-id',
experimental_document_server_management_api_secret:
'your-document-server-management-api-secret',
},
'your-ai-secret-key',
{ expiresIn: '1h' },
)See the authorization guide for more information.
Document Server credentials
Include these JWT claims when you want the Server AI Toolkit to fetch and save Tiptap Cloud documents automatically:
experimental_document_server_idexperimental_document_server_management_api_secret
Base URL
The hosted base URL is:
https://api.tiptap.devIn the examples below, requests are written as BASE_URL/v3/ai/....
How to provide the document
Tool endpoints accept exactly one document source:
document: Inline Tiptap JSONexperimental_documentOptions: A Tiptap Cloud document reference
experimental_documentOptions has the following shape:
documentId(string, required): The Tiptap Cloud document identifier.userId?(string | null, optional): Identifier attributed to edits the AI performs.field?(string | null, optional, default:"default"): Targets a specific collaborative field (Y.js XML fragment) inside the document when omitted ornull. Use this when your document stores multiple editable fields — for example a separate title and body — under the samedocumentId.
Example with a Tiptap Cloud document:
{
"experimental_documentOptions": {
"documentId": "your-document-id",
"userId": "ai-assistant"
}
}Example targeting a non-default collaborative field:
{
"experimental_documentOptions": {
"documentId": "your-document-id",
"userId": "ai-assistant",
"field": "title"
}
}Example with an inline document:
{
"document": {
"type": "doc",
"content": []
}
}Threads require a cloud document
Thread and comment endpoints always require experimental_documentOptions.documentId.
Formats
Most endpoints accept a format field:
"json": Standard JSON representation"shorthand": Tiptap Shorthand, a token-efficient format for prompts and model output
Tool endpoints
Get tool definitions
POST /v3/ai/toolkit/toolsReturns the available tool definitions for AI agents.
Request body:
editorContext(EditorContext, required). Editor context obtained from thegetEditorContextfunction. See installation guide.tools?(Record<string, boolean>, optional): Enable or disable individual tools. See the tools reference for the supported keys.operationMeta?(string, optional, default:"")format?('json' | 'shorthand', optional, default:'json')schemaAwarenessData?(SchemaAwarenessData, optional, deprecated): UseeditorContextinstead. Supported for backward compatibility.
Response:
prompt(string): Add this to the system prompt for your AI request. It teaches the AI how the Tiptap document works, what elements it can contain, and how the document format works.tools(array)name(string)description(string)inputSchema(object): JSON schema for the AI-generated toolinput.
Execute a tool
POST /v3/ai/toolkit/execute-toolExecutes one Server AI Toolkit tool such as tiptapRead, tiptapEdit, getThreads, or
editThreads. See the
tools reference for every
supported tool and its type shapes.
Request body:
toolName(string, required)input(required): Tool input generated by the AI model. The shape depends ontoolName; see the tools reference.editorContext(EditorContext, required unlessschemaAwarenessDatais provided)schemaAwarenessData?(SchemaAwarenessData, optional, deprecated): UseeditorContextinstead. Supported for backward compatibility.toolConfig?(optional): Tool-specific parameters provided by the developer, not by the AI model. See the tools reference.format?('json' | 'shorthand', optional, default:'json')reviewOptions?(ReviewOptions, optional, default:{ mode: 'disabled' })experimental_commentsOptions?({ threadData?: Record<string, unknown>, commentData?: Record<string, unknown> }, optional): Metadata attached to new threads and comments created during thread-related tool execution.- exactly one of:
documentexperimental_documentOptions
Response:
output: tool response that should be read by the AI model. See the tools reference.toolResult: tool response in a format that is designed to be parsed by the developer. Should not be read by the AI. See the tools reference.docChanged(boolean): whether the document changed as a result of the tool calldocument(object | null): the document after the AI made changes to it
Stream a tool
POST /v3/ai/toolkit/stream-toolStreams tool execution. The request shape matches POST /v3/ai/toolkit/execute-tool. The streaming response format is still being finalized.
Because this endpoint works on the document over a live connection, it can return connection and concurrency errors that the REST endpoints don't. They arrive as an error event in the response stream — see Streaming and session errors.
Legacy workflow endpoints
Workflow endpoints are deprecated
Workflow endpoints are preserved for existing integrations. They will be removed in a future release.
Get a workflow definition
Use one of these endpoints:
POST /v3/ai/toolkit/workflows/editPOST /v3/ai/toolkit/workflows/insert-contentPOST /v3/ai/toolkit/workflows/proofreaderPOST /v3/ai/toolkit/workflows/threads
Request body:
format?('json' | 'shorthand', optional, default:'json'). Theproofreaderworkflow only accepts'shorthand'.
Response:
systemPrompt(string)outputSchema(object)
Example:
curl --location 'BASE_URL/v3/ai/toolkit/workflows/edit' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_JWT_TOKEN' \
--header 'X-App-Id: YOUR_APP_ID' \
--data '{
"format": "shorthand"
}'
Read document content for a workflow
POST /v3/ai/toolkit/read/read-documentReads document content and returns workflow-ready content.
Request body:
range?({ from: number, to: number }, optional, default: entire document)editorContext(EditorContext, required unlessschemaAwarenessDatais provided)schemaAwarenessData?(SchemaAwarenessData, optional, deprecated): UseeditorContextinstead. Supported for backward compatibility.format?('json' | 'shorthand', optional, default:'json')chunkSize?(number, optional, default:32000)reviewOptions?(ReviewOptions, optional, default:{ mode: 'disabled' })- exactly one of:
documentexperimental_documentOptions
Response:
outputsuccess(boolean)content?(unknown)error?(string)
docChanged(boolean)document(object | null)
Example:
curl --location 'BASE_URL/v3/ai/toolkit/read/read-document' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_JWT_TOKEN' \
--header 'X-App-Id: YOUR_APP_ID' \
--data '{
"editorContext": { /* editor context data */ },
"format": "shorthand",
"reviewOptions": {
"mode": "disabled"
},
"experimental_documentOptions": {
"documentId": "your-document-id",
"userId": "ai-assistant"
}
}'
Read the current selection
POST /v3/ai/toolkit/read/read-selectionReads the selection and returns either a selection payload or an explicit empty state.
Request body:
range({ from: number, to: number }, required)editorContext(EditorContext, required unlessschemaAwarenessDatais provided)schemaAwarenessData?(SchemaAwarenessData, optional, deprecated): UseeditorContextinstead. Supported for backward compatibility.format?('json' | 'shorthand', optional, default:'json')chunkSize?(number, optional, default:32000)reviewOptions?(ReviewOptions, optional, default:{ mode: 'disabled' })- exactly one of:
documentexperimental_documentOptions
Response:
output- when the selection is empty:
isEmpty(true)
- when the selection contains content:
isEmpty(false)content(unknown)nodeHashes(string[])nodeRange({ from: number, to: number })prompt(string)
- when the selection is empty:
docChanged(boolean)document(object | null)
Example:
curl --location 'BASE_URL/v3/ai/toolkit/read/read-selection' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_JWT_TOKEN' \
--header 'X-App-Id: YOUR_APP_ID' \
--data '{
"editorContext": { /* editor context data */ },
"range": { "from": 10, "to": 42 },
"format": "shorthand",
"experimental_documentOptions": {
"documentId": "your-document-id"
}
}'
Read threads
POST /v3/ai/toolkit/read/threadsReads all threads and comments for a Tiptap Cloud document.
Request body:
editorContext(EditorContext, required unlessschemaAwarenessDatais provided)schemaAwarenessData?(SchemaAwarenessData, optional, deprecated): UseeditorContextinstead. Supported for backward compatibility.format('json' | 'shorthand', required)experimental_documentOptions({ documentId: string, userId?: string, field?: string | null }, required)
Response:
outputthreads?(unknown[])error?(string)
docChanged(boolean)document(object | null)
Example:
curl --location 'BASE_URL/v3/ai/toolkit/read/threads' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_JWT_TOKEN' \
--header 'X-App-Id: YOUR_APP_ID' \
--data '{
"editorContext": { /* editor context data */ },
"format": "shorthand",
"experimental_documentOptions": {
"documentId": "your-document-id",
"userId": "ai-assistant"
}
}'
Execute the insert-content workflow
POST /v3/ai/toolkit/execute-workflow/insert-contentRequest body:
input(unknown, required): Generated insert-content payloadrange?({ from: number, to: number }, optional)editorContext(EditorContext, required unlessschemaAwarenessDatais provided)schemaAwarenessData?(SchemaAwarenessData, optional, deprecated): UseeditorContextinstead. Supported for backward compatibility.format?('json' | 'shorthand', optional, default:'json')chunkSize?(number, optional, default:32000)reviewOptions?(ReviewOptions, optional, default:{ mode: 'disabled' })- exactly one of:
documentexperimental_documentOptions
Response:
outputerror?(string)range?({ from: number, to: number })
docChanged(boolean)document(object | null)
Example:
curl --location 'BASE_URL/v3/ai/toolkit/execute-workflow/insert-content' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_JWT_TOKEN' \
--header 'X-App-Id: YOUR_APP_ID' \
--data '{
"editorContext": { /* editor context data */ },
"input": "This is the replacement content.",
"range": { "from": 10, "to": 42 },
"format": "shorthand",
"reviewOptions": {
"mode": "disabled"
},
"experimental_documentOptions": {
"documentId": "your-document-id",
"userId": "ai-assistant"
}
}'
Execute the Tiptap Edit workflow
POST /v3/ai/toolkit/execute-workflow/tiptap-editRequest body:
input(object, required): Edit workflow operationseditorContext(EditorContext, required unlessschemaAwarenessDatais provided)schemaAwarenessData?(SchemaAwarenessData, optional, deprecated): UseeditorContextinstead. Supported for backward compatibility.format?('json' | 'shorthand', optional, default:'json')chunkSize?(number, optional, default:32000)reviewOptions?(ReviewOptions, optional, default:{ mode: 'disabled' })- exactly one of:
documentexperimental_documentOptions
Response:
outputoperationResults?(array)reason?('validationError' | 'unexpectedError')error?(string)
docChanged(boolean)document(object | null)
Example:
curl --location 'BASE_URL/v3/ai/toolkit/execute-workflow/tiptap-edit' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_JWT_TOKEN' \
--header 'X-App-Id: YOUR_APP_ID' \
--data '{
"editorContext": { /* editor context data */ },
"input": {
"operations": [
{
"type": "replace",
"target": "abc123",
"content": "# Updated heading\n\nUpdated paragraph content."
}
]
},
"format": "shorthand",
"reviewOptions": {
"mode": "disabled"
},
"experimental_documentOptions": {
"documentId": "your-document-id",
"userId": "ai-assistant"
}
}'
Execute the proofreader workflow
POST /v3/ai/toolkit/execute-workflow/proofreaderRequest body:
input(object, required): Proofreader operationseditorContext(EditorContext, required unlessschemaAwarenessDatais provided)schemaAwarenessData?(SchemaAwarenessData, optional, deprecated): UseeditorContextinstead. Supported for backward compatibility.format('shorthand', required)chunkSize?(number, optional, default:32000)reviewOptions?(ReviewOptions, optional, default:{ mode: 'disabled' })- exactly one of:
documentexperimental_documentOptions
Response:
outputoperationResults(array)
docChanged(boolean)document(object | null)
Example:
curl --location 'BASE_URL/v3/ai/toolkit/execute-workflow/proofreader' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_JWT_TOKEN' \
--header 'X-App-Id: YOUR_APP_ID' \
--data '{
"editorContext": { /* editor context data */ },
"input": {
"operations": []
},
"format": "shorthand",
"reviewOptions": {
"mode": "trackedChanges",
"trackedChangesOptions": {
"userId": "ai-assistant"
}
},
"experimental_documentOptions": {
"documentId": "your-document-id"
}
}'
Execute the comments workflow
POST /v3/ai/toolkit/execute-workflow/threadsRequest body:
input(object, required): Thread operationseditorContext(EditorContext, required unlessschemaAwarenessDatais provided)schemaAwarenessData?(SchemaAwarenessData, optional, deprecated): UseeditorContextinstead. Supported for backward compatibility.format?('json' | 'shorthand', optional, default:'json')experimental_documentOptions({ documentId: string, userId?: string, field?: string | null }, required)experimental_commentsOptions?({ threadData?: Record<string, unknown>, commentData?: Record<string, unknown> }, optional): Metadata attached to new threads and comments created by the workflow.
Response:
outputoperations?(array)error?(string)
docChanged(boolean)document(object | null)
Example:
curl --location 'BASE_URL/v3/ai/toolkit/execute-workflow/threads' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_JWT_TOKEN' \
--header 'X-App-Id: YOUR_APP_ID' \
--data '{
"editorContext": { /* editor context data */ },
"input": {
"operations": [
{
"type": "createComment",
"threadId": "thread-123",
"content": "Please clarify this point."
}
]
},
"format": "shorthand",
"experimental_documentOptions": {
"documentId": "your-document-id",
"userId": "ai-assistant"
},
"experimental_commentsOptions": {
"threadData": {
"source": "ai"
},
"commentData": {
"source": "ai"
}
}
}'
Error handling
REST endpoints return errors with this shape:
{
"error": {
"message": "Human-readable description of what went wrong.",
"status": 502,
"code": "document_load_failed"
}
}message(string): Human-readable description. Don't match on it — the wording can change.status(number): The HTTP status code, also set on the response.code(string): A stable identifier you can branch on. Validation failures usecode: "validation_failed"and add anissuesarray.
The API uses standard HTTP error codes:
400 Bad Request: Invalid body, invalid format, missing document source, or missing Document Server credentials401 Unauthorized: Missing or invalid JWT or App ID404 Not Found: Unknown endpoint or tool422 Unprocessable Entity: Validation failed429 Too Many Requests: Duplicate request or rate-limit protection500 Internal Server Error: Unexpected server error502 Bad Gateway: Failed to load or save the Tiptap Cloud document
Streaming and session errors
POST /v3/ai/toolkit/stream-tool works on a Tiptap Cloud document over a live
connection: it joins the document in real time so the AI edits the latest content alongside other
collaborators. Errors that happen while establishing or using that connection are delivered in-band
as an error event in the response stream, not as an HTTP status:
{ "type": "error", "code": "concurrent_edit_conflict", "status": 409, "message": "..." }The status field mirrors the equivalent HTTP status. Possible code values:
code | status | When it happens | How to handle |
|---|---|---|---|
concurrent_edit_conflict | 409 | The document changed (for example, a user edited it) while the tool was running, so the change couldn't be applied on top of stale content. | Re-run the request. The AI reads the latest content on the next attempt. Safe to retry automatically. See Handle concurrent edits. |
schema_mismatch | 409 | The editor schema in the request doesn't match the schema already in use for this document, or the stored document can't be parsed with it. | Send a consistent editorContext schema for the same document. Don't retry with the same schema. |
websocket_auth_failed | 401 | Tiptap Cloud rejected the document credentials when opening the connection. | Refresh the Document Server JWT claims, then retry. Don't retry with the same token. |
websocket_connection_failed | 502 | The connection to Tiptap Cloud closed before the initial sync completed. | Retry with backoff. Check connectivity and Tiptap Cloud status. |
websocket_sync_timeout | 504 | The document didn't finish its initial sync within the time limit. | Retry with backoff. Large documents and degraded connectivity make this more likely. |
session_limit_reached | 503 | The server is handling the maximum number of concurrent document sessions. | Retry with backoff. Sessions are released as they go idle. |
session_creation_cancelled | 503 | The session was torn down before it finished connecting, usually during a server restart. | Retry. |
Specific to Tiptap Cloud documents
These errors only occur on the live-connection path used by stream-tool. Requests that send an
inline document never open a connection, so they don't return session or WebSocket errors.
Handle concurrent edits
concurrent_edit_conflict is expected in collaborative documents. While the AI reads a document,
calls the model, and writes its changes, a user can edit the same document in between. Rather than
overwrite that edit, the toolkit rejects the write so no work is lost.
Treat it as a safe, retriable signal: when you receive an error event with
code: "concurrent_edit_conflict", run the same tool call again. The toolkit re-reads the document,
so the AI works from the user's latest changes. Cap the number of retries to avoid long loops on a
document that's being edited heavily.
// `runStream` sends the start/delta/end messages and resolves to the stream's
// final `error` event, or `null` when the stream completes successfully.
async function streamToolWithRetry(body: unknown, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const error = await runStream(body)
if (!error) {
return // completed successfully
}
if (error.code !== 'concurrent_edit_conflict') {
throw new Error(`stream-tool failed: ${error.code}`)
}
// Document changed mid-flight — retry with the latest content.
}
throw new Error('Tool call kept conflicting with concurrent edits.')
}