API reference

Every endpoint of the OpenCharts REST API, generated live from the OpenAPI 3.1 spec so it always matches production. Authenticate with Authorization: Bearer oc_…. Create keys on the Developers hub.

https://www.opencharts.com/api/v190 operationsv1.0.0

Meta

1 endpoint
GET/openapi→ 200no auth

Fetch this OpenAPI document.

Account

2 endpoints
GET/account→ 200read scope

Get the authenticated account (whoami).

GET/account/credits→ 200read scope

Get the plan + AI credit balance.

Personas

12 endpoints
GET/personas→ 200read scope

List personas.

POST/personas→ 201write scope

Create a persona.

GET/personas/{id}→ 200read scope

Get a persona.

Path params: {id}

PATCH/personas/{id}→ 200write scope

Update a persona.

Path params: {id}

DELETE/personas/{id}→ 204write scope

Delete a persona.

Path params: {id}

POST/personas/{id}/publish→ 200write scope

Publish a persona.

Path params: {id}

GET/personas/{id}/deployments→ 200read scope

List a persona's deployments.

Path params: {id}

POST/personas/{id}/deployments→ 201write scope

Create a persona deployment.

Path params: {id}

POST/personas/{id}/conversations→ 201write scope

Start a persona conversation.

Path params: {id}

POST/personas/{id}/conversations/{cid}/end→ 200write scope

End a persona conversation.

Path params: {id}, {cid}

GET/personas/{id}/conversations/{cid}/events→ 200read scope

List conversation events.

Path params: {id}, {cid}

GET/personas/faces→ 200read scope

List available persona faces.

Projects

4 endpoints
GET/projects→ 200read scope

List projects.

GET/projects/{id}→ 200read scope

Get a project.

Path params: {id}

PATCH/projects/{id}→ 200write scope

Update a project's metadata or content.

Path params: {id}

DELETE/projects/{id}→ 204write scope

Delete a project.

Path params: {id}

Creation

6 endpoints
POST/flowcharts→ 201write scope

Create a flowchart project.

POST/whiteboards→ 201write scope

Create a whiteboard project.

POST/notes→ 201write scope

Create a notes project.

POST/presentations→ 201write scope

Create a presentation from a brief or outline. The slides generate in the background: poll the returned `job` (a deck_-prefixed id) at GET /jobs/{id} until it completes. Send an Idempotency-Key header to make retries safe.

Request body · PresentationCreate
namerequired
string

Deck title.

description
string
brief
string

Plain-language brief: audience, goal, key points. Theo authors the outline from it.

outline
array<object>

Explicit slides, used as written. Omit when passing `brief`.

slideCount
integer

Target slide count for a brief-authored outline (default 8). Ignored with `outline`.

themeId
string

Built-in theme id (for example `modern-dark`).

visualStyle
string

Image art style for the slides (default `illustration`).

slideMode
creator | content

`creator` (default): every slide is a fully designed image. `content`: editable text slides with image backgrounds.

brandUrl
string

The brand's homepage; the deck theme is extracted from it.

companyName
string

The brand's display name (used with `brandUrl`).

tags
array<string>
GET/templates→ 200read scope

List templates.

POST/templates/{id}/use→ 201write scope

Create a project from a template.

Path params: {id}

Sheets

10 endpoints
GET/sheets→ 200read scope

List sheets.

POST/sheets→ 201write scope

Create a sheet.

GET/sheets/{id}→ 200read scope

Read a sheet's rows.

Path params: {id}

GET/sheets/{id}/range→ 200read scope

Read a sheet range.

Path params: {id}

POST/sheets/{id}/ops→ 200write scope

Apply or propose structured sheet ops.

Path params: {id}

Request body · SheetOpsRequest
opsrequired
array<object>
propose
boolean
summary
string
POST/sheets/{id}/edit→ 200ai scope

Edit a sheet with a natural-language instruction.

Path params: {id}

Request body · SheetEditRequest
instructionrequired
string
propose
boolean
GET/sheets/{id}/commits→ 200read scope

