---
name: levelblue-otx-ingestion
description: "How to get LevelBlue Open Threat Exchange OTX data into a pandas DataFrame and Parquet: access, the request, the format, limits, quirks and terms."
---

# Load subscribed OTX pulse indicators into pandas and Parquet

Source entry: https://signal-lab-jet.vercel.app/datasets/levelblue-otx
Provider: https://otx.alienvault.com/api
Checked against the provider's documentation: 2026-11-01

## Access
- Create a free OTX account and place its API key in OTX_API_KEY.
- The subscribed-pulses endpoint returns intelligence from publishers followed by the account.

## Request
```bash
curl -H "X-OTX-API-KEY: $OTX_API_KEY" 'https://otx.alienvault.com/api/v1/pulses/subscribed?page=1&limit=50'
```

## Format
JSON page of pulse objects with pulse identity, author, description, tags, references, timestamps and nested typed indicators.

## Load into pandas and Parquet
Needs Python 3 with requests, pandas and pyarrow.
```python
"""OTX subscribed pulse indicators -> pandas -> Parquet."""
import os
import requests
import pandas as pd

URL = "https://otx.alienvault.com/api/v1/pulses/subscribed"
headers = {"X-OTX-API-KEY": os.environ["OTX_API_KEY"]}
r = requests.get(URL, headers=headers, params={"page": 1, "limit": 50}, timeout=60)
r.raise_for_status()
rows = []
for pulse in r.json().get("results", []):
    for indicator in pulse.get("indicators", []):
        rows.append({"pulse_id": pulse.get("id"), "pulse_name": pulse.get("name"),
                     "pulse_created": pulse.get("created"), "pulse_modified": pulse.get("modified"),
                     "indicator": indicator.get("indicator"), "indicator_type": indicator.get("type"),
                     "indicator_created": indicator.get("created"), "indicator_title": indicator.get("title")})
df = pd.DataFrame(rows)
for column in ("pulse_created", "pulse_modified", "indicator_created"):
    if column in df:
        df[column] = pd.to_datetime(df[column], utc=True, errors="coerce")
df.to_parquet("otx_subscribed_indicators.parquet", index=False)
print(len(df), "indicators from", df["pulse_id"].nunique(), "pulses")
```
Not run: it needs a free API key.

## Limits
- Provider article: 10,000 requests per hour with an API key; the article was published in 2017.
- OTX is free to end users for non-commercial use.
- Current response pagination and total subscribed data depend on the account's subscriptions.

## Quirks
- Pulse quality and update cadence depend on the contributing publisher.
- Keep pulse provenance with each flattened indicator because the same indicator may occur in multiple pulses.
- Do not visit or fetch an indicator URL or host; retrieve only metadata through OTX.

## Terms and attribution
- OTX is licensed for non-commercial use under the provider's end-user agreement.
- Do not share private, non-public indicators or commercially exploit OTX access.
- Terms page: https://www.levelblue.com/legal/otx-eula-terms

## Sources
- https://www.levelblue.com/legal/otx-eula-terms: "OTX is free to end users for non-commercial use."
- https://www.levelblue.com/blogs/levelblue-blog/the-upgraded-alienvault-otx-api-ways-to-score-swag: "10,000 requests per hour rather than just 1,000 requests per hour."
- https://www.levelblue.com/blogs/levelblue-blog/otx-is-now-a-free-stix-taxii-server: "provides a formal way to describe threat intelligence"

## Help improve this skill
Missing a source, or see something to improve? Call the MCP tool suggest_source or feedback (or POST /api/suggest or /api/feedback). Reviewed ideas: https://signal-lab-jet.vercel.app/agent-suggestions

Information for educational purposes only. Free tiers, limits and licenses belong to the providers and may change; check the provider's own terms before using the data.
