Skip to content

External Feed API

The External Feed API is a read-only REST API that allows MSSP partners, customer SOC teams, SOAR platforms, and external SIEMs to pull security data from RhythmX. API keys authenticate requests and control access scope — master keys see all entities, scoped keys see only their entity.


How It Works

flowchart LR
    A["MSSP Partner<br>or Customer SOC"] -->|"X-API-Key header"| B["API Key<br>Authentication"]
    B --> C{"Master Key?"}
    C -->|"Yes (entity_name=*)"| D["All Entities<br>or filter by ?entity_name="]
    C -->|"No (entity_name=CustomerA)"| E["CustomerA Only"]
    D --> F["JSON Response<br><i>Paginated</i>"]
    E --> F

    style A fill:#4a148c,stroke:#9c27b0,color:#fff
    style B fill:#b71c1c,stroke:#f05545,color:#fff
    style C fill:#e65100,stroke:#ff9800,color:#fff
    style D fill:#0d47a1,stroke:#42a5f5,color:#fff
    style E fill:#0d47a1,stroke:#42a5f5,color:#fff
    style F fill:#1b5e20,stroke:#4c8c4a,color:#fff

Base URL

https://<your-rhythmx-host>/api/v1/feed

All endpoints are prefixed with /api/v1/feed.


Authentication

Include your API key in the X-API-Key header with every request.

curl -H "X-API-Key: smk_your_api_key_here" \
     https://rhythmx.example.com/api/v1/feed/health

Key Types

Key Type entity_name in DB Behavior
Master Key * Sees all entities. Pass ?entity_name=X to filter, or omit for all.
Scoped Key CustomerA Only sees CustomerA data. Cannot access other entities.
Legacy unscoped (blank) Treated as a master key. Creating one is no longer permitted — see below.

The key's entity is the access boundary — not ?entity_name=

A key's entity_name is what actually restricts access. The ?entity_name= query parameter is supplied by the caller, so it is a convenience filter for master keys, never an access control: any client holding a master key can change it or leave it out and receive all entities.

To restrict a consumer to one tenant, issue a scoped key. A scoped key ignores ?entity_name= entirely, so it cannot be re-targeted at another entity, and requests for another entity's incident, case or alert by ID return 404.

Blank entity keys

entity_name was previously optional, and a blank value was documented as granting access to all entities. It did — but such a key also ignored ?entity_name=, so unlike a real master key it could not be narrowed to a single entity for a given request.

Blank keys now behave exactly like *, so existing integrations are unaffected and the filter works as expected. New keys must state their scope explicitly — either an entity name or *. See API Key Management.

Key Properties

  • Non-retrievable — plaintext key shown only once at creation
  • Revocable — can be disabled or deleted anytime
  • Re-scopable — a key's entity_name can be changed without reissuing the secret
  • Expirable — optional expiration date
  • Rate-limited — 120 requests per minute per API key (configurable via FEED_RATE_LIMIT env var)

Scopes and per-key rate limits are informational

The scopes and rate_limit_per_minute fields are stored on the key and shown in the admin UI, but are not currently enforced. Every valid key can read all endpoints listed here, and the rate limit applied is the server-wide FEED_RATE_LIMIT value (counted per key). Plan access around the key's entity scope, which is enforced, and treat scopes as a label recording intent rather than a restriction.


Rate Limiting

All external feed endpoints are rate-limited per API key to prevent abuse:

Setting Default Configurable
Requests per minute 120 FEED_RATE_LIMIT in .env.production
Scope Per API key Each key is counted separately
Response when exceeded 429 Too Many Requests Includes Retry-After header

Example error when rate limit is exceeded:

{
  "error": "Rate limit exceeded",
  "retry_after": 30
}

Every key is counted separately, but they all share the same limit: the server-wide FEED_RATE_LIMIT. The rate_limit_per_minute value stored on an individual key is not currently applied.

Space out bulk sweeps — polling many endpoints back-to-back can trip the limit.


Common Parameters

These parameters are available on all list endpoints:

Parameter Type Description
entity_name string Filter by entity (master keys only — scoped keys ignore this)
since string ISO 8601 timestamp — only return records created/updated after this time
limit integer Results per page (default: 50, max: 500)
offset integer Pagination offset (default: 0)

Incremental Polling (since parameter)

The since parameter enables efficient incremental polling — consumers only pull data that has changed since their last request. Supported on all endpoints that return time-based data.

Accepted formats:

?since=2026-03-20T10:30:00Z       (ISO 8601 with timezone)
?since=2026-03-20T10:30:00        (ISO 8601 without timezone — treated as UTC)
?since=2026-03-20                  (date only — from midnight UTC)

Example — poll every 5 minutes for new alerts:

# First call — get everything from the last hour
curl -H "X-API-Key: smk_abc12345..." \
     "https://rhythmx.example.com/api/v1/feed/alerts?since=2026-03-20T09:30:00Z"

# Subsequent calls — only get what's new since last poll
curl -H "X-API-Key: smk_abc12345..." \
     "https://rhythmx.example.com/api/v1/feed/alerts?since=2026-03-20T09:35:00Z"

Endpoints supporting since:

Endpoint Time column filtered
/incidents updated_at
/cases updated_at
/alerts system_time
/alerts/stats system_time
/risks/top-users system_time
/risks/top-computers system_time
/risks/top-rules system_time
/mitre/tactics system_time
/mitre/techniques system_time

