> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tracecat.com/llms.txt
> Use this file to discover all available pages before exploring further.

# HTTP

Connect to any REST API with the `core.http_request`, `core.http_poll`, and
`core.http_paginate` actions.

## Authentication

### API keys

Store API keys and static tokens as
[Secrets](/automations/core-concepts/secrets), then add the secret to
`headers`.

Pass a secret as a bearer token:

```yaml theme={null}
headers:
  Authorization: Bearer ${{ SECRETS.<secret_name>.<SECRET_KEY> }}
```

If the API expects a dedicated key header, use the same expression:

```yaml theme={null}
headers:
  X-API-Key: ${{ SECRETS.<secret_name>.<SECRET_KEY> }}
```

### OAuth

Connect an [OAuth integration](/automations/integrations/oauth-integrations),
then pass its managed access token as a bearer token.

OAuth expressions use the provider's exact ID, not its display name.

* Built-in providers use stable lowercase IDs assigned by Tracecat, with
  underscores between words, such as `slack`, `google_drive`, and
  `microsoft_sentinel`.
* Custom providers use an ID derived from the provider name, or from the
  requested ID when you create one through the API. Tracecat slugifies it with
  underscores and prepends `custom_`. `My Security API` becomes
  `custom_my_security_api`. If that ID is already used for the same grant type,
  Tracecat appends `_1`, `_2`, and so on.

Append `_oauth` to the exact provider ID for the secret name. For the key,
uppercase the complete provider ID, preserve its underscores and any numeric
suffix, then append `_USER_TOKEN` for `authorization_code` or `_SERVICE_TOKEN`
for `client_credentials`.

```yaml theme={null}
# authorization_code grant
${{ SECRETS.<provider_id>_oauth.<PROVIDER_ID_UPPER>_USER_TOKEN }}

# client_credentials grant
${{ SECRETS.<provider_id>_oauth.<PROVIDER_ID_UPPER>_SERVICE_TOKEN }}
```

A built-in `google_drive` authorization-code provider and a custom
`custom_my_security_api` client-credentials provider resolve as:

```yaml theme={null}
${{ SECRETS.google_drive_oauth.GOOGLE_DRIVE_USER_TOKEN }}
${{ SECRETS.custom_my_security_api_oauth.CUSTOM_MY_SECURITY_API_SERVICE_TOKEN }}
```

When either grant type is allowed, use a fallback:

```yaml theme={null}
${{ SECRETS.microsoft_sentinel_oauth.MICROSOFT_SENTINEL_USER_TOKEN || SECRETS.microsoft_sentinel_oauth.MICROSOFT_SENTINEL_SERVICE_TOKEN }}
```

<Info>
  Tracecat refreshes expiring authorization-code tokens when the provider
  issued a refresh token, and reacquires client-credentials tokens with the
  stored client credentials. The expression resolves only to the current
  access-token string, which may be a JWT or an opaque token, not the refresh
  token.
</Info>

## `core.http_request`

Perform a HTTP request to a given URL.

### Secrets

Optional secrets:

* `mtls`: required values `TLS_CERTIFICATE`, `TLS_PRIVATE_KEY`.
* `ca_cert`: required values `CA_CERTIFICATE`.

### Inputs

