Skip to content

C2 Campaigns

C2 campaigns are the top-level organizational unit for red team engagements in Stentor. A campaign groups beacons, tasks, credentials, and listeners into a single operational context -- enabling per-engagement reporting, activity tracking, and credential aggregation across all associated sessions.


Overview

Every beacon can be assigned to exactly one campaign. Once associated, all tasks executed through that beacon and all credentials harvested from it are attributed to the campaign. This powers the reporting engine (PDF, DOCX, CSV, JSON, TSV, XML) and gives operators a unified view of engagement activity.

graph LR
    C[Campaign] --> B1[Beacon 1]
    C --> B2[Beacon 2]
    C --> B3[Beacon 3]
    B1 --> T1[Tasks]
    B2 --> T2[Tasks]
    B3 --> T3[Tasks]
    B1 --> CR1[Credentials]
    B2 --> CR2[Credentials]
    C --> L[Linked Listeners]
    L --> AB[Auto-associate<br/>new beacons]
    C --> R[Reports & Export]

Campaign Object

Field Type Description
id UUID Unique campaign identifier
name string Campaign display name
description string Free-text description
status string active, paused, or archived
created_by UUID Operator who created the campaign
start_date timestamp Engagement start date (optional)
end_date timestamp Engagement end date (optional)
created_at timestamp Record creation time
updated_at timestamp Last modification time
recording_mode boolean Whether recording mode is enabled
beacon_count int Live count of associated beacons
credential_count int Live count of harvested credentials
task_count int Live count of executed tasks

Live Counts

The beacon_count, credential_count, and task_count fields are computed at query time and reflect the current state of the database. They are not stored on the campaign record itself.


Campaign CRUD

Create a Campaign

POST /api/v1/c2/campaigns
Field Type Required Description
name string Yes Campaign name
description string No Campaign description
start_date RFC3339 No Engagement start date
end_date RFC3339 No Engagement end date
curl -s -X POST "https://stentor.app/api/v1/c2/campaigns" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "ACME Corp Pentest Q1",
    "description": "Quarterly external penetration test",
    "start_date": "2026-02-01T00:00:00Z",
    "end_date": "2026-02-28T23:59:59Z"
  }' | jq
{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "name": "ACME Corp Pentest Q1",
  "description": "Quarterly external penetration test",
  "status": "active",
  "created_by": "operator-uuid",
  "start_date": "2026-02-01T00:00:00Z",
  "end_date": "2026-02-28T23:59:59Z",
  "created_at": "2026-02-21T10:00:00Z",
  "updated_at": "2026-02-21T10:00:00Z",
  "recording_mode": false,
  "beacon_count": 0,
  "credential_count": 0,
  "task_count": 0
}

New campaigns are created with status: "active" by default.

List All Campaigns

GET /api/v1/c2/campaigns

Returns all campaigns for the authenticated operator, ordered by newest first. Each campaign in the response includes live beacon_count, credential_count, and task_count values computed via batch queries.

curl -s "https://stentor.app/api/v1/c2/campaigns" \
  -H "Authorization: Bearer $TOKEN" | jq

Get a Campaign

GET /api/v1/c2/campaigns/:id
curl -s "https://stentor.app/api/v1/c2/campaigns/$CAMPAIGN_ID" \
  -H "Authorization: Bearer $TOKEN" | jq

Update a Campaign

PUT /api/v1/c2/campaigns/:id
Field Type Required Description
name string Yes Updated name
description string No Updated description
status string Yes active, paused, or archived
start_date RFC3339 No Updated start date
end_date RFC3339 No Updated end date
curl -s -X PUT "https://stentor.app/api/v1/c2/campaigns/$CAMPAIGN_ID" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "ACME Corp Pentest Q1 (Complete)",
    "description": "Engagement completed successfully",
    "status": "archived"
  }' | jq

Valid Statuses

The status field must be one of active, paused, or archived. Any other value returns a 400 error.

Delete (Archive) a Campaign

DELETE /api/v1/c2/campaigns/:id

Campaigns are soft-deleted by setting their status to archived. No data is permanently removed.

curl -s -X DELETE "https://stentor.app/api/v1/c2/campaigns/$CAMPAIGN_ID" \
  -H "Authorization: Bearer $TOKEN"
# Returns 204 No Content

Beacon Association

Beacons are assigned to campaigns either manually via the API or automatically through linked listeners. Each beacon can belong to at most one campaign at a time.

Associate a Beacon

POST /api/v1/c2/campaigns/:id/beacons
Field Type Required Description
beacon_id UUID Yes ID of the beacon to associate
curl -s -X POST "https://stentor.app/api/v1/c2/campaigns/$CAMPAIGN_ID/beacons" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"beacon_id": "beacon-uuid-here"}' | jq