Response Format

All endpoints return this structure:

{
  "success": true,
  "data": [ ... ],
  "pagination": {
    "total": 150,
    "limit": 50,
    "offset": 0,
    "has_more": true
  },
  "timestamp": "2026-03-20T10:30:00Z"
}

Endpoints

Health Check

GET /api/v1/feed/health

Returns the API service status.

Example:

curl -H "X-API-Key: smk_abc12345..." \
     https://rhythmx.example.com/api/v1/feed/health

Response:

{
  "status": "ok",
  "service": "external_feed_api"
}


List Incidents

GET /api/v1/feed/incidents

Returns security incidents. Each incident represents a group of related alarms for an actor (user or computer).

Parameters:

Parameter Type Description
entity_name string (Optional) Filter by entity/site. Master keys only — scoped keys ignore this.
status string OPEN, IN_PROGRESS, CLOSED, FALSE_POSITIVE
since string ISO 8601 — incidents updated after this time
limit integer Results per page (default: 50)
offset integer Pagination offset

Example:

# All entities
curl -H "X-API-Key: smk_abc12345..." \
     "https://rhythmx.example.com/api/v1/feed/incidents?status=OPEN&limit=20"

# Specific entity
curl -H "X-API-Key: smk_abc12345..." \
     "https://rhythmx.example.com/api/v1/feed/incidents?entity_name=Primary%20Site&status=OPEN"

Response:

{
  "success": true,
  "data": [
    {
      "incident_id": "INC-a1b2c3d4",
      "group_key": "jsmith",
      "actor_type": "user",
      "entity_name": "Primary Site",
      "status": "OPEN",
      "assigned_analyst": null,
      "first_alarm_time": "2026-03-02T08:15:00Z",
      "last_alarm_time": "2026-03-02T14:20:00Z",
      "created_at": "2026-03-02T14:22:00Z",
      "updated_at": "2026-03-02T14:22:00Z",
      "closed_at": null,
      "external_ticket_id": null,
      "external_system": null,

      "severity": "HIGH",
      "attack_type": "CREDENTIAL_THEFT,SYSTEM_BINARY_ANOMALY",
      "system_state": "ESCALATED"
    }
  ],
  "pagination": {
    "total": 12,
    "limit": 20,
    "offset": 0,
    "has_more": false
  },
  "timestamp": "2026-03-20T10:30:00Z"
}


severity vs system_state — they are computed differently

severity comes from the actor's risk score alone (>=80 CRITICAL, >=60 HIGH, >=40 MEDIUM, else LOW). system_state also escalates on correlated threat cases: risk >= 70 OR (cases > 1 AND risk >= 40).

So an incident can legitimately read MEDIUM while sitting at ESCALATED — a moderate-risk actor showing several distinct attack patterns. The reverse also occurs: risk 60-69 with one case is HIGH but only ACTIVE. The two scales are not aligned at any threshold, so do not derive one from the other or treat a mismatch as an error.


Get Incident Detail

GET /api/v1/feed/incidents/{incident_id}

Returns full details for a single incident including all fields.

Example:

curl -H "X-API-Key: smk_abc12345..." \
     https://rhythmx.example.com/api/v1/feed/incidents/INC-a1b2c3d4


GET /api/v1/feed/incidents/{incident_id}/related

Returns the incident and everything correlated to its actor in one call — LogRhythm alarms, RhythmX Sigma alerts, and threat cases — each with a per-rule rollup and a capped record list. All sections are scoped to the incident's entity.

Parameters:

Parameter Type Description
window string incident (default) bounds results to the incident's activity window; all returns the actor's full retained history in this entity.
limit integer Max records per section (default: 100, max: 500). Rollups and total counts are always complete.

Example:

curl -H "X-API-Key: smk_abc12345..." \
     "https://rhythmx.example.com/api/v1/feed/incidents/INC-a1b2c3d4/related?window=all&limit=250"

Response (abridged):

{
  "success": true,
  "incident": { "incident_id": "INC-a1b2c3d4", "group_key": "jsmith", "actor_type": "user", "entity_name": "Primary Site" },
  "window": { "mode": "incident", "start": "2026-03-02T08:15:00Z", "end": "2026-03-02T14:20:00Z" },
  "logrhythm_alarms": { "total": 3, "rule_groups": [ ... ], "alarms": [ ... ], "returned": 3 },
  "sigma_alerts": { "total": 49, "unique_rules": 8, "max_risk": 34, "rule_groups": [ ... ], "alerts": [ ... ], "returned": 49 },
  "threat_cases": { "total": 2, "cases": [ /* each with alerts_retained / alerts_available / alerts_expired */ ], "returned": 2 },
  "limit_per_section": 100,
  "timestamp": "2026-03-20T10:30:00Z"
}

See the dedicated Incident Feed API page for the full field-by-field reference of every section.


List Cases

GET /api/v1/feed/cases

Returns threat cases — correlated attack patterns detected by the analytics engine.

Parameters:

Parameter Type Description
entity_name string (Optional) Filter by entity/site. Master keys only — scoped keys ignore this.
status string OPEN, IN_PROGRESS, CLOSED, FALSE_POSITIVE
severity string CRITICAL, HIGH, MEDIUM, LOW
case_type string e.g., LATERAL_MOVEMENT, RANSOMWARE_INDICATORS, BRUTE_FORCE, CREDENTIAL_THEFT, PERSISTENCE, PRIVILEGE_ESCALATION, RECONNAISSANCE
since string ISO 8601 — cases updated after this time
limit integer Results per page (default: 50)
offset integer Pagination offset

