TobyToby
On this page

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.

Native API is separate

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#

ItemDetail
Base URLhttp://127.0.0.1:<port> (default 7847)
BindingLocalhost only
AuthNone (local trust)
EncodingJSON; 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#

MethodPathPurpose
ANY/api/healthDaemon health and identity handshake
GET/api/statusVersion, persona, model, integrations, skill count
GET/api/dashboardAggregator payload (all categories; internal / debug)
GET/api/dashboard/:sectionAggregator list for one category (not used by home cards)
GET/api/dashboard/:section/contentHome card block content (flow output; preferred)
GET/api/dashboard/:section/summaryAlias of /content
GET/api/flowsFlow list for the app UI (id, name, description, builtin, persona, nodes, destinations, timestamps; built-ins seeded on list)
POST/api/flowsCreate a custom flow
GET/api/flows/catalogConnected plugin tools (including input schemas) for the flow editor
GET/api/flows/:idOne flow plus its stored document
PUT/api/flows/:idReplace a custom flow
DELETE/api/flows/:idDelete a custom flow
POST/api/flows/:id/runRun a flow now and deliver destinations
GET/api/flows/runsFlow execution history summaries
GET/api/flows/runs/:idOne flow run with per-node detail
GET / POST/api/user-toolsList or create reusable script tools
GET / PUT / DELETE/api/user-tools/:idInspect, edit, or delete a script tool
POST/api/user-tools/testTest draft script code and inputs without saving
POST/api/user-tools/generateGenerate replacement source from an instruction and current draft settings
POST/api/user-tools/:id/testTest a saved script with a JSON input object
POST/api/issuesFile or forward a GitHub issue report
GET/api/daemon/statusProcess + inbound chat status
POST/api/daemon/restartRestart the daemon
POST/api/daemon/stopStop the daemon
GET/api/listen/statusListen manager state
POST/api/listen/startStart helper-backed capture
POST/api/listen/stopStop capture (save or discard)
GET/api/listen/recordingsList saved recordings
GET/api/listen/recordings/:idRecording detail + transcript + summary
PATCH/api/listen/recordings/:idUpdate name, description, or linked chat
DELETE/api/listen/recordings/:idDelete a recording
DELETE/api/listen/recordings/:id/audioDelete a recording's audio files (keeps transcript + summary)
POST/api/listen/recordings/:id/transcribeTranscribe or retranscribe
POST/api/listen/recordings/:id/summarizeSummarize or re-summarize a transcript
GET/api/sessionsList chat sessions
POST/api/sessionsCreate a session
GET/api/sessions/:idSession transcript and settings
PATCH/api/sessions/:idRename or update settings
DELETE/api/sessions/:idDelete a session
POST/api/sessions/:id/bootstrapAssemble-only bootstrap
POST/api/sessions/:id/turnChat turn (SSE stream)
POST/api/sessions/:id/turn/:turnId/cancelCancel an in-flight turn
POST/api/sessions/:id/turn/:turnId/ask-user/:requestIdAnswer askUser prompt
GET/api/sessions/:id/planActive plan summary
POST/api/sessions/:id/plan/skipSkip a plan phase
POST/api/sessions/:id/plan/cancelCancel the active plan
GET/api/personasPersona picker list
GET/api/personas/:namePersona detail
GET/api/personas/image/:filenamePersona image asset
GET/api/ai/providersAI provider catalog with live models when configured
GET/api/ai/providers/usagePlan usage / balance for all providers
GET/api/ai/providers/:id/usagePlan usage / balance for one provider
GET/api/modulesConnected chat modules
GET/api/skillsLocal skills list
GET/api/skills/:nameSkill body
GET/api/skills/:name/iconSkill icon
GET/api/pluginsDiscovered plugins
GET/api/plugins/:name/iconPlugin icon
GET/api/projectsList projects
POST/api/projectsCreate a project
GET / PATCH / DELETE/api/projects/:idOrSlugProject detail, update, delete
GET/api/projects/:idOrSlug/treeProject folder tree
POST/api/projects/:idOrSlug/sessionsCreate a project-scoped session
GET/api/releases/changelogChangelog payload
GET/api/memoriesSearch / page memories
POST/api/memoriesCreate a manual memory
GET/api/memories/:idOne memory
PATCH/api/memories/:idUpdate a memory
DELETE/api/memories/:idDelete a memory
GET/api/memories/:id/explainSource / audit explanation
GET/api/librarySearch / page library items
POST/api/libraryAdd a file (filename, mediaType, dataBase64)
GET / PATCH / DELETE/api/library/:idLibrary item detail, update, delete
GET/api/configure/treeFull configure tree + values
GET/api/configure/sectionsLightweight settings sections
GET/api/configure/sections/:sectionKeyOne section detail
PATCH/api/configure/valuesPersist configure changes
POST/api/configure/actions/:actionNamed configure actions
POST/api/flows/triggers/parse-cronParse cron / natural language
GET/api/flows/triggers/activity?flowId=Trigger decisions and run links
GET/api/integrations/:name/statusIntegration status
GET/api/integrations/:name/setup-guideSetup wizard steps
POST/api/integrations/:name/connectConnect
POST/api/integrations/:name/disconnectDisconnect
POST/api/integrations/:name/reauthorizeReauthorize
POST/api/integrations/:name/setupRun 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
  }
}
FieldMeaning
versionToby release version
executablePathAbsolute path of the binary or entry script
execKind"compiled" or "source"
tobyDirData directory for this process
entryScriptTS/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.

  • 409 if already recording
  • 500 if 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).

  • 400 invalid JSON
  • 409 no active recording
  • 500 finalization 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.

  • 404 if 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.

  • 400 invalid body
  • 404 not found

DELETE /api/listen/recordings/:id#

{ "ok": true }
  • 404 / 500 on 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.

  • 404 not found
  • 500 on 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.

  • 400 no readable audio
  • 404 not found
  • 500 transcription failure (error is also appended to recording metadata)

Sessions#

GET /api/sessions#

QueryDefaultMaxDescription
limit50500Max 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: true runs 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;
};
  • 404 if 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 / dryRun override session settings for this turn
  • clientTurnId is optional correlation / idempotency
  • steering: true may cancel an active turn before submitting
  • generatePlan may 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;
};
  • 404 if 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#

QueryDefaultDescription
q—Search query (keywords and meaning)
limit50 (max 500)Page size
offset0Skip 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#

QueryDefaultDescription
q—Search query (keywords and meaning)
originallall, manual, chat, or project
refreshomit1 scans chats and projects for files not yet cataloged
limit50 (max 500)Page size
offset0Skip 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.

  • 400 if changes missing
  • 403 if the patch cannot be applied

POST /api/configure/actions/:action#

Body: Record<string, string>.

ActionRequest fieldsResponse
create-persona—{ "ok": true, "personaName": string }
delete-personapersonaName{ "ok": true }
set-default-personapersonaName{ "ok": true }
clear-default-persona—{ "ok": true }
update-skill-fielddirName, field, value{ "ok": true }
delete-skilldirName{ "ok": true }
create-schedule—{ "ok": true, "scheduleId": string }
update-schedule-fieldscheduleId, field, value{ "ok": true }
delete-schedulescheduleId{ "ok": true }
  • update-skill-field.field: name, summary, description, or enabled. summary and the compatibility alias description update the same required summary stored in SKILL.md frontmatter as description.
  • 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#

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.