Skip to content

Rapid Response API

The NodeZero GraphQL API gives you programmatic access to Rapid Response data and actions. You can retrieve advisories, list affected assets, inspect event timelines, annotate assets with remediation status, and export host lists — all without touching the Portal UI.

Requirements

All examples below use the H3_API_URL environment variable:

export H3_API_URL=https://api.gateway.horizon3ai.com/v1/graphql
export H3_API_URL=https://api.gateway.horizon3ai.eu/v1/graphql

List Rapid Response Advisories

rapid_responses_page returns a paginated list of advisories your organization has access to, including current status, vulnerability metadata, and per-advisory asset status breakdowns.

curl \
  -X POST $H3_API_URL \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $H3_API_JWT" \
  -d @- <<HERE
{
  "query": "
    query ListRapidResponses(\$page_input: PageInput) {
      rapid_responses_page(page_input: \$page_input) {
        rapid_responses {
          uuid
          name
          short_name
          status
          test_available
          vendors
          products
          asset_status_counts {
            status
            count
          }
        }
        page_info {
          page_num
          page_size
        }
      }
    }",
  "variables": {
    "page_input": {
      "page_num": 1,
      "page_size": 10
    }
  }
}
HERE
import os
import requests

query = '''
    query ListRapidResponses($page_input: PageInput) {
      rapid_responses_page(page_input: $page_input) {
        rapid_responses {
          uuid
          name
          short_name
          status
          test_available
          vendors
          products
          asset_status_counts {
            status
            count
          }
        }
        page_info {
          page_num
          page_size
        }
      }
    }
'''
variables = {"page_input": {"page_num": 1, "page_size": 10}}
url = os.environ["H3_API_URL"]
headers = {"Authorization": f"Bearer {os.environ['H3_API_JWT']}"}
response = requests.post(url, headers=headers, json={"query": query, "variables": variables})
result = response.json() if response.status_code == 200 else None
Result
{
  "data": {
    "rapid_responses_page": {
      "rapid_responses": [
        {
          "uuid": "aaaa1111-bbbb-2222-cccc-dddd3333eeee",
          "name": "CVE-2024-12345: Example Vendor Remote Code Execution",
          "short_name": "CVE-2024-12345",
          "status": "IN_PROGRESS",
          "test_available": true,
          "vendors": ["Example Vendor"],
          "products": ["Example Product 1.x"],
          "asset_status_counts": [
            { "status": "EXPLOITABLE", "count": 3 },
            { "status": "MITIGATED", "count": 1 }
          ]
        }
      ],
      "page_info": {
        "page_num": 1,
        "page_size": 10
      }
    }
  }
}

List Affected Assets

rapid_response_assets_page returns one row per affected host per advisory. Filter by rapid_response_uuid to scope results to a specific advisory. Each row includes the current exposure status, when the window opened, and any linked ticket or annotation.

Exposure statuses: EXPLOITABLE, REGRESSED, POTENTIALLY_RELEVANT, VULNERABLE, MITIGATED, NOT_EXPLOITABLE

curl \
  -X POST $H3_API_URL \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $H3_API_JWT" \
  -d @- <<HERE
{
  "query": "
    query ListRRAssets(\$page_input: PageInput) {
      rapid_response_assets_page(page_input: \$page_input) {
        rapid_response_assets {
          uuid
          asset_name
          exposure_status
          status_changed_at
          is_external
          exposure_opened_at
          exposure_mitigated_at
          last_annotation_status
          last_ticket_uuid
        }
        page_info {
          page_num
          page_size
        }
      }
    }",
  "variables": {
    "page_input": {
      "page_num": 1,
      "page_size": 25,
      "filter_by_inputs": [
        {
          "field_name": "rapid_response_uuid",
          "values": ["<rapid-response-uuid>"]
        }
      ]
    }
  }
}
HERE
import os
import requests

query = '''
    query ListRRAssets($page_input: PageInput) {
      rapid_response_assets_page(page_input: $page_input) {
        rapid_response_assets {
          uuid
          asset_name
          exposure_status
          status_changed_at
          is_external
          exposure_opened_at
          exposure_mitigated_at
          last_annotation_status
          last_ticket_uuid
        }
        page_info {
          page_num
          page_size
        }
      }
    }
'''
variables = {
    "page_input": {
        "page_num": 1,
        "page_size": 25,
        "filter_by_inputs": [
            {"field_name": "rapid_response_uuid", "values": ["<rapid-response-uuid>"]}
        ]
    }
}
url = os.environ["H3_API_URL"]
headers = {"Authorization": f"Bearer {os.environ['H3_API_JWT']}"}
response = requests.post(url, headers=headers, json={"query": query, "variables": variables})
result = response.json() if response.status_code == 200 else None
Result
{
  "data": {
    "rapid_response_assets_page": {
      "rapid_response_assets": [
        {
          "uuid": "bbbb2222-cccc-3333-dddd-eeee4444ffff",
          "asset_name": "10.0.1.42",
          "exposure_status": "EXPLOITABLE",
          "status_changed_at": "2024-01-15T08:30:00Z",
          "is_external": false,
          "exposure_opened_at": "2024-01-15T08:30:00Z",
          "exposure_mitigated_at": null,
          "last_annotation_status": null,
          "last_ticket_uuid": null
        }
      ],
      "page_info": {
        "page_num": 1,
        "page_size": 25
      }
    }
  }
}