Example:

curl -H "X-API-Key: smk_abc12345..." \
     "https://rhythmx.example.com/api/v1/feed/cases?severity=CRITICAL&case_type=RANSOMWARE_INDICATORS"

Response:

{
  "success": true,
  "data": [
    {
      "case_id": "CASE-x1y2z3",
      "entity_name": "Primary Site",
      "case_type": "RANSOMWARE_INDICATORS",
      "status": "OPEN",
      "severity": "CRITICAL",
      "primary_user": "admin",
      "affected_computers": "DC01,WS-PC42",
      "alert_count": 47,
      "max_risk_score": 95,
      "first_alert_time": "2026-03-19T03:10:00Z",
      "last_alert_time": "2026-03-19T04:45:00Z",
      "created_at": "2026-03-19T04:46:00Z",
      "updated_at": "2026-03-19T04:46:00Z",

      "alerts_retained": 47,
      "alerts_available": true,
      "alerts_expired": false
    }
  ],
  "alert_retention_days": 30,
  "pagination": {
    "total": 3,
    "limit": 50,
    "offset": 0,
    "has_more": false
  },
  "timestamp": "2026-03-20T10:30:00Z"
}

A case outlives the alerts behind it — check alerts_expired

RhythmX keeps Sigma alerts for 30 days (alert_retention_days), but a threat case is kept indefinitely. An older case therefore still reports the alert_count it was raised with, while the alerts themselves no longer exist.

Field Meaning
alerts_retained How many of the case's alerts still exist
alerts_available alerts_retained > 0
alerts_expired alerts_retained == 0 — the evidence is gone

alerts_retained is the real count, not an inference from dates, so a partially purged case reports honestly — e.g. alert_count: 47 with alerts_retained: 12.

Do not treat an empty alert list as an empty case. Check alerts_expired first: false means the case genuinely had no matching alerts, true means they aged out. The same three fields appear wherever a case is returned — /cases/{id}, /incidents/{id}/related and /risks/actors/{actor}/activity.

This mirrors logs_available / logs_expired on the LogRhythm alarm drill-down, which exists for the same reason.


Get Case Detail

GET /api/v1/feed/cases/{case_id}

Returns full details for a single threat case, including the evidence-retention fields described under List Cases.

Example:

curl -H "X-API-Key: smk_abc12345..." \
     https://rhythmx.example.com/api/v1/feed/cases/CASE-x1y2z3

When the case's alerts have aged out, the response says so explicitly rather than returning an empty alert list:

{
  "success": true,
  "data": {
    "case_id": "CASE-x1y2z3",
    "alert_count": 47,
    "alerts_retained": 0,
    "alerts_available": false,
    "alerts_expired": true
  },
  "alert_retention_days": 30,
  "message": "The alerts behind this case are no longer retained. RhythmX keeps Sigma alerts for 30 days; the case itself, its counts and its timeline are retained."
}

message is present only when the evidence has expired, so its presence is itself a reliable signal.


List Alerts

GET /api/v1/feed/alerts

Returns RhythmX detection alerts — individual rule matches from the detection engine.

Parameters:

Parameter Type Description
entity_name string (Optional) Filter by entity/site. Master keys only — scoped keys ignore this.
rule_level string informational, low, medium, high, critical
since string ISO 8601 — alerts with system_time after this
limit integer Results per page (default: 50, max: 500)
offset integer Pagination offset

Example:

curl -H "X-API-Key: smk_abc12345..." \
     "https://rhythmx.example.com/api/v1/feed/alerts?rule_level=critical&limit=10"

Response:

{
  "success": true,
  "data": [
    {
      "id": 154832,
      "title": "Mimikatz Command Line Usage",
      "description": "Detects Mimikatz command line arguments",
      "rule_level": "critical",
      "tags": "attack.credential_access,attack.t1003",
      "tactics": "credential_access",
      "techniques": "T1003",
      "computer_name": "DC01",
      "user_id": "admin",
      "ip_address": "10.0.5.42",
      "entity_name": "Primary Site",
      "system_time": "2026-03-19T14:22:00Z",
      "risk": 95,
      "image_path": "C:\\Windows\\Temp\\mimi.exe",
      "command_line": "sekurlsa::logonpasswords",
      "count": 1,
      "first_seen": "2026-03-19T14:22:00Z",
      "last_seen": "2026-03-19T14:22:00Z"
    }
  ],
  "pagination": {
    "total": 5,
    "limit": 10,
    "offset": 0,
    "has_more": false
  },
  "timestamp": "2026-03-20T10:30:00Z"
}


Get Alert Detail

GET /api/v1/feed/alerts/{alert_id}

Returns full details for a single alert including raw log data.


Alert Statistics

GET /api/v1/feed/alerts/stats

Returns aggregate alert statistics.

Parameters:

Parameter Type Description
entity_name string (Optional) Filter by entity/site. Master keys only — scoped keys ignore this.

Example:

curl -H "X-API-Key: smk_abc12345..." \
     https://rhythmx.example.com/api/v1/feed/alerts/stats

Response:

{
  "success": true,
  "data": {
    "total_alerts": 45832,
    "by_level": {
      "critical": 127,
      "high": 1893,
      "medium": 12450,
      "low": 28100,
      "informational": 3262
    },
    "by_entity": [
      { "entity_name": "Primary Site", "count": 28000 },
      { "entity_name": "Branch Office", "count": 17832 }
    ],
    "top_rules": [
      { "title": "Remote Access Tool Execution", "rule_level": "high", "count": 4521 },
      { "title": "Suspicious PowerShell Command", "rule_level": "medium", "count": 3102 }
    ]
  },
  "timestamp": "2026-03-20T10:30:00Z"
}


SOC Metrics Overview

GET /api/v1/feed/metrics/overview

Returns high-level SOC metrics — counts across incidents, cases and alerts, plus the risk roll-up. Everything needed for a dashboard's top KPI row in one call.

Parameters:

Parameter Type Description
entity_name string (Optional) Filter by entity/site. Master keys only — scoped keys ignore this.

Example:

curl -H "X-API-Key: smk_abc12345..." \
     https://rhythmx.example.com/api/v1/feed/metrics/overview

Response:

{
  "success": true,
  "data": {
    "total_incidents": 342,
    "open_incidents": 28,
    "total_cases": 89,
    "open_cases": 15,
    "total_alerts": 45832,
    "critical_alerts": 127,
    "high_risk_entities": 4,

    "entity_risk_score": 88,
    "entity_risk_level": "critical",
    "risky_users": 32,
    "risky_computers": 6,
    "critical_actors": 15,
    "high_actors": 23
  },
  "timestamp": "2026-03-20T10:30:00Z"
}

Field Meaning
entity_risk_score / _level Highest actor risk in scope. With a scoped key (or ?entity_name=) this is that entity's risk; across all entities it is the worst score anywhere.
risky_users / risky_computers Actors at critical or high risk level — not every actor with an alert.
high_risk_entities Count of entities having critical or high alerts. Distinct from the risk score above.

Incidents by Severity & Status

GET /api/v1/feed/metrics/incidents/severity

Incidents broken down by severity, by status, and by the two crossed. Every severity and status is returned including zero counts, so a stacked bar or matrix can be drawn without filling in gaps.

Parameters:

Parameter Type Description
status string (Optional) Only count this status. Omit for all.
entity_name string (Optional) Filter by entity/site. Master keys only — scoped keys ignore this.

Response:

{
  "success": true,
  "data": {
    "total_incidents": 210,
    "by_severity": [
      { "severity": "CRITICAL", "count": 22 },
      { "severity": "HIGH",     "count": 35 },
      { "severity": "MEDIUM",   "count": 18 },
      { "severity": "LOW",      "count": 135 }
    ],
    "by_status": [
      { "status": "OPEN", "count": 45 },
      { "status": "IN_PROGRESS", "count": 4 },
      { "status": "CLOSED", "count": 161 }
    ],
    "by_severity_and_status": [
      { "severity": "CRITICAL",
        "statuses": [ { "status": "OPEN", "count": 20 }, { "status": "IN_PROGRESS", "count": 2 }, { "status": "CLOSED", "count": 0 } ],
        "total": 22 }
    ]
  }
}

How incident severity is derived

Severity is the actor's risk score, bucketed — >= 80 CRITICAL, >= 60 HIGH, >= 40 MEDIUM, otherwise LOW — so it lines up with the risk levels reported by /risks/actors and /entities/risk.

It is a point-in-time snapshot, stamped when the incident last synced. An actor whose risk has moved since will not be reflected until the next sync, so a bare severity can lag the actor's live score — use /risks/actors when you need the current value.

Severity is also returned per incident on /incidents as severity, so a dashboard table needs only one call.


Severity Breakdown

GET /api/v1/feed/metrics/severity

Returns incident and case counts broken down by status and severity.

Parameters:

Parameter Type Description
entity_name string (Optional) Filter by entity/site. Master keys only — scoped keys ignore this.

Response:

{
  "success": true,
  "data": {
    "incidents_by_status": {
      "OPEN": 28,
      "IN_PROGRESS": 12,
      "CLOSED": 285,
      "FALSE_POSITIVE": 17
    },
    "cases_by_severity": {
      "CRITICAL": 3,
      "HIGH": 22,
      "MEDIUM": 41,
      "LOW": 23
    },
    "cases_by_type": {
      "LATERAL_MOVEMENT": 12,
      "BRUTE_FORCE": 18,
      "CREDENTIAL_THEFT": 8,
      "PERSISTENCE": 15,
      "RANSOMWARE_INDICATORS": 3
    }
  },
  "timestamp": "2026-03-20T10:30:00Z"
}


Mean Time to Respond (MTTR)

GET /api/v1/feed/metrics/mttr

Returns average time from incident creation to closure.

Parameters:

Parameter Type Description
entity_name string (Optional) Filter by entity/site. Master keys only — scoped keys ignore this.

Response:

{
  "success": true,
  "data": {
    "overall_mttr_minutes": 142,
    "closed_incidents": 285,
    "by_status": {
      "CLOSED": { "avg_minutes": 125, "count": 268 },
      "FALSE_POSITIVE": { "avg_minutes": 45, "count": 17 }
    }
  },
  "timestamp": "2026-03-20T10:30:00Z"
}


GET /api/v1/feed/metrics/trends

Returns daily incident and case creation counts over time.

Parameters:

Parameter Type Description
entity_name string (Optional) Filter by entity/site. Master keys only — scoped keys ignore this.
days integer Number of days to look back (default: 30, max: 90)

Example:

curl -H "X-API-Key: smk_abc12345..." \
     "https://rhythmx.example.com/api/v1/feed/metrics/trends?days=7"

Response:

{
  "success": true,
  "data": {
    "daily": [
      { "date": "2026-03-19", "incidents": 5, "cases": 2 },
      { "date": "2026-03-18", "incidents": 8, "cases": 3 },
      { "date": "2026-03-17", "incidents": 3, "cases": 1 },
      { "date": "2026-03-16", "incidents": 12, "cases": 4 },
      { "date": "2026-03-15", "incidents": 6, "cases": 2 },
      { "date": "2026-03-14", "incidents": 4, "cases": 1 },
      { "date": "2026-03-13", "incidents": 7, "cases": 3 }
    ]
  },
  "timestamp": "2026-03-20T10:30:00Z"
}


Incident Ageing

GET /api/v1/feed/metrics/incidents/aging

How long open incidents have been open — the backlog view. Every bucket is always returned, including empty ones, so a chart can be drawn without having to invent the missing categories.

Parameters:

Parameter Type Description
entity_name string (Optional) Filter by entity/site. Master keys only — scoped keys ignore this.

Response:

{
  "success": true,
  "data": {
    "buckets": [
      { "age_bucket": "0-24h", "count": 4,  "oldest": "2026-03-19T10:17:34Z", "avg_age_hours": 9.2 },
      { "age_bucket": "1-3d",  "count": 7,  "oldest": "2026-03-17T08:02:11Z", "avg_age_hours": 51.0 },
      { "age_bucket": "3-7d",  "count": 12, "oldest": "2026-03-14T11:40:02Z", "avg_age_hours": 118.4 },
      { "age_bucket": "7-30d", "count": 11, "oldest": "2026-02-20T12:08:31Z", "avg_age_hours": 533.3 },
      { "age_bucket": "30d+",  "count": 3,  "oldest": "2025-12-27T10:53:24Z", "avg_age_hours": 2586.7 }
    ],
    "open_total": 37,
    "older_than_7d": 14,
    "oldest_open": "2025-12-27T10:53:24Z",
    "avg_age_hours": 421.8
  }
}

Age is measured from created_at to now, and only incidents that are not CLOSED are counted — an ageing report is about what is still outstanding. /metrics/mttr answers the opposite question: how long closed incidents took to resolve.


Incidents by Attack Type / MITRE

GET /api/v1/feed/metrics/incidents/attack-types

Incidents grouped by attack category, with the MITRE tactic each maps to, plus a by-tactic roll-up.

Parameters:

Parameter Type Description
status string Incident status to count. Default: open only. ALL for every status.
entity_name string (Optional) Filter by entity/site. Master keys only — scoped keys ignore this.

Response:

{
  "success": true,
  "data": {
    "total_incidents": 45,
    "by_attack_type": [
      { "attack_type": "CREDENTIAL_THEFT",      "incident_count": 31, "mitre_tactic": "credential-access",    "mitre_tactic_id": "TA0006" },
      { "attack_type": "SYSTEM_BINARY_ANOMALY", "incident_count": 17, "mitre_tactic": "defense-evasion",      "mitre_tactic_id": "TA0005" },
      { "attack_type": "APT_CHAIN",             "incident_count": 3,  "mitre_tactic": "multi-stage",          "mitre_tactic_id": null }
    ],
    "by_mitre_tactic": [
      { "tactic": "credential-access", "tactic_id": "TA0006", "incident_count": 34, "attack_types": ["CREDENTIAL_THEFT", "PASSWORD_SPRAYING"] },
      { "tactic": "defense-evasion",   "tactic_id": "TA0005", "incident_count": 17, "attack_types": ["SYSTEM_BINARY_ANOMALY"] }
    ],
    "unmapped": ["APT_CHAIN"]
  }
}

Counts sum above total_incidents — by design

An incident can carry several attack categories, so it is counted once per distinct category. total_incidents is the unduplicated figure. Categories with no MITRE equivalent (multi-stage patterns such as APT_CHAIN) are listed under unmapped.

This is incident-level attribution, derived from the attack categories the correlation engine assigns to each incident. For tactic counts at the individual alert level, use MITRE ATT&CK Tactics below.


MITRE ATT&CK Tactics

GET /api/v1/feed/mitre/tactics

Returns alert counts grouped by MITRE ATT&CK tactic.

Parameters:

Parameter Type Description
entity_name string (Optional) Filter by entity/site. Master keys only — scoped keys ignore this.
limit integer Results per page (default: 50, max: 500)
offset integer Pagination offset (default: 0)

Response:

{
  "success": true,
  "data": [
    { "tactic": "credential_access", "count": 4521 },
    { "tactic": "lateral_movement", "count": 3102 },
    { "tactic": "execution", "count": 2890 },
    { "tactic": "persistence", "count": 2145 },
    { "tactic": "defense_evasion", "count": 1876 }
  ],
  "pagination": {
    "total": 11,
    "limit": 50,
    "offset": 0,
    "has_more": false
  },
  "timestamp": "2026-03-20T10:30:00Z"
}


MITRE ATT&CK Techniques

GET /api/v1/feed/mitre/techniques

