# Telegram Scraper + AI Analysis — Posts, Sentiment, MCP-Ready (`ml_boost/tg-apify-actor`) Actor

Scrape any public Telegram channel and enrich each post with Gemini AI — sentiment, topics, summaries, translation, entities, and image descriptions. Built-in content moderation. MCP-ready for Claude Desktop and AI agents.

- **URL**: https://apify.com/ml\_boost/tg-apify-actor.md
- **Developed by:** [ML Boost](https://apify.com/ml_boost) (community)
- **Categories:** Social media, AI, Lead generation
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).


# README

## Telegram Scraper + AI Analysis

Scrape any public Telegram channel and get **full message metadata** — reactions, views, link previews, media URLs, forwards, replies — with **optional Gemini AI enrichment** for sentiment, topics, summaries, translation, and named entities.

Built for AI agents, data pipelines, and analytics workflows. No Telegram API credentials required — works on public channel previews.

---

### What you get

- **17+ fields per message**: plain text, HTML with formatting, ISO timestamps, views as integers, full reactions array (including paid Telegram Stars), media URLs, video duration, link previews, extracted URLs and emails
- **Channel metadata**: title, description, subscribers, photo/video/link counts, verification status, avatar
- **6 AI enrichments** (paid Apify plans only): sentiment, topic tags, one-line summaries, translation, entity extraction, and **image descriptions** (Gemini vision) — each enabled independently. Free-tier users get full scraping; AI flags are silently skipped with a notice record
- **Built-in content moderation** via OpenAI (text + image): policy-violating posts are flagged and skipped before any AI call, so you don't pay tokens on abusive content
- **Two output formats**: JSON (default, full fidelity) or Markdown with YAML frontmatter (drop-in for RAG pipelines)
- **Incremental mode**: store the last-seen message ID per channel and only fetch new posts on subsequent runs — perfect for Apify Schedules

---

### Quick start

Minimal input:

```json
{
  "channels": ["durov"],
  "maxMessagesPerChannel": 200
}
````

With AI enrichment:

```json
{
  "channels": ["durov", "telegram"],
  "maxMessagesPerChannel": 500,
  "enrichSentiment": true,
  "enrichTopics": true,
  "enrichSummary": true
}
```

Channel inputs accept any of: `durov`, `@durov`, `https://t.me/durov`, `t.me/s/durov`.

***

### Pricing (pay-per-event)

| Event | Price | Marketing display |
|---|---:|---|
| Actor run start | $0.003 | $0.003 per run |
| Channel info record | $0.001 | $1 per 1,000 channels |
| Message record | $0.003 | **$3 per 1,000 messages** |
| AI enrichment call | $0.0008 | $0.80 per 1,000 calls |
| Gemini input tokens (per 100) | $0.0003 | $3 per 1M tokens |
| Gemini output tokens (per 100) | $0.0015 | $15 per 1M tokens |

#### Typical costs

| Scenario | Approx cost |
|---|---:|
| 1,000 messages, no AI | **~$3.00** |
| 1,000 messages with sentiment | ~$5.00 |
| 1,000 messages with sentiment + topics | ~$7.00 |
| 1,000 messages with all 5 text enrichments | ~$13.00 |
| 1,000 messages with 1 image each, descriptions only | ~$10.00 |
| 10,000 messages with sentiment (typical run) | ~$50.00 |

AI enrichment is **opt-in per flag** — pure scraping never triggers any AI cost. Token charges use Google's exact reported usage, with our 5–6× markup priced in.

***

### Input parameters

