Skip to content
ReferenceHTTP API

HTTP API

The routes fxbot's server answers under /api. Each one calls the fxbot object, which has the rules for its input.

For typed calls from a page or a script, use the client.

Rules

  • The server listens on FXBOT_HOST, default 127.0.0.1, at FXBOT_PORT, default 8787. See Configuration.
  • Every answer has the header x-fxbot-version, the running version.
  • A write, any method but GET and HEAD, whose Origin header names another host than the request's Host header gets 403, { "error": "This request came from another site." }. A request with no Origin header, such as one from curl or Node's fetch, passes. The server sends no CORS headers.
  • A route that reads a body takes a JSON object. Anything else gets 400, "body must be a JSON object". A body is at most 1 MiB, and a message's at most 12 MiB. A larger one gets 413.
  • An error comes back as JSON, { "error": "<message>" }, with the SDK's status, or 500 for an unexpected one, which the server logs. An error after an answer has started, as in a stream, closes the connection.
  • A path or method the server doesn't serve gets 404, not found, or 405, method not allowed.
  • A bot id is 12 hex characters. Most routes under /api/profiles/<id> get 400 for another shape, and 404 for an unknown bot.

Status codes

Status When
200 Done
201 Made: a bot, a memory, a provider, a sign-in, an add-on, or an OAuth sign-in
202 An update was asked for
304 A library file whose ETag matches If-None-Match
400 Bad input, or a request fxbot refuses
403 A write from another site
404 No such route, or a missing bot, provider, memory, library item, add-on, or sign-in
405 A method the path doesn't take
409 An add-on changed since its preview, a skill is already added, or an update is running
413 A body over its limit
500 An unexpected error
502 A service fxbot asked failed

Health and updates

Method Path Body or query Answer
GET /api/health { "ok": true, "version": "<version>" }
GET /api/update UpdateStatus
POST /api/update 202 and UpdateStatus. Asks the machine to update to the newest release the check found.

UpdateStatus is { version, latest, canUpdate, updating }. canUpdate is true only in an install made by setup.sh, where FXBOT_CONTROL is set. updating is { to, steps, error? } while an update runs, or after one failed, else null. POST gets 400 outside such an install, and when the running version is the newest, and 409 while an update runs.

Add-ons

Method Path Body or query Answer
GET /api/addons Addon[]
GET /api/addons/search q, kind (mcp or skill), everything=1 AddonSearch, or SkillSearch for kind=skill
POST /api/addons/preview { "ref": AddonRef } AddonPreview, or SkillPreview for a skill
POST /api/addons { "ref": AddonRef, "hash": "<preview's hash>" } 201 and Addon
POST /api/addons/<id>/update { "hash": "<preview's hash>" } Addon
PATCH /api/addons/<id> Any of enabled, bots (all or each), allowed (tool names) Addon
DELETE /api/addons/<id> { "ok": true }
PUT /api/addons/<id>/secrets Any of headers (name to value), clientId, clientSecret Addon
GET /api/addons/<id>/skills ?file=, default SKILL.md { "text": "<file>" }
POST /api/addons/<id>/sign-in 201 and OAuthAttempt. The request's Origin is where the sign-in comes back to.
PUT /api/profiles/<id>/addons/<addonId> { "enabled": true } Addon. Turns the add-on on or off for one bot.

AddonRef is { url, transport?, headers?, allowLocal? }, { registry, version?, allowLocal?, from? }, or { skill, from? }: see Add-ons.

OAuth

Method Path Body or query Answer
GET /api/oauth/callback The authorization server's ?state=&code= An HTML page: 200, "Connected. You can close this tab.", or 400, "Sign-in failed: …"
GET /api/oauth/attempts/<attemptId> OAuthStatus
POST /api/oauth/attempts/<attemptId>/paste { "address": "<the page's address>" } OAuthStatus

GET /callback, outside /api, answers the same page, for a loopback redirect to the server's own port on this machine.

Settings