Returns alert counts grouped by MITRE ATT&CK technique.

Parameters:

Parameter Type Description
entity_name string (Optional) Filter by entity/site. Master keys only — scoped keys ignore this.
limit integer Results per page (default: 50, max: 500)
offset integer Pagination offset (default: 0)

Response:

{
  "success": true,
  "data": [
    { "technique": "T1003", "count": 2341 },
    { "technique": "T1059", "count": 1892 },
    { "technique": "T1021", "count": 1654 },
    { "technique": "T1053", "count": 1230 },
    { "technique": "T1078", "count": 987 }
  ],
  "pagination": {
    "total": 45,
    "limit": 50,
    "offset": 0,
    "has_more": false
  },
  "timestamp": "2026-03-20T10:30:00Z"
}


List Entities

GET /api/v1/feed/entities

Returns all entities (sites/customers) with their incident, case, and alert counts.

Parameters:

Parameter Type Description
limit integer Results per page (default: 50, max: 500)
offset integer Pagination offset (default: 0)

Response:

{
  "success": true,
  "data": [
    {
      "entity_name": "Primary Site",
      "incident_count": 215,
      "case_count": 52,
      "alert_count": 28000
    },
    {
      "entity_name": "Branch Office",
      "incident_count": 127,
      "case_count": 37,
      "alert_count": 17832
    }
  ],
  "pagination": {
    "total": 2,
    "limit": 50,
    "offset": 0,
    "has_more": false
  },
  "timestamp": "2026-03-20T10:30:00Z"
}


Entity Risk

GET /api/v1/feed/entities/risk

Per-entity risk roll-up — the overall risk score plus the counts behind it. This is the endpoint for a dashboard's top-line KPI row.

Parameters:

Parameter Type Description
entity_name string (Optional) Filter by entity/site. Master keys only — scoped keys ignore this.
limit integer Results per page (default: 50, max: 500)
offset integer Pagination offset (default: 0)

Response:

{
  "success": true,
  "data": [
    {
      "entity_name": "Primary Site",
      "risk_score": 88,
      "risk_level": "critical",
      "avg_actor_risk": 24.5,
      "incidents": 210,
      "open_incidents": 45,
      "risky_users": 32,
      "risky_computers": 6,
      "critical_actors": 15,
      "high_actors": 23,
      "total_actors": 1450,
      "last_activity": "2026-03-20T06:24:36Z"
    }
  ]
}

Entity risk is the MAX actor risk, not the average

One actor at 95 is precisely what the score exists to surface; averaging it against thousands of quiet actors would bury it. avg_actor_risk is returned alongside so you can show both. risky_users and risky_computers count actors at critical or high risk level — not every actor that happens to have an alert.


Entity Summary

GET /api/v1/feed/entities/{entity_name}/summary

Returns a detailed summary for a specific entity.

Example:

curl -H "X-API-Key: smk_abc12345..." \
     "https://rhythmx.example.com/api/v1/feed/entities/Primary%20Site/summary"

Response:

{
  "success": true,
  "data": {
    "entity_name": "Primary Site",
    "incidents": {
      "total": 215,
      "open": 18,
      "in_progress": 8,
      "closed": 180,
      "false_positive": 9
    },
    "cases": {
      "total": 52,
      "critical": 2,
      "high": 12,
      "medium": 25,
      "low": 13
    },
    "alerts": {
      "total": 28000,
      "critical": 82,
      "high": 1200
    },
    "top_rules": [
      { "title": "Remote Access Tool Execution", "count": 2800 },
      { "title": "Suspicious PowerShell Command", "count": 1950 }
    ],
    "top_users": [
      { "user_id": "admin", "alert_count": 450, "total_risk": 12500 },
      { "user_id": "svc_backup", "alert_count": 320, "total_risk": 8900 }
    ],
    "top_computers": [
      { "computer_name": "DC01", "alert_count": 890, "total_risk": 24000 },
      { "computer_name": "WS-PC42", "alert_count": 650, "total_risk": 18000 }
    ]
  },
  "timestamp": "2026-03-20T10:30:00Z"
}


Top Risky Actors

GET /api/v1/feed/risks/actors

Returns the highest-risk users, computers and IPs ranked by the risk score the platform itself computes — a 0–100 score from seven weighted factors, the same number used internally. This is the endpoint to build a "Top Risky Users" or "Top Risky Computers" panel on.

Parameters:

Parameter Type Description
actor_type string user, host or ip. Omit for all.
risk_level string critical, high, medium, low
min_risk number Only actors scoring at or above this (0–100)
entity_name string (Optional) Filter by entity/site. Master keys only — scoped keys ignore this.
limit integer Results per page (default: 50, max: 500)
offset integer Pagination offset (default: 0)

Response:

{
  "success": true,
  "data": [
    {
      "actor": "jsmith",
      "actor_type": "user",
      "entity_name": "Primary Site",
      "risk_score": 88,
      "risk_level": "critical",
      "activity": {
        "alarms_total": 12, "alarms_7d": 4, "alarms_24h": 1,
        "critical_alarms": 2, "high_alarms": 5, "max_rbp": 75,
        "sigma_alerts": 240, "threat_cases": 18,
        "unique_rules": 8, "unique_tactics": 6, "unique_techniques": 11
      },
      "risk_factors": {
        "threat_cases": 95.0, "sigma_alerts": 88.0, "volume": 76.5,
        "velocity": 82.0, "diversity": 71.4, "mitre": 60.0, "recency": 90.0
      },
      "first_seen": "2026-03-01T14:37:20Z",
      "last_seen": "2026-03-20T10:34:13Z"
    }
  ],
  "pagination": { "total": 1450, "limit": 50, "offset": 0, "has_more": true }
}

