LiyaEngine Docs
API Reference

Workflows

GET/POST /v1/workflows, GET/PATCH/DELETE /v1/workflows/{workflowIdOrKey}, plus toggle, deploy, webhook secret rotate, run, and run history — multi-step automations via API key.

Dashboard

Multi-step automations via API key — chains of intents, agent runs, tool calls, and conditions. This is the same resource as the dashboard's Workflow Builder and the same underlying service — creating or deploying a workflow here shows up in the dashboard immediately, and vice versa.

Every {workflowIdOrKey} below accepts either the database id or the human-readable workflow_key.

List workflows

GET https://api.liyaengine.ai/v1/workflows
Authorization: Bearer liya_xxxxxxxxxxxx

Returns only this tenant's deployed and active workflows, each with its callable endpoint. Drafts and toggled-off workflows stay dashboard-only.

{
  "success": true,
  "data": [
    {
      "id": "wf_01HZ...",
      "name": "Lead Intake",
      "workflow_key": "lead-intake",
      "status": "active",
      "is_active": true,
      "trigger_type": "webhook",
      "trigger_config": { "slug": "a1b2c3...", "has_secret": true },
      "steps": [ "..." ],
      "endpoint": "/v1/workflows/lead-intake/run",
      "method": "POST"
    }
  ]
}

Create a workflow

POST https://api.liyaengine.ai/v1/workflows
Authorization: Bearer liya_xxxxxxxxxxxx
Content-Type: application/json

{
  "name": "Lead Intake",
  "steps": [
    { "step_type": "trigger", "config": { "trigger_subtype": "webhook" } },
    { "step_type": "ai_intent", "config": { "intent_id": "intent_01HZ..." } }
  ]
}

Created in draft status with an auto-generated workflow_key — use the deploy endpoint below to publish it. Returns 201 with { "data": { "workflow": {...} } }.

step_type is one of trigger, action, condition, switch, delay, loop, ai_intent, ai_agent, approval, end. switch, delay and loop are reserved and don't run yet. Set a schedule or event trigger on the trigger step's config — see Triggers. Give a step an id (any client-chosen string) and reference it from another step's on_success_ref/on_failure_ref to wire up branching — both get resolved to real step ids server-side.

Get a workflow

GET https://api.liyaengine.ai/v1/workflows/{workflowIdOrKey}
Authorization: Bearer liya_xxxxxxxxxxxx

Returns the full workflow definition and its steps. Only a deployed, active workflow is visible here — a draft 404s until you deploy it.

Update a workflow

PATCH https://api.liyaengine.ai/v1/workflows/{workflowIdOrKey}
Authorization: Bearer liya_xxxxxxxxxxxx
Content-Type: application/json

{
  "name": "Lead Intake v2",
  "steps": [ "..." ]
}

All fields optional — only what you pass gets changed. steps is upsert-by-id: include a step's existing id to edit it in place, omit id for a new step, and leave out an existing step's id entirely to delete it. workflow_key can be changed here too (409 WORKFLOW_KEY_TAKEN if already in use).

On a live workflow, changes to steps take effect immediately unless you send "as_draft": true. With it, the change is kept as unpublished changes: live runs keep using the published steps until you call deploy, which validates the draft and makes it live as a new version. DELETE /v1/workflows/{workflowIdOrKey}/draft throws the unpublished changes away (409 NO_DRAFT if there are none). The dashboard editor always saves this way.

Toggle a workflow

PATCH https://api.liyaengine.ai/v1/workflows/{workflowIdOrKey}/toggle
Authorization: Bearer liya_xxxxxxxxxxxx

Flips is_active on an already-deployed workflow — a pure runtime on/off switch that doesn't touch its definition. Returns 409 DEPLOY_REQUIRED on a draft; deploy it first.

Deploy a workflow

POST https://api.liyaengine.ai/v1/workflows/{workflowIdOrKey}/deploy
Authorization: Bearer liya_xxxxxxxxxxxx

The draft → active transition — a deliberate, separately audited step distinct from toggle. For a webhook-triggered workflow, the first deploy also mints the webhook endpoint and signing secret:

{
  "success": true,
  "data": {
    "workflow": { "...": "..." },
    "webhook_url": "https://api.liyaengine.ai/webhooks/workflows/a1b2c3...",
    "webhook_secret": "8f3e9c2a1b7d4f0e..."
  }
}

webhook_url/webhook_secret are only present when a webhook is newly minted (the first deploy of a webhook-triggered workflow). webhook_secret is shown here exactly once — store it immediately. Every later GET/list response only ever exposes trigger_config.has_secret: true, never the plaintext secret. Redeploying an already-deployed workflow does not regenerate it.

Rotate the webhook secret

POST https://api.liyaengine.ai/v1/workflows/{workflowIdOrKey}/webhook-secret/rotate
Authorization: Bearer liya_xxxxxxxxxxxx
Content-Type: application/json

{ "grace_period_seconds": 300 }

Only valid for a deployed, webhook-triggered workflow (409 NOT_A_WEBHOOK_WORKFLOW otherwise). The previous secret stays valid for grace_period_seconds (0–3600, default 300) so in-flight senders don't break mid-rollover; pass 0 for an immediate hard cutover.

