Skip to content

Playbooks

Playbooks are operator-defined, multi-step workflows that automate sequences of beacon techniques. Each playbook contains an ordered list of steps -- action steps that enqueue C2 tasks, and conditional steps that branch based on live beacon metadata. When executed against a beacon, the playbook engine evaluates conditions in real time, selects the appropriate branch, enqueues all resulting tasks, and logs every decision for post-operation review.


Overview

A playbook solves the problem of repeatedly running the same sequence of post-exploitation techniques across multiple beacons. Instead of manually issuing commands one at a time, an operator defines a playbook once and executes it against any active beacon.

Key capabilities:

  • Ordered step execution -- Steps run in sequence by their order field
  • Conditional branching -- Steps can branch based on beacon properties (OS, hostname, elevated, architecture, etc.)
  • Nested conditions -- Conditional steps support if/then/else and multi-branch (switch-style) patterns, with arbitrary nesting depth
  • Execution logging -- Every decision (which branch was taken, what condition values were observed) is recorded
  • Full CRUD API -- Create, read, update, delete, and execute playbooks via REST
flowchart TD
    A[Operator triggers playbook] --> B[Fetch playbook steps]
    B --> C{Step type?}
    C -->|action| D[Map technique to C2 task]
    D --> E[Enqueue task for beacon]
    E --> F[Log action step]
    C -->|conditional| G[Evaluate condition against beacon state]
    G -->|if/then/else| H{Condition true?}
    H -->|yes| I[Execute then steps]
    H -->|no| J[Execute else steps]
    G -->|multi-branch| K{Evaluate branches in order}
    K -->|match| L[Execute matched branch steps]
    K -->|no match| M[Execute else steps or skip]
    I --> F
    J --> F
    L --> F
    M --> F
    F --> N{More steps?}
    N -->|yes| C
    N -->|no| O[Return execution ID + task IDs]

Playbook Object

A playbook is stored in the c2_playbooks table and returned as JSON from all API endpoints.

Field Type Description
id string (UUID) Unique playbook identifier
name string Display name
description string Optional description
steps PlaybookStep[] Ordered array of action and conditional steps
created_by string (UUID) Operator who created the playbook
created_at string (ISO 8601) Creation timestamp
updated_at string (ISO 8601) Last update timestamp

Step Types

Every step has a type field that determines its behavior. Steps without a type field default to "action" for backward compatibility.

Action Steps

An action step maps a technique_id to a C2 task and enqueues it for the target beacon. The params object is passed through to the technique mapper.

Field Type Required Description
type string No "action" (default if omitted)
technique_id string Yes Technique identifier (e.g., "exec.shell", "creds.hashdump", "privesc.getsystem")
params object No Technique-specific parameters
order integer Yes Execution order (lower runs first)
{
  "type": "action",
  "technique_id": "exec.shell",
  "params": { "command": "whoami /all" },
  "order": 1
}

Conditional Steps

A conditional step evaluates beacon metadata at execution time and branches to different sub-steps based on the result. Two patterns are supported:

  1. If/then/else -- A single condition with then and optional else branches
  2. Multi-branch (switch) -- An ordered list of named branches; the first matching branch wins, with an optional else fallback
Field Type Required Description
type string Yes Must be "conditional"
order integer Yes Execution order
conditional object Yes The conditional configuration (see below)

The conditional object:

Field Type Required Description
condition ConditionGroup Yes (if/then/else) Primary condition to evaluate
then PlaybookStep[] Yes (if/then/else) Steps to run if condition is true
else PlaybookStep[] No Steps to run if condition is false or no branch matches
branches ConditionalBranch[] No Multi-branch list (evaluated in order, first match wins)

Each ConditionalBranch:

Field Type Required Description
name string Yes Human-readable label (used in execution logs)
condition ConditionGroup Yes Branch condition
steps PlaybookStep[] Yes Steps to execute if this branch matches

Condition Engine

Conditions evaluate live beacon metadata at execution time. They are composed into groups using boolean combinators.

Condition Fields

The following beacon metadata fields can be referenced in conditions:

