# Stormy Cookbook

> Open-source cookbook for the Stormy Social Data API and MCP server (Model Context Protocol) — one REST API for the TikTok API, YouTube API, Instagram API, LinkedIn API, X (Twitter) API and Reddit API. Search creators, resolve profiles, read posts and find verified emails from Claude, Cursor, Codex, ChatGPT or curl. One key, no scrapers.

- **Type:** MCP server
- **Install:** `agentstack add mcp-oneinterface-stormy-cookbook`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [OneInterface](https://agentstack.voostack.com/s/oneinterface)
- **Installs:** 0
- **Category:** [Communication](https://agentstack.voostack.com/c/communication)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [OneInterface](https://github.com/OneInterface)
- **Source:** https://github.com/OneInterface/stormy-cookbook
- **Website:** https://stormy.ai

## Install

```sh
agentstack add mcp-oneinterface-stormy-cookbook
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Stormy Cookbook — TikTok, YouTube, Instagram, LinkedIn, X and Reddit API recipes for AI agents

Open-source, copy-pasteable recipes for the **Stormy Social Data API** and the **Stormy MCP server** (Model Context Protocol, Streamable HTTP).

One HTTP contract — `search`, `profile`, `emails`, `jobs` — across six social networks. No proxy pool, no six vendor SDKs, no scraper to babysit. Point your agent at `https://stormy.ai/mcp`, or `curl` the REST base at `https://stormy.ai/api/v1`.

[](LICENSE)
[](https://github.com/OneInterface/stormy-cookbook/actions/workflows/link-check.yml)
[](https://modelcontextprotocol.io)
[](#what-you-can-do-on-each-network)
[](https://stormy.ai/docs)

**Jump to:** [Recipes](#recipes) · [MCP quickstart](#quickstart-a-connect-the-mcp-server) · [REST quickstart](#quickstart-b-call-the-rest-api-directly) · [Endpoints](#the-whole-api-on-one-screen) · [Pricing](#pricing) · [Errors](#errors-and-retries) · [FAQ](#faq)

---

## Why this exists

Every "get social data" project starts the same way: six different APIs, six auth schemes, six rate limits, six response shapes, and a scraper that breaks on a Tuesday. Then you bolt an LLM on top and discover none of it is shaped for an agent — no cost signal, no idempotency, no durable jobs, no machine-readable capability list.

Stormy is that layer, already built. This cookbook is how you use it.

  

---

## Quickstart A: connect the MCP server

The Stormy **MCP server** speaks Streamable HTTP at `https://stormy.ai/mcp` and authenticates with an HTTP bearer token. Get a key at [stormy.ai/account](https://stormy.ai/account) and export it:

```bash
export STORMY_API_KEY="stm_live_..."   # never commit this
```

### Claude Code

```bash
claude mcp add --transport http stormy https://stormy.ai/mcp \
  --header "Authorization: Bearer $STORMY_API_KEY"
```

### Codex CLI (`~/.codex/config.toml`)

```toml
[mcp_servers.stormy]
url = "https://stormy.ai/mcp"
bearer_token_env_var = "STORMY_API_KEY"
```

### Cursor / Windsurf / any `mcp.json` client

```json
{
  "mcpServers": {
    "stormy": {
      "url": "https://stormy.ai/mcp",
      "headers": {
        "Authorization": "Bearer ${STORMY_API_KEY}"
      }
    }
  }
}
```

### ChatGPT custom connector

```text
Name: Stormy Social Data
MCP URL: https://stormy.ai/mcp
Authentication: OAuth
```

### The ten MCP tools you get

| Tool | What it does |
| --- | --- |
| `search_people(platform, query, limit=10, fresh=false)` | Search one network from a natural-language query |
| `lookup_profile(target, platform=null, fresh=false, include_posts=false)` | Resolve a URL / `@handle` / channel ID to a normalized profile |
| `find_emails(platform, targets)` | Verified contact emails for 1–25 Instagram, TikTok or YouTube profiles |
| `estimate_price(quantity=100, include_email=false)` | Rate card + a maximum estimate, spends nothing |
| `account_status()` | Plan, remaining prepaid usage, top-up URL |
| `describe_social_data()` | The machine-readable platform / field / pricing / workflow contract |
| `start_social_job(operation, arguments, idempotency_key, ...)` | Queue fresh, bulk or email work durably |
| `get_social_job(job_id)` | Status, progress, `poll_after_seconds`, result, full timeline |
| `list_social_jobs(status=null, limit=20)` | Recover prior work instead of resubmitting |
| `cancel_social_job(job_id)` | Cancel queued / scheduled / throttled / retrying work |

Then just ask:

> Find 25 TikTok creators posting about home espresso, pull their follower counts, and tell me what it cost.

---

## Quickstart B: call the REST API directly

Base URL: **`https://stormy.ai/api/v1`** (also reachable at `https://api.stormy.ai/api/v1`).
Auth: `Authorization: Bearer ` **or** `X-API-Key: `. Never put a key in a URL, a JSON body, a prompt, or an MCP tool argument.

### curl

```bash
curl -X POST 'https://stormy.ai/api/v1/search' \
  -H "Authorization: Bearer $STORMY_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: espresso-tiktok-2026-07' \
  -d '{
    "platform": "tiktok",
    "query": "home espresso and coffee gear creators",
    "limit": 25,
    "fresh": true
  }'
```

### Python

```python
import os

import requests

response = requests.post(
    "https://stormy.ai/api/v1/search",
    headers={
        "Authorization": f"Bearer {os.environ['STORMY_API_KEY']}",
        "Idempotency-Key": "espresso-tiktok-2026-07",
    },
    json={
        "platform": "tiktok",
        "query": "home espresso and coffee gear creators",
        "limit": 25,
        "fresh": True,
    },
    timeout=90,
)
response.raise_for_status()
payload = response.json()

for creator in payload["results"]:
    print(creator["handle"], creator["follower_count"])

print("cost:", payload["usage"]["cost_usd"], "USD")
```

### TypeScript

```ts
const response = await fetch("https://stormy.ai/api/v1/search", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.STORMY_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "espresso-tiktok-2026-07",
  },
  body: JSON.stringify({
    platform: "tiktok",
    query: "home espresso and coffee gear creators",
    limit: 25,
    fresh: true,
  }),
});

if (!response.ok) throw new Error(await response.text());
const { results, usage } = await response.json();
console.log(results.length, "creators for", usage.cost_usd, "USD");
```

### What comes back

```json
{
  "ok": true,
  "platform": "tiktok",
  "query": "home espresso and coffee gear creators",
  "fresh": true,
  "results": [
    {
      "id": "6812...",
      "handle": "@homebarista",
      "nickname": "Home Barista",
      "url": "https://tiktok.com/@homebarista",
      "signature": "Espresso at home, no snobbery.",
      "verified": false,
      "follower_count": 184000,
      "following_count": 312,
      "likes_count": 4210000,
      "video_count": 612
    }
  ],
  "usage": {
    "operation": "fresh_discovery",
    "metered_results": 25,
    "billed_results": 25,
    "cost_usd": "2.00",
    "credits": 200,
    "plan": "paid",
    "remaining_usage_usd": "23.00"
  }
}
```

Every successful response carries a `usage` receipt. You are billed for **outcomes**, not requests.

---

## What you can do on each network

  

| Network | `POST /search` | `POST /profile` | `include_posts` | `POST /emails` | Public fields |
| --- | --- | --- | --- | --- | --- |
| **TikTok API** | Creators by niche | `@handle` or URL | Videos, views, shares, saves, music, hashtags | ✅ verified email | 27 |
| **YouTube API** | Channels by topic | Handle or channel ID | Videos, transcripts, captions | ✅ verified email | 25 |
| **Instagram API** | Creators by niche | Username or URL | Posts, captions, engagement, location | ✅ verified email | 23 |
| **X (Twitter) API** | Accounts and posts | `@handle` or URL | Posts, views, bookmarks, conversation ID | — | 26 |
| **LinkedIn API** | People and companies | Profile URL | Posts, reaction breakdowns, cadence | — | 31 |
| **Reddit API** | Threads by topic | Author karma and age | Score, upvote ratio, comment count | — | 17 |

Field lists are authoritative in `GET /api/v1/capabilities` → `fields_by_platform`. Treat every platform-specific field as **nullable** — you get it when it is public and the source supplied it.

Per-network field lists (click to expand)

**TikTok** — profile: `id`, `sec_uid`, `handle`, `nickname`, `url`, `signature`, `avatar_url`, `verified`, `follower_count`, `following_count`, `likes_count`, `video_count` · posts: `video_id`, `url`, `description`, `create_time`, `duration`, `views`, `likes`, `comments`, `shares`, `saves`, `image_url`, `is_pinned`, `music`, `hashtags`, `caption_url`

**YouTube** — profile: `channel_id`, `handle`, `name`, `url`, `subscribers`, `description`, `videos_count`, `total_views`, `profile_image_url`, `banner_image_url`, `country`, `keywords`, `links` · posts: `video_id`, `url`, `title`, `description`, `published_at`, `duration`, `views`, `likes`, `comments`, `thumbnail_url`, `transcript`, `caption_url`

**Instagram** — profile: `username`, `full_name`, `biography`, `profile_pic_url`, `follower_count`, `following_count`, `posts_count`, `avg_engagement_rate`, `biolinks`, `country`, `is_verified`, `is_business_account`, `business_category_name` · posts: `media_id`, `post_url`, `caption`, `taken_at`, `like_count`, `comment_count`, `image_url`, `location`, `location_data`, `comments`

**X (Twitter)** — profile: `id`, `handle`, `name`, `url`, `description`, `profile_image_url`, `verified`, `location`, `website`, `followers`, `following`, `posts_count`, `joined_at` · posts: `id`, `url`, `text`, `created_at`, `language`, `likes`, `replies`, `reposts`, `quotes`, `views`, `bookmarks`, `conversation_id`, `author`

**LinkedIn** — profile: `id`, `name`, `linkedin_url`, `headline`, `country`, `country_iso_2`, `followers`, `total_posts`, `posts_last_6_months`, `posting_frequency`, `avg_likes`, `avg_comments`, `avg_reposts`, `avg_total_interactions`, `top_post_text`, `top_post_interactions`, `last_post_date`, `ai_summary`, `is_suitable_for_promotion`, `relevant_posts` · posts: `post_url`, `text`, `headline`, `posted_datetime`, `total_interactions`, `num_likes`, `num_comments`, `num_reposts`, `num_reactions_breakdown`, `poster_name`, `poster_linkedin_url`

**Reddit** — profile: `author`, `author_url`, `karma`, `account_created_at` · posts: `id`, `url`, `permalink`, `subreddit`, `author`, `title`, `text`, `created_at`, `score`, `upvote_ratio`, `comments_count`, `is_self`, `over_18`

---

## Recipes

Every recipe is a single runnable markdown file with real code and a real cost estimate. Full index with difficulty and pricing: **[recipes/README.md](recipes/README.md)**.

| # | Recipe | Networks | What you get |
| --- | --- | --- | --- |
| 01 | [Find influencers by niche](recipes/01-find-influencers-by-niche.md) | TikTok, YouTube, Instagram | A ranked shortlist of creators with follower counts and engagement |
| 02 | [Build an outreach list with verified emails](recipes/02-build-an-outreach-list-with-verified-emails.md) | TikTok, YouTube, Instagram | Search → filter → `/emails` → CSV, paying only for hits |
| 03 | [Enrich a CRM from handles](recipes/03-enrich-a-crm-from-handles.md) | All six | Handles in, normalized profile rows out |
| 04 | [Competitor content analysis](recipes/04-competitor-content-analysis.md) | TikTok, YouTube, Instagram | Which of a rival's posts actually worked, and why |
| 05 | [Monitor a creator over time](recipes/05-monitor-a-creator-over-time.md) | Any | A daily snapshot job and a growth delta |
| 06 | [Cross-platform audience research](recipes/06-cross-platform-audience-research.md) | All six | One query fanned out across six networks, merged |
| 07 | [Reddit topic listening](recipes/07-reddit-topic-listening.md) | Reddit, X | Which threads are moving on a topic you care about |
| 08 | [Durable jobs for large collections](recipes/08-durable-jobs-for-large-collections.md) | All six | 1,000+ results without holding an HTTP connection |
| 09 | [Use Stormy from an MCP agent](recipes/09-agent-workflows-with-mcp.md) | All six | Prompts + tool policy for Claude Code, Cursor, Codex |
| 10 | [Handle errors, rate limits and billing](recipes/10-errors-rate-limits-and-billing.md) | — | 402 / 429 / 503 handling, headless top-up, idempotency |

---

## The whole API on one screen

### Paid calls

| Method | Path | Body | Notes |
| --- | --- | --- | --- |
| `POST` | `/search` | `platform`, `query`, `limit` (1–100, default 10), `fresh` (default `false`) | Cache-first. `fresh=true` calls a live provider |
| `POST` | `/profile` | `target`, `platform?`, `fresh`, `include_posts` | Returns `data`, not `results`. `platform` is optional when `target` is a URL |
| `POST` | `/emails` | `platform` (`instagram` \| `tiktok` \| `youtube`), `targets` (1–25) | The only endpoint that returns contact data |
| `POST` | `/jobs` | `operation`, `arguments`, `delay_seconds` (0–604800), `priority` (−10…10), `max_attempts` (1–10) | `202 Accepted`. `operation` ∈ `search_people`, `lookup_profile`, `find_emails` |
| `GET` | `/jobs/{job_id}` | `?include_events=true` | Status, progress and the event timeline |
| `GET` | `/jobs` | `?status=running&limit=20` | List recent jobs |
| `DELETE` | `/jobs/{job_id}` | — | Cancel non-terminal work |

### Free calls

| Method | Path | Notes |
| --- | --- | --- |
| `GET` | `/pricing?quantity=100&include_email=true` | Authoritative rate card + a maximum estimate |
| `GET` | `/capabilities` | Platforms, per-platform fields, endpoints, job statuses, agent policy |
| `GET` | `/account` | Plan, `usage_balance`, scopes, top-up URL |
| `POST` | `/account/top-up` | `amount_usd`, `note?` → an instant Stripe `checkout_url` |
| `GET` `POST` `DELETE` | `/keys`, `/keys/{id}` | List, mint and revoke API keys |

Legacy aliases `/social/search`, `/social/profile` and `/social/emails` still work; new clients should use the short paths.

Send an **`Idempotency-Key`** header on any paid call. Retrying with the same key never duplicates work or charges.

### Machine-readable contract

| Resource | URL |
| --- | --- |
| Capabilities JSON |  |
| OpenAPI |  |
| `llms.txt` |  |
| Human docs |  |

---

## Pricing

One credit is one US cent. **You are charged for successful outcomes only** — a verified-email lookup that finds nothing costs `$0.00`.

| Operation | Price | When it applies |
| --- | --- | --- |
| Cached result | **$0.01** | `fresh=false` on `/search` or `/profile` |
| Fresh profile | **$0.05** | `/profile` with `fresh=true` |
| Fresh discovery | **$0.08** | Per matching person returned by `/search` with `fresh=true` |
| Verified email | **$0.15** | Per email actually found by `/emails` |

- **Free preview:** 50 cached results per rolling 30 days. No fresh data, no emails.
- **Paid:** $50 / month, including $25 (2,500 credits) of usage. Overage is metered at the rates above.

Worked examples (straight from `GET /pricing`):

| Workload | Cost |
| --- | --- |
| 100 cached profiles | $1.00 |
| 100 fresh profiles | $5.00 |
| 100 fresh matching people | $8.00 |
| 100 verified emails | $15.00 |
| 100 fresh people **+** their verified emails | $23.00 |

Ask before you spend — `estimate_price` / `GET /pricing` is free:

```bash
curl 'https://stormy.ai/api/v1/pricing?quantity=250&include_email=true'
```

---

## Errors and retries

| Status | Code | What to do |
| --- | --- | --- |
| `400` | `invalid_request` | Fix the body. Unsupported platform, empty query, `limit` out of 1–100, more than 25 email targets |
| `401` | `invalid_token` | Key missing, expired, revoked or wrong |
| `402` | `upgrade_required` | Return the `upgrade_url` to the user |
| `402` | `insufficient_balance` | `POST /account/top-up` with `error.recommended_topup_usd`, hand back `checkout_url`, poll `GET /account`, retry |
| `404` | `job_not_found` | Job missing or owned by another account |
| `429` | `rate_limit` | Wait `Retry-After` seconds. Do **not** retry immediately |
| `429` | provider cooldown | The shared upstream pool is cooling down — switch to a durable job |
| `503` | `provider_not_configured` | Our deployment problem, not your request. Retry later |

Durable jobs never fail on a rate limit: they move to `throttled` **without consuming an attempt** and resume after the cooldown. Job statuses are `queued`, `scheduled`, `running`, `throttled`, `retrying`, `succeeded`, `failed`, `cancelled`. Poll only after `poll_after_seconds`, and stop when `terminal` is `true`.

Full worked handling in [recipe 10](recipes/10-errors-rate-limits-and-billing.md).

---

## Privacy and scope

- Stormy returns **public social data only**.
- `search` and `profile` responses have `email`, `business_email`, `contact_email`, `phone` and `phone_number` recursively stripped at the API boundary. This is enforced server-side, not by convention.
- `POST /emails` is the only path to contact data, it is opt-in, it is limited to Instagram / TikTok / YouTube, and it is billed only when a verifi

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [OneInterface](https://github.com/OneInterface)
- **Source:** [OneInterface/stormy-cookbook](https://github.com/OneInterface/stormy-cookbook)
- **License:** MIT
- **Homepage:** https://stormy.ai

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-oneinterface-stormy-cookbook
- Seller: https://agentstack.voostack.com/s/oneinterface
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