List a sheet's commits.

Path params: {id}

GET/sheets/{id}/commits/{commitId}→ 200read scope

Get a commit diff.

Path params: {id}, {commitId}

POST/sheets/{id}/commits/{commitId}/resolve→ 200write scope

Merge or reject a proposed commit.

Path params: {id}, {commitId}

POST/sheets/{id}/rollback→ 200write scope

Roll a sheet back to a commit.

Path params: {id}

Boards

10 endpoints
GET/boards→ 200read scope

List boards.

POST/boards→ 201write scope

Create a board.

GET/boards/{id}→ 200read scope

Get a board.

Path params: {id}

PATCH/boards/{id}→ 200write scope

Update a board.

Path params: {id}

DELETE/boards/{id}→ 204write scope

Delete a board.

Path params: {id}

POST/boards/{id}/duplicate→ 201write scope

Duplicate a board.

Path params: {id}

GET/boards/{id}/cards→ 200read scope

List a board's cards.

Path params: {id}

POST/boards/{id}/cards→ 201write scope

Create a card.

Path params: {id}

PATCH/boards/{id}/cards/{cardId}→ 200write scope

Update a card.

Path params: {id}, {cardId}

DELETE/boards/{id}/cards/{cardId}→ 204write scope

Delete a card.

Path params: {id}, {cardId}

Chat

1 endpoint
POST/chat→ 200ai scope

Run a Theo chat turn (UI-message SSE stream by default; `stream: false` for the assembled message + artifacts).

Request body · ChatRequest
conversationId
string

Continue an existing conversation (prior history loads server-side).

message
string

The prompt (shorthand for a single user message).

messages
array<object>
mode
fast | think

v1 launch modes.

stream
boolean

true = AI SDK UI-message SSE stream; false = assembled JSON message + artifacts.

Conversations

6 endpoints
GET/conversations→ 200read scope

List conversations.

POST/conversations→ 201write scope

Create a conversation.

Request body · ConversationCreate
title
string
mode
string

Chat mode preference (e.g. `fast`, `think`).

GET/conversations/{id}→ 200read scope

Get a conversation.

Path params: {id}

PATCH/conversations/{id}→ 200write scope

Update a conversation's title or mode.

Path params: {id}

Request body · ConversationUpdate
title
string
mode
string

Chat mode preference (e.g. `fast`, `think`).

DELETE/conversations/{id}→ 204write scope

Delete a conversation and its messages.

Path params: {id}

GET/conversations/{id}/messages→ 200read scope

List a conversation's messages (oldest first).

Path params: {id}

Media

7 endpoints
POST/extract-flowchart→ 201ai scope

Extract a flowchart project from a document (PDF, PPTX, or image).

Request body · ExtractFlowchartRequest
documentBase64required
string

Base64-encoded document content (no `data:` prefix). Max 10MB decoded.

mimeTyperequired
application/pdf | application/vnd.openxmlformats-officedocument.presentationml.presentation | image/png | image/jpeg | image/webp
fileName
string

Original file name (used as a title fallback).

POST/images→ 200ai scope

Generate an image synchronously.

Request body · ImageRequest
promptrequired
string
engine
auto | theo-creative | theo-imagine | theo-photo | theo-rapid | theo-design | …

Defaults to theo-ultra (highest fidelity) on paid plans; a Free-plan call falls back to theo-creative and the response carries a `notes` entry saying so. An explicit theo-ultra on Free returns 403. grok-theo (Grok + Theo, the xAI co-brand) is accepted only on workspaces that enable it; elsewhere it resolves to the default.

aspectRatio
1:1 | 16:9 | 9:16 | 4:3 | 3:4
style
string
referenceImageUrls
array<string>
includeBase64
boolean
POST/images/moonlight→ 200ai scope

Generate an image on Theo Moonlight, our own image model, synchronously (safe-for-work only; seedable; renders at the published defaults unless told otherwise).

Request body · MoonlightImageRequest
promptrequired
string