| Field | Type | Default | Description |
|---|---|---|---|
| `channels` | string\[] | required | Channel usernames or `t.me/...` URLs |
| `maxMessagesPerChannel` | int | 200 | Capped at 5,000 |
| `outputFormat` | enum | `json` | `json` / `markdown` / `both` |
| `includeChannelInfo` | bool | true | Emit one channel metadata record per channel |
| `incrementalMode` | bool | false | Resume from KV-store cursor |
| `enrichSentiment` | bool | false | `{label, score, rationale}` |
| `enrichTopics` | bool | false | 1–5 tags from controlled vocabulary |
| `enrichSummary` | bool | false | One-sentence summary |
| `enrichTranslation` | bool | false | Translate to `translationTarget` |
| `translationTarget` | string | `en` | Language code (`en`, `es`, `de`, `ru`, `zh`, …) |
| `enrichEntities` | bool | false | `{companies, people, places, tokens}` |
| `enrichImageDescriptions` | bool | false | 1–2 sentence vision description for each image (up to 4/post) |

***

### Output schema

Each dataset record has a `recordType` field: either `channel_info` or `message`.

#### `channel_info` record

```json
{
  "recordType": "channel_info",
  "channel": "durov",
  "title": "Pavel Durov",
  "username": "durov",
  "description": "Founder of Telegram.",
  "subscribers": 11600000,
  "photos": 96,
  "videos": 40,
  "links": 183,
  "verified": true,
  "avatarUrl": "https://cdn4.telesco.pe/...",
  "url": "https://t.me/durov"
}
```

#### `message` record

```json
{
  "recordType": "message",
  "channel": "durov",
  "messageId": 489,
  "url": "https://t.me/durov/489",
  "text": "Group admins can assign tags to users …",
  "textHtml": "<div ...>...</div>",
  "postedAt": "2026-04-15T01:20:29+00:00",
  "views": 2580000,
  "reactions": [
    {"emojiId": "5265077361648368841", "count": 23300, "isPaid": false},
    {"count": 3870, "isPaid": true}
  ],
  "reactionsTotal": 42280,
  "isForwarded": false,
  "isEdited": false,
  "author": "Pavel Durov",
  "mediaType": "video",
  "mediaUrl": "https://cdn4.telesco.pe/...mp4",
  "videoDuration": "0:09",
  "imageUrls": [],
  "linkPreview": null,
  "extractedLinks": [],
  "extractedEmails": [],

  "sentiment": {"label": "positive", "score": 0.95, "rationale": "..."},
  "topics": ["tech", "business"],
  "summary": "Telegram group admins can now assign user tags…",
  "translation": "Los administradores de grupo pueden…",
  "entities": {"companies": [], "people": [], "places": [], "tokens": []},
  "imageDescriptions": [
    {
      "url": "https://cdn4.telesco.pe/file/…",
      "description": "A smartphone screen displays an audio selection menu …",
      "contains_text": true
    }
  ]
}
```

AI enrichment fields are only present when their corresponding flag is enabled.

If a post fails moderation, all AI fields are skipped and a `moderation` field is set:

```json
{
  "moderation": {"flagged": true, "categories": ["harassment", "violence"]}
}
```

***

### Hard safety caps

These prevent any accidental runaway cost:

- 5,000 messages per channel per run
- 50,000 total messages per run
- 10,000 total AI enrichments per run (safety cap; typical paid runs are well below)
- 2,000-character input clamp before each Gemini call
- 500-token output cap per Gemini call
- 4 images max per post when image descriptions are enabled
- 6 MB max image size for vision processing
- 60 Gemini calls per minute (self-rate-limited)
- 30-minute run timeout
- Posts flagged by OpenAI content moderation skip all AI calls automatically

***

### Use it from AI agents (MCP)

This actor is exposed as a tool via the [Apify MCP server](https://mcp.apify.com) — Claude Desktop, Cursor, ChatGPT, n8n, and LangChain can all call it with natural language. The input schema is written so an LLM can pick the right flags without needing additional prompting.

#### Claude Desktop

Add this to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=ml_boost/tg-apify-actor",
      "headers": {
        "Authorization": "Bearer <YOUR_APIFY_TOKEN>"
      }
    }
  }
}
```

Restart Claude Desktop and try: *"Scrape the last 100 posts from @durov and give me sentiment and topics for each."*

#### Cursor

In Cursor settings → MCP, add a new server:

- **URL**: `https://mcp.apify.com?tools=ml_boost/tg-apify-actor`
- **Headers**: `Authorization: Bearer <YOUR_APIFY_TOKEN>`

