Skip to content

Chat API

The chat endpoint is Studio's primary interface for AI-powered content management. It uses Server-Sent Events (SSE) to stream the AI response and tool execution results in real time.

Internal SPA endpoint

This documents the internal chat endpoint that powers the Studio web app. It is authenticated with a session cookie and gated by workspace + project roles (owner / admin / member, combined with the project role editor / reviewer / viewer). For programmatic, API-key access, use the Conversation API instead — it has its own 3-role model.

Chat (SSE)

Start a conversation with the AI agent.

POST /api/workspaces/:workspaceId/projects/:projectId/chat

Request Body

typescript
interface ChatRequest {
  message: string
  conversationId?: string  // Resume existing conversation
  model?: string           // AI model override
  context: {
    activeModelId: string | null
    activeLocale: string
    activeEntryId: string | null
    panelState: 'overview' | 'model' | 'branch' | 'vocabulary'
    activeBranch: string | null
    contextItems?: ContextItem[]
  }
}
FieldTypeRequiredDescription
messagestringYesUser message text
conversationIdstringNoExisting conversation ID for multi-turn
modelstringNoAI model override (default: determined by plan)
contextChatUIContextYesCurrent UI state for context-aware responses

Context Items

The contextItems array allows the client to pin specific content items to the conversation:

typescript
interface ContextItem {
  type: 'model' | 'entry' | 'field' | 'asset'
  modelId: string
  modelName?: string
  entryId?: string
  fieldId?: string
  assetId?: string
  data?: unknown
}

Response (SSE Stream)

The response is a stream of Server-Sent Events with data: lines containing JSON:

data: {"type":"conversation","conversationId":"uuid"}

data: {"type":"text","content":"I'll create a new blog post"}

data: {"type":"tool_use","id":"tool_1","name":"save_content"}

data: {"type":"tool_result","id":"tool_1","name":"save_content","result":{"branch":"cr/content/blog-post/en/...","merged":true}}

data: {"type":"text","content":"Done! I've created the blog post and it has been auto-merged."}

data: {"type":"done","usage":{"inputTokens":1234,"outputTokens":567},"affected":{"models":["blog-post"],"locales":["en"],"snapshotChanged":false,"branchesChanged":true}}

Event Types

Event TypeDescriptionKey Fields
conversationConversation created/resumedconversationId
textAI text chunk (streaming)content
tool_useTool execution startedid, name
tool_resultTool execution completedid, name, result
doneStream completeusage, affected
errorError occurredmessage

Affected Resources

The done event includes an affected object that tells the client what to refresh:

typescript
interface AffectedResources {
  models: string[]        // Model IDs with content changes
  locales: string[]       // Locales that were modified
  snapshotChanged: boolean // Model definitions changed (reload schema)
  branchesChanged: boolean // Branch list changed (reload branches)
  branch?: string         // Specific branch created/merged/deleted
}

Available Agent Tools

The agent can call these tools based on user permissions:

Roles here are the internal agent's effective roles (workspace admin/owner, or the member's project role viewer/reviewer/editor). "Min Role" is the lowest role that may invoke the tool.

ToolDescriptionMin Role
list_modelsList all content modelsviewer
get_contentRead content entriesviewer
save_contentCreate/update contenteditor
delete_contentDelete entries or dictionary keyseditor
save_modelCreate/update model definitionsadmin
validateValidate content against schemasviewer
list_branchesList pending cr/* branchesviewer
merge_branchMerge a content branchreviewer
reject_branchReject/delete a branchreviewer
init_projectInitialize .contentrain/ structureadmin
copy_localeCopy content between localeseditor
brain_queryRead from brain cacheviewer
brain_searchFull-text search across contentviewer
brain_analyzeContent analysis (SEO, parity, stale, quality)viewer
validate_schemaComprehensive schema validationviewer
search_mediaSearch media libraryviewer
upload_mediaUpload media from URLeditor
get_mediaGet media asset metadataviewer
update_statusPublish/unpublish/archive entrieseditor
update_mediaUpdate asset alt text, tags, focal pointeditor
delete_mediaDelete an asset and its variantseditor
branch_healthCheck pending-branch warn/block thresholdsviewer
relation_expandResolve forward/reverse relations for an entryviewer
vocabularyRead or update the project glossaryeditor
add_localeRegister a new supported localeadmin
delete_modelPermanently delete a model and its contentadmin
list_submissionsList form submissionsviewer
approve_submissionApprove form submissionreviewer
reject_submissionReject form submissionreviewer

Error Codes

StatusCondition
400Missing message or invalid context
401No valid session
403No project access or AI feature not available on plan
429Monthly message limit exceeded

Example

bash
curl -X POST http://localhost:3000/api/workspaces/{wsId}/projects/{projId}/chat \
  -H "Content-Type: application/json" \
  -H "Cookie: h3-session=..." \
  -d '{
    "message": "Create a blog post titled Hello World",
    "context": {
      "activeModelId": "blog-post",
      "activeLocale": "en",
      "activeEntryId": null,
      "panelState": "model",
      "activeBranch": null
    }
  }' \
  --no-buffer

Attachments

Pre-upload files or pasted links before sending a chat message. This endpoint converts each input into AI content blocks (AttachmentRef[]); the client then carries those blocks in the chat POST body, keeping the SSE handler JSON-only and letting the UI show per-attachment progress and errors.

POST /api/workspaces/:workspaceId/projects/:projectId/attachments

The channel is selected by Content-Type:

  • Link paste (JSON): { "url": "https://..." } or { "urls": ["https://...", ...] }. Remote fetches are SSRF-guarded.
  • File upload (multipart/form-data): one or more file parts, plus an optional intent part. intent=media persists images to the media library; any other value (or absent) treats them as ephemeral context.

Limits

LimitValue
Max items per request10 (URLs or files)
Max file size32 MB per file

Response

json
{
  "attachments": [
    {
      "id": "att_diagram.png",
      "source": "upload",
      "filename": "diagram.png",
      "mime": "image/png",
      "kind": "image",
      "blocks": [ /* AIContentBlock[] */ ],
      "error": null
    }
  ]
}

source is upload (multipart) or link (URL). Per-item failures (e.g. a file over the size limit) are reported inline via the error field rather than failing the whole request.

Plan gating

The endpoint itself is not feature-gated -- text, PDF, and link attachments ride the existing chat token metering. Only the image-to-CDN branch is plan-gated: in Community Edition it degrades to inline base64 instead of a CDN upload.

Auth

Requires workspace owner, admin, or member role. A member must also belong to the project.

Error Codes

StatusCondition
400No URL provided, more than 10 items, or no file part in the multipart body
403Member is not a project member
404Project not found in the workspace

Conversations

List Conversations

GET /api/workspaces/:workspaceId/projects/:projectId/conversations

Delete Conversation

DELETE /api/workspaces/:workspaceId/projects/:projectId/conversations/:conversationId

Get Conversation Messages

GET /api/workspaces/:workspaceId/projects/:projectId/conversations/:conversationId/messages

Released under the AGPL-3.0 License.