View the Advisory Timeline

rapid_response_events_page returns the RR-level timeline — one entry per lifecycle event (advisory published, pentest completed, ticket linked, annotation added, etc.).

curl \
  -X POST $H3_API_URL \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $H3_API_JWT" \
  -d @- <<HERE
{
  "query": "
    query RRTimeline(\$page_input: PageInput) {
      rapid_response_events_page(page_input: \$page_input) {
        rapid_response_events {
          uuid
          event_type
          occurred_at
          op_id
          user_account_uuid
          ticket_uuid
        }
        page_info {
          page_num
        }
      }
    }",
  "variables": {
    "page_input": {
      "page_num": 1,
      "page_size": 25,
      "filter_by_inputs": [
        {
          "field_name": "rapid_response_uuid",
          "values": ["<rapid-response-uuid>"]
        }
      ],
      "order_by": "occurred_at",
      "sort_order": "DESC"
    }
  }
}
HERE
import os
import requests

query = '''
    query RRTimeline($page_input: PageInput) {
      rapid_response_events_page(page_input: $page_input) {
        rapid_response_events {
          uuid
          event_type
          occurred_at
          op_id
          user_account_uuid
          ticket_uuid
        }
        page_info {
          page_num
        }
      }
    }
'''
variables = {
    "page_input": {
        "page_num": 1,
        "page_size": 25,
        "filter_by_inputs": [
            {"field_name": "rapid_response_uuid", "values": ["<rapid-response-uuid>"]}
        ],
        "order_by": "occurred_at",
        "sort_order": "DESC"
    }
}
url = os.environ["H3_API_URL"]
headers = {"Authorization": f"Bearer {os.environ['H3_API_JWT']}"}
response = requests.post(url, headers=headers, json={"query": query, "variables": variables})
result = response.json() if response.status_code == 200 else None
Result
{
  "data": {
    "rapid_response_events_page": {
      "rapid_response_events": [
        {
          "uuid": "cccc3333-dddd-4444-eeee-ffff5555aaaa",
          "event_type": "NODEZERO_PENTEST",
          "occurred_at": "2024-01-15T08:30:00Z",
          "op_id": "abcdef12-3456-7890-abcd-ef1234567890",
          "user_account_uuid": null,
          "ticket_uuid": null
        },
        {
          "uuid": "dddd4444-eeee-5555-ffff-aaaa6666bbbb",
          "event_type": "VENDOR_ADVISORY",
          "occurred_at": "2024-01-10T12:00:00Z",
          "op_id": null,
          "user_account_uuid": null,
          "ticket_uuid": null
        }
      ],
      "page_info": {
        "page_num": 1
      }
    }
  }
}

View Per-Asset History

rapid_response_asset_events_page returns the history for a single asset — every pentest result, annotation, ticket link, and CSV download that affected it. Filter by rapid_response_asset_uuid to retrieve one asset's full timeline.

curl \
  -X POST $H3_API_URL \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $H3_API_JWT" \
  -d @- <<HERE
{
  "query": "
    query AssetHistory(\$page_input: PageInput) {
      rapid_response_asset_events_page(page_input: \$page_input) {
        rapid_response_asset_events {
          uuid
          event_type
          status
          prior_status
        }
        page_info {
          page_num
        }
      }
    }",
  "variables": {
    "page_input": {
      "page_num": 1,
      "page_size": 25,
      "filter_by_inputs": [
        {
          "field_name": "rapid_response_asset_uuid",
          "values": ["<rapid-response-asset-uuid>"]
        }
      ],
      "order_by": "occurred_at",
      "sort_order": "DESC"
    }
  }
}
HERE
import os
import requests

query = '''
    query AssetHistory($page_input: PageInput) {
      rapid_response_asset_events_page(page_input: $page_input) {
        rapid_response_asset_events {
          uuid
          event_type
          status
          prior_status
        }
        page_info {
          page_num
        }
      }
    }
'''
variables = {
    "page_input": {
        "page_num": 1,
        "page_size": 25,
        "filter_by_inputs": [
            {"field_name": "rapid_response_asset_uuid", "values": ["<rapid-response-asset-uuid>"]}
        ],
        "order_by": "occurred_at",
        "sort_order": "DESC"
    }
}
url = os.environ["H3_API_URL"]
headers = {"Authorization": f"Bearer {os.environ['H3_API_JWT']}"}
response = requests.post(url, headers=headers, json={"query": query, "variables": variables})
result = response.json() if response.status_code == 200 else None
Result
{
  "data": {
    "rapid_response_asset_events_page": {
      "rapid_response_asset_events": [
        {
          "uuid": "eeee5555-ffff-6666-aaaa-bbbb7777cccc",
          "event_type": "NODEZERO_PENTEST",
          "status": "EXPLOITABLE",
          "prior_status": null
        }
      ],
      "page_info": {
        "page_num": 1
      }
    }
  }
}

