Server API
The Server API is the localhost HTTP API served by Toby’s background service (daemon). Toby.app, scheduled jobs, and other local clients use it for chat, sessions, configuration, recordings metadata, projects, memories, library files, and more.
This page is the endpoint reference. For access model, config, and how this differs from Toby.app’s Native API, see Local APIs.
Paths under /api/native/* are not part of this API. They are hosted by Toby.app on the port in ~/.toby/native-port. See the Native API reference.
Access model#
| Item | Detail |
|---|---|
| Base URL | http://127.0.0.1:<port> (default 7847) |
| Binding | Localhost only |
| Auth | None (local trust) |
| Encoding | JSON; SSE for chat turns |
| Errors | { "error": string } on non-2xx |
Unknown /api/* | 404 |
{
"web": {
"enabled": true,
"port": 7847
}
}Common types#
type ChatSessionSettings = {
persona?: string;
modules?: readonly string[];
dryRun?: boolean;
debug?: boolean;
};
type SessionSummary = {
id: string;
name: string;
createdAt: string;
updatedAt: string;
};
type PlanSummary = {
id: string;
goal: string;
status: string;
phases: readonly {
id: string;
label: string;
status: string;
}[];
};Transcript entries and chat stream events are structured JSON objects emitted during a turn (see Chat turns).
Endpoint index#
| Method | Path | Purpose |
|---|---|---|
ANY | /api/health | Daemon health and identity handshake |
GET | /api/status | Version, persona, model, integrations, skill count |
GET | /api/dashboard | Aggregator payload (all categories; internal / debug) |
GET | /api/dashboard/:section | Aggregator list for one category (not used by home cards) |
GET | /api/dashboard/:section/content | Home card block content (flow output; preferred) |
GET | /api/dashboard/:section/summary | Alias of /content |
GET | /api/flows | Flow list for the app UI (id, name, description, builtin, persona, nodes, destinations, timestamps; built-ins seeded on list) |
POST | /api/flows | Create a custom flow |
GET | /api/flows/catalog | Connected plugin tools (including input schemas) for the flow editor |
GET | /api/flows/:id | One flow plus its stored document |
PUT | /api/flows/:id | Replace a custom flow |
DELETE | /api/flows/:id | Delete a custom flow |
POST | /api/flows/:id/run | Run a flow now and deliver destinations |
GET | /api/flows/runs | Flow execution history summaries |
GET | /api/flows/runs/:id | One flow run with per-node detail |
GET / POST | /api/user-tools | List or create reusable script tools |
GET / PUT / DELETE | /api/user-tools/:id | Inspect, edit, or delete a script tool |
POST | /api/user-tools/test | Test draft script code and inputs without saving |
POST | /api/user-tools/generate | Generate replacement source from an instruction and current draft settings |
POST | /api/user-tools/:id/test | Test a saved script with a JSON input object |
POST | /api/issues | File or forward a GitHub issue report |
GET | /api/daemon/status | Process + inbound chat status |
POST | /api/daemon/restart | Restart the daemon |
POST | /api/daemon/stop | Stop the daemon |
GET | /api/listen/status | Listen manager state |
POST | /api/listen/start | Start helper-backed capture |
POST | /api/listen/stop | Stop capture (save or discard) |
GET | /api/listen/recordings | List saved recordings |
GET | /api/listen/recordings/:id | Recording detail + transcript + summary |
PATCH | /api/listen/recordings/:id | Update name, description, or linked chat |
DELETE | /api/listen/recordings/:id | Delete a recording |
DELETE | /api/listen/recordings/:id/audio | Delete a recording's audio files (keeps transcript + summary) |
POST | /api/listen/recordings/:id/transcribe | Transcribe or retranscribe |
POST | /api/listen/recordings/:id/summarize | Summarize or re-summarize a transcript |
GET | /api/sessions | List chat sessions |
POST | /api/sessions | Create a session |
GET | /api/sessions/:id | Session transcript and settings |
PATCH | /api/sessions/:id | Rename or update settings |
DELETE | /api/sessions/:id | Delete a session |
POST | /api/sessions/:id/bootstrap | Assemble-only bootstrap |
POST | /api/sessions/:id/turn | Chat turn (SSE stream) |
POST | /api/sessions/:id/turn/:turnId/cancel | Cancel an in-flight turn |
POST | /api/sessions/:id/turn/:turnId/ask-user/:requestId | Answer askUser prompt |
GET | /api/sessions/:id/plan | Active plan summary |
POST | /api/sessions/:id/plan/skip | Skip a plan phase |
POST | /api/sessions/:id/plan/cancel | Cancel the active plan |
GET | /api/personas | Persona picker list |
GET | /api/personas/:name | Persona detail |
GET | /api/personas/image/:filename | Persona image asset |
GET | /api/ai/providers | AI provider catalog with live models when configured |
GET | /api/ai/providers/usage | Plan usage / balance for all providers |
GET | /api/ai/providers/:id/usage | Plan usage / balance for one provider |
GET | /api/modules | Connected chat modules |
GET | /api/skills | Local skills list |
GET | /api/skills/:name | Skill body |
GET | /api/skills/:name/icon | Skill icon |
GET | /api/plugins | Discovered plugins |
GET | /api/plugins/:name/icon | Plugin icon |
GET | /api/projects | List projects |
POST | /api/projects | Create a project |
GET / PATCH / DELETE | /api/projects/:idOrSlug | Project detail, update, delete |
GET | /api/projects/:idOrSlug/tree | Project folder tree |
POST | /api/projects/:idOrSlug/sessions | Create a project-scoped session |
GET | /api/releases/changelog | Changelog payload |
GET | /api/memories | Search / page memories |
POST | /api/memories | Create a manual memory |
GET | /api/memories/:id | One memory |
PATCH | /api/memories/:id | Update a memory |
DELETE | /api/memories/:id | Delete a memory |
GET | /api/memories/:id/explain | Source / audit explanation |
GET | /api/library | Search / page library items |
POST | /api/library | Add a file (filename, mediaType, dataBase64) |
GET / PATCH / DELETE | /api/library/:id | Library item detail, update, delete |
GET | /api/configure/tree | Full configure tree + values |
GET | /api/configure/sections | Lightweight settings sections |
GET | /api/configure/sections/:sectionKey | One section detail |
PATCH | /api/configure/values | Persist configure changes |
POST | /api/configure/actions/:action | Named configure actions |
POST | /api/flows/triggers/parse-cron | Parse cron / natural language |
GET | /api/flows/triggers/activity?flowId= | Trigger decisions and run links |
GET | /api/integrations/:name/status | Integration status |
GET | /api/integrations/:name/setup-guide | Setup wizard steps |
POST | /api/integrations/:name/connect | Connect |
POST | /api/integrations/:name/disconnect | Disconnect |
POST | /api/integrations/:name/reauthorize | Reauthorize |
POST | /api/integrations/:name/setup | Run plugin setup |
GET | /icons/* | Static provider / asset icons |
Health and status#
ANY /api/health#
Returns 200 when the daemon HTTP server is reachable. Prefer GET.
Includes an identity payload so Toby.app can confirm it is talking to the expected binary (version, path, compiled vs source).
{
"ok": true,
"daemon": true,
"identity": {
"version": "1.2.3",
"executablePath": "/Applications/Toby.app/Contents/Resources/toby",
"execKind": "compiled",
"tobyDir": "/Users/you/.toby",
"pid": 12345,
"startedAt": "2026-01-01T00:00:00.000Z",
"entryScript": null
}
}| Field | Meaning |
|---|---|
version | Toby release version |
executablePath | Absolute path of the binary or entry script |
execKind | "compiled" or "source" |
tobyDir | Data directory for this process |
entryScript | TS/JS entry path when execKind is "source"; otherwise null |
GET /api/status#
type ChatStatusResponse = {
version: string;
persona: string;
model: string;
connectedIntegrations: readonly string[];
skillCount: number;
};GET /api/daemon/status#
When inbound config is incomplete or disabled (disabledReason is set),
status is reported as "disabled" even if a previous listener has not
finished tearing down. Configure changes to chatInbound.* (and related
inbound credentials/toggles) hot-reload the inbound supervisor.
type DaemonStatusResponse = {
process: {
pid: number;
uptimeSeconds: number;
startedAt: string;
intervalSeconds: number | null;
logPath: string;
webPort: number | null;
executablePath: string;
execKind: "compiled" | "source";
version: string;
tobyDir: string;
entryScript: string | null;
};
chatInbound: {
enabled: boolean;
integration: string | null;
integrationLabel: string | null;
status: "disabled" | "idle" | "connecting" | "connected" | "error";
detail: string | null;
disabledReason: string | null;
updatedAt: string;
};
};POST /api/daemon/restart#
{ "ok": true, "restarting": true }Schedules a detached restart after the response is flushed.
POST /api/daemon/stop#
{ "ok": true, "stopping": true }Schedules SIGTERM after the response is flushed. The API goes offline when the process exits.
Dashboard#
GET /api/dashboard#
Returns aggregator list/count data for all categories. Home cards do not use this; it remains for debug and tooling.
GET /api/dashboard/:section#
Returns aggregator detail for one category (email, tasks, calendar). Unknown categories return 404 with { "error": "Unknown dashboard category: …" }.
Optional query: ?fresh=1 bypasses the short category cache.
GET /api/dashboard/:section/content#
Returns block content for a home card body (flow-generated markdown plus light meta such as count and launchUrls), or null when no providers are connected. Preferred path for Toby.app.
Optional query: ?fresh=1 regenerates content (bypasses caches; used for manual refresh).
GET /api/dashboard/:section/summary#
Alias of /content (legacy path name).
Issues#
POST /api/issues#
Files or opens a GitHub issue report helper.
Request body:
{
type: "bug" | "feature";
details: string; // required
source?: "tui" | "native-app";
repo?: string; // optional override
}Success: { "ok": true, "url": string, "number": number }.
If automatic filing fails: { "ok": false, "fallbackUrl": string, "reason": string }.
Listen and recordings#
These endpoints manage the daemon-facing recording lifecycle (list, detail, patch, delete, transcribe). Toby.app often captures audio through its Native API, then uses this Server API for transcription and browsing.
Daemon listen start/stop is separate from Toby.app’s in-process native capture state.
GET /api/listen/status#
type ListenManagerState = {
status: "idle" | "starting" | "recording" | "stopping" | "error";
session?: {
id: string;
startedAt: string;
sources: { mic: boolean; system: boolean };
};
outputDir?: string;
message?: string;
error?: string;
};POST /api/listen/start#
Starts helper-backed capture (default mic + system audio). Returns ListenManagerState.
409if already recording500if capture cannot start
POST /api/listen/stop#
Optional JSON body. Empty body saves by default. Use { "action": "discard" } to discard (when supported by the manager path).
400invalid JSON409no active recording500finalization failure
GET /api/listen/recordings#
type ListenRecordingsListResponse = {
recordings: Array<{
id: string;
dir: string;
name?: string;
description?: string;
createdAt: string;
startedAt: string;
stoppedAt?: string;
durationMs?: number;
sources: { mic: boolean; system: boolean };
hasAudio: boolean;
hasTranscript: boolean;
}>;
};Newest first.
GET /api/listen/recordings/:id#
Full metadata, playable audioPath (prefers combined.m4a, then mic WAV, then system WAV), transcript text, optional segments/warnings.
404if not found
PATCH /api/listen/recordings/:id#
type PatchRecordingRequest = {
name?: string;
description?: string;
chatSessionId?: string | null;
};At least one of name, description, or chatSessionId is required. Returns the same detail shape as GET.
400invalid body404not found
DELETE /api/listen/recordings/:id#
{ "ok": true }404/500on failure
DELETE /api/listen/recordings/:id/audio#
Deletes the recording's audio files (combined.m4a, mic.wav, system.wav)
while keeping the recording entry, transcript, summary, and metadata. Records
audioDeletedAt in metadata and returns the refreshed recording detail.
404not found500on failure
POST /api/listen/recordings/:id/transcribe#
Runs transcription against the recording’s audio and updates artifacts.
A successful run clears any prior AI summary and prior metadata.errors, then
writes a new summary and, when the recording has no name, a short title. A
summary or title failure stays on metadata.errors and does not fail the
transcription. Toby.app calls this endpoint (as an event stream, with
reuseLiveTranscript: true) after Stop; that is what auto-summarizes a
recording.
Optional body:
{ "recordingsDir": "/path/to/recordings" }Clients that send Accept: text/event-stream may receive a streaming transcription progress response; otherwise JSON is returned.
400no readable audio404not found500transcription failure (error is also appended to recording metadata)
Sessions#
GET /api/sessions#
| Query | Default | Max | Description |
|---|---|---|---|
limit | 50 | 500 | Max sessions to return |
type SessionsListResponse = {
sessions: SessionSummary[];
};POST /api/sessions#
Empty body is allowed.
type CreateSessionRequest = {
name?: string;
persona?: string;
modules?: readonly string[];
dryRun?: boolean;
debug?: boolean;
bootstrap?: boolean;
};- Default name:
"New chat" bootstrap: trueruns assemble-only bootstrap (no model call)
Response 201:
type CreateSessionResponse = {
id: string;
name: string;
settings: ChatSessionSettings;
};GET /api/sessions/:id#
type SessionDetailResponse = {
id: string;
name: string;
transcript: readonly unknown[];
messageCount: number;
settings: ChatSessionSettings;
activePlan: PlanSummary | null;
};404if missing
PATCH /api/sessions/:id#
type PatchSessionRequest = Partial<{
name: string;
persona: string;
modules: readonly string[];
dryRun: boolean;
debug: boolean;
}>;Blank name is ignored; settings merge with existing values.
DELETE /api/sessions/:id#
{ "ok": true }POST /api/sessions/:id/bootstrap#
type BootstrapSessionRequest = {
initialText?: string;
};
type BootstrapSessionResponse = {
messageCount: number;
};Chat turns#
POST /api/sessions/:id/turn#
Submits a user turn and streams events as server-sent events.
Content-Type of the response: text/event-stream; charset=utf-8.
type TurnRequestBody = {
text: string; // required (non-blank after trim)
persona?: string;
modules?: readonly string[];
dryRun?: boolean;
clientTurnId?: string;
steering?: boolean;
generatePlan?: boolean;
};persona/modules/dryRunoverride session settings for this turnclientTurnIdis optional correlation / idempotencysteering: truemay cancel an active turn before submittinggeneratePlanmay be present on the shared type; behavior depends on the current handler
SSE format
- Heartbeats: comment lines
: keep-alive - Default messages:
data: <ChatEvent JSON> - Named terminal events:
event: done,event: error,event: ask_user_prompt
done payload:
type TurnDonePayload = {
turnId: string;
text: string;
appliedActions: readonly string[];
sessionName?: string;
usage?: unknown;
warnings?: readonly string[];
};error payload:
type TurnErrorPayload = {
turnId?: string;
error: string;
};ask_user_prompt payload:
type AskUserPromptPayload = {
turnId: string;
requestId: string;
query: string;
options: readonly string[];
};Errors before the stream starts: 400 (bad JSON / blank text), 404 (session). Failures after streaming starts use event: error.
Example:
curl -N -X POST "http://127.0.0.1:7847/api/sessions/<id>/turn" \
-H "Content-Type: application/json" \
-d '{"text":"Summarize my calendar for today"}'POST /api/sessions/:id/turn/:turnId/cancel#
type CancelTurnResponse = {
ok: true;
cancelled: true;
};404if turn not found, already done, or session missing
POST /api/sessions/:id/turn/:turnId/ask-user/:requestId#
type AskUserAnswerRequest = {
selectedIndex: number;
selectedLabel: string;
rawInput?: string;
};Missing fields normalize to selectedIndex: -1, empty labels/input.
{ "ok": true }Plans#
GET /api/sessions/:id/plan#
type SessionPlanResponse = {
plan: PlanSummary | null;
};POST /api/sessions/:id/plan/skip#
{ "planId": "…", "phaseId": "…" }POST /api/sessions/:id/plan/cancel#
{ "planId": "…" }Metadata#
GET /api/personas#
type PersonasResponse = {
personas: readonly {
name: string;
label: string;
imagePath?: string;
imageUrl?: string;
}[];
};GET /api/personas/:name#
Persona detail for the named persona (404 if missing).
GET /api/personas/image/:filename#
Serves a persona image. Built-in portraits (toby.png, mailman.png) come from the bundled persona assets. User uploads are stored under ~/.toby/persona/images/. default.png maps to the bundled Toby portrait when no user default exists.
GET /api/ai/providers#
AI provider catalog used by settings and persona editor model pickers.
When a provider is configured (API key / credentials present — or Ollama base URL), Toby queries that provider’s models API and returns that list as-is. When unconfigured or the remote list fails, the response falls back to built-in curated model ids. User customModels from config are appended only when not already present.
type AIProvidersResponse = {
providers: readonly {
id: string;
displayName: string;
models: string[];
allowCustomModel: boolean;
configured: boolean;
}[];
};GET /api/ai/providers/usage#
Plan usage and balance for all registered AI providers. Providers that do not expose balance APIs return supported: false with "N/A" display labels. Results are cached for 60 seconds.
Vercel AI Gateway reports gateway balance and lifetime spend. When the account can query AI Gateway spend reports, the response also includes today, this UTC week (Monday–Sunday), and this UTC month. OpenRouter reports lifetime spend, those same UTC periods, and remaining credit for the saved API key. When the key has no spending cap, remaining credit is omitted. Period fields are omitted when the provider does not return them. The settings card shows an em dash for each omitted amount. A reported zero stays $0.00.
type AIProvidersUsageResponse = {
usage: readonly {
providerId: string;
supported: boolean;
currency?: "USD";
totalSpent?: number;
remaining?: number;
totalSpentLabel?: string;
remainingLabel?: string;
spentDaily?: number;
spentWeekly?: number;
spentMonthly?: number;
spentDailyLabel?: string;
spentWeeklyLabel?: string;
spentMonthlyLabel?: string;
unavailableReason?: string;
fetchedAt: string;
}[];
};GET /api/ai/providers/:id/usage#
Plan usage and balance for a single AI provider. Bypasses the cache, so it is suitable for a manual refresh action.
type AIProviderUsageResponse = {
usage: {
providerId: string;
supported: boolean;
currency?: "USD";
totalSpent?: number;
remaining?: number;
totalSpentLabel?: string;
remainingLabel?: string;
spentDaily?: number;
spentWeekly?: number;
spentMonthly?: number;
spentDailyLabel?: string;
spentWeeklyLabel?: string;
spentMonthlyLabel?: string;
unavailableReason?: string;
fetchedAt: string;
};
};GET /api/modules#
type ModulesResponse = {
modules: readonly {
name: string;
displayName: string;
connected: true;
}[];
};GET /api/skills#
type SkillsResponse = {
skills: readonly {
dirName: string;
name: string;
summary: string;
/** Compatibility alias of summary. */
description: string;
enabled: boolean;
}[];
};GET /api/skills/:name / GET /api/skills/:name/icon#
Skill detail includes summary and its compatibility alias description, plus
the instructional bodyMarkdown and optional icon asset.
GET /api/plugins / GET /api/plugins/:name/icon#
Discovered installable plugins and icons.
GET /api/releases/changelog#
Changelog / release notes payload. Optional limit query (default/max typically 10).
Projects#
GET /api/projects#
{ projects: ProjectPayload[] }POST /api/projects#
type CreateProjectRequest = {
name?: string;
summary?: string;
folderPath?: string;
personaName?: string | null;
};Response 201: { project: ProjectPayload }.
GET /api/projects/:idOrSlug#
{
project: ProjectPayload;
sessions: SessionSummary[]; // project-scoped sessions
}ProjectPayload includes id, slug, name, summary, folderPath, personaName, outputsDir, skillsDir, timestamps.
PATCH /api/projects/:idOrSlug#
Partial update of name, summary, folderPath, personaName.
DELETE /api/projects/:idOrSlug#
{ "ok": true }GET /api/projects/:idOrSlug/tree#
{ "tree": /* folder tree nodes */ }POST /api/projects/:idOrSlug/sessions#
Creates a chat session bound to the project.
type CreateProjectSessionRequest = {
name?: string;
persona?: string;
modules?: readonly string[];
dryRun?: boolean;
debug?: boolean;
};Response 201: { session: … }.
Memories#
GET /api/memories#
| Query | Default | Description |
|---|---|---|
q | — | Search query (keywords and meaning) |
limit | 50 (max 500) | Page size |
offset | 0 | Skip count |
type MemoriesResponse = {
memories: MemoryItem[];
limit: number;
offset: number;
total: number;
hasMore: boolean;
};
type MemoryItem = {
id: string;
type: string;
subject?: string;
value: string;
confidence: number;
sensitivity: string;
visibility: string;
createdAt: string;
updatedAt: string;
embeddingModel?: string;
};POST /api/memories#
Create a manual memory. Body must include non-empty value. Optional: type, subject, confidence, sensitivity, visibility, expiresAt.
Response 201: { memory: MemoryItem }.
GET /api/memories/:id#
{ memory: MemoryItem }PATCH /api/memories/:id#
Updatable fields: value, subject, type, sensitivity, visibility, confidence, expiresAt. At least one required.
DELETE /api/memories/:id#
{ "ok": true }GET /api/memories/:id/explain#
type MemoryExplainResponse = {
explanation: {
item: MemoryItem;
sources: Array<{ id: string; system: string; label?: string }>;
auditTrail: Array<{ action: string; at: string; reason?: string }>;
};
};Library#
Indexed file catalog. See Library. Add is asynchronous; chat tools wait for indexing.
GET /api/library#
| Query | Default | Description |
|---|---|---|
q | — | Search query (keywords and meaning) |
origin | all | all, manual, chat, or project |
refresh | omit | 1 scans chats and projects for files not yet cataloged |
limit | 50 (max 500) | Page size |
offset | 0 | Skip count |
type LibraryListResponse = {
items: LibraryItem[];
limit: number;
offset: number;
total: number;
hasMore: boolean;
catalogSyncing: boolean;
};
type LibraryItem = {
id: string;
title: string;
originalFilename: string;
relativePath: string;
mimeType: string;
byteSize: number;
contentHash: string;
description: string;
status: "pending" | "ready" | "failed";
error?: string | null;
source: "ui" | "chat" | "tool" | "project";
createdAt: string;
updatedAt: string;
embeddingModel?: string;
references?: Array<{
id: string;
kind: "manual" | "chat" | "project";
label: string;
detail?: string | null;
sessionId?: string | null;
projectId?: string | null;
createdAt: string;
}>;
};The list response also includes catalogSyncing. It is true while Toby is still copying chat attachments and project files into the library.
POST /api/library#
Body: { filename, mediaType?, dataBase64 }. Returns { item, duplicate } (201 new, 200 duplicate).
GET /api/library/:id#
{ item: LibraryItem }PATCH /api/library/:id#
Updatable: title, description, and/or replacement filename / mediaType / dataBase64.
DELETE /api/library/:id#
{ "ok": true, "id": "…" }Configure#
GET /api/configure/tree#
type ConfigureTreeResponse = {
tree: SettingsItem;
values: Record<string, string>;
integrationLabels: Record<string, string>;
};
type SettingsItem = {
label: string;
kind: string;
key: string;
navKey?: string;
children?: SettingsItem[];
masked?: boolean;
multiline?: boolean;
options?: string[];
selectChoices?: { value: string; label: string }[];
currentValue?: string;
readOnly?: boolean;
};Secrets are redacted (placeholder ••••••).
GET /api/configure/sections#
Lightweight section nodes for settings navigation (for example chatInbound, defaults, ai, projects).
GET /api/configure/sections/:sectionKey#
Full section detail for a top-level or nested key (for example ai.openai).
PATCH /api/configure/values#
type ConfigurePatchRequest = {
changes: Record<string, string>;
};Response matches GET /api/configure/tree.
400ifchangesmissing403if the patch cannot be applied
POST /api/configure/actions/:action#
Body: Record<string, string>.
| Action | Request fields | Response |
|---|---|---|
create-persona | — | { "ok": true, "personaName": string } |
delete-persona | personaName | { "ok": true } |
set-default-persona | personaName | { "ok": true } |
clear-default-persona | — | { "ok": true } |
update-skill-field | dirName, field, value | { "ok": true } |
delete-skill | dirName | { "ok": true } |
create-schedule | — | { "ok": true, "scheduleId": string } |
update-schedule-field | scheduleId, field, value | { "ok": true } |
delete-schedule | scheduleId | { "ok": true } |
update-skill-field.field:name,summary,description, orenabled.summaryand the compatibility aliasdescriptionupdate the same required summary stored in SKILL.md frontmatter asdescription.update-schedule-field.field:enabled("Yes"/"true"),name,prompt,persona,cron
Schedules#
POST /api/flows/triggers/parse-cron#
Parses natural-language or cron expressions for the schedules UI.
Flow trigger activity#
GET /api/flows/triggers/activity?flowId=… returns recent decisions and links
to normal Flow runs. GET /api/flows/triggers/status reports native observation
status. Triggers are embedded in Flow create/update definitions; there are no
separate Schedule or Automation APIs.
GET /api/integrations/:name/status#
Connection state, health, tools list, setup flags, auth methods.
GET /api/integrations/:name/setup-guide#
Onboarding wizard steps for the integration.
POST /api/integrations/:name/connect#
POST /api/integrations/:name/disconnect#
POST /api/integrations/:name/reauthorize#
POST /api/integrations/:name/setup#
Connect / disconnect / reauthorize / run plugin setup. Success typically { "ok": true }; failures return error JSON with appropriate status codes.
Icons#
GET /icons/*#
Static AI provider and related icon assets (not under /api/, but served by the same local server).
See also#
- Local APIs — Server API vs Native API
- Native API — Toby.app TCC-gated endpoints
- Architecture — how Toby.app and the daemon fit together
- Toby.app — product UI surfaces
- Contributor reference on GitHub: server-api.md
Home action entry point#
POST /api/flows/:id/run uses a manual trigger by default. Home Actions buttons
send { "source": "home.action" } to select a Home Screen Action trigger.
Each entry point requires its own enabled trigger and uses the same run history.
Home Screen Action triggers require a Home Actions placement; saving the Flow
automatically keeps that placement consistent.