The association is persisted in both the database and the in-memory beacon registry.

Disassociate a Beacon

DELETE /api/v1/c2/campaigns/:id/beacons/:beaconId
curl -s -X DELETE "https://stentor.app/api/v1/c2/campaigns/$CAMPAIGN_ID/beacons/$BEACON_ID" \
  -H "Authorization: Bearer $TOKEN"
# Returns 204 No Content

List Campaign Beacons

GET /api/v1/c2/campaigns/:id/beacons

Returns all beacons currently associated with the campaign from the in-memory beacon registry.

curl -s "https://stentor.app/api/v1/c2/campaigns/$CAMPAIGN_ID/beacons" \
  -H "Authorization: Bearer $TOKEN" | jq

Listener Linking

Link listeners to a campaign so that any new beacon checking in through that listener is automatically assigned to the campaign. This eliminates the need to manually associate every new beacon.

POST /api/v1/c2/campaigns/:id/listeners
Field Type Required Description
listener_id UUID Yes ID of the listener to link
curl -s -X POST "https://stentor.app/api/v1/c2/campaigns/$CAMPAIGN_ID/listeners" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"listener_id": "listener-uuid-here"}' | jq

Idempotent Operation

Linking the same listener twice is safe -- the operation uses ON CONFLICT DO NOTHING and returns success without duplicating the association.

DELETE /api/v1/c2/campaigns/:id/listeners/:listenerId
curl -s -X DELETE "https://stentor.app/api/v1/c2/campaigns/$CAMPAIGN_ID/listeners/$LISTENER_ID" \
  -H "Authorization: Bearer $TOKEN"
# Returns 204 No Content

List Linked Listeners

GET /api/v1/c2/campaigns/:id/listeners

Returns an array of listener UUID strings.

curl -s "https://stentor.app/api/v1/c2/campaigns/$CAMPAIGN_ID/listeners" \
  -H "Authorization: Bearer $TOKEN" | jq

Activity Log

The activity log provides a paginated, chronological view of all tasks executed across every beacon in the campaign.

GET /api/v1/c2/campaigns/:id/activity
Parameter Type Default Description
limit int 50 Maximum tasks per page (capped at 200)
offset int 0 Pagination offset
curl -s "https://stentor.app/api/v1/c2/campaigns/$CAMPAIGN_ID/activity?limit=25&offset=0" \
  -H "Authorization: Bearer $TOKEN" | jq
{
  "tasks": [
    {
      "id": "task-uuid",
      "beacon_id": "beacon-uuid",
      "task_type": "shell",
      "data": "{\"command\":\"whoami\"}",
      "status": "completed",
      "output": "CORP\\jdoe",
      "created_at": "2026-02-21T14:30:00Z",
      "completed_at": "2026-02-21T14:30:01Z"
    }
  ],
  "total": 342
}

Tasks are ordered newest-first. The total field enables pagination UI by reporting the full count of tasks across all campaign beacons.


Credential Aggregation

Retrieve all credentials harvested from any beacon in the campaign in a single query.

GET /api/v1/c2/campaigns/:id/credentials
curl -s "https://stentor.app/api/v1/c2/campaigns/$CAMPAIGN_ID/credentials" \
  -H "Authorization: Bearer $TOKEN" | jq

Credential Object

Field Type Description
id UUID Credential record ID
session_id UUID Associated session (optional)
beacon_id UUID Beacon that harvested the credential
credential_type string Type (e.g., ntlm, plaintext, kerberos)
username string Account username
domain string Domain name
secret string Password hash or plaintext
host string Source host
source string How the credential was obtained (e.g., hashdump, mimikatz)
note string Operator notes
created_at timestamp When the credential was captured

Recording Mode

Toggle recording mode on a campaign to flag it for enhanced activity logging.

POST /api/v1/c2/campaigns/:id/record-mode
Field Type Required Description
enabled boolean Yes Whether to enable recording mode
curl -s -X POST "https://stentor.app/api/v1/c2/campaigns/$CAMPAIGN_ID/record-mode" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"enabled": true}' | jq

Response: {"recording_mode": true}


Campaign Export

Export campaign data in multiple formats and report types through a single endpoint.

GET /api/v1/c2/campaigns/:id/export?format=<format>&type=<type>

Formats

Format Content-Type Notes
pdf application/pdf All report types supported
docx application/vnd.openxmlformats-officedocument.wordprocessingml.document Supports additional options (see below)
json application/json All report types supported
csv text/csv All types except full
tsv text/tab-separated-values All types except full
xml application/xml All types except full

Report Types