{
  "success": true,
  "data": {
    "webhook_url": "https://api.liyaengine.ai/webhooks/workflows/a1b2c3...",
    "webhook_secret": "1a2b3c4d5e6f7g8h...",
    "previous_secret_valid_until": "2026-09-18T23:25:09.618Z"
  }
}

The new webhook_secret is returned exactly once here too — same rule as deploy.

Delete a workflow

DELETE https://api.liyaengine.ai/v1/workflows/{workflowIdOrKey}
Authorization: Bearer liya_xxxxxxxxxxxx

A hard delete — unlike Collections and Agents, there's no soft-delete/inactive state to recover from. Returns { "success": true } with no data.

Run a workflow

POST https://api.liyaengine.ai/v1/workflows/{workflowIdOrKey}/run
Authorization: Bearer liya_xxxxxxxxxxxx
Content-Type: application/json

{ "input": { "email": "[email protected]" } }
{
  "success": true,
  "data": {
    "run_id": "run_01HZ...",
    "conversation_id": "convo_01HZ...",
    "status": "completed",
    "trace": [ "..." ]
  }
}

A synchronous, authenticated counterpart to the async webhook trigger — same execution engine, request/response instead of fire-and-forget. Only an active workflow can be run. Pass a previously-returned conversation_id to resume a paused (needs_input) run instead of starting fresh. A run that reaches an approval step returns status: "awaiting_approval" and an approval_id; decide it with POST /v1/workflows/approvals/{approvalId}/decision to resume the same run (see Approval steps). A run that reaches a delay step returns status: "waiting" and a resume_at time, and continues on its own (see Switch, delay and loop). Send an Idempotency-Key header and a repeated call returns the first run (idempotent_replay: true) instead of starting another; a workflow at its concurrent-run limit answers 429 CONCURRENCY_LIMIT with Retry-After (see Concurrency and repeated calls). A run waiting at a Wait for callback step is waiting too; the other system continues it with POST /webhooks/workflow-callbacks/{token} (the address is {{run.callback_url}} inside the workflow). Set max_concurrent_runs with PATCH /v1/workflows/{workflow}. Run statuses: running, completed, needs_input, awaiting_approval, waiting, rejected, cancelled, failed. A failed run that saved a step can be continued with POST /v1/workflows/{workflowId}/runs/{runId}/retry, and a waiting one stopped with POST /v1/workflows/{workflowId}/runs/{runId}/cancel; a run's detail carries can_retry and can_cancel (see Failed and waiting runs).

Files in a run

Put files in input.attachments as { "file_name", "file_base64" } — up to 10 files, 15 MB each, 18 MB per run. Before the run starts, each one is read the same way as a knowledge upload: images and scanned PDF pages are transcribed through your model routing on paid plans. Each entry is replaced by { file_name, format, pages, text, warnings }, and the file itself is never stored. The run input also gets two text fields:

  • attachment_texts: one string per file.
  • attachments_text: every file under a ## File: <name> heading. Map it into an intent step with {{trigger.attachments_text}}.
{
  "input": {
    "email_text": "Please quote the attached risk.",
    "attachments": [
      { "file_name": "acord-125.pdf", "file_base64": "JVBERi0xLjcK..." },
      { "file_name": "sov.xlsx", "file_base64": "UEsDBBQABgAI..." }
    ]
  }
}

The webhook trigger, /run/stream and the builder's Test run (via Attach files) accept the same field. Entries of attachments that aren't { file_name, file_base64 } are passed through unchanged. If a file can't be read, the run doesn't start: the response is 400 for an unsupported type, 413 if it's too large, or 422 if the file can't be read or needs transcription your workspace doesn't have.

In input mappings, a path to an object, such as {{extract.output}}, resolves to that object as JSON. Use it to hand a whole step result to the next intent.

Saved values (workflow state)

GET    https://api.liyaengine.ai/v1/workflows/{workflowIdOrKey}/state
DELETE https://api.liyaengine.ai/v1/workflows/{workflowIdOrKey}/state/{key}
Authorization: Bearer liya_xxxxxxxxxxxx

The values a workflow's State steps keep between runs (see State). GET returns { "scope": "live", "entries": [{ "key", "value", "expires_at", "updated_at" }] }, without values that have expired; add ?scope=test for the separate copy the editor's test runs use. DELETE removes one key (for example to reset a counter) and returns 404 STATE_NOT_FOUND if nothing is kept under it. Deletes are recorded in the audit log.

Run history

GET https://api.liyaengine.ai/v1/workflows/{workflowIdOrKey}/runs
GET https://api.liyaengine.ai/v1/workflows/{workflowIdOrKey}/runs/{runId}

runs is a paginated list (page, pageSize, status query params); runs/{runId} returns one run's full step-by-step trace.

Using an SDK

import { LiyaEngine } from '@liyaengine/sdk';

const client = new LiyaEngine({ apiKey: process.env.LIYA_API_KEY! });

const workflow = await client.workflows.create({
  name: 'Lead Intake',
  steps: [{ step_type: 'trigger', config: { trigger_subtype: 'webhook' } }],
});

// Capture webhook_secret immediately — it's never returned again.
const { webhook_url, webhook_secret } = await client.workflows.deploy(workflow.workflow_key);

const result = await client.workflows.run(workflow.workflow_key, {
  input: { email: '[email protected]' },
});

On this page