The scene: subject, composition, lighting, style.

negativePrompt
string

Not used by this model: leave it out or send an empty string. A non-empty value is refused with a 400. Every render still goes through the endpoint's safety checks; they are not a knob.

aspectRatio
1:1 | 3:4 | 4:3 | 2:3 | 3:2 | 9:16 | …

Frame shape. Every shape renders at about one megapixel; the response reports the exact width and height.

seed
integer

Fix the seed to reproduce a render. Omit for a random one; the response echoes the seed used.

POST/videos→ 202ai scope

Start a video generation job (pick a tier: Fast, Max, Cinema, or Cinema Ultra).

Request body · VideoRequest
promptrequired
string

The scene: subject, action, camera, setting, mood.

aspectRatio
21:9 | 16:9 | 4:3 | 1:1 | 3:4 | 9:16

Frame shape. Default 16:9. The standard tiers render 16:9 and 9:16 only; another shape resolves to the NEAREST ratio they do render.

modelTier
fast | max | cinema | cinema-ultra

Which tier renders the clip. A premium tier on a workspace that has not enabled it lands on `max` with a note saying so.

resolution
480p | 720p | 1080p | 4k

Output quality. Premium tiers only; the standard tiers always render 1080p. `cinema` reaches 4k, and `cinema-ultra` tops out at 720p (it trades quality for length).

clipSeconds
integer

Clip length in seconds. Premium tiers only; the standard tiers render a fixed ~8s clip. `cinema` reaches 15s and `cinema-ultra` 30s; over a tier's ceiling clamps to its longest.

referenceImageUrls
array<string>

ONE url = the opening frame to animate (image-to-video). SEVERAL = how the subject should look. Trimmed to the resolved tier's own budget (1 on the standard tiers, 9 on Cinema, 30 on Cinema Ultra); when only one survives it becomes the opening frame.

referenceImageUrl
string

Single first-frame anchor. Kept for compatibility; prefer `referenceImageUrls`. Passing both puts this one first.

POST/songs→ 202ai scope

Start a song generation job.

Request body · SongRequest
stylerequired
string
title
string
lyrics
string
autoLyrics
boolean
instrumental
boolean
tier
auto | premium | fast
POST/code-builds→ 202ai scope

Start a Code Canvas build job.

Request body · CodeBuildRequest
promptrequired
string
name
string
framework
html | react | auto
planFirst
boolean
POST/real-world/spaces→ 202ai scope