Method Path Body or query Answer
GET /api/settings Settings
PATCH /api/settings Any of defaultProvider, embedder, embeddingProvider, embeddingModel, messageVectors, and local Settings
GET /api/settings/embedding EmbeddingStatus
  • local is cpu, gpu, or off. It asks the machine to run its local embedder that way. Outside an install made by setup.sh, it gets 400.
  • EmbeddingStatus adds what the machine runs to the SDK's status. local is the machine's embedder, or null outside an install made by setup.sh. switching is a change it hasn't finished, or why one failed. canSwitch is whether local can be asked for. When the machine runs no embedder while Settings asks for local, error says so.

Providers

Method Path Body or query Answer
GET /api/providers Provider[]
POST /api/providers NewProvider: kind, and any of name, baseUrl, apiKey, model, contextWindow 201 and Provider
PATCH /api/providers/<id> Any of name, baseUrl, apiKey, model, contextWindow, teamId Provider
DELETE /api/providers/<id> { "ok": true }
GET /api/providers/<id>/models Model[]
GET /api/providers/<id>/teams Team[]

A key goes up in apiKey. No answer carries it: a Provider has hasKey and signedIn.

Sign-ins

Method Path Body or query Answer
POST /api/sign-ins kind: chatgpt, grok, or vercel. provider, optional: a provider of that kind to sign in again. 201 and SignIn: { id, verificationUrl, userCode, expiresAt }
GET /api/sign-ins/<id> SignInStatus: { status, provider?, error? }, with status waiting, done, or failed
DELETE /api/sign-ins/<id> { "ok": true }

Library

Method Path Body or query Answer
GET /api/library q, profile (a bot id), kind (image or document), before (a page's next), limit (1 to 100, default 60) LibraryPage: { items, next }
GET /api/library/<id> LibraryItem
DELETE /api/library/<id> { "ok": true }
GET /api/library/<id>/file ?download=1 The file's bytes
GET /api/profiles/<id>/uploads/<sha256>.<ext> The bytes of an image, at its address from before the library

A file's answer has these headers:

Header Value
content-type An image's type, or text/plain; charset=utf-8 for every document
x-content-type-options nosniff
content-security-policy default-src 'none'; style-src 'unsafe-inline'; sandbox
cache-control private, no-cache
etag The SHA-256 of its bytes, in quotes. A matching If-None-Match gets 304 with no body.
content-disposition With ?download=1: attachment, with the item's name

Bots

Method Path Body or query Answer
GET /api/profiles Profile[]
POST /api/profiles name, and any of instructions, model, provider, shape, color 201 and Profile
GET /api/profiles/<id> Profile
PATCH /api/profiles/<id> Any of the fields, and pinned Profile
DELETE /api/profiles/<id> { "ok": true }
GET /api/profiles/<id>/earlier Turn[]: the chats before the current one
GET /api/profiles/<id>/skills BotSkill[]
GET /api/profiles/<id>/memories ?query=&limit= Memory[]
POST /api/profiles/<id>/memories { "text": "<fact>" } 201 and Memory
PATCH /api/profiles/<id>/memories/<memoryId> { "text": "<fact>" } Memory
DELETE /api/profiles/<id>/memories/<memoryId> { "ok": true }

Chat

Method Path Body or query Answer
POST /api/profiles/<id>/messages Message: text, messageId, and optional files, each { name, type, data } with data in base64. At most 12 MiB. { messageId, sessionId }, once the message is stored
GET /api/profiles/<id>/stream cursor, default 0 NDJSON lines, from the cursor on, then each new one

The stream:

  • answers 200 with content-type: application/x-ndjson and cache-control: no-store, and the x-fxbot-version header
  • replays the chat's lines from cursor, then stays open for new ones. 0 replays the whole chat. Each line's cursor is where to ask again to get the lines after it.
  • ends at once for a bot with no chat yet, or whose provider is gone
  • stops when the client closes the connection
  • gets 400 for a cursor that isn't a whole number

The lines' fields are in Stream lines.

curl -N "http://127.0.0.1:8787/api/profiles/<id>/stream?cursor=0"

Outside /api

GET /callback finishes an MCP sign-in, as above. Every other path serves the page from packages/ui/dist/: the file at that path, or index.html, so the app's routes load. Without a build, it gets 404, { "error": "no build in packages/ui/dist/; run npm run build" }.