SpecterOps Attack Paths API
The Attack Paths API from SpecterOps — 10 operation(s) for attack paths.
The Attack Paths API from SpecterOps — 10 operation(s) for attack paths.
openapi: 3.0.3
info:
title: BloodHound AD Base Entities Attack Paths API
contact:
name: BloodHound Enterprise Support
url: https://bloodhound.specterops.io/
email: support@specterops.io
license:
name: Apache-2.0
url: https://www.apache.org/licenses/LICENSE-2.0
version: v2
description: "This is the API that drives BloodHound Enterprise and Community Edition.\nEndpoint availability is denoted using the `Community` and `Enterprise` tags.\n\nContact information listed is for BloodHound Enterprise customers. To get help with\nBloodHound Community Edition, please join our\n[Slack community](https://ghst.ly/BHSlack/).\n\n## Authentication\n\nThe BloodHound API supports two kinds of authentication: JWT bearer tokens and Signed Requests.\nFor quick tests or one-time calls, the JWT used by your browser may be the simplest route. For\nmore secure and long lived API integrations, the recommended option is signed requests.\n\n### JWT Bearer Token\n\nThe API will accept calls using the following header structure in the HTTP request:\n```\nAuthorization: Bearer $JWT_TOKEN\n```\nIf you open the Network tab within your browser, you will see calls against the API made utilizing\nthis structure. JWT bearer tokens are supported by the BloodHound API, however it is recommended\nthey only be used for temporary access. JWT tokens expire after a set amount of time and require\nre-authentication using secret credentials.\n\n### Signed Requests\n\nSigned requests are the recommended form of authentication for the BloodHound API. Not only are\nsigned requests better for long lived integrations, they also provide more security for the\nrequests being sent. They provide authentication of the client, as well as verification of request\nintegrity when received by the server.\n\nSigned requests consist of three main parts: The client token ID, the request timestamp, and a\nbase64 encoded HMAC signature. These three pieces of information are sent with the request using\nthe following header structure:\n\n```\nAuthorization: bhesignature $TOKEN_ID\nRequestDate: $RFC3339_DATETIME\nSignature: $BASE64ENCODED_HMAC_SIGNATURE\n```\n\nTo use signed requests, you will need to generate an API token. Each API token generated in the\nBloodHound API comes with two parts: The Token ID, which is used in the `Authorization` header,\nand the Token Key, which is used as part of the HMAC hashing process. The token ID should be\nconsidered as public (like a username) and the token key should be considered secret (like a\npassword). Once an API token is generated, you can use the key to sign requests.\n\nFor more documentation about how to work with authentication in the API, including examples\nof how to generate an API token in the BloodHound UI, please refer to this support doc:\n[Working with the BloodHound API](https://bloodhound.specterops.io/integrations/bloodhound-api/working-with-api).\n\n#### Signed Request Pseudo-code Example\n\nFirst, a digest is initiated with HMAC-SHA-256 using the token key as the digest key:\n```python\ndigester = hmac.new(sha256, api_token_key)\n```\n\nOperationKey is the first HMAC digest link in the signature chain. This prevents replay attacks that\nseek to modify the request method or URI. It is composed of concatenating the request method and\nthe request URI with no delimiter and computing the HMAC digest using the token key as the digest\nsecret:\n```python\n# Example: GET /api/v2/test/resource HTTP/1.1\n# Signature Component: GET/api/v2/test/resource\ndigester.write(request_method + request_uri)\n\n# Update the digester for further chaining\ndigester = hmac.New(sha256, digester.hash())\n```\n\nDateKey is the next HMAC digest link in the signature chain. This encodes the RFC3339\nformatted datetime value as part of the signature to the hour to prevent replay\nattacks that are older than max two hours. This value is added to the signature chain\nby cutting off all values from the RFC3339 formatted datetime from the hours value\nforward:\n```python\n# Example: 2020-12-01T23:59:60Z\n# Signature Component: 2020-12-01T23\nrequest_datetime = date.now()\ndigester.write(request_datetime[:13])\n\n# Update the digester for further chaining\ndigester = hmac.New(sha256, digester.hash())\n```\n\nBody signing is the last HMAC digest link in the signature chain. This encodes the\nrequest body as part of the signature to prevent replay attacks that seek to modify\nthe payload of a signed request. In the case where there is no body content the\nHMAC digest is computed anyway, simply with no values written to the digester:\n```python\nif request.body is not empty:\n digester.write(request.body)\n```\n\nFinally, base64 encode the final hash and write the three required headers before\nsending the request:\n```python\nencoded_hash = base64_encode(digester.hash())\nrequest.header.write('Authorization', 'bhesignature ' + token_id)\nrequest.header.write('RequestDate', request_datetime)\nrequest.header.write('Signature', encoded_hash)\n```\n"
servers:
- url: /
description: This is the base path for all endpoints, relative to the domain where the API is being hosted.
security:
- JWTBearerToken: []
- SignedRequest: []
RequestDate: []
HMACSignature: []
tags:
- name: Attack Paths
paths:
/api/v2/domains/{domain_id}/attack-path-findings:
parameters:
- $ref: '#/components/parameters/header.prefer'
- name: domain_id
description: Domain ID
in: path
required: true
schema:
type: string
get:
operationId: ExportAttackPathFindings
summary: Export attack path findings
description: Export the finding table for a given attack path
deprecated: true
tags:
- Attack Paths
parameters:
- name: finding
description: Finding Type
in: query
required: true
schema:
type: string
- name: filterAccepted
description: Risk acceptance filter
in: query
schema:
$ref: '#/components/schemas/enum.risk-acceptance'
- name: sort_by
description: Sort by column. The only sortable column is `finding`.
in: query
schema:
$ref: '#/components/schemas/api.params.query.sort-by'
responses:
'200':
$ref: '#/components/responses/csv-response'
'400':
$ref: '#/components/responses/bad-request'
'401':
$ref: '#/components/responses/unauthorized'
'403':
$ref: '#/components/responses/forbidden'
'404':
$ref: '#/components/responses/not-found'
'429':
$ref: '#/components/responses/too-many-requests'
'500':
$ref: '#/components/responses/internal-server-error'
/api/v2/attack-paths/finding-trends:
parameters:
- $ref: '#/components/parameters/header.prefer'
get:
operationId: FindingTrendsForEnvironment
summary: List finding trends
description: Lists findings and their changes in between two dates for an environment
tags:
- Attack Paths
parameters:
- name: environments
description: Environment IDs
in: query
required: true
schema:
type: array
items:
type: string
- name: start
description: Beginning datetime of range (inclusive) in RFC-3339 format; Defaults to current datetime minus 30 days
in: query
schema:
type: string
format: date-time
- name: end
description: Ending datetime of range (exclusive) in RFC-3339 format; Defaults to current datetime
in: query
schema:
type: string
format: date-time
- $ref: '#/components/parameters/query.asset-group-tag-id'
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/api.response.time-window'
- type: object
properties:
environments:
type: array
items:
type: string
data:
type: object
properties:
findings:
type: array
items:
type: object
properties:
environment_ids:
type: array
items:
type: string
finding:
type: string
display_title:
type: string
display_type:
type: string
composite_risk:
type: number
format: double
finding_count_start:
type: integer
finding_count_end:
type: integer
finding_count_increase:
type: integer
finding_count_decrease:
type: integer
impact_count:
type: integer
nullable: true
exposure_count:
type: integer
nullable: true
archived_count:
type: integer
description: 'Total number of findings of this type that were
archived (deleted during analysis reconciliation)
within the requested time window. Summed across
all snapshots in the window.
'
total_finding_count_start:
type: integer
total_finding_count_end:
type: integer
'400':
$ref: '#/components/responses/bad-request'
'401':
$ref: '#/components/responses/unauthorized'
'403':
$ref: '#/components/responses/forbidden'
'404':
$ref: '#/components/responses/not-found'
'429':
$ref: '#/components/responses/too-many-requests'
'500':
$ref: '#/components/responses/internal-server-error'
/api/v2/attack-path-types:
parameters:
- $ref: '#/components/parameters/header.prefer'
get:
operationId: ListAttackPathTypes
summary: List all attack path types
description: Lists all possible attack path types
tags:
- Attack Paths
parameters:
- name: sort_by
description: Sort by column. The only sortable column is `finding`.
in: query
schema:
$ref: '#/components/schemas/api.params.query.sort-by'
- name: finding
in: query
schema:
$ref: '#/components/schemas/api.params.predicate.filter.string'
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
type: string
'400':
$ref: '#/components/responses/bad-request'
'401':
$ref: '#/components/responses/unauthorized'
'404':
$ref: '#/components/responses/not-found'
'429':
$ref: '#/components/responses/too-many-requests'
'500':
$ref: '#/components/responses/internal-server-error'
/api/v2/attack-paths:
parameters:
- $ref: '#/components/parameters/header.prefer'
put:
operationId: StartAnalysisBhe
summary: Start analysis
description: Starts generating attack paths
tags:
- Attack Paths
responses:
'202':
$ref: '#/components/responses/no-content'
'401':
$ref: '#/components/responses/unauthorized'
'403':
$ref: '#/components/responses/forbidden'
'429':
$ref: '#/components/responses/too-many-requests'
'500':
$ref: '#/components/responses/internal-server-error'
/api/v2/attack-paths/details:
parameters:
- $ref: '#/components/parameters/header.prefer'
get:
operationId: GetAllAttackPathFindings
summary: Get all attack path findings
description: Fetches all attack path findings and their associated details, including documentation.
tags:
- Attack Paths
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/api.response.all-findings'
'400':
$ref: '#/components/responses/bad-request'
'401':
$ref: '#/components/responses/unauthorized'
'403':
$ref: '#/components/responses/forbidden'
'429':
$ref: '#/components/responses/too-many-requests'
'500':
$ref: '#/components/responses/internal-server-error'
/api/v2/attack-paths/findings:
parameters:
- $ref: '#/components/parameters/header.prefer'
get:
operationId: ListAttackPathFindings
summary: List attack path findings
description: 'Returns a unified, paginated list of attack path findings. Defaults to active findings of both relationship and list types unless filtered otherwise.
'
tags:
- Attack Paths
parameters:
- name: sort_by
description: 'Sortable columns are `severity`, `finding_type`, `finding`, `title`, `platform`, `environment_id`, `environment_name`,
`zone_id`, `zone_name`, `source_principal_id`, `source_principal_kind`, `source_principal_name`, `target_principal_id`, `target_principal_kind`, `target_principal_name`, `status`, `first_seen`, `last_seen`.
'
in: query
schema:
$ref: '#/components/schemas/api.params.query.sort-by'
- name: severity
description: Filter by severity level.
in: query
schema:
$ref: '#/components/schemas/api.params.predicate.filter.string'
- name: finding_type
description: Filter by finding type. When not provided, both types are returned.
in: query
schema:
$ref: '#/components/schemas/api.params.predicate.filter.string'
- name: finding
description: Filter by finding name.
in: query
schema:
$ref: '#/components/schemas/api.params.predicate.filter.string'
- name: title
description: Filter by human-readable finding title.
in: query
schema:
$ref: '#/components/schemas/api.params.predicate.filter.string'
- name: platform
description: Filter by platform.
in: query
schema:
$ref: '#/components/schemas/api.params.predicate.filter.string'
- name: environment_id
description: 'Filter by one or more environment identifiers. Repeat the parameter to include multiple environments — results are the union of all specified values.
'
in: query
style: form
explode: true
schema:
type: array
items:
type: string
- name: environment_name
description: Filter by environment display name.
in: query
schema:
$ref: '#/components/schemas/api.params.predicate.filter.string'
- name: asset_group_tag_id
description: 'Filter by one or more asset group tag identifiers. Repeat the parameter to include multiple asset group tags — results are the union of all specified values.
Cannot be combined with the `zone_id` filter.
'
in: query
style: form
explode: true
schema:
type: array
items:
type: integer
- name: zone_id
description: Filter by a single zone identifier. Cannot be combined with `asset_group_tag_id`.
in: query
schema:
$ref: '#/components/schemas/api.params.predicate.filter.string'
- name: zone_name
description: Filter by zone name (e.g. "Tier Zero").
in: query
schema:
$ref: '#/components/schemas/api.params.predicate.filter.string'
- name: source_principal_id
description: Filter by source principal identifier.
in: query
schema:
$ref: '#/components/schemas/api.params.predicate.filter.string'
- name: source_principal_kind
description: Filter by source principal kind.
in: query
schema:
$ref: '#/components/schemas/api.params.predicate.filter.string'
- name: source_principal_name
description: Filter by source principal display name.
in: query
schema:
$ref: '#/components/schemas/api.params.predicate.filter.string'
- name: target_principal_id
description: Filter by target principal identifier.
in: query
schema:
$ref: '#/components/schemas/api.params.predicate.filter.string'
- name: target_principal_kind
description: Filter by target principal kind.
in: query
schema:
$ref: '#/components/schemas/api.params.predicate.filter.string'
- name: target_principal_name
description: Filter by target principal display name.
in: query
schema:
$ref: '#/components/schemas/api.params.predicate.filter.string'
- name: status
description: Filter by finding status.
in: query
schema:
$ref: '#/components/schemas/api.params.predicate.filter.string'
- name: first_seen
description: Filter by first seen timestamp.
in: query
schema:
$ref: '#/components/schemas/api.params.predicate.filter.string'
- name: last_seen
description: Filter by last seen timestamp.
in: query
schema:
$ref: '#/components/schemas/api.params.predicate.filter.string'
- $ref: '#/components/parameters/query.skip'
- $ref: '#/components/parameters/query.limit'
responses:
'200':
description: OK
content:
text/csv:
schema:
type: string
format: binary
examples:
Unified CSV export:
value: 'Severity,Finding,Title,FindingType,Platform,EnvironmentID,EnvironmentName,ZoneID,ZoneName,SourcePrincipalID,SourcePrincipalKind,SourcePrincipalName,TargetPrincipalID,TargetPrincipalKind,TargetPrincipalName,Status,FirstSeen,LastSeen
critical,T0GenericWrite,GenericWrite Privileges on Tier Zero Objects,relationship,Active Directory,S-1-5-21-1974516972-3116780949-2584384717,CORP1.LAB.HOME-LABS.LOL,1,Tier Zero,RPATTON@CORP1.LAB.HOME-LABS.LOL,User,Robert Patton,ADMIN@CORP1.LAB.HOME-LABS.LOL,User,Domain Admin,active,2026-04-11T00:30:18Z,2026-04-14T16:57:35Z
high,Kerberoasting,Kerberoastable User Accounts,list,Active Directory,S-1-5-21-1974516972-3116780949-2584384717,CORP1.LAB.HOME-LABS.LOL,1,Tier Zero,,,, PSX_D_BA@CORP1.LAB.HOME-LABS.LOL,User,Backup Admin,accepted,2026-04-11T00:30:18Z,2026-04-14T16:57:35Z
'
application/json:
schema:
allOf:
- $ref: '#/components/schemas/api.response.pagination'
- type: object
properties:
data:
type: array
items:
type: object
properties:
severity:
type: string
description: Severity level.
enum:
- critical
- high
- moderate
- low
finding_type:
type: string
description: The type of finding.
enum:
- relationship
- list
finding:
type: string
description: Finding name.
title:
type: string
description: Human-readable finding title. May vary based on the zone context (e.g., "Tier Zero" vs "Privilege Zone" variants).
platform:
type: string
description: Platform the finding belongs to.
environment_id:
type: string
description: Environment identifier.
environment_name:
type: string
description: Human-readable environment display name resolved from the graph. Falls back to environment_id when unavailable.
zone_id:
type: integer
description: Zone identifier (asset group tag id).
zone_name:
type: string
description: Human-readable zone name. Empty when the finding is not tied to a named zone.
source_principal_id:
type: string
description: Source principal identifier. Omitted for list findings.
source_principal_kind:
type: string
description: Source principal kind. Omitted for list findings.
source_principal_name:
type: string
description: Source principal display name resolved from principal properties. Empty when unavailable or for list findings.
target_principal_id:
type: string
description: Target principal identifier.
target_principal_kind:
type: string
description: Target principal kind.
target_principal_name:
type: string
description: Target principal display name resolved from principal properties. Empty when unavailable.
status:
type: string
description: Finding status.
enum:
- active
- accepted
- remediated
- deprecated
first_seen:
type: string
format: date-time
description: Timestamp of when the finding was first created.
last_seen:
type: string
format: date-time
description: Timestamp of last update.
examples:
Mixed findings (relationship and list):
summary: Unified response with both finding types
value:
count: 2
skip: 0
limit: 10
data:
- severity: critical
finding: T0GenericWrite
title: GenericWrite Privileges on Tier Zero Objects
finding_type: relationship
platform: Active Directory
environment_id: S-1-5-21-1974516972-3116780949-2584384717
environment_name: CORP1.LAB.HOME-LABS.LOL
zone_id: 1
zone_name: Tier Zero
source_principal_id: RPATTON@CORP1.LAB.HOME-LABS.LOL
source_principal_kind: User
source_principal_name: Robert Patton
target_principal_id: ADMIN@CORP1.LAB.HOME-LABS.LOL
target_principal_kind: User
target_principal_name: Domain Admin
status: active
first_seen: '2026-04-11T00:30:18.491666Z'
last_seen: '2026-04-14T16:57:35.675609Z'
- severity: high
finding: Kerberoasting
title: Kerberoastable User Accounts
finding_type: list
platform: Active Directory
environment_id: S-1-5-21-1974516972-3116780949-2584384717
environment_name: CORP1.LAB.HOME-LABS.LOL
zone_id: 1
zone_name: Tier Zero
source_principal_id: ''
source_principal_kind: ''
source_principal_name: ''
target_principal_id: PSX_D_BA@CORP1.LAB.HOME-LABS.LOL
target_principal_kind: User
target_principal_name: Backup Admin
status: accepted
first_seen: '2026-04-11T00:30:18.491666Z'
last_seen: '2026-04-14T16:57:35.675609Z'
'400':
$ref: '#/components/responses/bad-request'
'401':
$ref: '#/components/responses/unauthorized'
'403':
$ref: '#/components/responses/forbidden'
'404':
$ref: '#/components/responses/not-found'
'429':
$ref: '#/components/responses/too-many-requests'
'500':
$ref: '#/components/responses/internal-server-error'
/api/v2/domains/{domain_id}/available-types:
parameters:
- $ref: '#/components/parameters/header.prefer'
- name: domain_id
description: Domain ID
in: path
required: true
schema:
type: string
- $ref: '#/components/parameters/query.asset-group-tag-id'
get:
operationId: ListAvailableAttackPathTypesForDomain
summary: List available attack paths
description: Lists available attack path types for a domain
tags:
- Attack Paths
parameters:
- name: sort_by
description: Sort by column. The only sortable column is `finding`.
in: query
schema:
$ref: '#/components/schemas/api.params.query.sort-by'
- name: finding
in: query
schema:
$ref: '#/components/schemas/api.params.predicate.filter.string'
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
type: string
'400':
$ref: '#/components/responses/bad-request'
'401':
$ref: '#/components/responses/unauthorized'
'403':
$ref: '#/components/responses/forbidden'
'404':
$ref: '#/components/responses/not-found'
'429':
$ref: '#/components/responses/too-many-requests'
'500':
$ref: '#/components/responses/internal-server-error'
/api/v2/domains/{domain_id}/details:
parameters:
- $ref: '#/components/parameters/header.prefer'
- name: domain_id
description: Domain, Tenant, or Environment ID
in: path
required: true
schema:
type: string
- $ref: '#/components/parameters/query.asset-group-tag-id'
get:
operationId: ListDomainAttackPathsDetails
summary: List domain attack paths details
description: 'Lists detailed data about attack paths for a domain.
__Note:__ `ImpactCount`, `ImpactPercentage`, `ExposureCount`, `ExposurePercentage` and `Severity` will have a value other than zero when butterfly analysis is enabled.
'
tags:
- Attack Paths
parameters:
- name: Accept
in: query
description: Media type to determine the response format (application/json or text/csv).
required: false
schema:
$ref: '#/components/schemas/api.params.predicate.filter.string'
enum:
- application/json
- text/csv
- name: finding
in: query
required: true
schema:
$ref: '#/components/schemas/api.params.predicate.filter.string'
- name: sort_by
description: Sortable columns are `domain_sid`, `index`, `AcceptedUntil`, `id`, `created_at`, `updated_at`, `deleted_at`, `exposure_percentage`, `impact_percentage`. Relationship risks can be sorted on `FromPrincipal` and `ToPrincipal` in addition to the sortable columns for List Risks.
in: query
schema:
$ref: '#/components/schemas/api.params.query.sort-by'
- name: FromPrincipal
deprecated: true
in: query
schema:
$ref: '#/components/schemas/api.params.predicate.filter.string'
- name: ToPrincipal
deprecated: true
in: query
schema:
$ref: '#/components/schemas/api.params.predicate.filter.string'
- name: from_principal
in: query
schema:
$ref: '#/components/schemas/api.params.predicate.filter.string'
- name: to_principal
in: query
schema:
$ref: '#/components/schemas/api.params.predicate.filter.string'
- name: principals_hash
in: query
schema:
$ref: '#/components/schemas/api.params.predicate.filter.string'
- name: Accepted
deprecated: true
in: query
schema:
$ref: '#/components/schemas/api.params.predicate.filter.string'
- name: AcceptedUntil
deprecated: true
in: query
schema:
$ref: '#/components/schemas/api.params.predicate.filter.time'
- name: accepted_until
in: query
schema:
$ref: '#/components/schemas/api.params.predicate.filter.time'
- name: Principal
in: query
schema:
$ref: '#/components/schemas/api.params.predicate.filter.string'
- name: domain_sid
in: query
schema:
$ref: '#/components/schemas/api.params.predicate.filter.string'
- name: id
in: query
schema:
$ref: '#/components/schemas/api.params.predicate.filter.integer'
- $ref: '#/components/parameters/query.created-at'
- $ref: '#/components/parameters/query.updated-at'
- $ref: '#/components/parameters/query.deleted-at'
- $ref: '#/components/parameters/query.skip'
- $ref: '#/components/parameters/query.limit'
enum:
- application/json
- text/csv
responses:
'200':
description: OK
content:
text/csv:
schema:
type: string
format: binary
examples:
Butterfly Relationship Finding:
value: 'id,finding_type,exposure_count,exposure_percentage,impact_count,impact_percentage,domain_sid,from_principal_name,from_principal,from_principal_kind,to_principal_name,to_principal,to_principal_kind,combo_graph_relation_id,domain,accepted,accepted_until,principals_hash,created_at,updated_at,is_inherited
1,Tier Zero Generic Write,500,0.6711,1000,0.9961,5,S-1-5-21-123-123-123,RPATTON@TITANCORP.LOCAL,S-1-5-21-123-123-123,User,RPATTON DELEGATION TEST@TITANCORP.LOCAL,S-1-5-21-124-124-124,User,2,TITANCORP,true,0001-01-01T00:00:00Z,1lQiVG3hOuM=,2022-08-17T23:44:24Z,2024-12-18T15:01:00.176572729Z,Yes
'
Metatree Relationship Finding:
value: 'id,finding_type,exposure_count,exposure_percentage,impact_count,impact_percentage,domain_sid,from_pr
# --- truncated at 32 KB (62 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/specterops/refs/heads/main/openapi/specterops-attack-paths-api-openapi.yml