Show the factors, not just the score

risk_factors carries the seven weighted components behind risk_score. Surfacing them — on hover, or in a drill-down — lets an analyst see why an actor scores what it does. A bare number invites "why 92?" and is hard to defend in a review.


Actor Activity (drill-down)

GET /api/v1/feed/risks/actors/{actor}/activity

Everything observed for one actor: risk, LogRhythm alarms, RhythmX Sigma alerts, threat cases and incidents. This is the drill-down behind a top-risky-actor row.

Parameters:

Parameter Type Description
entity_name string Required for master keys. Scoped keys supply it automatically.
actor_type string user, host or ip. Inferred if omitted.
days integer Look-back window (default: 30, max: 365)
limit integer Max records per section (default: 100, max: 500)

An actor is only unique within an entity

The same account name — administrator, svc_backup — exists in many entities, and they are different accounts. A master key must pass entity_name; without it the request returns 400 rather than a response that merges tenants.

Response:

{
  "success": true,
  "actor": {
    "actor": "jsmith", "actor_type": "user", "entity_name": "Primary Site",
    "risk_score": 88, "risk_level": "critical",
    "first_seen": "2026-03-01T14:37:20Z", "last_seen": "2026-03-20T10:34:13Z"
  },
  "window": { "days": 30 },
  "logrhythm_alarms": { "total": 12, "rule_groups": [ /* per-rule counts */ ] },
  "sigma_alerts": { "total": 240, "unique_rules": 8, "max_risk": 95,
                    "rule_groups": [ /* per-rule counts */ ] },
  "threat_cases": { "total": 18, "cases": [ /* … */ ] },
  "incidents": { "total": 2, "incidents": [ /* … */ ] }
}


Top Risky Users (legacy)

GET /api/v1/feed/risks/top-users

Returns users ranked by cumulative alert risk — the sum of risk across their Sigma alerts.

Prefer /risks/actors

This ranks by summed alert risk, which is unbounded: an actor with many low-risk alerts can outrank one with a genuinely high score. It is kept unchanged for existing consumers. For a risk ranking that matches the platform's own, use /risks/actors.

Parameters:

Parameter Type Description
entity_name string (Optional) Filter by entity/site. Master keys only — scoped keys ignore this.
limit integer Results per page (default: 50, max: 500)
offset integer Pagination offset (default: 0)

Response:

{
  "success": true,
  "data": [
    { "user_id": "admin", "entity_name": "Primary Site", "total_risk": 12500, "alert_count": 450 },
    { "user_id": "svc_backup", "entity_name": "Primary Site", "total_risk": 8900, "alert_count": 320 },
    { "user_id": "jsmith", "entity_name": "Branch Office", "total_risk": 6200, "alert_count": 180 }
  ],
  "pagination": {
    "total": 85,
    "limit": 50,
    "offset": 0,
    "has_more": true
  },
  "timestamp": "2026-03-20T10:30:00Z"
}


Top Risky Computers (legacy)

GET /api/v1/feed/risks/top-computers

Returns computers ranked by cumulative alert risk.

Prefer /risks/actors?actor_type=host

As with /risks/top-users, this sums alert risk rather than using the platform's computed score. See /risks/actors.

Parameters:

Parameter Type Description
entity_name string (Optional) Filter by entity/site. Master keys only — scoped keys ignore this.
limit integer Results per page (default: 50, max: 500)
offset integer Pagination offset (default: 0)

Response:

{
  "success": true,
  "data": [
    { "computer_name": "DC01", "entity_name": "Primary Site", "total_risk": 24000, "alert_count": 890 },
    { "computer_name": "WS-PC42", "entity_name": "Primary Site", "total_risk": 18000, "alert_count": 650 },
    { "computer_name": "EXCH01", "entity_name": "Branch Office", "total_risk": 9500, "alert_count": 420 }
  ],
  "pagination": {
    "total": 42,
    "limit": 50,
    "offset": 0,
    "has_more": false
  },
  "timestamp": "2026-03-20T10:30:00Z"
}


Top Triggered Rules

GET /api/v1/feed/risks/top-rules

Returns the most frequently triggered detection rules.

Parameters:

Parameter Type Description
entity_name string (Optional) Filter by entity/site. Master keys only — scoped keys ignore this.
limit integer Results per page (default: 50, max: 500)
offset integer Pagination offset (default: 0)

Response:

{
  "success": true,
  "data": [
    { "title": "Remote Access Tool Execution", "rule_level": "high", "count": 4521, "description": "Detects execution of known remote access tools" },
    { "title": "Suspicious PowerShell Command", "rule_level": "medium", "count": 3102, "description": "Detects suspicious PowerShell command line patterns" },
    { "title": "Mimikatz Command Line Usage", "rule_level": "critical", "count": 127, "description": "Detects Mimikatz command line arguments" }
  ],
  "pagination": {
    "total": 156,
    "limit": 50,
    "offset": 0,
    "has_more": true
  },
  "timestamp": "2026-03-20T10:30:00Z"
}



Entity Filtering (Master Keys)