#### Local stdio (npx) — alternative

If you prefer a local wrapper instead of the hosted HTTP endpoint:

```json
{
  "mcpServers": {
    "apify": {
      "command": "npx",
      "args": ["-y", "@apify/actors-mcp-server", "--tools", "ml_boost/tg-apify-actor"],
      "env": { "APIFY_TOKEN": "<YOUR_APIFY_TOKEN>" }
    }
  }
}
```

The hosted endpoint is recommended — it supports output-schema inference, so the agent sees the structured response shape, not just raw JSON.

#### n8n / LangChain / Zapier

Use the Apify integration node and pick this actor. The input schema renders as a form with sensible defaults; pass `channels` and toggle enrichments as needed.

#### ChatGPT Custom GPT

Create a Custom GPT with an Action pointing at `https://api.apify.com/v2/acts/ml_boost~tg-apify-actor/run-sync-get-dataset-items?token=YOUR_TOKEN`. ChatGPT will read the input schema automatically.

#### Why this actor is good for AI agents

- **Structured output**: Every record has stable, documented field names — no string parsing required.
- **Opt-in enrichment**: Agents enable only the AI fields they need, keeping cost low for simple queries.
- **Idempotent incremental mode**: Run the same query 1× per hour and only get new posts each time, no duplicates.
- **Built-in moderation**: The actor refuses to enrich content that fails OpenAI's policy check, so agents don't accidentally launder abusive content through downstream AI calls.

***

### Scheduling

Combine `incrementalMode: true` with an Apify Schedule (e.g. every 6 hours) to build a real-time monitor for any channel. The actor stores per-channel cursors in its Key-Value store; only new posts since the last run are fetched and charged.

***

### What channels work

Any public Telegram channel with a t.me preview page — that's most of them. The actor will return a clear error record (`recordType: "error"`) for:

