📘 Public beta · Endpoints are stable; OpenAPI specs and SDKs ship monthly. See changelog →
Products
AI Automation
Quickstart

AI Automation · Quickstart

Define a flow with an LLM classification step, trigger it, watch it run.

Time: ~15 minutes.

Prerequisites

Sandbox API key with scopes: flows:write, runs:write.

1. Create a flow

POST/api/flows
Auth · API keyScope · flows:write
{
  "slug": "ticket-triage-v1",
  "name": "Ticket triage",
  "description": "Classify and route a customer-support ticket using Quantum AI",
  "spec": {
    "startNodeId": "classify",
    "nodes": [
      {
        "id": "classify",
        "type": "llm_step",
        "model": "quantum-ai:default",
        "systemPrompt": "You are a triage assistant for an Indonesian bank's customer-support inbox. Classify the user's message into one of: account_access, transaction_dispute, card_request, general_inquiry, complaint. Also rate urgency 1-5 and write a one-sentence summary.",
        "userPrompt": "Ticket subject: ${trigger.subject}\\nTicket body: ${trigger.body}",
        "outputSchema": {
          "type": "object",
          "properties": {
            "category":  { "type": "string", "enum": ["account_access", "transaction_dispute", "card_request", "general_inquiry", "complaint"] },
            "urgency":   { "type": "integer", "minimum": 1, "maximum": 5 },
            "summary":   { "type": "string" }
          },
          "required": ["category", "urgency", "summary"]
        }
      },
      {
        "id": "route",
        "type": "decision",
        "expression": "step.classify.output.urgency",
        "cases": {
          "5":  { "next": "page_oncall" },
          "4":  { "next": "page_oncall" }
        },
        "defaultCase": "tag_ticket"
      },
      {
        "id": "page_oncall",
        "type": "http_sink",
        "url": "https://your-pager.example.com/page",
        "body": {
          "title": "Urgent ticket: ${step.classify.output.summary}",
          "category": "${step.classify.output.category}"
        }
      },
      {
        "id": "tag_ticket",
        "type": "service_call",
        "service": "custom:crm",
        "method": "PATCH",
        "path": "/api/tickets/${trigger.ticketId}",
        "body": {
          "category":   "${step.classify.output.category}",
          "urgency":    "${step.classify.output.urgency}",
          "ai_summary": "${step.classify.output.summary}"
        }
      }
    ],
    "edges": [
      { "from": "classify",      "to": "route" },
      { "from": "route",         "to": "page_oncall", "case": "5" },
      { "from": "route",         "to": "page_oncall", "case": "4" },
      { "from": "route",         "to": "tag_ticket" }
    ]
  },
  "triggerType": "api",
  "status": "active"
}

Response (201):

{
  "data": {
    "flow": {
      "id": "flw_01HXY...",
      "slug": "ticket-triage-v1",
      "name": "Ticket triage",
      "status": "active",
      "triggerType": "api",
      "version": 1,
      "webhookToken": "wht_...",
      "webhookSecret": "qe_whsec_...",
      "createdAt": "..."
    }
  }
}

webhookToken and webhookSecret are populated even for triggerType: api — you can switch trigger modes later without re-creating.

2. Trigger a run

POST/api/flows/{id}/execute
Auth · API keyScope · runs:write
curl -X POST .../api/flows/ticket-triage-v1/execute \
  -H "Authorization: Bearer $QE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "ticketId": "tkt_01HXY...",
    "subject": "Saldo saya berkurang tanpa transaksi",
    "body":    "Halo, pagi ini saya cek aplikasi dan saldo saya berkurang Rp 5 juta. Tapi saya tidak ingat melakukan transfer apapun. Tolong cek!"
  }'

Response (202):

{
  "data": {
    "run": {
      "id": "run_01HXY...",
      "flowId": "flw_01HXY...",
      "status": "pending",
      "trigger": "manual",
      "createdAt": "..."
    }
  }
}

3. Watch it run

GET/api/runs/{runId}
Auth · API keyScope · runs:read
curl .../api/runs/run_01HXY... \
  -H "Authorization: Bearer $QE_API_KEY"
{
  "data": {
    "flow":  { "id": "flw_01HXY...", "name": "Ticket triage", "slug": "ticket-triage-v1" },
    "run": {
      "id": "run_01HXY...",
      "flowId": "flw_01HXY...",
      "status": "succeeded",
      "trigger": "manual",
      "input": { ... },
      "steps": [
        {
          "id": "stp_01HXY...",
          "nodeId": "classify",
          "type": "llm_step",
          "status": "succeeded",
          "output": {
            "text": "{\"category\":\"transaction_dispute\",\"urgency\":5,\"summary\":\"Customer reports unauthorized IDR 5M debit; needs immediate fraud investigation.\"}",
            "parsed": {
              "category": "transaction_dispute",
              "urgency": 5,
              "summary": "Customer reports unauthorized IDR 5M debit; needs immediate fraud investigation."
            },
            "model": "quantum-ai:default",
            "inferenceMs": 1250
          }
        },
        { "id": "stp_02HXZ...", "nodeId": "route",       "type": "decision",  "status": "succeeded", "output": { "selected": "5" } },
        { "id": "stp_03HXA...", "nodeId": "page_oncall", "type": "http_sink", "status": "succeeded", "output": { "statusCode": 200 } }
      ],
      "finishedAt": "..."
    }
  }
}

Notice step.classify.output.parsed — the LLM's response was schema-validated and parsed for you. Downstream nodes use the parsed fields.

4. Convert to a webhook-triggered flow

If you want your CRM to fire a flow when a new ticket arrives, switch triggerType:

curl -X PATCH .../api/flows/ticket-triage-v1 \
  -H "Authorization: Bearer $QE_API_KEY" \
  -d '{ "triggerType": "webhook" }'

Now your CRM can POST to:

POST https://sandbox.quantumelixir.tech/ai-automation/api/triggers/wh/wht_...
X-Quantum-Signature: sha256=<hmac of body with webhookSecret>
Content-Type: application/json

{ "ticketId": "...", "subject": "...", "body": "..." }

See Triggers → for the signature scheme.

5. Convert to a scheduled flow

For "every 15 minutes, fetch new tickets and triage":

curl -X PATCH .../api/flows/ticket-triage-v1 \
  -d '{
    "triggerType": "cron",
    "cronExpression": "*/15 * * * *",
    "cronEnabled": true
  }'

GET /api/cron-preview?expression=*/15+*+*+*+* previews the next N fire times.

LLM cost is logged per step

Every llm_step records token usage on the step's output.tokens field. Aggregate these across runs for a per-flow cost dashboard. The dashboard's Billing → Usage page does this automatically.

Next steps