<ParamField path="method" type="string" required>
  HTTP request method

  Allowed values: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`.
</ParamField>

<ParamField path="url" type="string" required>
  The destination of the HTTP request
</ParamField>

<ParamField path="auth" type="map[string, string] | null">
  Basic auth credentials with `username` and `password` keys

  Default: `null`.
</ParamField>

<ParamField path="base64_encode_data" type="boolean">
  Base64 encode the raw response body before returning. Use this for binary downloads to prevent corruption from text decoding.

  Default: `false`.
</ParamField>

<ParamField path="content" type="string | null">
  Raw string content to send as the request body (POST, PUT, and PATCH). Cannot be combined with payload, form\_data, or files.

  Default: `null`.
</ParamField>

<ParamField path="files" type="map[string, string | FileUploadData] | null">
  Files to upload using multipart/form-data. The dictionary key is the form field name. The value can be a simple base64 encoded string (filename defaults to form field name), or a dictionary with 'filename', 'content\_base64', and optional 'content\_type'.

  Default: `null`.
</ParamField>

<ParamField path="follow_redirects" type="boolean">
  Follow HTTP redirects

  Default: `false`.
</ParamField>

<ParamField path="form_data" type="object | null">
  Form encoded data in request body (POST, PUT, and PATCH)

  Default: `null`.
</ParamField>

<ParamField path="headers" type="map[string, string | null] | null">
  HTTP request headers

  Default: `null`.
</ParamField>

<ParamField path="ignore_status_codes" type="array[integer] | null">
  If specified, these status codes will not be treated as errors. Defaults to None.

  Default: `null`.
</ParamField>

<ParamField path="max_redirects" type="integer">
  Maximum number of redirects

  Default: `20`.
</ParamField>

<ParamField path="params" type="object | null">
  URL query parameters

  Default: `null`.
</ParamField>

<ParamField path="payload" type="object | array[any] | null">
  JSON serializable data in request body (POST, PUT, and PATCH)

  Default: `null`.
</ParamField>

<ParamField path="timeout" type="number">
  Timeout in seconds

  Default: `10.0`.
</ParamField>

<ParamField path="verify_ssl" type="boolean">
  Verify SSL certificates. Defaults to True, disable at own risk.

  Default: `true`.
</ParamField>

### Examples

**Basic request**

```yaml theme={null}
- ref: fetch_alert
  action: core.http_request
  args:
    url: https://api.example.com/alerts/${{ TRIGGER.alert_id }}
    method: GET
    headers:
      Authorization: Bearer ${{ SECRETS.alerts.API_TOKEN }}
```

## `core.http_poll`

Perform a HTTP request to a given URL with optional polling.

### Poll condition examples

`poll_condition` receives a response with `data`, `status_code`, and
`headers`. The action stops polling when the lambda returns `True`.

When the API has exactly one nonterminal state, `!=` against it stops
on every terminal state, and the `.get` default treats a missing
`status` as that state:

```python theme={null}
lambda response: response["data"].get("status", "processing") != "processing"
```

If the API also uses nonterminal states such as `queued` or `pending`,
list its terminal states with `in`:

```python theme={null}
lambda response: response["data"].get("status") in ["completed", "failed", "cancelled"]
```

With `poll_max_attempts: 0`, polling never stops if the condition never
matches.

Stop on an HTTP status code:

```python theme={null}
lambda response: response["status_code"] == 200
```

Stop when a response header has the expected value:

```python theme={null}
lambda response: response["headers"].get("x-status") == "completed"
```

### Secrets

Optional secrets:

* `mtls`: required values `TLS_CERTIFICATE`, `TLS_PRIVATE_KEY`.
* `ca_cert`: required values `CA_CERTIFICATE`.

### Inputs

<ParamField path="method" type="string" required>
  HTTP request method

  Allowed values: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`.
</ParamField>

<ParamField path="url" type="string" required>
  The destination of the HTTP request
</ParamField>

<ParamField path="auth" type="map[string, string] | null">
  Basic auth credentials with `username` and `password` keys

  Default: `null`.
</ParamField>

<ParamField path="follow_redirects" type="boolean">
  Follow HTTP redirects

  Default: `false`.
</ParamField>

<ParamField path="form_data" type="object | null">
  Form encoded data in request body (POST, PUT, and PATCH)

  Default: `null`.
</ParamField>

<ParamField path="headers" type="map[string, string | null] | null">
  HTTP request headers

  Default: `null`.
</ParamField>

<ParamField path="max_redirects" type="integer">
  Maximum number of redirects

  Default: `20`.
</ParamField>

<ParamField path="params" type="object | null">
  URL query parameters

  Default: `null`.
</ParamField>

<ParamField path="payload" type="object | array[any] | null">
  JSON serializable data in request body (POST, PUT, and PATCH)

  Default: `null`.
</ParamField>

<ParamField path="poll_condition" type="string | null">
  Python lambda function when evaluated to True, stops polling. The function receives a dict with `headers`, `data`, and `status_code` fields.

  Default: `null`.
</ParamField>

<ParamField path="poll_interval" type="number | null">
  Interval in seconds between polling attempts. If not specified, defaults to polling with exponential wait.

  Default: `null`.
</ParamField>

