Skip to content

Tipeline Server — API Reference ​

The server exposes two separate APIs on the same port (4001 by default):

  • REST (HTTP) — session/user management, API tokens, webhooks, audit log, run history.
  • WebSocket — everything that talks to the Core engine itself: listing and running tasks, pipelines, variables, schedules, hubs, etc.

They share one authentication mechanism (a bearer token), but you'll use them for different things — see Which one do I need? below.

This is a different system from the CLI's remote add <name> <host> <token> command, which connects straight to Core's own WebSocket port using Core's own single static token — it never talks to this server at all. If you're scripting against the CLI, that token has nothing to do with anything on this page.

Contents ​

  1. Authentication
  2. Roles & scopes
  3. REST endpoints
  4. WebSocket gateway
  5. Rate limits & request limits
  6. Which one do I need?

1. Authentication ​

Every request (REST or WebSocket) authenticates the same way: an Authorization: Bearer <token> header. There are two kinds of token, and they're created differently:

Session tokenAPI token
Created byPOST /auth/login (+ /auth/login/mfa if MFA is on)A human, manually, in the Desktop app's API Tokens tab (or POST /tokens directly)
Who/what uses itA person using the Desktop appA script, CI pipeline, or other non-interactive caller
ExpiryNever, unless you set oneWhatever you chose at creation (30/90/180 days, or never)
RotatableNo — sign in again insteadYes (POST /tokens/:id/rotate)
RevocableYes — shows up in the Sessions tabYes — shows up in the API Tokens tab

Logging in ​

bash
curl -s -X POST http://localhost:4001/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"admin@example.com","password":"changeme"}'

Two possible responses:

jsonc
{ "token": "a1b2c3..." }                          // MFA not enabled — you're done
{ "mfaRequired": true, "challengeId": "f09e..." }   // MFA enabled — one more step

If you got mfaRequired, submit the 6-digit code from the authenticator app:

bash
curl -s -X POST http://localhost:4001/auth/login/mfa \
  -H "Content-Type: application/json" \
  -d '{"challengeId":"f09e...","code":"123456"}'
# → { "token": "a1b2c3..." }

Use that token on every subsequent request: -H "Authorization: Bearer $TOKEN".

Creating an API token (for scripts/CI) ​

Requires an existing session token (bootstrap once, by hand or via the Desktop app):

bash
curl -s -X POST http://localhost:4001/tokens \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $SESSION_TOKEN" \
  -d '{"name":"nightly-etl-runner","scope":"editor","expiresIn":"90d"}'
# → { "token": "f9e8d7...", "id": "...", "scope": "editor", "kind": "api", ... }

Copy token now — it's only ever returned once, at creation or rotation. The server stores only its SHA-256 hash and can't recover the raw value.


2. Roles & scopes ​

Three roles, each a strict superset of the next (defined in apps/server/src/auth/Permissions.ts):

