Skip to main content
POST
/
v1
/
admin
/
control-plane
/
workflow-monitors
/
*
Admin Control Plane Workflow Monitors
curl --request POST \
  --url 'https://api.glider.fi/v1/admin/control-plane/workflow-monitors/*' \
  --header 'X-API-KEY: <api-key>'
These routes are the first concrete migration seam from apps/engine workflow-monitor orchestration into Cloudflare Workflows. The ownership split is:
  • Cloudflare agent-control-plane owns due scanning cadence and durable workflow instances
  • platform-api owns the thin authenticated admin surface
  • WorkflowsFacade.tickMonitor(...) remains the deterministic shared mutation path

Scope

  • Internal-only
  • Protected by ADMIN_AUTH_SECRET_TOKEN
  • Intended for apps/agent-control-plane, not browser traffic

Endpoints

POST /v1/admin/control-plane/workflow-monitors/due

Load runnable monitor rows from the canonical workflow-monitor repository. Request body:
{
  "asOfIso": "2026-04-14T12:30:00.000Z",
  "limit": 100
}
Response:
{
  "asOfIso": "2026-04-14T12:30:00.000Z",
  "due": [
    {
      "monitorStatus": "waiting",
      "nextCheckAt": "2026-04-14T12:00:00.000Z",
      "ownerAddress": "0x1111111111111111111111111111111111111111",
      "requestId": "workflow-monitor-tick:wf_due_1:2026-04-14T12:30:00.000Z",
      "workerId": "cloudflare:workflow-monitor:wf_due_1",
      "workflowId": "wf_due_1",
      "workflowStatus": "active"
    }
  ]
}
Notes:
  • requestId is the stable idempotency key Cloudflare should preserve when creating its own workflow instance
  • workerId is the stable worker identity passed through to tickMonitor(...)
  • due selection stays authoritative in the shared workflow repository for now

POST /v1/admin/control-plane/workflow-monitors/tick

Execute one monitor tick through WorkflowsFacade.tickMonitor(...). Request body:
{
  "asOfIso": "2026-04-14T12:30:00.000Z",
  "ownerAddress": "0x1111111111111111111111111111111111111111",
  "requestId": "workflow-monitor-tick:wf_due_1:2026-04-14T12:30:00.000Z",
  "triggerEvent": {
    "kind": "market_price",
    "watchKey": "market.price:eth:8453:coinbase",
    "summary": "ETH crossed the configured threshold.",
    "dependencies": {
      "marketPrices": [
        {
          "assetId": "eth:8453",
          "usd": 1250,
          "venue": "coinbase"
        }
      ]
    }
  },
  "workerId": "cloudflare:workflow-monitor:wf_due_1",
  "workflowId": "wf_due_1"
}
triggerEvent is optional. The current accepted kinds are:
  • market_price
  • market_candles
  • approval_pending_count
  • account_buying_power_usd
  • account_cash_usd
  • portfolio_nav_usd
  • portfolio_cash_pct
  • portfolio_drift_from_target
  • portfolio_concentration
  • portfolio_held_asset_performance
  • portfolio_held_asset_metrics
Response:
{
  "ownerAddress": "0x1111111111111111111111111111111111111111",
  "requestId": "workflow-monitor-tick:wf_due_1:2026-04-14T12:30:00.000Z",
  "userId": "usr_123",
  "workerId": "cloudflare:workflow-monitor:wf_due_1",
  "workflow": {
    "workflowId": "wf_due_1",
    "title": "Monitor ETH",
    "status": "pending_approval",
    "kind": "monitor_then_action",
    "triggerKind": "authored_sdk",
    "linkedPortfolioIds": ["pf_123"],
    "linkedOperationIds": [],
    "nextRunAt": null,
    "lastRunAt": null
  },
  "currentExecution": null,
  "monitorState": {
    "status": "ready",
    "triggerSummary": "ETH crossed the configured threshold.",
    "readyAt": "2026-04-14T12:30:00.000Z",
    "startedAt": null,
    "retryAfter": null,
    "waitingUntil": null,
    "completedAt": null,
    "failedAt": null
  }
}
Notes:
  • userId is resolved from the canonical linked wallet address when available
  • Cloudflare uses userId plus linkedPortfolioIds[0] to mirror the monitor tick into UserAgent / PortfolioAgent
  • this route is intentionally thin; it does not invent a new monitor execution model

POST /v1/admin/control-plane/workflow-monitors/notify-execution

Validate a terminal execution ref and forward it into the Cloudflare-owned enqueue-tick seam. Request body:
{
  "asOfIso": "2026-04-14T18:10:00.000Z",
  "executionRequestId": "req_wf_due_1_step_1",
  "operationId": "op_wf_due_1_step_1",
  "runId": "run_wf_due_1_step_1"
}
Response:
{
  "asOfIso": "2026-04-14T18:10:00.000Z",
  "enqueued": true,
  "matchedExecution": {
    "operationId": "op_wf_due_1_step_1",
    "runId": "run_wf_due_1_step_1"
  },
  "ownerAddress": "0x1111111111111111111111111111111111111111",
  "portfolioId": "pf_123",
  "requestId": "workflow-monitor-execution:wf_due_1:2026-04-14T18:10:00.000Z",
  "workerId": "cloudflare:workflow-monitor-execution:wf_due_1",
  "workflowId": "wf_due_1"
}
Notes:
  • this route is the event-driven companion to due plus tick
  • it can resolve the owning workflow from canonical workflow_execution_refs using operationId, runId, executionRequestId, and eventId
  • it also resolves the primary linked portfolioId so the Cloudflare worker can look up an already waiting monitor session on PortfolioAgent
  • if no execution ref matches, it returns 202 with enqueued: false instead of treating the callback as an error
  • it forwards into POST /api/workflow-monitors/enqueue-tick on the Cloudflare worker instead of mutating workflow state locally

