Integrations
Endpoints that back optional platform integrations: the AI assistant, support
chat, documentation proxying, remote device access and build workers. Each
integration depends on server configuration and reports 503 when it is not
configured.
AI assistant (Cody)
Base path: /api/copilotkit.
| Method | Path | Purpose |
|---|---|---|
| GET | /api/copilotkit/status | Report whether the assistant is available |
| ALL | /api/copilotkit/* | CopilotKit v2 (AG-UI) runtime endpoint |
GET /api/copilotkit/status
{ "available": true }
available is false when neither GOOGLE_API_KEY nor GEMINI_API_KEY is set
on the server. In that case every other path under /api/copilotkit returns:
{ "error": "No AI provider configured. Set GOOGLE_API_KEY or GEMINI_API_KEY environment variable." }
with status 503.
The catch-all route is the CopilotKit v2 runtime, not a hand-written REST API.
The v2 client calls /info for agent discovery, /agent/:id/connect,
/agent/:id/run, /agent/:id/stop, and /transcribe beneath the base path.
Use the CopilotKit client library rather than calling these directly — the
protocol (including streaming) is defined by CopilotKit.
The server exposes a single agent, default, running Gemini 2.5 Flash with a
searchKnowledgeBase tool backed by a Pinecone index of the AppBlocks
documentation. Project-editing tools are supplied by the frontend at runtime, so
an external client will get conversation and documentation search but none of the
project manipulation the web app offers.
This route is registered before body parsing so the runtime can read the raw request stream. Send requests exactly as the CopilotKit client does.
Support chat (Chatwoot)
Base path: /api/chatwoot. These endpoints bridge the in-app assistant to a
Chatwoot support inbox, so a conversation with the AI can be handed over to a
human agent.
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | /api/chatwoot/status | public | Report configuration |
| POST | /api/chatwoot/session | session or guestId | Create/restore a contact and conversation |
| GET | /api/chatwoot/conversations | session or guestId | List the contact's conversations |
| POST | /api/chatwoot/conversations | session or guestId | Force-create a new conversation |
| POST | /api/chatwoot/messages | session or guestId | Mirror a message into Chatwoot |
| GET | /api/chatwoot/messages | session or guestId | Poll messages |
| POST | /api/chatwoot/request-human | session or guestId | Ask for a human agent |
| POST | /api/chatwoot/webhook | public | Receive Chatwoot events |
Actors and ownership
Every call resolves an actor: either the signed-in user or an anonymous guest
identified by a client-generated guestId. The actor determines ownerKey, the
contact identifier used with Chatwoot. ownerKey must be sent on most calls and
is checked against the resolved actor — a mismatch returns
403 {"error":"ownerKey does not match session owner"}, and an unresolvable
actor returns 401 {"error":"Login or guestId is required"}.
When Chatwoot is not configured, all of these return
503 {"error":"Chatwoot is not configured"}.
Status
GET /api/chatwoot/status
{
"available": true,
"botApi": true,
"agentApi": true,
"assistantMirrorApi": "bot",
"anonymousAllowed": true
}
assistantMirrorApi reports which credential set is used to mirror assistant
replies (bot, agent or none).
Session
POST /api/chatwoot/session
{
"projectId": "664a1f2b8c9d0e0011223301",
"projectName": "Boiler control",
"guestId": "guest-8f2a…",
"conversationId": 4711
}
Creates or restores the Chatwoot contact and conversation:
{
"contactIdentifier": "user-8a3f…",
"conversationId": 4711,
"pubsubToken": "…",
"ownerKey": "user-8a3f…",
"anonymous": false
}
Anonymous actors without a conversationId get a response with null
identifiers — no contact is created until they actually send something.
Conversations
GET /api/chatwoot/conversations?ownerKey=…&contactIdentifier=…&guestId=…
POST /api/chatwoot/conversations
GET returns { "conversations": [ … ] }, newest first, or an empty array when
the caller has no contact yet. POST takes
{ guestId, ownerKey, projectId, projectName } and always creates a new
conversation, returning { contactIdentifier, conversationId }.
Messages
POST /api/chatwoot/messages
GET /api/chatwoot/messages?contactIdentifier=…&conversationId=…&afterId=0&ownerKey=…
POST mirrors one message:
{
"contactIdentifier": "user-8a3f…",
"conversationId": 4711,
"ownerKey": "user-8a3f…",
"role": "user",
"content": "My device is offline",
"guestId": "guest-8f2a…",
"projectId": "664a1f2b…",
"projectName": "Boiler control"
}
content, role and ownerKey are required (400 otherwise). role must be
user (delivered as an incoming contact message) or assistant (delivered as a
bot message). Mirroring an assistant message may be skipped when no bot/agent
credentials are configured, in which case the response carries
{"message":null,"skipped":true,"reason":"…"}.
GET requires contactIdentifier, conversationId and ownerKey, and returns
{ "messages": [ … ] } — contact, bot and human agent messages, excluding private
notes, with only messages newer than afterId. Poll it to display agent replies.
Request a human
POST /api/chatwoot/request-human
{
"conversationId": 4711,
"contactIdentifier": "user-8a3f…",
"ownerKey": "user-8a3f…",
"guestId": "guest-8f2a…",
"projectId": "664a1f2b…",
"projectName": "Boiler control"
}
Reopens the conversation for human agents, clears the bot assignment and posts a
private note announcing the request. Returns
{ success, contactIdentifier, conversationId }. Requires Chatwoot's agent API
credentials, otherwise 503.
Webhook
POST /api/chatwoot/webhook
Public receiver for Chatwoot events. Always answers
200 {"received": true}. Agent replies are currently delivered to clients by
polling GET /api/chatwoot/messages, so no processing happens here.
Tibbit documentation proxy
GET /api/tibbits/docs/:model
Public. Fetches the documentation page for a Tibbit from docs.tibbo.com and
returns it as text/html. Underscores in :model are converted to hyphens, so
/api/tibbits/docs/00_3 proxies https://docs.tibbo.com/tibbit_00-3.
Upstream failures are relayed with the upstream status code (or 502) and an
empty body. The request times out after 10 seconds. This exists so the editor can
show Tibbit datasheets inline without hitting cross-origin restrictions.
Remote device access (TIDE)
Base paths: /tide/networks, /tide/client, /workers.
These are Socket.IO endpoints, not REST. The Express routes proxy WebSocket and HTTP polling traffic to an internal Socket.IO server with three namespaces:
| Path | Namespace | Used by |
|---|---|---|
/tide/networks/:userIdBase64 | /devices | A local network agent publishing discovered devices |
/tide/client/:networkId? | /tide | The IDE/editor talking to devices on a remote network |
/workers | /workers | Build workers taking jobs |
A network agent connects to /tide/networks/<base64 user id> (or the literal
public) and registers under that user. Clients connect to /tide/client, which
requires a session — the proxy rejects unauthenticated upgrades with
401 unauthorized — and receive a NETWORKS_UPDATED event listing the networks
available to them (their own plus any public ones).
Two plain HTTP routes accompany the socket traffic:
GET /tide/client/list
Returns the caller's networks, plus public networks:
[
{ "name": "Office LAN", "networkType": "private", "id": "aBcD1234" },
{ "name": "Demo lab", "networkType": "public", "id": "eFgH5678" }
]
GET /tide/client/remotedevices/:address
POST /tide/client/remotedevices/:address/settarget
DELETE /tide/client/remotedevices/:address/settarget
:address is a device MAC as seen by the network agent.
GET describes a discovered device:
{
"mac": "0.36.53.1.2.3",
"address": "0.36.53.1.2.3",
"lastUpdated": 1755412800000,
"userId": "8a3f…",
"nextAvailability": 1755413400000
}
userId is returned only when you hold the reservation. nextAvailability is
the epoch milliseconds at which the current reservation expires.
POST …/settarget reserves the device for exclusive access — required before
uploading or debugging over a shared network. A user may hold one reservation
at a time; taking a new one releases the previous. Reservations last
REMOTE_DEVICE_TIMEOUT minutes (default 10) and are swept once a minute.
| Status | Meaning |
|---|---|
200 | Reserved (or already yours) |
400 device not available | Someone else holds an unexpired reservation |
404 device not found | The address has not been discovered |
DELETE …/settarget releases your reservation; releasing a device you do not hold
returns 400 device not reserved.
Build workers
/workers
Socket.IO namespace used by build runners. A worker connects, sends an update
event with its key and capabilities (for example build:tios,
build:zephyr), and then receives job events. Workers report progress and
results back on the job event, and current load on load.
The dispatcher polls every second, picks up the oldest enqueued
build job whose type matches some worker's
capabilities, and assigns it to the worker with the lowest
active jobs × average duration score. Jobs whose type no worker can handle are
failed immediately with No compatible worker found. A worker connecting with an
unknown key is disconnected.
Submit and poll build jobs through /api/tasks; this
namespace is only for worker processes.