<ParamField path="poll_max_attempts" type="integer">
  Maximum number of polling attempts. If set to 0, the action will poll indefinitely (until timeout).

  Default: `10`.
</ParamField>

<ParamField path="poll_retry_codes" type="integer | array[integer] | null">
  Status codes on which the action will retry. Ignored if `poll_condition` is provided. If neither are specified, an error will be raised.

  Default: `null`.
</ParamField>

<ParamField path="timeout" type="number">
  Timeout in seconds

  Default: `10.0`.
</ParamField>

<ParamField path="verify_ssl" type="boolean">
  Verify SSL certificates. Defaults to True, disable at own risk.

  Default: `true`.
</ParamField>

### Examples

**Poll until terminal status**

```yaml theme={null}
- ref: wait_for_export
  action: core.http_poll
  args:
    url: https://api.example.com/exports/${{ TRIGGER.export_id }}
    method: GET
    headers:
      Authorization: Bearer ${{ SECRETS.exports.API_TOKEN }}
    poll_interval: 5
    poll_max_attempts: 24
    poll_condition: "lambda response: response['data'].get('status') in ['completed', 'failed', 'cancelled']"
```

## `core.http_paginate`

Paginate through a HTTP response.

<Tip>
  For large lists, request a bounded page and process each batch in a
  [While loop](/automations/core-actions/workflow-actions/while-loops)
  until the API returns no next cursor or no items.
</Tip>

### Inputs

<ParamField path="method" type="string" required>
  HTTP request method

  Allowed values: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`.
</ParamField>

<ParamField path="next_request" type="string" required>
  Python lambda function that returns the next request as a JSON of `url`, `method`, `headers`, `params`, `payload`, `form_data` to paginate to. The function receives a dict with `headers`, `data`, and `status_code` fields.
</ParamField>

<ParamField path="stop_condition" type="string" required>
  Python lambda function that determines when pagination should STOP. The function receives a dict with `headers`, `data`, and `status_code` fields.
</ParamField>

<ParamField path="url" type="string" required>
  The destination of the HTTP request
</ParamField>

<ParamField path="auth" type="map[string, string] | null">
  Basic auth credentials with `username` and `password` keys

  Default: `null`.
</ParamField>

<ParamField path="follow_redirects" type="boolean">
  Follow HTTP redirects

  Default: `false`.
</ParamField>

<ParamField path="form_data" type="object | null">
  Form encoded data in request body (POST, PUT, and PATCH)

  Default: `null`.
</ParamField>

<ParamField path="headers" type="map[string, string | null] | null">
  HTTP request headers

  Default: `null`.
</ParamField>

<ParamField path="items_jsonpath" type="string | null">
  JSONPath expression that evaluates to the items to paginate through.

  Default: `null`.
</ParamField>

<ParamField path="limit" type="integer">
  Maximum number of items to paginate through. Defaults to 1000.

  Default: `1000`.
</ParamField>

<ParamField path="max_redirects" type="integer">
  Maximum number of redirects

  Default: `20`.
</ParamField>

<ParamField path="params" type="object | null">
  URL query parameters

  Default: `null`.
</ParamField>

<ParamField path="payload" type="object | array[any] | null">
  JSON serializable data in request body (POST, PUT, and PATCH)

  Default: `null`.
</ParamField>

<ParamField path="timeout" type="number">
  Timeout in seconds

  Default: `10.0`.
</ParamField>

<ParamField path="verify_ssl" type="boolean">
  Verify SSL certificates. Defaults to True, disable at own risk.

  Default: `true`.
</ParamField>

### Examples

**Follow next page links**

```yaml theme={null}
- ref: list_findings
  action: core.http_paginate
  args:
    url: https://api.example.com/findings
    method: GET
    headers:
      Authorization: Bearer ${{ SECRETS.findings.API_TOKEN }}
    params:
      limit: 100
    items_jsonpath: $.items[*]
    stop_condition: "lambda response: response['data'].get('next_cursor') is None"
    next_request: "lambda response: {'url': 'https://api.example.com/findings', 'method': 'GET', 'headers': {'Authorization': 'Bearer ${{ SECRETS.findings.API_TOKEN }}'}, 'params': {'limit': 100, 'cursor': response['data']['next_cursor']}}"
```
