API
The Jimothy UI talks to the engine through one typed API (FactoryApi). In the desktop app it’s exposed to the renderer as window.factory over Electron IPC (one channel per method, factory:<method>). With Remote access on, the same methods are available over HTTP.
This isn’t a versioned public API; it can change between releases. It’s documented here so you can script Jimothy from your tailnet and understand what the remote UI can do.
Methods
Section titled “Methods”| Method | Description |
|---|---|
snapshot(): Snapshot |
Everything the UI needs: settings, projects, pipelines, accounts, triggers and their state, harnesses and detection status, channels, the latest 300 runs (without outputs), the latest 100 inbox items, data folder, version, webhook URL and remote status |
listRuns(filter?: { status?, pipelineId?, search?, limit? }): Run[] |
Runs, newest first (default limit 500), without step outputs and prompts |
getRun(id): Run | undefined |
One run with everything, including outputs, rendered prompts and the pipeline snapshot |
startRun({ pipelineId, issue?, variables? }): Run |
Start a run. issue needs at least title; key, description, url, labels, priority, source are optional. variables override pipeline variables. |
cancelRun(id) |
Cancel a queued or running run |
retryRun(id, fromStepId?): Run |
Re-queue a finished run, optionally from a step |
deleteRun(id) |
Delete a finished run and its logs |
approveStep(runId, stepId, approved: boolean, comment?, selected?: string[]) |
Approve or reject a waiting approval step. For a step with choices, approving needs selected: the option ids (numbers as strings) to pick, exactly one for single mode. Unknown ids are ignored. |
readStepLog(runId, stepId, attempt?): LogLine[] |
A step attempt’s log (default: the latest attempt) |
readRunLog(runId): LogLine[] |
The run’s setup log |
Projects and pipelines
Section titled “Projects and pipelines”| Method | Description |
|---|---|
saveProject(project): Project |
Create (empty id) or update a project |
deleteProject(id) |
Fails while the project still has pipelines |
savePipeline(pipeline): Pipeline |
Create or update; validates the pipeline first |
createPipelineFromTemplate(templateId, projectId?): Pipeline |
Templates: demo, feature, bugfix, blank |
duplicatePipeline(id): Pipeline |
Copy named … (copy) |
deletePipeline(id) |
Fails while a trigger uses it |
exportPipeline(id): string |
JSON with "jimothyPipeline": 1 |
importPipeline(json): Pipeline |
Import an export |
generatePipeline({ description, harnessId, model? }): Pipeline |
Have an agent design a pipeline (Build with AI) |
cancelPipelineGeneration() |
Stop it |
Accounts and triggers
Section titled “Accounts and triggers”| Method | Description |
|---|---|
saveConnection(connection): Connection |
Sign in / update an account (verified first) |
deleteConnection(id) |
Fails while triggers use it |
testConnection(connection): { ok, error?, account? } |
Verify credentials without saving |
saveTrigger(trigger): TriggerConfig |
Create or update a trigger |
deleteTrigger(id) |
Delete a trigger and its dedup state |
testTrigger(trigger): { ok, error?, issues } |
Preview matching issues / Validate |
pollTrigger(id): TriggerState |
Check now |
resetTrigger(id) |
Forget processed issues |
Harnesses
Section titled “Harnesses”| Method | Description |
|---|---|
saveHarness(harness): HarnessDefinition |
Create or update (built-ins are stored as overrides) |
deleteHarness(id) |
Delete a custom harness, or reset a built-in |
setHarnessEnabled(id, enabled) |
Enable / disable |
detectHarnesses(): Record<string, HarnessStatus> |
Re-detect |
listModels(harness): string[] |
Fetch models for an (unsaved) harness definition |
refreshModels(id): HarnessDefinition |
Fetch and save a harness’s models |
generateText({ target, instructions, harnessId, model?, current?, pipeline?, stepId?, requestId? }) |
Generate with AI for a prompt, system prompt or command |
cancelGenerate(requestId) |
Cancel it |
Notifications
Section titled “Notifications”| Method | Description |
|---|---|
saveChannel(channel) / deleteChannel(id) |
Manage channels |
testChannel(channel): { ok, error? } |
Send test |
markInboxRead(ids?) |
Mark items (or all) read |
clearInbox() |
Clear the inbox |
Settings and system
Section titled “Settings and system”| Method | Description |
|---|---|
saveSettings(settings): Settings |
Save settings (restarts the webhook server / remote access if needed) |
pickDirectory() |
Native folder picker — desktop only |
openExternal(url) |
Open an http(s) URL — desktop only (the remote UI opens links locally) |
openPath(path) |
Reveal a folder — desktop only |
openInEditor(path) |
Open in code, then cursor, else the file manager — desktop only |
remotePairing(): { url, token, qrSvg } |
Sign-in link and QR code — desktop only |
resetRemoteToken() |
Sign out all devices — desktop only |
platform is 'darwin', 'linux', or 'web' in the remote UI.
Events
Section titled “Events”on(event, callback) subscribes to pushes from the engine and returns an unsubscribe function.
| Event | Payload |
|---|---|
run |
A run that changed (coalesced, about every 60 ms) |
run-deleted |
The id of a deleted run |
logs |
A batch of { runId, stepId, attempt, line } log events (about every 80 ms) |
inbox |
A new inbox item |
snapshot |
A fresh snapshot after configuration or trigger state changed |
navigate |
A path to show (tray menu, notification clicks) — desktop only |
Remote HTTP API
Section titled “Remote HTTP API”With remote access on, every method except the desktop-only ones can be called over HTTP at the remote address.
Authentication: the session cookie from pairing, or Authorization: Bearer <token> (the token from the sign-in link, …/pair?t=<token>).
Calls: POST /api/call/<method> with Content-Type: application/json and a JSON array of arguments. Use null for omitted optional arguments.
| Status | Body |
|---|---|
| 200 | {"result": …} |
| 400 | {"error": "…"} — the method threw, or the body wasn’t an array |
| 401 | {"error": "Not signed in"} |
| 403 | {"error": "<method> is only available on the desktop"} |
| 415 | {"error": "Expected JSON"} |
Events: GET /api/events is a server-sent events stream with the events above (except navigate), each as event: <name> + data: <json>, plus a : ping comment every 20 seconds.
Examples from a machine on your tailnet:
BASE=https://my-mac.tail1234.ts.net:7443TOKEN=… # from the sign-in link
# Start a runcurl -s "$BASE/api/call/startRun" \ -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ -d '[{"pipelineId":"pl_0mfz…","issue":{"title":"Bump dependencies","key":"OPS-12"}}]'
# Approve a waiting stepcurl -s "$BASE/api/call/approveStep" \ -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ -d '["run_0mfz…","approve",true,"LGTM"]'
# Approve a step with choices, picking options 1 and 3curl -s "$BASE/api/call/approveStep" \ -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ -d '["run_0mfz…","pick",true,"Keep them short",["1","3"]]'
# Follow live eventscurl -N "$BASE/api/events" -H "Authorization: Bearer $TOKEN"For anything reachable from outside your tailnet, use a webhook trigger instead.