Add a Note or Mark Remediation Status

apply_annotations_to_rapid_response_assets lets you add a note or assign a remediation status to one or more assets. This is the primary "take action" mutation for Rapid Response — it records what your team did and why.

You must provide:

  • assets — a RapidResponseAssetsInput that selects which assets to annotate (by UUID, by status, or across the whole advisory)
  • status — one of: TO_DO, FIXED, RISK_ACCEPTED, COMPENSATING_CONTROL, VERIFY_COMPENSATING_CONTROL, POTENTIAL_FALSE_POSITIVE, MUTE, CLOSED
  • text — optional free-text note; required when status is RISK_ACCEPTED

Each call creates a new annotation event visible in each affected asset's timeline.

curl \
  -X POST $H3_API_URL \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $H3_API_JWT" \
  -d @- <<HERE
{
  "query": "
    mutation AnnotateRRAssets(\$input: ApplyAnnotationsToRapidResponseAssetsInput!) {
      apply_annotations_to_rapid_response_assets(input: \$input) {
        annotations_count
        annotations {
          uuid
          status
          text
        }
      }
    }",
  "variables": {
    "input": {
      "assets": {
        "rapid_response_uuid": "<rapid-response-uuid>",
        "rapid_response_asset_uuids": [
          "<asset-uuid-1>",
          "<asset-uuid-2>"
        ]
      },
      "status": "FIXED",
      "text": "Patched in maintenance window 2024-01-20"
    }
  }
}
HERE
import os
import requests

query = '''
    mutation AnnotateRRAssets($input: ApplyAnnotationsToRapidResponseAssetsInput!) {
      apply_annotations_to_rapid_response_assets(input: $input) {
        annotations_count
        annotations {
          uuid
          status
          text
        }
      }
    }
'''
variables = {
    "input": {
        "assets": {
            "rapid_response_uuid": "<rapid-response-uuid>",
            "rapid_response_asset_uuids": [
                "<asset-uuid-1>",
                "<asset-uuid-2>"
            ]
        },
        "status": "FIXED",
        "text": "Patched in maintenance window 2024-01-20"
    }
}
url = os.environ["H3_API_URL"]
headers = {"Authorization": f"Bearer {os.environ['H3_API_JWT']}"}
response = requests.post(url, headers=headers, json={"query": query, "variables": variables})
result = response.json() if response.status_code == 200 else None
Result
{
  "data": {
    "apply_annotations_to_rapid_response_assets": {
      "annotations_count": 2,
      "annotations": [
        {
          "uuid": "ffff6666-aaaa-7777-bbbb-cccc8888dddd",
          "status": "FIXED",
          "text": "Patched in maintenance window 2024-01-20"
        },
        {
          "uuid": "89018901-8901-8901-8901-890189018901",
          "status": "FIXED",
          "text": "Patched in maintenance window 2024-01-20"
        }
      ]
    }
  }
}

Annotate by status instead of by UUID

To annotate all EXPLOITABLE assets on an advisory at once, use rapid_response_asset_statuses instead of listing individual UUIDs:

"assets": {
  "rapid_response_uuid": "<rapid-response-uuid>",
  "rapid_response_asset_statuses": ["EXPLOITABLE"]
}

Download Affected Assets as CSV

rapid_response_asset_csv_presigned_url returns a presigned S3 URL for downloading a CSV of selected assets. The CSV is not returned inline — use the URL to fetch the file directly. Every download is recorded as an event on each affected asset's timeline.

curl \
  -X POST $H3_API_URL \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $H3_API_JWT" \
  -d @- <<HERE
{
  "query": "
    query RRAssetsCsv(\$rapid_response_assets: RapidResponseAssetsInput!) {
      rapid_response_asset_csv_presigned_url(rapid_response_assets: \$rapid_response_assets)
    }",
  "variables": {
    "rapid_response_assets": {
      "rapid_response_uuid": "<rapid-response-uuid>",
      "rapid_response_asset_statuses": ["EXPLOITABLE", "REGRESSED"]
    }
  }
}
HERE
import os
import requests

query = '''
    query RRAssetsCsv($rapid_response_assets: RapidResponseAssetsInput!) {
      rapid_response_asset_csv_presigned_url(
        rapid_response_assets: $rapid_response_assets
      )
    }
'''
variables = {
    "rapid_response_assets": {
        "rapid_response_uuid": "<rapid-response-uuid>",
        "rapid_response_asset_statuses": ["EXPLOITABLE", "REGRESSED"]
    }
}
url = os.environ["H3_API_URL"]
headers = {"Authorization": f"Bearer {os.environ['H3_API_JWT']}"}
response = requests.post(url, headers=headers, json={"query": query, "variables": variables})
presigned_url = response.json()["data"]["rapid_response_asset_csv_presigned_url"]

# Fetch the CSV
csv_response = requests.get(presigned_url)
with open("rr_assets.csv", "wb") as f:
    f.write(csv_response.content)
Result
{
  "data": {
    "rapid_response_asset_csv_presigned_url": "https://s3.amazonaws.com/h3-exports/rapid-response-assets.csv?X-Amz-Signature=..."
  }
}