← Files CrowdStrike Falcon FusionARCHIVED FILE

skills/authoring/references/http-actions.md

12.4 KB · Oct 2, 2026 · 00:31 UTC

↓ Download file

# HTTP Action reference

HTTP Actions call REST APIs directly from a workflow — no Falcon Foundry app
required. They cover the majority of API integrations: threat-intel enrichment,
notifications, and data lookups. All three types share `class: Inline.HTTPRequest`
and `version_constraint: ~1`.

## The three types

| Type | Use for | Network | Auth |
|------|---------|---------|------|
| Cloud HTTP Request | External internet APIs (VirusTotal, Slack, Microsoft Graph) | Public internet from the Falcon platform | API key, Basic, OAuth 2.0 |
| CrowdStrike HTTP Request | Falcon platform APIs | CrowdStrike endpoints (absolute, region host) | OAuth credential required (`config_id`) |
| On-Premises HTTP Request | Internal APIs behind a firewall | Via a **static** host group (dynamic not supported; ≤20 hosts) | API key, Basic, OAuth |

All three serialize to `class: Inline.HTTPRequest`. The type is a console-side
distinction; discover the exact action ID with `action_search.py --search "HTTP"`.

## The `http_transaction` block

Every HTTP Action carries an `http_transaction` map under `properties`:

```yaml
MyRequest:
  id: <action id from action_search.py>
  class: Inline.HTTPRequest
  name: Cloud HTTP Request - VirusTotal IP
  version_constraint: ~1
  next:
    - NextNode
  properties:
    # No credential here by default — configure it in the console after deploy.
    # See Authentication below.
    http_transaction:
      request_http_method: GET             # GET | POST | PUT | DELETE | ...
      request_url: https://www.virustotal.com/api/v3/ip_addresses/${data['WorkflowCustomVariable.ip']}
      request_content_type: NONE           # NONE | JSON
      request_headers: {}
      request_body: '{}'                   # present for POST/PUT; '{}' when none
```

- **`request_url`** supports `${...}` variable injection (trigger inputs,
  `${data['...']}` CEL refs, prior action outputs). Variables resolve at
  execution, not at test time.
- **`request_content_type`** is `NONE` for GET with no body, `JSON` when sending
  a body.
- **`request_query`** (optional) is the console "Query" tab: a UUID-keyed map of
  `{name, value}` pairs, encoded into the query string for you.

  ```yaml
      request_query:
        c373ed43-231d-4141-ba4e-c0214b9587bb:
          name: $select
          value: displayName,mail,jobTitle,department
  ```

## Authentication

**Secrets never appear in the workflow YAML.** The API key or client secret lives
in a credential configuration created in the Falcon console (CID-specific). The
YAML at most references that config by `definition_id`.

**Default: author credential-less, configure in the console after deploy.** For a
fresh integration (VirusTotal, DomainTools, etc.) you almost never have a
`definition_id` at authoring time, and you should NOT invent one. Author the HTTP
Action with **no** `definition_id` and **no** `authentication_option` — the
imported action simply shows Authentication = "None" in the console, which is a
valid, editable starting point. This is proven: a credential-less HTTP Action
imports cleanly, passes validation, renders in the console, and runs once you
attach a credential there. Then tell the user the console steps:

> In the workflow, open the Cloud HTTP Request action → **Authentication** →
> **Create new** → Configuration name: (e.g. `VirusTotal`) → Authentication type:
> **API key** → API secret key: `<your key>` → API key location: **Header** →
> Header name: `x-apikey` (per the API's docs) → **Test** → **Schema builder** →
> Save. (If a matching credential already exists, pick **Use existing** instead.)

**Never fabricate a `definition_id`.** A token like
`VIRUSTOTAL_CREDENTIAL_CONFIG_ID` or any non-hex placeholder is a broken
reference that dead-ends the user — worse than omitting it. `validate.py` flags a
placeholder `definition_id` on an HTTP Action. If you don't have a real 32-char
hex id, leave `definition_id` out entirely (credential-less, above).

**Reference an existing credential config only when the user supplies a real id.**
A real `definition_id` is 32 hex chars, CID-specific, and only exists once the
integration is configured in the console:

```yaml
    authentication_option: UseExisting
    definition_id: 7227ab386bd646c18b27716e8fff8d26   # real hex, user-supplied
```

**Attach-a-new-key shape** (what the console writes when you configure an API key
on the action) — `CreateNew` plus the header fields:

```yaml
    authentication_option: CreateNew
    api_key_header_label: x-apikey
    api_key_location: Header
    definition_id: 7227ab386bd646c18b27716e8fff8d26   # written by the console
```

**OAuth 2.0 (client credentials)** — e.g. Microsoft Graph, Okta:

```yaml
    authentication_option: CreateNew        # or UseExisting for a saved config
    definition_id: 662c4828b3804ad287acc7fc3cd9895b
    oauth_token_url: https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token
    oauth_scopes:
      - https://graph.microsoft.com/.default
```

`definition_id` values are **CID-specific** — never invent one. Prefer authoring
credential-less and configuring in the console; only set `definition_id` to a real
hex id the user provides. See the Console-Credential Boundary section in `SKILL.md`.

## CrowdStrike HTTP Request

Calls Falcon platform APIs. Verified live end-to-end (built, published, executed
in the console); see the worked example
`examples/tutorials/crowdstrike-http-request-falcon-api.yaml`. Four things the
console enforces that are easy to get wrong:

- **`request_url` is ABSOLUTE and region-specific** — e.g.
  `https://api.us-2.crowdstrike.com/alerts/queries/alerts/v2` (US-1 =
  `api.crowdstrike.com`, EU-1 = `api.eu-1.crowdstrike.com`). A relative path
  (`/alerts/...`) is rejected with "Invalid URL format".
- **No query string in the URL.** Query params go in the `request_query`
  UUID-map (the console "Query" tab); a `?filter=...` in the URL is rejected with
  "URL should not contain query parameters".
- **Authentication is REQUIRED** (no credential-less option, unlike a Cloud HTTP
  Request). Reference a saved credential with `authentication_option: UseExisting`
  plus `config_id` (32-hex) and `config_name`, or create one inline
  (`CreateNew` with `definition_id` and `oauth_token_url`). The API client behind
  the credential needs the scope for the endpoint (e.g. **Alerts** read for
  `/alerts/...`). `config_id`/`definition_id` are CID-specific — never invent one.
- `class` is still `Inline.HTTPRequest` (same as a Cloud HTTP Request; the type is
  a console-side distinction).

Common endpoints: `/alerts/queries/alerts/v2`, `/detects/queries/detects/v1`,
`/incidents/queries/incidents/v1`, `/devices/combined/devices/v1`.

**Querying alerts/detections (the common case).** To fetch a population of
alerts, GET `/alerts/queries/alerts/v2` (then POST the returned IDs to
`/alerts/entities/alerts/v2` with a JSON body `{"ids": [...]}` for full records).
Filter with FQL on **`severity_name`** — e.g. `severity_name:'High'` (or
`'Critical'`) — NOT the numeric `severity` field, which is a different scale and
will not match what a user means by "high-severity." Combine conditions with `+`
(AND) and use relative times: `severity_name:'High'+created_timestamp:>'now-24h'`.
This is the standalone, tenant-authenticated way to query the alert population; see
`event-query-vs-api.md` for when to use this vs. an Event Query vs. a Foundry
function.

## Reading the response

Reference the response in later steps by the action's label, using the
**direct JSON path** — there is **no `.HTTP.body.` prefix** (that form is
rejected at release as an unknown variable; confirmed live and against the
shipped VirusTotal example):

- `${data['<ActionLabel>.<json.path>']}` — a field in the JSON body, e.g.
  `${data['EnrichIP.data.attributes.last_analysis_stats.malicious']}`.
- `${data['<ActionLabel>.response_status_code']}` — the numeric status code
  (used in a condition `expression:` as `<ActionLabel>.response_status_code:200`).

**`_cs_inline_output_schema` is REQUIRED to reference response fields.** A Cloud
HTTP Request whose response fields are read by any downstream action MUST declare
a `_cs_inline_output_schema` (a JSON-schema string describing the response shape)
inside its `http_transaction` block. Without it, release validation rejects every
body-field reference with "property contains unknown variable" — even though
import and `validate.py` pass. The shipped `enrich-url-virustotal-zscaler-blocklist.yaml`
example shows a full `_cs_inline_output_schema`.

**Declaring the schema is the mechanism for forwarding real enrichment data — it
is NOT an obstacle to route around.** The whole point of an enrichment workflow is
to carry the *actual* API verdict (VirusTotal's `malicious`/`suspicious` counts,
a reputation score, a threat list) into the summary and email. Do that by adding
the `_cs_inline_output_schema` and then referencing the real response fields
downstream. Do NOT dodge the schema requirement by storing a hand-written status
string — a workflow that stores `"VirusTotal enrichment completed successfully"`
instead of the real verdict passes validation and releases, but it is **hollow**:
it makes the API call, discards the result, and emails the same canned text
whether the indicator is malicious or clean. That is worse than a failing
workflow because it looks like it works. If a response field is read downstream,
add the schema; never substitute a literal for the data.

The same rule applies to any enrichment source, not just HTTP. The shipped
`examples/threat-intel/analyze-enrich-epp-detection-llm.yaml` (a real Content
Library playbook, exported unmodified) is the reference for doing it right with a
Charlotte AI LLM: it decodes the completion and forwards the model's **actual**
fields — `risk_level`, `verdict`, `confidence`, `conclusion`, `recommended_actions`
— into detection comments and tags via `WorkflowCustomVariable`, gated on
`cs.json.valid(...)`. It never stores a canned "analysis completed" string.

**Parallel branches: store outputs in a variable before converging.** When
several HTTP actions run in parallel and a shared downstream action (an LLM
summary, an email) reads their fields, the release validator cannot resolve those
references directly — even with `_cs_inline_output_schema` — because it can't
guarantee which branch ran. Each branch must write its results to a
`WorkflowCustomVariable` via an `UpdateVariable` action, and the convergence
action must reference only `WorkflowCustomVariable.*` fields (always in scope).
**The value stored by `UpdateVariable` MUST be the real HTTP response, referenced
as `${data['<HTTPAction>.<json.path>']}` — not a static status string.** The HTTP
action still needs its `_cs_inline_output_schema` for that reference to resolve at
release. For example, store
`${data['EnrichIP.data.attributes.last_analysis_stats.malicious']}` (or the whole
`${data['EnrichIP.data']}` object), NOT `"IP enrichment completed"`:

```yaml
# RIGHT — forwards the real VirusTotal verdict
UpdateVariableIP:
    class: UpdateVariable
    properties:
        WorkflowCustomVariable:
            ip_malicious: ${data['EnrichIP.data.attributes.last_analysis_stats.malicious']}
            ip_reputation: ${data['EnrichIP.data.attributes.reputation']}

# WRONG — hollow: discards the API result, emails canned text every time
UpdateVariableIP:
    properties:
        WorkflowCustomVariable:
            ip_enrichment: "VirusTotal IP enrichment completed successfully."
```

The shipped `examples/threat-intel/domain-enrichment-pulsedive.yaml` is the
canonical fan-out → converge pattern: parallel Pulsedive lookups each stash their
**actual response object** into a `WorkflowCustomVariable` via `UpdateVariable`
(e.g. `indicator_details_result` ←
`${data['PulsediveGetIndicatorDetails...body']}` and `explore_indicators_result`),
and every action after the join reads real fields like
`${data['WorkflowCustomVariable.indicator_details_result'].indicator}`.

Response body is capped at 10 MB and must be a JSON **object**, not an array.
Branch on `response_status_code` with a condition node (e.g. `== 200` vs `== 404`)
to handle success and error paths separately.

## Templates

130+ vendor APIs (VirusTotal, Slack, PagerDuty, ServiceNow, …) have console
templates that pre-fill URL, method, headers, and auth type — you still supply
credentials. Templates cover one API call, not a whole workflow. They are
unrelated to Falcon Foundry app templates.

See `examples/threat-intel/` and `examples/response-actions/` for complete
HTTP-Action workflows that import cleanly.

SHA-256: 77e5150aeeb43f08466bc478d1bf3324c725a6d2a379c5d75bf8affa8dfbcf53