POST /v1/admin/control-plane/workflow-monitors/notify-trigger-match

Forward a centralized trigger-match event directly into the Cloudflare-owned monitor workflow lane without going through due. Request body:
{
  "asOfIso": "2026-04-14T12:31:00.000Z",
  "ownerAddress": "0x1111111111111111111111111111111111111111",
  "triggerSummary": "ETH crossed the configured threshold.",
  "workflowId": "wf_due_1"
}
Response:
{
  "asOfIso": "2026-04-14T12:31:00.000Z",
  "enqueued": true,
  "ownerAddress": "0x1111111111111111111111111111111111111111",
  "portfolioId": "pf_123",
  "requestId": "workflow-monitor-trigger:wf_due_1:2026-04-14T12:31:00.000Z",
  "triggerSummary": "ETH crossed the configured threshold.",
  "workerId": "cloudflare:workflow-monitor-trigger:wf_due_1",
  "workflowId": "wf_due_1"
}
Notes:
  • this is the first explicit ingress for a future centralized trigger matcher
  • platform-api still validates the workflow and owner via WorkflowsFacade.get(...)
  • the optional triggerEvent payload now supports authoritative fact packets for market_price, market_candles, approval_pending_count, account_buying_power_usd, account_cash_usd, portfolio_nav_usd, portfolio_cash_pct, portfolio_drift_from_target, portfolio_concentration, portfolio_held_asset_performance, and portfolio_held_asset_metrics
  • it resolves the primary linked portfolioId so Cloudflare can mirror the resulting monitor workflow state into PortfolioAgent / UserAgent
  • today it forwards into the same POST /api/workflow-monitors/enqueue-tick seam using reason: "trigger_match"
  • this is intentionally narrower than the future global matcher fabric; it is just the authenticated trigger-delivery seam

POST /v1/admin/control-plane/workflow-monitors/recover

Replay a stale or orphaned monitor wait through the Cloudflare-owned runtime. Request body:
{
  "asOfIso": "2026-04-14T12:45:00.000Z",
  "ownerAddress": "0x1111111111111111111111111111111111111111",
  "portfolioId": "pf_123",
  "workflowId": "wf_due_1"
}
Response:
{
  "asOfIso": "2026-04-14T12:45:00.000Z",
  "enqueued": true,
  "ownerAddress": "0x1111111111111111111111111111111111111111",
  "portfolioId": "pf_123",
  "requestId": "workflow-monitor-recovery:wf_due_1:2026-04-14T12:45:00.000Z",
  "workerId": "cloudflare:workflow-monitor-recovery:wf_due_1",
  "workflowId": "wf_due_1"
}
Notes:
  • this is an operator/admin replay surface, not a normal callback path
  • Cloudflare recovery is intentionally narrow and safe:
    • replay an active execution_completed or trigger_matched wait on the same workflow instance when possible
    • start a fresh authoritative tick only when no active instance is registered
    • fail closed when the active workflow is only sleeping on time instead of waiting on an event
  • optional triggerEvent fact packets can be supplied when recovery should replay a matcher-driven wake with authoritative dependency data

Runtime Behavior

The current flow is:
  1. Cloudflare calls due
  2. Cloudflare creates WorkflowMonitorTickWorkflow instances with the returned ids
  3. Each Cloudflare workflow calls tick
  4. platform-api delegates to WorkflowsFacade.tickMonitor(...)
  5. Cloudflare mirrors the resulting state into durable user/portfolio actors
The current event-driven companion flow is:
  1. engine persists a terminal execution update with canonical execution refs
  2. engine calls notify-execution
  3. platform-api resolves the owning workflow from canonical execution refs
  4. platform-api forwards into Cloudflare enqueue-tick with the linked portfolioId
  5. Cloudflare tries to route the callback directly into the active waiting WorkflowMonitorTickWorkflow session via sendEvent("execution_completed")
  6. if no active waiting session is registered, Cloudflare falls back to the previous create -> tick path
  7. the workflow calls tick
  8. Cloudflare mirrors the resulting state into durable user/portfolio actors
The current trigger-ingress companion flow is:
  1. a centralized matcher or other authoritative producer detects a match
  2. it calls notify-trigger-match
  3. platform-api validates the workflow and owner
  4. platform-api forwards into Cloudflare enqueue-tick with reason: "trigger_match"
  5. Cloudflare starts the durable WorkflowMonitorTickWorkflow without waiting for a global due scan
  6. the workflow calls tick
  7. Cloudflare mirrors the resulting state into durable user/portfolio actors
This keeps the durable orchestration move in Cloudflare while preserving the shared workflow facade as the mutation authority during migration.