Field Type Description
elevated boolean Whether the beacon runs with elevated privileges
os string Operating system (e.g., "Windows 10 Pro 19045")
hostname string Machine hostname
username string Current user (e.g., "CORP\\jsmith")
pid integer Beacon process ID
arch string Architecture ("x64" or "x86")
sleep integer Current sleep interval in milliseconds
jitter integer Current jitter percentage
tags string[] Operator-assigned tags
ip string Beacon IP address
note string Operator note

Operators

Available comparison operators depend on the field type:

Operator Description
== Exact equality
!= Not equal
contains Substring match
not_contains Substring exclusion
matches Regular expression match
in Value is in a provided list
Operator Description
== Equal
!= Not equal
> Greater than
< Less than
>= Greater than or equal
<= Less than or equal
Operator Description
== Equal
!= Not equal
Operator Description
contains Slice contains value
not_contains Slice does not contain value

Condition Groups

Conditions are composed into groups with boolean combinators:

Group Operator Description
and All conditions and sub-groups must be true (default if omitted)
or At least one condition or sub-group must be true
not Negates exactly one condition or sub-group

Groups can be nested to build arbitrarily complex boolean expressions:

{
  "operator": "and",
  "conditions": [
    { "field": "elevated", "operator": "==", "value": false }
  ],
  "groups": [
    {
      "operator": "or",
      "conditions": [
        { "field": "os", "operator": "contains", "value": "Windows 10" },
        { "field": "os", "operator": "contains", "value": "Windows 11" }
      ]
    }
  ]
}

This evaluates to: beacon is not elevated AND (OS contains "Windows 10" OR OS contains "Windows 11").

Empty Groups

An empty condition group (no conditions, no sub-groups) evaluates to true (vacuous truth). The default group operator is "and" when omitted.


API Reference

All playbook endpoints are under the cockpit route group and require JWT authentication.

Base path: /api/v1/cockpit/playbooks

Create Playbook

POST /api/v1/cockpit/playbooks

Request Body:

Field Type Required Description
name string Yes Playbook name
description string No Description
steps PlaybookStep[] Yes Array of step objects
curl -s -X POST https://stentor.app/api/v1/cockpit/playbooks \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Initial Recon",
    "description": "Basic host reconnaissance playbook",
    "steps": [
      {
        "type": "action",
        "technique_id": "exec.shell",
        "params": { "command": "whoami /all" },
        "order": 1
      },
      {
        "type": "action",
        "technique_id": "exec.shell",
        "params": { "command": "ipconfig /all" },
        "order": 2
      },
      {
        "type": "action",
        "technique_id": "exec.shell",
        "params": { "command": "net localgroup administrators" },
        "order": 3
      }
    ]
  }'

Response (201 Created):

{
  "id": "c3d4e5f6-a1b2-7890-abcd-ef1234567890",
  "name": "Initial Recon",
  "description": "Basic host reconnaissance playbook",
  "steps": [...],
  "created_by": "00000000-0000-0000-0000-000000000000",
  "created_at": "2026-02-21T10:00:00Z",
  "updated_at": "2026-02-21T10:00:00Z"
}

List Playbooks

GET /api/v1/cockpit/playbooks

Returns all playbooks ordered by creation date (newest first).

curl -s https://stentor.app/api/v1/cockpit/playbooks \
  -H "Authorization: Bearer $TOKEN"

Get Playbook

GET /api/v1/cockpit/playbooks/:id

curl -s https://stentor.app/api/v1/cockpit/playbooks/$PLAYBOOK_ID \
  -H "Authorization: Bearer $TOKEN"

Update Playbook

PUT /api/v1/cockpit/playbooks/:id

Request Body: Same as Create (all fields required).

curl -s -X PUT https://stentor.app/api/v1/cockpit/playbooks/$PLAYBOOK_ID \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Initial Recon v2",
    "description": "Updated reconnaissance playbook",
    "steps": [...]
  }'

Delete Playbook

DELETE /api/v1/cockpit/playbooks/:id

curl -s -X DELETE https://stentor.app/api/v1/cockpit/playbooks/$PLAYBOOK_ID \
  -H "Authorization: Bearer $TOKEN"

Response: 204 No Content

Execute Playbook

POST /api/v1/cockpit/playbooks/:id/execute

Executes the playbook against a target beacon. Conditional steps are evaluated using live beacon metadata from the beacon registry. All resulting tasks are enqueued and returned.

Request Body:

Field Type Required Description
beacon_id string (UUID) Yes Target beacon to execute against
curl -s -X POST https://stentor.app/api/v1/cockpit/playbooks/$PLAYBOOK_ID/execute \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"beacon_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"}'

Response (202 Accepted):

{
  "playbook_id": "c3d4e5f6-a1b2-7890-abcd-ef1234567890",
  "beacon_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "execution_id": "f7e8d9c0-b1a2-3456-7890-abcdef123456",
  "task_ids": [
    "11111111-2222-3333-4444-555555555555",
    "66666666-7777-8888-9999-aaaaaaaaaaaa",
    "bbbbbbbb-cccc-dddd-eeee-ffffffffffff"
  ],
  "status": "queued"
}

Beacon Must Be Active

The target beacon must exist in the live beacon registry (i.e., the implant must have checked in at least once). Executing against a beacon ID that is not in the registry returns an error.

Error Responses:

Status Body Cause
400 {"error": "invalid playbook ID"} Malformed UUID
400 {"error": "invalid beacon_id format"} Malformed beacon UUID
500 {"error": "beacon ... not found in registry"} Beacon not active
500 {"error": "get playbook: ..."} Playbook not found

List Executions

GET /api/v1/cockpit/playbooks/:id/executions

Returns recent execution log entries for a playbook. Each entry represents one step decision (action enqueued or conditional branch taken).

Query Parameters:

Parameter Type Default Description
limit integer 20 Maximum number of log entries to return
curl -s "https://stentor.app/api/v1/cockpit/playbooks/$PLAYBOOK_ID/executions?limit=50" \
  -H "Authorization: Bearer $TOKEN"

Get Execution Logs

GET /api/v1/cockpit/playbooks/:id/executions/:executionId/logs

Returns detailed decision logs for a specific execution run, ordered by step order and creation time.

curl -s "https://stentor.app/api/v1/cockpit/playbooks/$PLAYBOOK_ID/executions/$EXECUTION_ID/logs" \
  -H "Authorization: Bearer $TOKEN"

Response (200 OK):

[
  {
    "id": "aaa11111-bbbb-cccc-dddd-eeeeeeeeeeee",
    "playbook_id": "c3d4e5f6-a1b2-7890-abcd-ef1234567890",
    "execution_id": "f7e8d9c0-b1a2-3456-7890-abcdef123456",
    "beacon_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "step_order": 1,
    "step_type": "action",
    "branch_taken": null,
    "condition_values": null,
    "condition_result": null,
    "task_ids": ["11111111-2222-3333-4444-555555555555"],
    "created_at": "2026-02-21T10:01:00Z"
  },
  {
    "id": "bbb22222-cccc-dddd-eeee-ffffffffffff",
    "playbook_id": "c3d4e5f6-a1b2-7890-abcd-ef1234567890",
    "execution_id": "f7e8d9c0-b1a2-3456-7890-abcdef123456",
    "beacon_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "step_order": 2,
    "step_type": "conditional",
    "branch_taken": "then",
    "condition_values": { "elevated": false, "os": "Windows 10 Pro 19045" },
    "condition_result": true,
    "task_ids": ["66666666-7777-8888-9999-aaaaaaaaaaaa"],
    "created_at": "2026-02-21T10:01:01Z"
  }
]

Execution Log Fields

Each execution log entry records the outcome of a single step evaluation.

Field Type Description
id string (UUID) Log entry ID
playbook_id string (UUID) Parent playbook
execution_id string (UUID) Groups all entries from one execution run
beacon_id string (UUID) Target beacon
step_order integer Which step in the playbook
step_type string "action" or "conditional"
branch_taken string or null For conditionals: "then", "else", a branch name, or "none" (skipped)
condition_values object or null Snapshot of beacon field values used in condition evaluation
condition_result boolean or null Whether the condition evaluated to true
task_ids string[] C2 task IDs enqueued by this step
created_at string (ISO 8601) When this step was evaluated

Examples

Conditional Playbook: Privilege-Aware Recon

This playbook checks whether the beacon is elevated and runs different techniques accordingly:

curl -s -X POST https://stentor.app/api/v1/cockpit/playbooks \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Privilege-Aware Recon",
    "description": "Runs elevated or standard recon based on beacon integrity level",
    "steps": [
      {
        "type": "action",
        "technique_id": "exec.shell",
        "params": { "command": "whoami /priv" },
        "order": 1
      },
      {
        "type": "conditional",
        "order": 2,
        "conditional": {
          "condition": {
            "operator": "and",
            "conditions": [
              { "field": "elevated", "operator": "==", "value": true }
            ]
          },
          "then": [
            {
              "type": "action",
              "technique_id": "creds.hashdump",
              "params": {},
              "order": 1
            },
            {
              "type": "action",
              "technique_id": "exec.shell",
              "params": { "command": "netstat -ano" },
              "order": 2
            }
          ],
          "else": [
            {
              "type": "action",
              "technique_id": "exec.shell",
              "params": { "command": "net user %USERNAME% /domain" },
              "order": 1
            }
          ]
        }
      }
    ]
  }'

Multi-Branch Playbook: OS-Specific Actions

This playbook uses the multi-branch pattern to run different steps based on the beacon's operating system:

{
  "name": "OS-Specific Collection",
  "description": "Runs collection techniques based on target OS",
  "steps": [
    {
      "type": "conditional",
      "order": 1,
      "conditional": {
        "condition": { "operator": "and", "conditions": [] },
        "then": [],
        "branches": [
          {
            "name": "Windows 10",
            "condition": {
              "operator": "and",
              "conditions": [
                { "field": "os", "operator": "contains", "value": "Windows 10" }
              ]
            },
            "steps": [
              {
                "type": "action",
                "technique_id": "exec.shell",
                "params": { "command": "systeminfo" },
                "order": 1
              }
            ]
          },
          {
            "name": "Windows Server",
            "condition": {
              "operator": "and",
              "conditions": [
                { "field": "os", "operator": "contains", "value": "Server" }
              ]
            },
            "steps": [
              {
                "type": "action",
                "technique_id": "exec.shell",
                "params": { "command": "nltest /dclist:" },
                "order": 1
              }
            ]
          }
        ],
        "else": [
          {
            "type": "action",
            "technique_id": "exec.shell",
            "params": { "command": "hostname" },
            "order": 1
          }
        ]
      }
    }
  ]
}

Branch Evaluation Order

In multi-branch mode, branches are evaluated in array order. The first branch whose condition evaluates to true is executed. If no branch matches, the else steps run (if provided). If there is no else, the step is skipped and logged with branch_taken: "none".


UI Integration

The Playbooks page (/playbooks) provides a visual interface for playbook management:

  • Card grid -- Each playbook is displayed as a card showing name, description, step count, and a "Conditional" badge if any step uses branching
  • Execute -- Select an active beacon from the dropdown and click the play button to execute
  • History -- Click "History" to view past executions grouped by execution ID
  • Execution logs -- Drill into any execution to see step-by-step decision logs (which branches were taken, what condition values were observed)
  • Create/Edit -- The PlaybookDialog supports adding action and conditional steps with a visual condition editor

Cross-References

  • REST API Reference -- Playbook endpoints are listed under the Cockpit section
  • Beacon Commands -- The techniques referenced by technique_id in playbook steps
  • Scripting -- CNA scripts can also automate beacon workflows via event hooks

Batch Task Execution

Batch tasks let you execute a single task definition across multiple beacons simultaneously. The backend dispatches the task to each target beacon in parallel, tracks per-beacon progress, and aggregates results into a single batch record. This is the primary mechanism for running post-exploitation commands at scale -- for example, executing whoami /all across every Windows workstation in a group, or deploying a persistence mechanism to all beacons matching a hostname filter.

Targeting Beacons

There are two mutually exclusive ways to specify which beacons receive the batch task: explicit beacon IDs or filter-based targeting.

Explicit Selection

Select specific beacons by passing their UUIDs in the beacon_ids array. In the UI, shift-click or ctrl-click beacons in the cockpit table to select multiple, then right-click and choose "Batch Task".

curl -s -X POST https://stentor.app/api/v1/c2/batch-tasks \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "beacon_ids": [
      "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "b2c3d4e5-f6a1-8901-bcde-f12345678901"
    ],
    "task": {
      "type": "exec.shell",
      "data": {"command": "whoami /all"}
    }
  }'

Filter-Based Targeting

Instead of explicit IDs, pass a filter object and the backend resolves matching beacons from the live registry at execution time. All filter fields use case-insensitive substring matching.