Start a Theo Real World space job: photos of ONE room (up to 8, or one short video of it) become an imagined, walkable 3D room at real scale (a quick room in about a minute, the full room a few minutes later); a single photo alone becomes a measured, imagined, walkable space (a 360 preview with the room's dimensions, then a walkable .spz).

Request body · RealWorldSpaceRequest
imageUrl
string

ONE photo, as an http(s) URL the server can fetch (JPEG, PNG or WebP). Wide and level, with the floor and a wall in view. Send this OR `imageUrls` OR `videoUrl`.

imageUrls
array<string>

One to 8 photos of ONE room (JPEG, PNG or WebP), http(s) URLs the server can fetch, in the order they were taken: stand in one spot and turn between shots so each overlaps the last. Needs the room engine; a deployment without it answers 503.

videoUrl
string

One video of the room (MP4, MOV or WebM; 30 seconds and 100 MB or less), an http(s) URL the server can fetch: one slow, level sweep from one spot. The whole input; do not send photos beside it. Needs the room engine; a deployment without it answers 503.

quality
preview | full

`preview` stops at the first tier: the quick room (about 90 seconds; walkable in its own units), or the 360 panorama plus the dimensions on a one-photo space. `full` (default) continues to the final tier: the full room at real scale (about 8 minutes in total) or the walkable space (about 3 minutes), and costs more.

title
string

Optional label for the job; a room also reads it as a short description of the room.

Jobs

1 endpoint
GET/jobs/{id}→ 200read | ai scope

Poll any async job by its type-prefixed id.

Path params: {id}

Voice

4 endpoints
POST/speech→ 200ai scope

Speak one short line in one of Theo's voices, streamed back as raw 24 kHz mono PCM while it is produced. Send a longer reply one sentence per call. Charged per character spoken.

Request body · SpeechRequest
textrequired
string

One sentence or short line to speak. Speak a longer reply one sentence per call.

voice
string

A voice name from GET /speech/voices, such as `lucius`. Omit (or send `default`) for Theo's default voice.

GET/speech/voices→ 200read | ai scope

List Theo's voices by name (pass one as `voice` to POST /speech).

POST/live/session→ 200ai scope

Open a real-time voice conversation with Theo. Returns a short-lived token, locked to the persona, tools, voice and turn detection, that you connect to the realtime engine with to stream microphone audio up and Theo's voice back. A new session is charged once; resuming a dropped one with its handle is free.

Request body · LiveSessionRequest
persona
aibi

Who Theo is in the conversation. `aibi` (the default) is Theo talking through the AIBI desk robot.

voice
string

A voice name from GET /speech/voices, such as `lucius`. Omit (or send `default`) for Theo's default voice.

resumptionHandle
string

The latest resumption handle a dropped session sent. The new token continues that conversation and is not charged again.

deviceName
string

The robot's own name (for example `AIBI-133E`), so Theo can refer to it.

ambient
boolean

An always-listening session: Theo stays quiet unless someone speaks to him. Needs `features.proactiveAudio`; refused with a 400 when the deployment's engine lacks it.

POST/live/action→ 200ai scope

Run one of a live conversation's server tools (search, presentations, notes, flowcharts, whiteboards, images, projects, account, memory). Returns the line to hand back to the conversation and what was made. Charged per tool, only when it succeeds.

Request body · LiveActionRequest
actionrequired
search_web | create_presentation | create_notes | create_flowchart | create_whiteboard | generate_image | …

The server tool the conversation called. Tools the caller runs itself (the robot's own) never come here.

params
object

The call's arguments, exactly as the conversation produced them.

Agents

8 endpoints
POST/chat/completions→ 200ai scope

OpenAI-compatible chat completion over a Theo Agent (`model: "agent:<agentId>"`; the agent runs inline and replies, as JSON or as a single-chunk SSE stream when `stream: true`).

Request body · AgentCompletionRequest
modelrequired
string

`agent:<agentId>`.

messagesrequired
array<object>
stream
boolean

false (default): the assembled JSON. true: an SSE stream with keepalive comments while the agent works, then one `chat.completion.chunk` with the full reply and `[DONE]`.

user
string

Optional end-user id; doubles as the thread when `metadata.thread_id` is absent.

metadata
object
POST/agents/{id}/run→ 202ai scope

Dispatch a Theo Agent.

Path params: {id}

POST/agents/{id}/messages→ 202ai scope

Send a message to a goal-driven agent on a conversation thread (returns the run id to poll for the reply).

Path params: {id}

Request body · AgentMessageRequest
messagerequired
string
threadId
string

Caller-chosen conversation handle (letters, digits, `_ . : -`).

GET/agents/{id}/runs→ 200read scope

List an agent's runs.

Path params: {id}

GET/agents/{id}/runs/{runId}→ 200read scope

Get an agent run's status.

Path params: {id}, {runId}

GET/agents/{id}/runs/{runId}/result→ 200read | ai scope

Get an agent run's result: the reply, artifacts, pending approval, credits.

Path params: {id}, {runId}

POST/agents/{id}/runs/{runId}/approve→ 202ai scope

Approve or reject the action a paused agent run is waiting on (owner only).

Path params: {id}, {runId}

Request body · AgentRunApproveRequest
decisionrequired
approve | reject
reviewTokenrequired
string
scope
once | task | always

Use only a scope offered in the review. Reject always uses once.

GET/agents/{id}/usage→ 200read scope

Get an agent's run + credit usage over a window (`?days=7`, 1 to 90).

Path params: {id}

Research

2 endpoints
POST/research→ 202ai scope

Start a deep-research job (returns a res_-prefixed job id; poll it at GET /jobs/{id}).

Request body · ResearchRequest
queryrequired
string
GET/research/{jobId}→ 200read | ai scope

Poll a deep-research job (legacy alias of GET /jobs/{id}; accepts raw or res_-prefixed ids).

Path params: {jobId}

Drive

10 endpoints
GET/drive/files→ 200drive:read scope

List Drive files plus the folder tree and storage meter (filters: folderId, kind, starred, trash, teamId, limit).

POST/drive/files→ 201drive:write scope

Save a new file into Drive as text (content) or any format as base64 (contentBase64, up to 4 MB).

Request body · DriveFileCreate
namerequired
string

File name WITH extension (e.g. `Q3 summary.md`, `chart.png`).

content
string

UTF-8 text content (text formats only).

contentBase64
string

Base64-encoded bytes for any format, no `data:` prefix (4 MB decoded max).

folderId
string

Destination Drive folder id; omit or `root` for the Drive root.

description
string
tags
array<string>
GET/drive/files/{id}→ 200drive:read scope

Get a Drive file's metadata plus a time-limited downloadUrl for its raw bytes.

Path params: {id}

PATCH/drive/files/{id}→ 200drive:write scope

Rename, move, star, tag or describe a Drive file.

Path params: {id}

Request body · DriveFileUpdate
name
string
folderId
string

Destination Drive folder id, or `root`.

isStarred
boolean
tags
array<string>
description
string
GET/drive/files/{id}/content→ 200drive:read scope

Redirect (302) to a signed, time-limited stream of the file's raw bytes; follow redirects to download.

Path params: {id}

GET/drive/files/{id}/text→ 200drive:read scope

Read a Drive file as text (PDF and Office documents are parsed), capped at about 12K characters.

Path params: {id}

POST/drive/files/bulk→ 200drive:write scope

Move, star, unstar, trash (30-day restore) or restore many Drive files at once.

Request body · DriveBulkRequest
fileIdsrequired
array<string>
actionrequired
move | star | unstar | trash | restore

trash is the destructive floor (30-day restore); there is no permanent delete.

folderId
string

For move: the destination Drive folder id, or `root`.

GET/drive/folders→ 200drive:read scope

List the Drive folder tree.

POST/drive/folders→ 201drive:write scope

Create a Drive folder, optionally inside another Drive folder.

Request body · DriveFolderCreate
namerequired
string
parentFolderId
string

Parent Drive folder id; omit for the Drive root.

color
string

Optional hex color.

sticker
string

Optional single emoji.

Webhooks

6 endpoints
GET/webhooks→ 200read scope

List webhooks.

POST/webhooks→ 201write scope

Register a webhook (secret returned once).

Request body · WebhookCreate
namerequired
string
urlrequired
string
eventsrequired
array<project.created | project.updated | project.shared | project.deleted | team.member_added | team.member_removed | …>

Event names to subscribe to. The enum lists every currently emitted event (generated from the same registry the dispatcher validates against); unknown or not-yet-emitted events return a 400 naming them. Naming note: the `code` job type (job ids `code_...`) emits `code_build.*`, and Theo Agent runs emit `evi.run_*`.

GET/webhooks/{id}→ 200read scope

Get a webhook.

Path params: {id}

PATCH/webhooks/{id}→ 200write scope

Update a webhook.

Path params: {id}

Request body · WebhookUpdate
name
string
url
string
events
array<project.created | project.updated | project.shared | project.deleted | team.member_added | team.member_removed | …>
isActive
boolean
DELETE/webhooks/{id}→ 204write scope

Delete a webhook.

Path params: {id}

GET/webhooks/{id}/deliveries→ 200read scope

List a webhook's recent deliveries.

Path params: {id}

Lists return { data, total, cursor? }; async work returns a job envelope polled at GET /jobs/{id}; errors are { error: { code, message, requestId } }. Register webhooks on the Developers hub to skip polling.