API reference
Every route an API key opens, the scope each one needs, and the routes that refuse a key on purpose.
Everything an agent can reach over HTTPS, and the scope each route needs. The studio in a browser rides a signed session cookie; an agent outside that browser rides an API key - the same account, the same routes, a second credential.
Minting a key, the three scopes and the refusal codes are on Authentication. Putting a post out is on Posts. This page is the whole surface.
Machine-readable
OpenAPI 3.1 for every key-accepting route, no auth. Point a generator at it rather than reading this page, if that is what you are doing. The rendered operation list is at the bottom of this page.
The same spec is a ready Postman collection in the public
ShapelessAI API workspace
(team profile: ShapelessAI on Postman). Fork it,
set SHAPELESS_API_KEY to your key, and send GET /api/me.
It is also listed on RapidAPI as
ShapelessAI on RapidAPI, all 119 operations.
Through RapidAPI's gateway send your X-RapidAPI-Key and also Authorization: Bearer slk_...;
the gateway passes the Bearer key through, so calls run on your own ShapelessAI account and plan.
Paging
The thread lists - GET /api/conversations and GET /api/v1/jobs - answer 200 rows per page,
newest first, alongside a nextCursor. Non-null means older rows exist: send it back verbatim as
?before=<nextCursor> for the next page, and keep going until it is null. The cursor is the last
row's sort key (<updatedAtISO>~<id>), so a thread touched while you page is never skipped or
served twice; anything else is a 400. ?origin= on the jobs list filters the page it answers,
so a page can carry fewer than 200 jobs and still have a cursor - follow the cursor, not the count.
Routes that accept a key
Every row is enforced in the codebase by src/server/auth/route-scopes.test.ts: if a route and
this table disagree, the build fails.
Identity and accounts
| Method | Path | Scope | Purpose |
|---|---|---|---|
| GET | /api/me | read | Who am I, plan, credit balance, and next: up to three moves worth making on this account now (connect, post, make the image, hand over a goal), the skill at /skill.md and the docs. |
| GET | /api/me/email | read | Whether lifecycle email (posts ready, weekly summary) is on, and the last emails we sent. |
| PUT | /api/me/email | write | `{ lifecycle: true |
| GET | /api/connections | read | Connected social accounts (identity only, no tokens), plus connect: one clickable link per platform with no live account, and reconnect on an expired one. |
| POST | /api/connections/link | write | {platform} -> the one URL the human opens to connect an account there. 15 minutes, signed in as this account only; the platform's OAuth follows in the browser. |
| DELETE | /api/connections?id= | publish | Disconnect an account. Ends our ability to publish there. |
| PATCH | /api/connections/[id] | write | {groupId} or null: move an account into a group. |
| GET | /api/connections/[id]/tiktok-creator-info | read | TikTok's creator info for that connection (privacy levels, limits). Read it before every TikTok post; TikTok forbids caching it. |
| POST | /api/connections/tracked | write | {platform, handle, followers?}: track an account by handle, no OAuth, never a publish target. |
| GET/POST/PATCH | /api/groups | read / write | Account groups: list, create {name, color?}, reorder {orderedIds}. |
| PATCH/DELETE | /api/groups/[id] | write | Rename or recolor; delete (its accounts become ungrouped). |
| GET | /api/capabilities | none | Everything Shapeless can do, live or planned, as JSON - the same list as Capabilities. |
| POST | /api/capabilities/[id]/want | any, or none with {email} / {webhookUrl} | Tell me when a planned capability ships. |
| GET | /api/ad-accounts | read | Connected ad accounts (identity only, no tokens) + whether connecting one is configured. |
| DELETE | /api/ad-accounts?id= | publish | Unlink an ad account. Costs publish: it ends our ability to spend. |
Chat
| Method | Path | Scope | Purpose |
|---|---|---|---|
| POST | /api/studio | write | One agent turn, NDJSON stream of StudioEvents. |
| GET | /api/studio/tail | read | Re-attach to a detached run's event log. |
| POST | /api/studio/stop | write | Stop a detached run. |
| PUT | /api/studio/attachments/[filename] | write | Upload a chat attachment, returns its mediaKey. |
| GET | /api/conversations | read | Thread list, one page (see Paging). ?origin=chat,agent,job,identity keeps only those provenances (identity: started beside the graph on Shapeless Graph), ?q= searches titles, ?strategyId= narrows to one agent's wakes, ?limit= shrinks the page. unread counts threads that finished since they were last opened. |
| GET | /api/conversations/[id] | read | One transcript, with each post artifact's queue state. Reading it marks the thread opened. |
| PATCH/DELETE | /api/conversations/[id] | write | Rename or delete a thread. |
| POST | /api/conversations/[id]/opened | write | Mark a thread opened without reading it (clears its unread state). |
| GET | /api/conversations/[id]/panel | read | The panel beside the chat on the Ask page, a read-only document of tabs the browser and a terminal share: {conversationId, panel: {rev, items, active?, suggestions}, text}, text one plain line per tab. ?after=<rev>&wait=<seconds> holds up to 25 s until the panel changes. Reading it does not mark the thread opened. |
| POST | /api/conversations/[id]/panel | write | Change the panel: {ops: [...]} (open, update, show, close, pick, suggest, and edit {id, node, patch {text?, sub?, media?}}, the person's hand edit of one flow node; at most 50), folded in order through the reducer the browser uses. A bad op is a 400 naming which and why. |
| GET | /api/conversations/current/panel | read | The same, for the thread the account opened last (the chat most likely open in the browser), with its conversationId. |
Jobs
The durable alternative to stream plumbing. A job is a conversation whose ids the server mints:
you send a goal, get a 202 with the job's id, and the run keeps going whether or not you stay
connected. Poll the job for its transcript and outputs, or tail the live stream. Posting another
message to a job builds the conversation history server-side from the stored transcript, so
sending "continue" to a failed or stuck job resumes it.
| Method | Path | Scope | Purpose |
|---|---|---|---|
| POST | /api/v1/jobs | write | Start a job: {goal, label?, budgetUsd?, timezone?, attachments?, characters?} returns 202 {id, title, status}. |
| GET | /api/v1/jobs | read | List jobs with liveness, one page (see Paging). ?origin=job|chat|agent|identity filters by who started the thread. |
| GET | /api/v1/jobs/[id] | read | One job: enriched transcript, status/live, and an outputs summary (posts with queue state, media). |
| POST | /api/v1/jobs/[id]/messages | write | Another turn on the job: {text, budgetUsd?, timezone?, attachments?} returns 202 {id, status}. |
| POST | /api/v1/jobs/[id]/run | write | Run the job's newest proposed plan, as the Run button would: {budgetUsd?} returns 202 {id, status}. Deliverables land as proposed drafts. |
A running job streams into the same event log chat uses: follow it with
GET /api/studio/tail?conversationId=<id>, stop it with POST /api/studio/stop. The label
becomes the job's title; without one the goal's first line is. Refusals are the plan turn's:
402 when the month's free credits are spent, 429 when planning too fast, 400 for an attachment the
account cannot use.
Attaching files
attachments puts files on the message itself - the same thing the web composer's paperclip does,
and the agent sees them: an image's pixels are inlined for that turn, video and PDF go over by
storage URI, and the media key is named in the text so it survives in history after the pixels are
gone. Up to 6 per message; each entry needs a name and either text (inlined, 24k
characters) or a mediaKey:
Get a mediaKey by uploading the bytes to PUT /api/studio/attachments/[filename] (10MB images,
30MB video/PDF; filenames are [A-Za-z0-9._-]). Any media key the account owns also works - a
brand asset from workspace-assets/, media an earlier run produced - because the server copies it
into this account's chat-uploads/ prefix before the turn, which is the only prefix the engine
reads. A key belonging to another account is a 400 naming the file, not a run that dies halfway.
A malformed entry is a 400 too: attachments are never dropped silently.
References
The creators, competitors and peers an account works from, the people we suggest, and a browse of what other workspaces picked (counts of 3 or more only). See MCP for the whole loop.
| Method | Path | Scope | Purpose |
|---|---|---|---|
| GET | /api/v1/references | read | The sets with their members. |
| POST | /api/v1/references | write | Add a person: {handle, platform} or {url}, kind?: creator|competitor|peer, setId?, suggestionId?, personName?. Returns {member, created}. |
| DELETE | /api/v1/references/[id] | write | Remove a member (soft). ?reason=not_similar|not_relevant|low_quality|other. |
| GET | /api/v1/references/suggestions | read | Undecided suggestions with reasons [{code, evidence}]; ?kind=, ?limit=1..20. |
| POST | /api/v1/references/suggestions/[id]/decide | write | {decision: accept|reject, kind?, setId?}. Accept adds the member. |
| GET | /api/v1/references/browse | read | ?q=&platform=&limit=: your matches, picked counts, alsoPicked beside an exact handle, remaining today. 402 reference_browse_limit past the plan's day. |
POST /api/v1/jobs takes referenceId (a member id) and mode (clone, format or
inspiration, the default) to work from that person; POST /api/posts takes the same
provenance as inspiration: {kind, id, mode?, memberId?}.
Shapeless Graph
The graph of who is who on social. Reads are free; a pull holds its budget on the wallet and is charged what it cost. See Identity for the controls and the stream.
| Method | Path | Scope | Purpose |
|---|---|---|---|
| GET | /api/identity/search | read | ?q=&limit=: known people, accounts and organizations. |
| GET | /api/identity/node/[id] | read | One node: fields, edges, evidence, pulls, freshness. |
| GET | /api/identity/graph | read | ?anchor=: a node and its first ring. |
| GET | /api/identity/home | read | The home graph in one read: what you track, your company, and the ring of every spoke. |
| GET | /api/identity/people | read | ?q=&sort=&category=&niche=&scope=&limit=. |
| GET | /api/identity/facets | read | ?scope=: categories, niches, organizations with counts. |
| GET | /api/identity/similar | read | ?anchor=person:<id>&limit=&scope=: people like one person. |
| GET | /api/identity/library | read | ?anchor=&sort=&platform=&limit=&offset=: posts held. |
| GET/POST/DELETE | /api/identity/tracked | read / write | What you track; {node, role, note?} tracks, {node} stops. |
| GET | /api/identity/pull-estimate | read | ?node=&expansion=&since=&until=&sinceMonths=&maxItems=&budgetUsd=&force=: the price before the pull. |
| POST | /api/identity/resolve | write | {seed, kind?, budgetUsd?, force?}, NDJSON. Spends credit. |
| POST | /api/identity/expand | write | {node, expansion, budgetUsd?, since?, until?, sinceMonths?, maxItems?, force?}, NDJSON. Spends credit. |
| POST | /api/identity/post | write | {url, person?, budgetUsd?, force?}: one post by its link, NDJSON. Spends credit. |
Labels and an edge's confirmed or rejected status are written on the shared graph, so they stay with the founder's session for now and refuse a key.
Posts
| Method | Path | Scope | Purpose |
|---|---|---|---|
| GET | /api/posts | read | The queue: proposed, scheduled, published. ?status= and ?limit= narrow it. |
| POST | /api/posts | publish | Put a post on the rail: at scheduledAt, in the account's next free slot (queue: true), or now. |
| GET | /api/posts/[id] | read | One row's live state. |
| PATCH/DELETE | /api/posts/[id] | write | Edit or cancel a still-scheduled post (time, text, media, settings, firstComment, connectionId on the same platform). |
| GET | /api/v1/instagram/audio | read | Sounds an Instagram account may attach to a Reel: ?q=, ?type=music|original_sound, ?connectionId=. Pass an id as POST /api/posts audio.audioId. An Instagram Login connection answers 409 {needsConnection, connect: {url}}. |
| GET | /api/platforms | none | Every platform the rail posts to: limits, media rules, settingsSchema, first-comment support. |
| GET | /api/openapi.json | none | The OpenAPI 3.1 document for every route in this file. |
| GET/PATCH | /api/connections/[id]/queue | read / write | The account's posting slots and timezone, and the next free slot. |
| POST | /api/posts/[id]/approve | publish | Approve one proposal: proposed -> scheduled. A missing account answers 409 {needsConnection, connect: {url}} - hand connect.url to the human. |
| POST | /api/posts/[id]/publish | publish | Publish a queued post now. |
| POST | /api/posts/[id]/mark-posted | publish | "I posted it myself". |
| POST | /api/posts/[id]/revise | write | Rework a draft from feedback. |
| POST | /api/posts/[id]/boost | publish | Propose a paid boost of a published Meta post. |
| GET | /api/posts/[id]/comments | read | The thread under a published post, with each drafted reply. |
| POST | /api/posts/[id]/comments | publish | Stage your own reply to one comment. |
| POST | /api/posts/[id]/comments/refresh | publish | Read the thread from the platform now. |
| POST | /api/posts/resolve | publish (approve) / write (dismiss) | Resolve a batch of proposals. |
| GET | /api/inbox | read | Everything waiting on a human decision. |
POST /api/posts takes {connectionId, postText, mediaKeys?, documentTitle?, scheduledAt?, queue?, settings?, firstComment?}. scheduledAt omitted or in the past publishes now; queue: true takes
the account's next free posting slot instead (GET /api/connections/[id]/queue shows the slots).
settings follows the platform's settingsSchema from GET /api/platforms (TikTok privacyLevel,
Reddit subreddit, YouTube privacyStatus); documentTitle is required on YouTube. firstComment
is posted as the post's own first reply the moment it is live: LinkedIn (1250 chars), X (280),
Bluesky (300); other platforms answer 422. A platform rule that refuses the post answers 422 with the
rule in error. The Free plan posts ten a day, counted on the UTC day of the slot: the eleventh
answers 402 {code: "free_daily_cap", limit, day, resetsAt}. Paid plans have no cap. The row's
firstCommentUrl / firstCommentError say what became of the comment.
GET /api/posts returns the newest 50 rows by slot (?limit= up to 200), narrowed server-side by
?status= (any of proposed, scheduled, publishing, published, handoff, failed,
canceled). ?sort=views instead ranks published posts over the whole history by their latest
reading's impressions (views), most viewed first, narrowed by ?platform=, ?connectionId=,
?minViews= and ?since= (ISO). ?fresh=1 first re-reads, from the platforms, the numbers of
the listed posts whose reading is older than 6 hours (up to 25 posts, so the answer can take
~30 s); without it every reading is as of its own fetchedAt. GET /api/posts/[id] returns the same shape for one row. Two fields close the loop
between what an agent meant and what happened:
inspiration- what the post was made from:{kind, id, lane?, title?, url?}wherekindisformat-card,topical-storyorreference-post. Set it on a delivery through the artifact'sinspiredBymeta (the object, or a JSON string of it); anything malformed is dropped, never an error.nullwhen nobody said.metrics- how a published post did,nullfor anything not published and for a published post we hold no reading for yet:{latest, at24h, at7d, lift, baseline: {platform, n, median, scoredBy}}. Each sample is{fetchedAt, impressions, reactions, comments, shares};at24handat7dare the first readings taken at or after that age,nulluntil one exists. Sampling slows as a post ages, so on a sparsely sampled post the "24h" reading can be days late -fetchedAton the sample is what says how late, and it is on every sample for exactly that reason. Lift is this post's score divided by the median score of the same account's published posts on the same platform over the trailing 90 days2means it did twice its channel's median. It isnullwhen fewer than five scored posts stand behind that median (baseline.nsays how many do), because a two-post median invents findings.baseline.scoredByisimpressionswhere the platform reports reach andengagementswhere it does not - the same rule the Performance screen ranks by.
/api/posts/[id]/boost takes {budgetCents, days, objective} (engagement or traffic) and
spends nothing: it creates a proposed boost action, and only POST /api/actions/[id]/send
on that action creates the Meta campaign. The budget is the campaign's lifetime cap, billed by
Meta to the connected ad account. It refuses (402) without an active paid plan (the Free plan does
not qualify), and (409) when the post is not a published Facebook Page or Instagram post with a
known platform id, or when no active Meta ad account is connected.
GET /api/posts/[id]/comments answers {postId, platform, comments[], lastReadIso} from our own
ledgers - it never calls a platform. Each comment carries {id, authorHandle, text, url, atIso, isOwn, replyVerdict, reply}, where reply is the drafted action bound to it
({actionId, text, status, verdict, error, url, sentAtIso}) or null. POST with
{commentId, text} stages that reply as a proposed reply action - the same row the approvals
inbox holds - which POST /api/actions/[id]/send then sends; it refuses (409) when the
platform has no sanctioned reply path for that comment (replyVerdict: "task").
/comments/refresh runs the same engagement poll Cloud Tasks fires after publishing, and only
spends credits when there is a new comment to draft a reply to.
Agents (strategies) and the manager
| Method | Path | Scope | Purpose |
|---|---|---|---|
| GET | /api/strategies | read | The agent roster. |
| POST | /api/strategies | write, or publish with autoPublishPosts | Create an agent. |
| PATCH | /api/strategies/[id] | write, or publish with autoPublishPosts | Edit an agent. |
| POST | /api/strategies/[id]/run | publish | Wake an agent now. |
| POST | /api/strategies/suggest | write | One engine-studied agent seed for the editor. |
| POST | /api/manager/run | publish | "Plan now" through the first active agent. |
| GET | /api/manager/runs, /api/manager/runs/[id] | read | Wake runs and their proposals. |
| POST | /api/manager/runs/[id]/approve | publish | Approve a run's proposals. |
| POST | /api/manager/runs/[id]/dismiss | write | Dismiss a run with feedback. |
| GET/PUT | /api/manager/settings | read / write | Cadence and targeting. |
| GET | /api/manager/budget | read | Spend, allowance, paused state. |
Characters (actors)
An actor is workspace-level identity - a face, a voice, a style, a memory - that posts and
agents CAST. characters on a job is that cast, by slug or id; the agent is shown the account's
whole roster either way, so it can reuse an actor that fits instead of inventing another one.
A ref that names nobody is a 400 naming it, never a run that silently stars nobody.
| Method | Path | Scope | Purpose |
|---|---|---|---|
| GET | /api/characters | read | The roster: identity, voice (kind + the direction a designed one was written from), locks. |
| POST | /api/characters | write | Create one: {name, bio?, isUser?}. |
| GET | /api/characters/[id] | read | One actor. |
| PATCH | /api/characters/[id] | write | Edit it, including the three locks. |
| DELETE | /api/characters/[id] | write | Archive it (posts and agents may still cast it). |
| POST | /api/characters/[id]/voice | write | Design, clone, or speak - see below. |
POST /api/characters/[id]/voice takes one of:
{action: "design", voicePrompt}- writes a voice from casting direction. The direction is KEPT on the character and shown to the agent on every run.{action: "clone", sampleMediaKey, consent: true}- clones a 5-10s sample already in the account.consentis the caller attesting they hold the rights; it is stored.{action: "speak", text}- reads text in the actor's current voice, returns amediaKey.
A creation SPENDS CREDIT: the answer carries costUsd (what the provider billed us) and the
account is debited for it at the tier's rate, ledgered under voice:<design|clone>:<id>. An
empty wallet is a 402 with the same trial_exhausted code a run gives - never a silent
no-voice. speak is not a creation and is not billed here.
Engagement actions
| Method | Path | Scope | Purpose |
|---|---|---|---|
| POST | /api/actions/[id]/send | publish | Send a drafted comment or reply, or run an approved boost or campaign change. |
| POST | /api/actions/[id]/done | write | Mark a task card done. |
| POST | /api/actions/[id]/skip | write | Skip a proposed action. |
Paid campaigns
| Method | Path | Scope | Purpose |
|---|---|---|---|
| GET | /api/ads/campaigns/[id]?adAccountId= | read | One Meta campaign as Meta has it now: status, budget, end date. |
| POST | /api/ads/campaigns/[id] | publish | Pause, resume, change the budget or the end date. |
| DELETE | /api/ads/campaigns/[id]?adAccountId= | publish | Delete the campaign. |
POST /api/ads/campaigns/[id] takes {adAccountId, status?, budgetCents?, endsAt?} with absolute
targets (status is ACTIVE or PAUSED, budgetCents is in the budget's own kind, daily or
lifetime, and endsAt is an ISO date in the future). A change that can only lower spend (pause,
lower budget, earlier end) applies at once and answers {outcome: "applied", settings}. One that
could raise it (resume, higher budget, a later end on a daily budget) spends nothing yet: it
answers 201 {outcome: "proposed", action} with a proposed campaign_change action, and only
POST /api/actions/[id]/send on that action applies it. Raising needs an active paid plan (402),
and one waiting change per campaign (409). Only Meta campaigns are supported today.
Brand Memory, assets and media
| Method | Path | Scope | Purpose |
|---|---|---|---|
| GET | /api/workspace | read | The brain's file tree with its completion ring. |
| GET | /api/workspace/file?path= | read | One file. |
| PUT/DELETE | /api/workspace/file | write | Write or delete one file. |
| GET | /api/workspace/export | read | The whole brain as a zip. |
| GET | /api/workspace/onboarding | read | What is still open, as the questions a human answers, with hints - see Brand Memory. |
| POST | /api/brain/scan | write | Read a link and file what it says into Brand Memory (NDJSON stream). |
| POST | /api/brain/import | write | Import a .md/.txt/.pdf/.docx document. |
| GET | /api/brain/assets, /api/assets | read | Brand uploads, and the full asset library. |
| GET | /api/brain/assets/[filename] | read | Stream one asset (Range supported). |
| PUT/PATCH/DELETE | /api/brain/assets/[filename] | write | Upload, rename, delete an asset. |
| GET | /api/media/[...key] | read | Stream any media key this account owns. |
Analytics
| Method | Path | Scope | Purpose |
|---|---|---|---|
| GET | /api/metrics | read | Published posts with their latest samples and the 90-day series. |
| GET | /api/metrics/history?post= | read | One post's accrual curve against the account median. |
| GET | /api/metrics/followers | read | Follower counts and the weekly delta. |
| POST | /api/metrics/refresh | write | Refresh stale samples (calls the platforms). |
Profile audit
| Method | Path | Scope | Purpose |
|---|---|---|---|
| GET | /api/profile/audits | read | Latest score and open fix count per connected account. |
| GET | /api/profile/audits/[connectionId] | read | One account's fix list, each with ready-to-paste copy, and its last two scores. |
| POST | /api/profile/audits/[connectionId] | write | Read the live profile and grade it again (spends credits; 429 when run too often). |
| PATCH | /api/profile/audit-items/[id] | write | {status: todo | done | dismissed}. |
Tools
Every tool the studio agent holds, callable on its own - the same code, so the API never does a
different thing from the agent. GET /api/tools (no key) lists each one with its JSON Schema input
and default price. The body of a call is that input, plus an optional budgetUsd (default 2,
max 10); the credit is held for the budget and settled at what the call really cost.
| Method | Path | Scope | Purpose |
|---|---|---|---|
| GET | /api/tools | none | The catalog: {tools: [{name, description, inputSchema, costUsd}]}. |
| POST | /api/tools/[name] | write | Run one tool. Answers {tool, output, media: [{key, url}], chargedUsd}. |
| POST | /api/images | write | generate_image: {name, prompt, aspectRatio, size?, quality?, reference_keys?}. |
| POST | /api/videos/generate | write | generate_footage: AI footage from a prompt. |
| POST | /api/videos/edit | write | edit_footage: cut, reframe, cutout, captions, music, overlays on a video you uploaded. |
| POST | /api/videos/demo | write | demo_page: a recorded product demo of a page. |
| POST | /api/carousels | write | render_carousel: PDF plus one PNG per slide. |
| POST | /api/thumbnails | write | render_thumbnail: a frame of a video plus words. |
| POST | /api/speech | write | speak: up to 2,000 characters read into a WAV. {text, voice?, character?, energy?, style?}. |
| POST | /api/captions | write | caption_file: a word-timed .srt or .vtt for a video or audio file. {media_key, format?, names?}. Free. |
A file the tool made comes back in media with its URL; put its key on POST /api/posts
mediaKeys to publish it. Refusals: 400 the input does not match the schema (the message names
the field), 402 not enough credit (the body carries upgrade links for the human) or the call
costs more than budgetUsd, 404 no such tool,
422 the tool refused the input, 502 the tool failed. A refusal or failure is never charged.
Upload a video to edit with POST /api/studio/uploads first; its mediaKey is the input.
Speech. voice is one of six narrator voices - Sulafat (female, warm), Charon (male,
informative; the default), Kore (female, firm), Achird (male, friendly), Erinome (female, clear),
Schedar (male, even) - or pass character (an id or slug from GET /api/characters) to read in
that character's designed or cloned voice. energy is calm, confident (default), energetic
or hype; style adds direction in plain words.
Captions. The same ear the video editor cuts by: every word timed, cut into cues of at most two
42-character lines and six seconds. names spells your brand your way. The file text comes back
inline in output.text as well as filed at output.captionsKey. Burned-in captions are
POST /api/videos/edit.
Planned routes
A planned capability's route exists and answers 501 {code: "not_live", want, docs, sketch}
until it ships; in /api/openapi.json it is marked x-status: planned. The list, the sketches and
the want door are on Capabilities.
Cookie-only, on purpose
These refuse a bearer key. It is not an oversight:
- Key management (
/api/keys,/api/keys/[id]) - a key must never be able to mint itself a wider key. A leaked read key stays a leak, not a foothold. - Money (
/api/checkout,/api/portal) - buying and cancelling belong to the person paying. - Claiming a wiki key (
/api/wiki/keys/claim) - the signed-in person says yes to a key an agent already holds, on the activation page. - Sign-in (
/api/auth/*) and the platform OAuth flows (every/api/connections/<platform>start and callback) - browser redirect flows carrying platform grants. An agent gets the human there withPOST /api/connections/link; the identity list, the link, groups, tracked accounts and disconnect all take a key. - Founder metrics (
/api/metrics/agent-lanes*) and/metrics- gated onFOUNDER_EMAILS. - Webhooks and platform callbacks (
/api/polar/webhook,/api/meta/*,/api/subscribe) - they carry their own signatures. - Internal cron (
/api/internal/*) -x-internal-key, unchanged. - Dev (
/api/dev/*) and the daily-brief routes (/api/brain/brief/*), which are browser surfaces today.
The other two faces
The CLI and the MCP server are this same contract, wrapped. There is one implementation of every operation underneath and one scope policy judging it, so anything you can do here you can do there, and the refusals are identical.
OpenAPI
124 paths, version 1. Point a generator at /api/openapi.json rather than reading this page.
/api/meGET/api/me/emailGET · PUT/api/wiki/identityGET/api/platformsGET/api/openapi.jsonGET/api/ai-toolsGET/api/ai-tools/requirementsGET/api/ai-tools/statsGET/api/ai-tools/previewPOST/api/ai-tools/submitPOST/api/ai-tools/{slug}GET · PATCH/api/ai-tools/{slug}/verifyPOST/api/ai-tools/{slug}/statsGET/api/ai-tools/{slug}/clickPOST/api/capabilitiesGET/api/toolsGET/api/tools/{name}POST/api/imagesPOST/api/videos/generatePOST/api/videos/editPOST/api/videos/demoPOST/api/carouselsPOST/api/speechPOST/api/captionsPOST/api/thumbnailsPOST/api/capabilities/{id}/wantPOST/api/connectionsGET · DELETE/api/connections/linkPOST/api/connections/{id}PATCH/api/connections/{id}/tiktok-creator-infoGET/api/connections/trackedPOST/api/groupsGET · POST · PATCH/api/groups/{id}PATCH · DELETE/api/connections/{id}/queueGET · PATCH/api/postsGET · POST/api/v1/instagram/audioGET/api/posts/capGET/api/posts/{id}GET · PATCH · DELETE/api/posts/{id}/approvePOST/api/posts/{id}/publishPOST/api/posts/{id}/mark-postedPOST/api/posts/{id}/revisePOST/api/posts/{id}/boostPOST/api/posts/{id}/commentsGET · POST/api/posts/{id}/comments/refreshPOST/api/posts/resolvePOST/api/inboxGET/api/v1/jobsGET · POST/api/v1/jobs/{id}GET/api/v1/jobs/{id}/messagesPOST/api/v1/jobs/{id}/runPOST/api/v1/referencesGET · POST/api/v1/references/{id}DELETE/api/v1/references/suggestionsGET/api/v1/references/suggestions/{id}/decidePOST/api/v1/references/browseGET/api/identity/searchGET/api/identity/graphGET/api/identity/homeGET/api/identity/node/{id}GET/api/identity/peopleGET/api/identity/facetsGET/api/identity/similarGET/api/identity/libraryGET/api/identity/trackedGET · POST · DELETE/api/identity/pull-estimateGET/api/identity/resolvePOST/api/identity/expandPOST/api/identity/postPOST/api/studioPOST/api/studio/tailGET/api/studio/stopPOST/api/studio/attachments/{filename}PUT/api/studio/uploadsPOST/api/studio/uploads/{sessionId}PUT/api/studio/uploads/completePOST/api/conversationsGET/api/conversations/{id}GET · PATCH · DELETE/api/conversations/{id}/openedPOST/api/conversations/{id}/panelGET · POST/api/conversations/current/panelGET/api/strategiesGET · POST/api/strategies/{id}PATCH/api/strategies/{id}/runPOST/api/strategies/suggestPOST/api/manager/runPOST/api/manager/runsGET/api/manager/runs/{id}GET/api/manager/runs/{id}/approvePOST/api/manager/runs/{id}/dismissPOST/api/manager/settingsGET · PUT/api/manager/budgetGET/api/charactersGET · POST/api/characters/{id}GET · PATCH · DELETE/api/characters/{id}/voicePOST/api/actions/{id}/sendPOST/api/actions/{id}/donePOST/api/actions/{id}/skipPOST/api/ad-accountsGET · DELETE/api/ads/performanceGET/api/ads/refreshPOST/api/ads/campaigns/{id}GET · POST · DELETE/api/workspaceGET/api/workspace/fileGET · PUT · DELETE/api/workspace/exportGET/api/workspace/onboardingGET/api/brain/scanPOST/api/brain/importPOST/api/brain/assetsGET/api/brain/assets/{filename}GET · PUT · PATCH · DELETE/api/assetsGET/api/media/{key}GET/api/metricsGET/api/metrics/historyGET/api/metrics/followersGET/api/metrics/refreshPOST/api/profile/auditsGET/api/profile/audits/{connectionId}GET · POST/api/profile/audit-items/{id}PATCH/api/videos/renderPOST/api/trendsGET/api/influencersGET/api/videos/edit-longPOST/api/identitiesGET