Filter Field Type Description
os string Match beacons whose OS contains this substring (e.g., "Windows 10")
hostname string Match beacons whose hostname contains this substring (e.g., "WS")
username string Match beacons whose username contains this substring (e.g., "admin")
group_id string (UUID) Match beacons that belong to the specified group
curl -s -X POST https://stentor.app/api/v1/c2/batch-tasks \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "filter": {
      "os": "Windows 10",
      "hostname": "WS"
    },
    "task": {
      "type": "exec.shell",
      "data": {"command": "ipconfig /all"}
    }
  }'
curl -s -X POST https://stentor.app/api/v1/c2/batch-tasks \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "filter": {
      "group_id": "d4e5f6a1-b2c3-4567-8901-abcdef123456"
    },
    "task": {
      "type": "creds.hashdump",
      "data": {}
    }
  }'

Group-Based Targeting

Use the group_id filter to target all beacons in a named group. Groups can be managed from the cockpit UI or via the beacon grouping API. See Beacon Grouping for details on creating and managing groups.

Mutually Exclusive

beacon_ids and filter cannot be used together in the same request. If both are provided, the API returns an error: "beacon_ids and filter are mutually exclusive".

Batch Task Object

A batch task record tracks the overall job and is stored in the batch_tasks table.

Field Type Description
id string (UUID) Unique batch task identifier
task_type string The task type dispatched to each beacon (e.g., "exec.shell")
task_data object The task data/parameters passed to each beacon
status string Overall batch status: "pending", "running", or "completed"
total_count integer Total number of target beacons
completed_count integer Number of beacons that completed the task
failed_count integer Number of beacons that failed
created_by string (UUID) Operator who created the batch task
created_at string (ISO 8601) Creation timestamp
completed_at string (ISO 8601) or null Timestamp when all items finished (null while running)

Each beacon in the batch has a corresponding BatchTaskItem:

Field Type Description
id string (UUID) Item identifier
batch_id string (UUID) Parent batch task
beacon_id string (UUID) Target beacon
task_id string (UUID) or null The C2 task ID created for this beacon (null if not yet dispatched)
status string Per-beacon status: "pending", "dispatched", "completed", or "failed"
output string or null Task output from the beacon
error string or null Error message if the task failed
created_at string (ISO 8601) When the item was created
completed_at string (ISO 8601) or null When the item finished

Progress Tracking

Batch task progress is tracked using a sync-on-read pattern. When you fetch a batch task, the backend queries the underlying c2_tasks table for any dispatched items that have since completed or failed, and updates the batch items accordingly before returning the response.

API: GET /api/v1/c2/batch-tasks/:id returns the batch task with all its items and synced statuses.

curl -s https://stentor.app/api/v1/c2/batch-tasks/$BATCH_ID \
  -H "Authorization: Bearer $TOKEN"

Response:

{
  "id": "f1e2d3c4-b5a6-7890-abcd-ef1234567890",
  "task_type": "exec.shell",
  "task_data": {"command": "whoami /all"},
  "status": "running",
  "total_count": 5,
  "completed_count": 3,
  "failed_count": 1,
  "created_by": "00000000-0000-0000-0000-000000000000",
  "created_at": "2026-02-21T10:00:00Z",
  "completed_at": null,
  "items": [
    {
      "id": "aaa11111-...",
      "beacon_id": "a1b2c3d4-...",
      "task_id": "11111111-...",
      "status": "completed",
      "output": "USER INFORMATION\n----------------\nCORP\\jsmith ...",
      "error": null,
      "completed_at": "2026-02-21T10:00:05Z"
    },
    {
      "id": "bbb22222-...",
      "beacon_id": "b2c3d4e5-...",
      "task_id": "22222222-...",
      "status": "dispatched",
      "output": null,
      "error": null,
      "completed_at": null
    }
  ]
}

In the UI, the BatchProgressPanel displays a progress bar with beacon-by-beacon status indicators. Each item shows the target beacon's hostname, current task status, execution duration, and an output preview.

Aggregated Results

When all items in a batch have completed or failed, the batch status transitions to "completed" and the completed_at timestamp is set. The items array in the response contains the full per-beacon output.

Each item in the results includes:

Field Description
beacon_id Target beacon UUID
status "completed" or "failed"
output Full task output from the beacon
error Error message (if failed, or non-fatal error alongside output)
completed_at When this beacon's task finished

In the UI, the results view shows collapsible per-beacon output panels. Each panel displays the beacon hostname and output, with a copy-to-clipboard button for easy extraction.

Filtering Results

Poll the batch task endpoint and inspect each item's status field to filter by outcome. For example, to find only failed beacons, filter items where status == "failed" to quickly identify and troubleshoot issues.

Batch Task API Reference

All batch task endpoints are under the C2 route group and require JWT authentication.

Base path: /api/v1/c2/batch-tasks

Method Endpoint Description
POST /api/v1/c2/batch-tasks Create and execute a batch task
GET /api/v1/c2/batch-tasks List recent batch tasks (up to 50)
GET /api/v1/c2/batch-tasks/:id Get batch task with items and synced statuses

Create Response (201 Created):

{
  "id": "f1e2d3c4-b5a6-7890-abcd-ef1234567890",
  "total": 5,
  "dispatched": 4,
  "failed": 1,
  "errors": ["beacon b2c3d4e5-...: beacon not active"]
}

The dispatched count indicates how many beacons successfully received the task. The failed count and errors array report any beacons that could not be reached (e.g., beacon no longer active in the registry).


Scheduled Playbooks

Scheduled playbooks automate playbook execution on time-based schedules or event-based triggers without manual intervention. Instead of manually clicking "Execute" each time, an operator configures a schedule and the system runs the playbook automatically at the specified times or when specific events occur (such as a new beacon checking in).

Schedule Types

Each schedule has a schedule_type that determines when it fires:

Schedule Type Description Key Parameters
once Execute at a specific future time next_run_at (ISO 8601 timestamp)
interval Execute every N seconds interval_seconds (integer)
daily Execute daily at a specific time cron_expression (e.g., "0 9 * * *" for 9 AM daily)
weekly Execute weekly on a specific day/time cron_expression (e.g., "0 9 * * 1" for Monday 9 AM)

Once schedules are useful for maintenance windows or delayed operations -- for example, scheduling a credential harvesting playbook to run at 2 AM during a change window.

Interval schedules run repeatedly at a fixed cadence. For example, interval_seconds: 14400 runs the playbook every 4 hours.

Daily and Weekly schedules use standard cron expressions for precise timing control.

Trigger-Based Execution

In addition to time-based scheduling, playbooks can be triggered by events using the trigger_type field.

on_beacon_initial -- Automatically execute the playbook when any new beacon checks in for the first time. This is extremely useful for initial reconnaissance playbooks that run standard enumeration commands (whoami, ipconfig, net user, etc.) on every newly compromised host without operator intervention.

When trigger_type is set to "on_beacon_initial", the schedule fires on new beacon events instead of time-based triggers. The schedule_type and next_run_at fields are still required but serve as fallback scheduling parameters.

Rate Limiting

If many beacons check in simultaneously (for example, after deploying a payload across multiple hosts), each beacon gets its own independent playbook execution. Monitor execution logs during mass deployments to avoid overwhelming the C2 channel.

Creating Schedules

API: POST /api/v1/cockpit/playbooks/:id/schedules

curl -s -X POST https://stentor.app/api/v1/cockpit/playbooks/$PLAYBOOK_ID/schedules \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "schedule_type": "daily",
    "cron_expression": "0 9 * * *",
    "trigger_type": "scheduled",
    "next_run_at": "2026-02-22T09:00:00Z",
    "beacon_ids": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890"]
  }'
curl -s -X POST https://stentor.app/api/v1/cockpit/playbooks/$PLAYBOOK_ID/schedules \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "schedule_type": "interval",
    "interval_seconds": 14400,
    "trigger_type": "scheduled",
    "next_run_at": "2026-02-21T14:00:00Z",
    "beacon_ids": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890"]
  }'
curl -s -X POST https://stentor.app/api/v1/cockpit/playbooks/$PLAYBOOK_ID/schedules \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "schedule_type": "once",
    "trigger_type": "scheduled",
    "next_run_at": "2026-02-22T02:00:00Z",
    "beacon_ids": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890"]
  }'