- Private channels (no preview available)
- Telegram users or bots (not channels)
- Channels that don't exist
- Groups (out of scope for v1 — use Telegram's Bot API or MTProto for groups)

***

### Limits and known scope

This is v1. Features explicitly deferred to later releases:

- **MCP server layer** — coming next
- **Comments / discussion replies** under posts
- **Group chats** (channels only for now)
- **Cross-channel forward deduplication** (`forwardChainId`)
- **Member list extraction** (not exposed on t.me previews)
- **Spam / bot scoring**

***

### Support

Leave a review or contact the maintainer via the Apify Store actor page. Reviews and bug reports are read and responded to within 24h.

# Actor input Schema

## `channels` (type: `array`):

One or more public Telegram channel identifiers. Accepts plain usernames (`durov`), @-prefixed (`@durov`), or t.me URLs (`https://t.me/durov`). Private channels, users and bots are not supported.

## `maxMessagesPerChannel` (type: `integer`):

Maximum number of messages to scrape per channel. Iterates from newest to oldest. Hard-capped at 5,000 regardless of this value. Use a smaller number (50-200) for fast exploratory queries.

## `outputFormat` (type: `string`):

`json` returns one structured record per message with all fields (recommended for programmatic consumers and AI agents). `markdown` returns a compact YAML-frontmatter + body string ideal for RAG pipelines. `both` includes both.

## `includeChannelInfo` (type: `boolean`):

When true, emits one extra record per channel containing channel-level metadata (title, description, subscribers, photos/videos/links counts, verified, avatar). The record has `recordType: "channel_info"`.

## `incrementalMode` (type: `boolean`):

When true, only fetches messages newer than the highest message ID scraped in a previous run of this actor. The cursor is stored per channel in the actor's key-value store. Use this for scheduled monitoring (combine with Apify Schedules).

## `enrichSentiment` (type: `boolean`):

Classify each post as positive / neutral / negative with confidence score and one-line rationale. Adds a `sentiment: {label, score, rationale}` field. Token-metered ($3 per 1M input, $15 per 1M output).

## `enrichTopics` (type: `boolean`):

Assign 1-5 topic tags from this controlled vocabulary: politics, tech, finance, crypto, ai, sports, entertainment, science, business, lifestyle, news, other. Adds a `topics: [string]` field.

## `enrichSummary` (type: `boolean`):

Produce a single-sentence summary of each post. Adds a `summary: string` field. Useful for digests, briefings, and timeline views where the full text is too long.

## `enrichTranslation` (type: `boolean`):

Translate each post's text into the language specified by `translationTarget`. Adds a `translation: string` field. URLs, hashtags and @mentions are preserved verbatim.

## `translationTarget` (type: `string`):

ISO-639-1 language code for translations (e.g. `en`, `es`, `de`, `fr`, `ru`, `zh`, `ja`, `pt`). Only used when `enrichTranslation` is true.

## `enrichEntities` (type: `boolean`):

Extract named entities mentioned in each post. Adds an `entities: {companies, people, places, tokens}` field, where `tokens` are crypto tickers like BTC, ETH, SOL.

## `enrichImageDescriptions` (type: `boolean`):

Generate 1-2 sentence descriptions for each image attached to a post, using Gemini multimodal. Adds `imageDescriptions: [{url, description, contains_text}]`. Capped at 4 images per post and 6 MB per image. Vision tokens (~1,000+ per image) cost more than text — enable only when you actually need visual content extracted.

## Actor input object example

```json
{
  "channels": [
    "durov"
  ],
  "maxMessagesPerChannel": 200,
  "outputFormat": "json",
  "includeChannelInfo": true,
  "incrementalMode": false,
  "enrichSentiment": false,
  "enrichTopics": false,
  "enrichSummary": false,
  "enrichTranslation": false,
  "translationTarget": "en",
  "enrichEntities": false,
  "enrichImageDescriptions": false
}
```

# Actor output Schema

## `messages` (type: `string`):

Full dataset of scraped records — one per message plus one per channel.

## `messagesJsonl` (type: `string`):

Same dataset streamed as newline-delimited JSON. Good for piping into AI pipelines.

## `messagesCsv` (type: `string`):

Flat CSV export. Nested fields (reactions, sentiment, entities) serialise as JSON strings per cell.

## `incrementalCursors` (type: `string`):

Key-value store entry mapping each channel to the highest message ID scraped — read by future runs when incrementalMode is enabled.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "channels": [
        "durov"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("ml_boost/tg-apify-actor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "channels": ["durov"] }

# Run the Actor and wait for it to finish
run = client.actor("ml_boost/tg-apify-actor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "channels": [
    "durov"
  ]
}' |
apify call ml_boost/tg-apify-actor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=ml_boost/tg-apify-actor",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Telegram Scraper + AI Analysis — Posts, Sentiment, MCP-Ready",
        "description": "Scrape any public Telegram channel and enrich each post with Gemini AI — sentiment, topics, summaries, translation, entities, and image descriptions. Built-in content moderation. MCP-ready for Claude Desktop and AI agents.",
        "version": "0.0",
        "x-build-id": "7lsjVtCzrLT5Y7MvB"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/ml_boost~tg-apify-actor/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-ml_boost-tg-apify-actor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/ml_boost~tg-apify-actor/runs": {
            "post": {
                "operationId": "runs-sync-ml_boost-tg-apify-actor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/ml_boost~tg-apify-actor/run-sync": {
            "post": {
                "operationId": "run-sync-ml_boost-tg-apify-actor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "required": [
                    "channels"
                ],
                "properties": {
                    "channels": {
                        "title": "Channels",
                        "type": "array",
                        "description": "One or more public Telegram channel identifiers. Accepts plain usernames (`durov`), @-prefixed (`@durov`), or t.me URLs (`https://t.me/durov`). Private channels, users and bots are not supported.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "maxMessagesPerChannel": {
                        "title": "Max messages per channel",
                        "minimum": 1,
                        "maximum": 5000,
                        "type": "integer",
                        "description": "Maximum number of messages to scrape per channel. Iterates from newest to oldest. Hard-capped at 5,000 regardless of this value. Use a smaller number (50-200) for fast exploratory queries.",
                        "default": 200
                    },
                    "outputFormat": {
                        "title": "Output format",
                        "enum": [
                            "json",
                            "markdown",
                            "both"
                        ],
                        "type": "string",
                        "description": "`json` returns one structured record per message with all fields (recommended for programmatic consumers and AI agents). `markdown` returns a compact YAML-frontmatter + body string ideal for RAG pipelines. `both` includes both.",
                        "default": "json"
                    },
                    "includeChannelInfo": {
                        "title": "Include channel info record",
                        "type": "boolean",
                        "description": "When true, emits one extra record per channel containing channel-level metadata (title, description, subscribers, photos/videos/links counts, verified, avatar). The record has `recordType: \"channel_info\"`.",
                        "default": true
                    },
                    "incrementalMode": {
                        "title": "Incremental mode",
                        "type": "boolean",
                        "description": "When true, only fetches messages newer than the highest message ID scraped in a previous run of this actor. The cursor is stored per channel in the actor's key-value store. Use this for scheduled monitoring (combine with Apify Schedules).",
                        "default": false
                    },
                    "enrichSentiment": {
                        "title": "AI: Sentiment analysis",
                        "type": "boolean",
                        "description": "Classify each post as positive / neutral / negative with confidence score and one-line rationale. Adds a `sentiment: {label, score, rationale}` field. Token-metered ($3 per 1M input, $15 per 1M output).",
                        "default": false
                    },
                    "enrichTopics": {
                        "title": "AI: Topic tags",
                        "type": "boolean",
                        "description": "Assign 1-5 topic tags from this controlled vocabulary: politics, tech, finance, crypto, ai, sports, entertainment, science, business, lifestyle, news, other. Adds a `topics: [string]` field.",
                        "default": false
                    },
                    "enrichSummary": {
                        "title": "AI: One-line summary",
                        "type": "boolean",
                        "description": "Produce a single-sentence summary of each post. Adds a `summary: string` field. Useful for digests, briefings, and timeline views where the full text is too long.",
                        "default": false
                    },
                    "enrichTranslation": {
                        "title": "AI: Translation",
                        "type": "boolean",
                        "description": "Translate each post's text into the language specified by `translationTarget`. Adds a `translation: string` field. URLs, hashtags and @mentions are preserved verbatim.",
                        "default": false
                    },
                    "translationTarget": {
                        "title": "Translation target language",
                        "type": "string",
                        "description": "ISO-639-1 language code for translations (e.g. `en`, `es`, `de`, `fr`, `ru`, `zh`, `ja`, `pt`). Only used when `enrichTranslation` is true.",
                        "default": "en"
                    },
                    "enrichEntities": {
                        "title": "AI: Named entity extraction",
                        "type": "boolean",
                        "description": "Extract named entities mentioned in each post. Adds an `entities: {companies, people, places, tokens}` field, where `tokens` are crypto tickers like BTC, ETH, SOL.",
                        "default": false
                    },
                    "enrichImageDescriptions": {
                        "title": "AI: Image descriptions (vision)",
                        "type": "boolean",
                        "description": "Generate 1-2 sentence descriptions for each image attached to a post, using Gemini multimodal. Adds `imageDescriptions: [{url, description, contains_text}]`. Capped at 4 images per post and 6 MB per image. Vision tokens (~1,000+ per image) cost more than text — enable only when you actually need visual content extracted.",
                        "default": false
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