RoleCan do
adminEverything below, plus manage members, tokens (anyone's), webhooks, security settings, view the audit log, and read credentials (who can see what)
editorView, run, and edit tasks; manage their own tokens
viewerView tasks only; manage their own tokens

A user's role is set on their account (/users). An API token's scope is set at creation and can never exceed the creating user's own role — an editor can mint a viewer-scoped token for a read-only integration, but not an admin-scoped one.

Scope is enforced identically for REST and WebSocket: a viewer-scoped token gets 403/Permission denied on anything beyond read-only calls (see §4 for the exact list over WS).


3. REST endpoints ​

All bodies/responses are JSON. All require Authorization: Bearer <token> except where noted.

Auth & sessions ​

Method & pathAuthNotes
POST /auth/login—{email, password} → {token} or {mfaRequired, challengeId}
POST /auth/login/mfa—{challengeId, code} → {token}
GET /auth/meanyReturns the calling user's own record
GET /auth/mfa/statusany{enabled: boolean} for the calling user
GET /auth/mfa/enrollmentadmin{enabled, total} — workspace-wide MFA adoption
POST /auth/mfa/enrollanyStarts enrollment → {secret, otpauthUrl}
POST /auth/mfa/confirmany{code} → {backupCodes: string[]}, shown once
POST /auth/mfa/disableanyDisables MFA on the calling user's own account

API tokens (/tokens) ​

Method & pathAuthNotes
GET /tokensanyOwn tokens, or everyone's if admin. Includes session tokens too (kind: "session" vs "api")
POST /tokensany{name, scope?, expiresIn?: "30d"|"90d"|"180d"|null} → always creates kind: "api"
POST /tokens/:id/rotateowner or adminNew secret, same record. Rejected for session tokens — sign in again instead
POST /tokens/:id/revokeowner or adminRevoking your own current session signs you out immediately
DELETE /tokens/:idowner or adminSame as revoke

Users (/users) — admin only ​

Method & pathNotes
GET /usersList all users
POST /users{email, name, password, role?} — role defaults to editor, must be admin|editor|viewer
PATCH /users/:id{name?, role?, is_active?} — admins can't change their own role away from admin or deactivate themselves
DELETE /users/:idSoft-deactivates (same as PATCH {is_active: 0})

Webhooks (/webhooks) — management is admin only; triggering needs no auth ​

Method & pathAuthNotes
GET /webhooksadminList all
POST /webhooksadmin{name, task_identifier, method?, variables_mapping?} → {webhook, token} (token shown once)
GET /webhooks/:idadmin
PATCH /webhooks/:idadmin{name?, task_identifier?, method?, enabled?, variables_mapping?}
DELETE /webhooks/:idadminHard delete
POST /webhooks/:id/rotateadminNew secret → {webhook, token}
POST /webhooks/trigger/:tokennone — the 64-hex token in the URL is the credentialRuns the mapped task. Request body → variables.payload; variables_mapping pulls specific fields out by path (e.g. "userId": "body.user.id"); query params → variables.query. Returns 202 immediately — the task runs async

Webhooks are the answer to "let an external system (GitHub, Stripe, a cron service) trigger one specific task" — see §6.

Task ownership & run history ​

Method & pathAuthNotes
GET /workflows/ownershipanyAll task ownership/visibility records
PATCH /workflows/:identifier/visibilityowner or admin{visibility: "shared"|"private"}
GET /pipeline-audit?limit=&offset=anyPast task runs: task_identifier, status (running|completed|error), triggered_by_type, timestamps. limit capped at 500
GET /audit-log?limit=&offset=&action=adminSecurity/admin activity: logins, token/user/webhook/session changes. Same pagination cap

Misc ​

Method & pathAuth
GET /healthnone (still rate-limited)

4. WebSocket gateway ​

This is how you actually run a task and watch it execute — there's no REST equivalent, because streaming live progress (onAction per step) doesn't fit a single request/response.

Connect, with the bearer token on the upgrade request (validated once, at connect time — not re-checked per message):

js
import WebSocket from 'ws';
const ws = new WebSocket('wss://your-server:4001', {
  headers: { Authorization: `Bearer ${TOKEN}` },
});

Send a command — every message needs an event name and a correlationId you choose, so you can match the response:

json
{ "event": "exec", "correlationId": "run-1", "payload": { "identifier": "nightly-etl", "variables": { "env": "prod" } } }

Receive events — same correlationId comes back, event name is on + the command, capitalized:

json
{ "event": "onTaskStart", "correlationId": "run-1" }
{ "event": "onAction", "correlationId": "run-1", "data": { "identifier": "step-1" } }
{ "event": "onTaskEnd", "correlationId": "run-1" }

(onTaskError instead of onTaskEnd on failure.)

Available commands ​

The full allowlist lives in apps/server/src/ws/WsGateway.ts (ALLOWED_COMMANDS, ~70 entries) — anything not on that list gets rejected with {"event":"error","data":{"message":"Unknown command: ..."}}. Grouped by area:

  • Tasks: taskList, taskDetail, taskNew, taskDuplicate, taskUpdateSource, taskUpdateMeta, taskValidate, taskInstall, taskExportAll, taskReload, taskUninstall, taskUploadImage
  • Execution: exec, debugNext, debugRun, debugPause, debugStop
  • Pipelines: pipelineList, pipelineDetail, pipelineStats, pipelineResume, pipelineClear, pipelineClearAll
  • Apps, Variables, Tags, Schedules, Plugins, Projects, Hubs, Connections: full CRUD per area (see source for exact names)
  • System: status, stop
  • Agent: agentPrompt, agentRunList, agentRunSetPipeline

Viewer-scoped tokens ​

A viewer-scoped connection can only call the read-only subset: taskList, taskDetail, pipelineList, pipelineDetail, pipelineStats, appList, appDetail, tableDataFetch, variableList, variableUsage, tagList, scheduleList, status, settingsList, settingsGetApiToken, pluginList, pluginDetail, pluginUsage, projectList, projectDetail, agentRunList, connectionList, connectionGetActive, hubList, hubCatalog. Everything else — including exec — gets {"event":"error","data":{"message":"Permission denied: viewers cannot run \"...\""}}.

Task-mutating commands (taskUpdateSource, taskUpdateMeta, taskUninstall, taskReload) are additionally gated by task ownership: a non-admin gets rejected if someone else owns that specific task, whether it's shared or private.

taskDetail, exec, taskValidate, taskDuplicate, and taskUploadImage are additionally gated by visibility: a non-owner, non-admin calling any of these on a task someone else has marked private gets a generic Task not found — not a permission error, so probing identifiers can't confirm a private task exists or who owns it. A shared task someone else owns can still be viewed/run by anyone (just not edited/reloaded/uninstalled).


5. Rate limits & request limits ​

LimitValueConfigurable via
General requests100/min per IPTIPELINE_RATE_LIMIT_MAX, TIPELINE_RATE_LIMIT_WINDOW_MS
/auth/login, /auth/login/mfa10/min per IPfixed
Request body size1 MBTIPELINE_MAX_BODY_BYTES
/pipeline-audit, /audit-log page size500 maxfixed (limit query param, defaults to 100)

Exceeding a rate limit returns 429 with a Retry-After header.


Which one do I need? ​

  • Building a UI or a tool a human watches live? WebSocket — you want the streamed onAction progress.
  • Want one specific external system (GitHub, Stripe, a cron host) to kick off one specific task when something happens over there? A webhook — no token management on their end, the URL itself is the credential, and it's already wired to one task.
  • Want a script/CI job to run a task on a schedule and check later whether it succeeded, without maintaining a live connection? An API token: WS to trigger (exec), then plain GET /pipeline-audit later to check status.
  • Want a single credential to dynamically pick which task to run at call time (a bot command, a dashboard)? Nothing built for this yet — see the design notes on the Server overview. Webhooks and scoped API tokens cover every need we've actually had so far; this would be new surface, not something that already exists under a different name.

Released under the Apache License 2.0.