curl -s -X POST https://stentor.app/api/v1/cockpit/playbooks/$PLAYBOOK_ID/schedules \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "schedule_type": "weekly",
    "cron_expression": "0 9 * * 1",
    "trigger_type": "scheduled",
    "next_run_at": "2026-02-24T09:00:00Z",
    "beacon_ids": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890"]
  }'
curl -s -X POST https://stentor.app/api/v1/cockpit/playbooks/$PLAYBOOK_ID/schedules \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "schedule_type": "once",
    "trigger_type": "on_beacon_initial",
    "next_run_at": "2026-12-31T23:59:59Z"
  }'

UI workflow: In the Playbooks page, click the "Schedule" button on any playbook card to open the PlaybookScheduleDialog. Select the schedule type, configure parameters (cron expression, interval, target time), choose target beacons, and toggle the enabled switch.

Managing Schedules

List Schedules

GET /api/v1/cockpit/playbooks/:id/schedules

Returns all schedules for a playbook.

curl -s https://stentor.app/api/v1/cockpit/playbooks/$PLAYBOOK_ID/schedules \
  -H "Authorization: Bearer $TOKEN"

Update Schedule

PUT /api/v1/cockpit/playbooks/:id/schedules/:scheduleId

Modify schedule parameters or enable/disable a schedule without deleting it.

# Disable a schedule
curl -s -X PUT https://stentor.app/api/v1/cockpit/playbooks/$PLAYBOOK_ID/schedules/$SCHEDULE_ID \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"enabled": false}'
# Update cron expression
curl -s -X PUT https://stentor.app/api/v1/cockpit/playbooks/$PLAYBOOK_ID/schedules/$SCHEDULE_ID \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"cron_expression": "0 6 * * *"}'

Delete Schedule

DELETE /api/v1/cockpit/playbooks/:id/schedules/:scheduleId

Permanently removes a schedule.

curl -s -X DELETE https://stentor.app/api/v1/cockpit/playbooks/$PLAYBOOK_ID/schedules/$SCHEDULE_ID \
  -H "Authorization: Bearer $TOKEN"

Response: 204 No Content

Execution Logs & Trigger Badges

Scheduled executions appear in the same execution log as manual runs. Each log entry includes a trigger_type field indicating how the execution was triggered:

Trigger Badge Value Description
Manual "manual" Operator clicked Execute in the UI or called the execute API directly
Scheduled "scheduled" Execution was triggered by a time-based schedule
Beacon Initial "on_beacon_initial" Execution was triggered by a new beacon check-in

API: GET /api/v1/cockpit/playbooks/:id/executions includes the trigger_type field in each execution entry. Use this field to filter execution logs by trigger type.

curl -s "https://stentor.app/api/v1/cockpit/playbooks/$PLAYBOOK_ID/executions?limit=50" \
  -H "Authorization: Bearer $TOKEN"

In the UI, execution history entries display a colored trigger badge next to the execution timestamp, making it easy to distinguish between manual and automated runs at a glance.

Schedule Object

A playbook schedule is stored in the c2_playbook_schedules table and returned as JSON from all schedule endpoints.

Field Type Description
id string (UUID) Unique schedule identifier
playbook_id string (UUID) Parent playbook
schedule_type string "once", "interval", "daily", or "weekly"
cron_expression string or null Cron expression for daily/weekly schedules
interval_seconds integer or null Interval in seconds for interval schedules
trigger_type string "scheduled" (default) or "on_beacon_initial"
next_run_at string (ISO 8601) Next scheduled execution time
last_run_at string (ISO 8601) or null Last execution timestamp
enabled boolean Whether the schedule is active
beacon_ids string[] Target beacon UUIDs for scheduled execution
created_at string (ISO 8601) Creation timestamp
updated_at string (ISO 8601) Last update timestamp

Schedule API Reference

All schedule endpoints are nested under a playbook and require JWT authentication.

Base path: /api/v1/cockpit/playbooks/:id/schedules

Method Endpoint Description
POST /api/v1/cockpit/playbooks/:id/schedules Create a schedule for a playbook
GET /api/v1/cockpit/playbooks/:id/schedules List all schedules for a playbook
PUT /api/v1/cockpit/playbooks/:id/schedules/:scheduleId Update a schedule
DELETE /api/v1/cockpit/playbooks/:id/schedules/:scheduleId Delete a schedule