Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

REST API: Flows

Flow CRUD, deploy, tests, versions, presence, deployment history.

All endpoints under /api/v1/flows. All require scaicore:view (read) or scaicore:manage (write) plus per-flow ACLs (the caller must hold the requested access level on the specific flow).

GET /v1/flows#

List flows visible to the caller (filtered by ACLs).

Query params:

  • search — substring filter on name.
  • limit — page size, default 100.

Response:

jsonc
{
  "items": [
    {
      "id": "flow_abc123",
      "name": "Customer Support",
      "description": "Triage + KB + HITL",
      "owner_id": "usr_xxx",
      "tenant_id": "tnt_acme",
      "version": "1.0.2",
      "created_at": "2026-04-01T...",
      "updated_at": "2026-04-29T..."
    }
  ],
  "total": 1
}

POST /v1/flows#

Create a flow.

Body:

jsonc
{
  "name": "My Flow",
  "description": "Optional",
  "content": { /* full FlowGraph JSON */ }
}

Response: 201 Created with the full FlowDetail (summary + content).

Owner is the calling principal, implicit admin ACL.

GET /v1/flows/{flow_id}#

Read one flow with its content.

Response: FlowDetail — summary fields + the content (full FlowGraph JSON).

scaicore:view + view ACL on this flow.

PUT /v1/flows/{flow_id}#

Update a flow. Partial body — any field omitted is left unchanged.

Body:

jsonc
{
  "name": "Renamed flow",
  "description": "New description",
  "content": { /* new FlowGraph */ }   // optional; bumps version + writes a new storage key
}

If content is provided, the version is patch-bumped (1.0.21.0.3) and the new content is written to object storage at flows/{tenant}/{flow}/v{new_version}.flow.json. The old version's content stays in object storage (enables version history + diff).

If only metadata fields change, the version stays.

scaicore:manage + edit ACL.

DELETE /v1/flows/{flow_id}#

Soft-delete a flow. Sets deleted_at; content stays in object storage for audit/restore.

scaicore:manage + admin ACL.

POST /v1/flows/{flow_id}/deploy#

Compile the flow + deploy to ScaiGrid + optionally publish as chat model. See Concepts: Architecture for what happens.

Body:

jsonc
{
  "publish_as_model": true,        // overrides flow.config.publish_as_model if set
  "group_ids": ["grp_xxx"]         // overrides flow.config.model_visibility_group_ids
}

Both fields are optional; defaults come from the flow's config.

Response:

jsonc
{
  "core_id": "core_xxx",
  "published_slug": "scaicore/acme/my-flow",      // null if not published
  "published_model_id": "model_xxx",              // null if not published
  "scaiqueue_provisioning": {                     // null if no scaiqueue topology
    "scope_id": "sc_xxx",
    "scope_slug": "support",
    "queue_ids": { "review": "q_xxx" }
  }
}

scaicore:manage + deploy ACL.

Errors:

  • 412 missing_scaigrid_credentials — no ScaiGrid token available (caller has no JWT and no per-tenant API key is set).
  • 502 scaicore_compile_failed — flow doesn't compile.
  • 502 scaiqueue_provision_failed — pre-deploy ScaiQueue provisioning failed.
  • 502 scaicore_create_failed — ScaiGrid rejected the manifest.

POST /v1/flows/{flow_id}/run-tests#

Run the flow's tests[] fixtures against a deployed Core.

Body:

jsonc
{
  "core_id": "core_xxx",
  "name_filter": "Refund"          // optional substring filter
}

Response:

jsonc
{
  "results": [
    {
      "name": "Refund request",
      "ok": true,
      "expect": { "intent": "complaint" },
      "output": { "intent": "complaint", "urgency": "medium", ... },
      "error": null
    }
  ],
  "passed": 1,
  "failed": 0
}

scaicore:manage + deploy ACL.

GET /v1/flows/{flow_id}/versions#

List every historical version of the flow.

Response:

jsonc
[
  { "version": "1.0.3", "is_current": true, "last_modified": "2026-04-29..." },
  { "version": "1.0.2", "is_current": false, "last_modified": "2026-04-28..." },
  { "version": "1.0.1", "is_current": false, "last_modified": "..." }
]

scaicore:view + view ACL.

GET /v1/flows/{flow_id}/versions/{version}#

Fetch a specific historical version's content.

Response: FlowDetail with the historical content.

POST /v1/flows/{flow_id}/presence/heartbeat#

Used by the canvas to announce "I'm editing this flow right now". Stored in-memory with a 30-second TTL. The toolbar's presence-avatar row reads from GET /v1/flows/{id}/presence.

Body:

jsonc
{ "display_name": "Alice", "color": "#a4ff32" }

Response: 204 No Content.

GET /v1/flows/{flow_id}/presence#

List currently-active editors (heartbeat within the last 30 s), excluding the calling user.

Response:

jsonc
{
  "peers": [
    { "user_id": "usr_xxx", "display_name": "Bob",
      "color": "#3284ff", "last_seen_ago_s": 4 }
  ]
}

DELETE /v1/flows/{flow_id}/presence#

Explicit "I'm leaving" — fires on beforeunload from the canvas. 204 No Content.

GET /v1/flows/{flow_id}/deployments#

List historical deploys of this flow.

Response:

jsonc
[
  { "core_id": "core_xxx",
    "deployed_at": "2026-04-29...",
    "deployed_by": "usr_xxx",
    "published_slug": "scaicore/acme/my-flow",
    "flow_version": "1.0.2" }
]

Used by the canvas Live Runs panel to map a flow → its core_id(s) so it can poll events for them.

WebSocket: /v1/flows/{flow_id}/sync#

CRDT relay. Binary Yjs protocol messages shuttled between connected peers in the same flow. Authenticated via the same access token (passed as a query param or subprotocol header — see the canvas implementation for the wire format).

Out of scope for typical REST consumers; the canvas uses it for real-time collaborative editing.

Updated 2026-05-18 16:05:27 View source (.md) rev 3