Type Key Description
Full Campaign full Complete engagement summary combining all sections
Activity Timeline activity Chronological task execution with MITRE ATT&CK mappings
Hosts hosts Target systems with sessions and discovered services
Indicators of Compromise indicators Payloads, network IOCs, file/registry artifacts
Sessions sessions Beacon session details with command history
TTPs ttp MITRE ATT&CK technique usage with tactic statistics
Social Engineering social_eng Profiler visit analytics with geographic breakdowns

Format Restrictions

The full report type is only available in pdf, json, and docx formats. Requesting full with csv, tsv, or xml returns a 400 error.

DOCX Options

The DOCX format supports additional query parameters for customization:

Parameter Type Description
title string Custom report title
description string Description paragraph
host_filter string[] Filter to specific hostnames
mask_emails bool Replace emails with ***@***.***
mask_passwords bool Replace NTLM hashes with ********

Examples

curl -s "https://stentor.app/api/v1/c2/campaigns/$CAMPAIGN_ID/export?format=pdf&type=full" \
  -H "Authorization: Bearer $TOKEN" \
  -o campaign-report.pdf
curl -s "https://stentor.app/api/v1/c2/campaigns/$CAMPAIGN_ID/export?format=json&type=ttp" \
  -H "Authorization: Bearer $TOKEN" | jq
curl -s "https://stentor.app/api/v1/c2/campaigns/$CAMPAIGN_ID/export?format=csv&type=activity" \
  -H "Authorization: Bearer $TOKEN" \
  -o activity.csv
curl -s "https://stentor.app/api/v1/c2/campaigns/$CAMPAIGN_ID/export?format=docx&type=full&mask_emails=true&mask_passwords=true&title=Engagement+Report" \
  -H "Authorization: Bearer $TOKEN" \
  -o engagement-report.docx
curl -s "https://stentor.app/api/v1/c2/campaigns/$CAMPAIGN_ID/export?format=xml&type=indicators" \
  -H "Authorization: Bearer $TOKEN" \
  -o indicators.xml
curl -s "https://stentor.app/api/v1/c2/campaigns/$CAMPAIGN_ID/export?format=tsv&type=sessions" \
  -H "Authorization: Bearer $TOKEN" \
  -o sessions.tsv

For detailed information on report contents, see Campaign Reports.


Specialized Reports

Social Engineering Report

A dedicated endpoint for the social engineering report as JSON (separate from the export endpoint):

GET /api/v1/c2/campaigns/:id/report/social-eng
curl -s "https://stentor.app/api/v1/c2/campaigns/$CAMPAIGN_ID/report/social-eng" \
  -H "Authorization: Bearer $TOKEN" | jq

Custom Template Reports

Render campaign data through a custom Go template for bespoke reporting needs.

Execute template:

POST /api/v1/c2/campaigns/:id/report/custom
Field Type Required Description
template string Yes Go text/template string
curl -s -X POST "https://stentor.app/api/v1/c2/campaigns/$CAMPAIGN_ID/report/custom" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"template": "Campaign: {{.Name}} | Beacons: {{len .Beacons}}"}' | jq

Validate template (dry run):

POST /api/v1/c2/campaigns/:id/report/validate-template
curl -s -X POST "https://stentor.app/api/v1/c2/campaigns/$CAMPAIGN_ID/report/validate-template" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"template": "{{.Name}}"}' | jq

Response: {"valid": true, "error": ""} or {"valid": false, "error": "template: ...: unexpected ..."}.


API Reference

Method Endpoint Description
POST /api/v1/c2/campaigns Create a campaign
GET /api/v1/c2/campaigns List all campaigns
GET /api/v1/c2/campaigns/:id Get a campaign
PUT /api/v1/c2/campaigns/:id Update a campaign
DELETE /api/v1/c2/campaigns/:id Archive a campaign
POST /api/v1/c2/campaigns/:id/beacons Associate a beacon
DELETE /api/v1/c2/campaigns/:id/beacons/:beaconId Disassociate a beacon
GET /api/v1/c2/campaigns/:id/beacons List campaign beacons
POST /api/v1/c2/campaigns/:id/listeners Link a listener
DELETE /api/v1/c2/campaigns/:id/listeners/:listenerId Unlink a listener
GET /api/v1/c2/campaigns/:id/listeners List linked listeners
GET /api/v1/c2/campaigns/:id/activity Get activity log
GET /api/v1/c2/campaigns/:id/credentials Get aggregated credentials
GET /api/v1/c2/campaigns/:id/export Export campaign report
POST /api/v1/c2/campaigns/:id/record-mode Toggle recording mode
GET /api/v1/c2/campaigns/:id/report/social-eng Social engineering report
POST /api/v1/c2/campaigns/:id/report/custom Execute custom template
POST /api/v1/c2/campaigns/:id/report/validate-template Validate custom template

See Also