Master keys (entity_name=*) can filter by entity on any endpoint:

# All entities
curl -H "X-API-Key: smk_master_key..." \
     "https://rhythmx.example.com/api/v1/feed/incidents"

# Specific entity
curl -H "X-API-Key: smk_master_key..." \
     "https://rhythmx.example.com/api/v1/feed/incidents?entity_name=Primary%20Site"

# Scoped keys ignore the entity_name parameter
curl -H "X-API-Key: smk_customer_key..." \
     "https://rhythmx.example.com/api/v1/feed/incidents"
# ^ Always returns only that customer's data

?entity_name= is accepted on every list, metrics, MITRE and risk endpoint. It is not a security boundary — see Key Types.

Entity name matching is case-insensitive. PRIMARY SITE, Primary Site and primary site all select the same entity, so you do not need to know the stored casing to filter.

Normalise casing when consuming responses

The entity_name returned in a response reflects how it is stored, and that differs by data source: incident and entity records return the canonical form, while alert, case and risk records return it lowercased. Compare entity names case-insensitively rather than with an exact string match, or records for one entity may appear to belong to two.

Use /entities to discover valid entity names.


Error Codes

Code Meaning
200 Success
400 Bad request (missing required parameter)
401 Invalid, expired, or disabled API key
403 Entity not permitted for this key (see Entity Summary)
404 Resource not found, or the resource belongs to another entity
429 Rate limit exceeded
500 Internal server error

Error Response:

{
  "error": "Invalid or disabled API key"
}

404 on another entity's resource

Fetching an incident, case or alert by ID that belongs to a different entity returns 404, not 403 — the record is simply not visible to that key. A 404 therefore means "not found for you", which is intentional: it avoids confirming that an ID exists under another entity.


Quick Reference

Endpoint Method Description
/api/v1/feed/health GET Service health check
/api/v1/feed/incidents GET List incidents
/api/v1/feed/incidents/{id} GET Incident detail
/api/v1/feed/incidents/{id}/related GET Incident + related LogRhythm alarms, Sigma alerts & cases
/api/v1/feed/cases GET List threat cases
/api/v1/feed/cases/{id} GET Case detail
/api/v1/feed/alerts GET List RhythmX alerts
/api/v1/feed/alerts/{id} GET Alert detail
/api/v1/feed/alerts/stats GET Alert statistics
/api/v1/feed/metrics/overview GET SOC overview metrics
/api/v1/feed/metrics/severity GET Severity/status breakdown
/api/v1/feed/metrics/mttr GET Mean time to respond
/api/v1/feed/metrics/trends GET Daily incident/case trends
/api/v1/feed/metrics/incidents/severity GET Incidents by severity & status
/api/v1/feed/metrics/incidents/aging GET Open-incident backlog by age
/api/v1/feed/metrics/incidents/attack-types GET Incidents by attack type / MITRE tactic
/api/v1/feed/mitre/tactics GET MITRE ATT&CK tactics
/api/v1/feed/mitre/techniques GET MITRE ATT&CK techniques
/api/v1/feed/entities GET List entities with counts
/api/v1/feed/entities/risk GET Per-entity risk roll-up
/api/v1/feed/entities/{name}/summary GET Entity detail summary
/api/v1/feed/risks/actors GET Top risky actors by computed risk score
/api/v1/feed/risks/actors/{actor}/activity GET All activity observed for one actor
/api/v1/feed/risks/top-users GET Top users by cumulative alert risk (legacy)
/api/v1/feed/risks/top-computers GET Top computers by cumulative alert risk (legacy)
/api/v1/feed/risks/top-rules GET Top triggered rules

API Key Management

API keys are managed by administrators through the Integration Admin interface in System Settings.

Field Description
Label Human-readable label for the key
Description Purpose of the key
Entity Scope Required. Either a single entity, or All entities (master key)
Scopes Recorded intent — not currently enforced
Rate Limit Recorded per key — not currently applied (see Rate Limiting)
Expires At Optional expiration date
Enabled Whether the key is active

Choosing an entity scope

Entity scope is a deliberate choice at creation and cannot be left blank:

  • Single entity — the key reads only that entity and cannot be re-targeted. Use this for a customer, a customer's SOC, or any third-party consumer.
  • All entities (master key) — stores *. The key reads every entity and may narrow individual requests with ?entity_name=. Use this only for your own aggregation systems; anyone holding it can read all tenants.

A single-entity key must name the entity exactly as it appears on incidents (matching is case-insensitive). A name that matches no entity is not rejected — the key authenticates normally and simply returns empty results, which is a common cause of "my key returns nothing". Confirm the name against /entities before issuing the key.

Attempting to create a key without an entity scope returns:

{
  "error": "entity_name is required",
  "detail": "Set entity_name to the entity this key may read, or to '*' to create a master key that can read all entities and be narrowed per-request with ?entity_name=."
}

Re-scoping an existing key

A key's entity scope can be changed in place, which keeps the existing secret valid — useful for correcting a key's scope without coordinating a rotation with whoever is using it:

curl -X PATCH -H "Authorization: Bearer <admin-jwt>" \
     -H "Content-Type: application/json" \
     -d '{"entity_name": "CustomerA"}' \
     "https://rhythmx.example.com/api/api-keys/<key_id>"

The same rule applies — a blank value is rejected. Use * to widen a key to all entities, or an entity name to restrict it.