# Echonews News Intelligence API (`echonews.io/news-intelligence-api`) Actor

News intelligence API for deduplicated, clustered Stories and source Posts from X, Telegram, and RSS. Search by keyword or meaning, filter structured news data, inspect multilingual source Posts and translations, and retrieve media.

- **URL**: https://apify.com/echonews.io/news-intelligence-api.md
- **Developed by:** [Echonews](https://apify.com/echonews.io) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$5.00 / 1,000 api requests

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/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

**Echonews News Intelligence API** provides structured, normalized, and deduplicated news data from **X (Twitter), Telegram, and RSS** through a lightweight Apify Actor. It clusters related source Posts into stable **Stories**, normalizes Story-level text to English, preserves multilingual source Posts and available English translations, and supports **keyword, semantic, and hybrid news search**.

Use Echonews as a **news API**, **news data API**, or **news intelligence API** for media monitoring, financial markets and securities research, market and competitive intelligence, news aggregation, journalism and OSINT research, AI agents, RAG pipelines, dashboards, and other workflows that need structured news context instead of a flat stream of duplicate articles and posts.

Website: [echonews.io](https://echonews.io)\
Support: <support@echonews.io>

### Coverage at a glance

- Over **4,000 actively scanned sources** across X, Telegram, and RSS
- Up to **180K raw posts per day**
- Source content in **122 languages**, with English translation available where supported
- **9 top-level categories** and **248 subcategories**
- Continuous **24/7 news ingestion and processing**
- Low-latency delivery designed to stay close to **real time**

### News intelligence API features

Echonews combines a Story-level intelligence layer with access to the underlying source material. The Actor exposes the same structured data model through an allow-listed RPC interface.

```text
Clustered Stories                         ✓
Cross-source deduplication                ✓
Stable Story IDs                          ✓
English-normalized Stories                ✓

Raw Post feed + structural filtering      ✓
Original multilingual Posts               ✓
Original + English-translated Post text   ✓

Keyword Story search                      ✓
Semantic Story search                     ✓
Hybrid Story search                       ✓

Categories/subcategories                  ✓
Source ID filtering                       ✓
Source-type filtering                     ✓
Source coverage filtering                 ✓
Post-language filtering                   ✓
Verified-only Source filtering            ✓

Historical date filtering                 ✓
Cursor-based pagination                   ✓
Top/popular Stories                       ✓
Mention counts by source type             ✓

Source catalog                            ✓
Source avatars                            ✓
Source coverage                           ✓

Post media                                ✓
Story-level media aggregation             ✓
On-demand Telegram photo materialization  ✓
```

Unlike a flat article feed, Echonews keeps one deduplicated Story as the primary news entity while preserving access to the original Posts that contributed to it.

### What can you build with Echonews?

**Media monitoring and news monitoring.** Follow topics across X, Telegram, and RSS without manually deduplicating every repeated report.

**Financial markets, securities, and market intelligence.** Track news around companies, industries, equities, regulators, macro themes, and market-moving topics using semantic discovery, categories, source filters, and chronological feeds. Echonews provides news intelligence, not prices, quotes, fundamentals, or trading data.

**Competitive intelligence.** Monitor companies, sectors, regions, Sources, and emerging topics while reducing duplicate reporting into stable Story entities.

**News aggregation and dashboards.** Build feeds around stable Story IDs instead of presenting every source mention as a separate event.

**Journalism and OSINT research.** Find a Story, inspect its source Posts, original text, translations, Source metadata, timestamps, and media.

**AI agents and RAG.** Supply agents with structured current-events context, semantic news search, source evidence, and normalized Story data rather than unstructured search-result pages.

**Multilingual news workflows.** Work with English-normalized Story entities while retaining original-language Posts and available English translations.

### Quick start

This Actor uses Apify authentication. You need an **Apify API token**.

The synchronous endpoint is:

```text
https://api.apify.com/v2/actors/echonews.io~news-intelligence-api/run-sync
```

Send your Apify token in the `Authorization` header:

```http
Authorization: Bearer YOUR_APIFY_TOKEN
```

#### Search news by topic or meaning

```bash
curl -X POST \
  "https://api.apify.com/v2/actors/echonews.io~news-intelligence-api/run-sync" \
  -H "Authorization: Bearer YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "operation": "stories.search",
    "query": "energy market",
    "mode": "hybrid",
    "sort": "relevance",
    "limit": 20
  }'
```

`stories.search.query` must be **English** and must contain at least one Latin letter.

#### Get the latest World Stories

```bash
curl -X POST \
  "https://api.apify.com/v2/actors/echonews.io~news-intelligence-api/run-sync" \
  -H "Authorization: Bearer YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "operation": "stories.list",
    "categories": ["world"],
    "limit": 20
  }'
```

#### Example paginated response

Successful backend JSON is returned unchanged; the Actor does not add another response wrapper.

The response below is shortened to one Story for readability. A page contains **up to** `limit` items and may contain fewer.

```json
{
  "data": [
    {
      "id": 19621027,
      "title": "Philippines to Establish AI Data Center Hub",
      "summary": "The Philippines has designated 1,620 hectares in New Clark City for the development of an artificial intelligence (AI) data center parts hub. This initiative aims to boost the country's tech industry and attract investments.",
      "tags": [
        "AI",
        "Data Center",
        "Philippines",
        "New Clark City",
        "Tech Industry"
      ],
      "category": {
        "id": 2,
        "slug": "world",
        "name": "World"
      },
      "subcategories": [
        {
          "id": 6,
          "slug": "judiciary",
          "name": "Judiciary"
        },
        {
          "id": 8,
          "slug": "regulators",
          "name": "Regulators"
        },
        {
          "id": 40,
          "slug": "asia",
          "name": "Asia"
        }
      ],
      "created_at": "2026-09-20T05:37:24.314228Z",
      "updated_at": "2026-09-20T05:14:55Z",
      "links": {
        "self": "/v1/stories/19621027",
        "posts": "/v1/posts?story_id=19621027",
        "media": "/v1/stories/19621027/media"
      }
    }
  ],
  "next_cursor": "eyJ2IjoxLC...",
  "has_more": true
}
```

`next_cursor` is intentionally shortened in this documentation because cursors are opaque and should be copied unchanged from the real response.

#### Python example

The Apify Python client returns run metadata from `call()`. Read the Actor's `OUTPUT` record to get the Echonews response.

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")

run_input = {
    "operation": "stories.search",
    "query": "energy market",
    "mode": "hybrid",
    "sort": "relevance",
    "limit": 20,
}

run = client.actor("echonews.io/news-intelligence-api").call(
    run_input=run_input,
)

if run is None:
    raise RuntimeError("Actor run could not be started.")

record = client.key_value_store(
    run.default_key_value_store_id
).get_record("OUTPUT")

if record is None:
    print("No result")
else:
    print(record["value"])
```

### Choose an operation

Every run accepts one JSON object with a required `operation` field. Only the fields supported by that operation are accepted.

| Goal | Operation | Main required fields |
|---|---|---|
| List or filter Sources | `sources.list` | - |
| Get one Source | `sources.get` | `source_id` |
| List Source coverage locations | `locations.list` | - |
| List Categories | `categories.list` | - |
| List or search Subcategories | `subcategories.list` | - |
| Get chronological Stories | `stories.list` | - |
| Get popularity-ranked Stories | `stories.top` | - |
| Get one Story | `stories.get` | `story_id` |
| Search Stories | `stories.search` | `query`, `mode`, `sort` |
| Get Story media | `stories.media.list` | `story_id` |
| List source Posts | `posts.list` | - |
| Get one Post | `posts.get` | `post_id` |
| Materialize a Telegram photo | `posts.media.resolve` | `post_id`, `media_id` |

### Core concepts

The data model is easiest to understand as a hierarchy of a normalized Story and its underlying source evidence:

```text
Story
├── English title / summary / tags
├── Category + Subcategories
└── related Posts
    ├── Post
    │   ├── Source (X / Telegram / RSS)
    │   ├── source timestamp + language
    │   ├── original text
    │   ├── English translation when available
    │   └── media
    ├── Post
    └── Post
```

A Story **groups** related Posts conceptually, but Story JSON does not embed every full Post object. Use `posts.list` with `story_id` when you want the underlying Posts.

#### Story

A **Story** is a stable, deduplicated news event assembled from related Posts across Sources and languages. New Posts can join an existing Story, so its `title`, `summary`, `tags`, and `updated_at` may change as coverage evolves.

Story-level text is normalized to English.

Representative Story object from a live response:

```json
{
  "id": 19621038,
  "title": "Super Contributions Surge After Tax Changes",
  "summary": "Superannuation contributions have surged by more than a third following changes to budget taxes. The alterations to negative gearing and capital gains tax have made super funds more appealing, leading to an increase in voluntary contributions.",
  "tags": [
    "Superannuation",
    "Budget Tax Changes",
    "Voluntary Contributions",
    "Investment"
  ],
  "category": {
    "id": 4,
    "slug": "business",
    "name": "Business"
  },
  "subcategories": [
    {
      "id": 64,
      "slug": "budget",
      "name": "Budget"
    },
    {
      "id": 66,
      "slug": "trade",
      "name": "Trade"
    },
    {
      "id": 70,
      "slug": "equities",
      "name": "Equities"
    }
  ],
  "created_at": "2026-09-20T05:38:04.254158Z",
  "updated_at": "2026-09-20T05:07:32Z"
}
```

#### Post

A **Post** is one source item from X, Telegram, or RSS. Language belongs to the Post, not to the Source.

A Post can expose Source metadata, the source timestamp, original language, original text, an available English translation, an external URL, deduplication information, and attached media. The end-to-end workflow below shows a real Telegram Post with original Ukrainian text, an English translation, and on-demand media.

For RSS, `text.original` is the text supplied by the feed, commonly the feed description. It is not necessarily the full article body available at `external_url`.

#### Source

A **Source** is an X account, Telegram channel, or RSS feed. Source metadata can include name, description, type, handle or feed URL, avatar, verification state, and geographic coverage.

Source coverage describes the Source's geographic focus. It does **not** mean that a Story happened in that location.

Allowed Source types are:

```text
x
telegram
rss
```

#### Categories and Subcategories

A Story has one primary Category and can have multiple Subcategories. Use category and subcategory **slugs**, not display names, in filters.

Categories and Subcategories are separate reference datasets; Subcategories are not exposed as mandatory children of one Category.

#### Media

Media belongs to Posts. `stories.media.list` aggregates media from the Posts associated with a Story.

Media types:

```text
image
video
animation
```

Availability:

```text
ready
on_demand
```

Common fields include `id`, `type`, `availability`, `url`, `thumbnail_url`, `width`, `height`, `duration_ms`, `mime_type`, `views`, and `variants`. Optional platform-specific fields are omitted when unavailable.

### End-to-end example: from a Story to source media

The following shortened workflow uses values from one live Actor session. It shows how an application or AI agent can move from a normalized Story to the underlying Telegram source Post and finally materialize an on-demand image.

#### 1. Find a Story

Request:

```json
{
  "operation": "stories.list",
  "updated_after": "2026-09-19T00:00:00Z",
  "categories": ["world", "business"],
  "source_types": ["x", "telegram"],
  "post_languages": ["en", "uk"],
  "verified_only": false,
  "order": "desc",
  "limit": 20
}
```

One Story from the returned page:

```json
{
  "id": 19595872,
  "title": "Air Defense Activity in Kyiv Region",
  "summary": "Air defense forces in Kyiv region are actively engaging enemy targets as of 20:09 local time.",
  "category": {
    "id": 2,
    "slug": "world",
    "name": "World"
  },
  "updated_at": "2026-09-20T05:09:45Z"
}
```

#### 2. Get the source Posts behind that Story

Request:

```json
{
  "operation": "posts.list",
  "story_id": 19595872
}
```

One returned Telegram Post:

```json
{
  "id": 44195867,
  "story_id": 19595872,
  "source": {
    "id": 1190,
    "name": "24 Канал | Новини України",
    "type": "telegram",
    "handle": "channel24_ua"
  },
  "source_time": "2026-09-20T05:09:08Z",
  "language": "uk",
  "external_url": "https://t.me/channel24_ua/233260",
  "media_counts": {
    "images": 1,
    "videos": 0,
    "animations": 0
  }
}
```

#### 3. Get the Post text and media

Request:

```json
{
  "operation": "posts.get",
  "post_id": 44195867
}
```

Relevant response fields:

```json
{
  "text": {
    "original": "⚫️На Київщині працюють сили ППО, - ОВА.",
    "translated_en": "On Kyiv Oblast, air defense forces are working, - OWG."
  },
  "media": [
    {
      "id": "44195867:0",
      "type": "image",
      "availability": "on_demand",
      "width": 1280,
      "height": 879,
      "resolve_url": "/v1/posts/44195867/media/44195867:0/resolve"
    }
  ]
}
```

#### 4. Materialize the Telegram image

Request:

```json
{
  "operation": "posts.media.resolve",
  "post_id": 44195867,
  "media_id": "44195867:0"
}
```

Response:

```json
{
  "id": "44195867:0",
  "type": "image",
  "availability": "ready",
  "url": "https://nbg1.your-objectstorage.com/telegram-media-files/channel24_ua_233260_5294481137713487637.jpg",
  "width": 1280,
  "height": 879,
  "expires_at": "2026-10-01T00:00:00Z"
}
```

The media URL is temporary; respect `expires_at`.

### Common API conventions

#### Timestamps and calendar dates

Timestamp fields use ISO-8601 UTC values:

```text
2026-09-06T00:00:00Z
```

Calendar-day fields use UTC dates:

```text
2026-09-04
```

For Story filters:

- `updated_after` is inclusive: `updated_at >= updated_after`;
- `updated_before` is exclusive: `updated_at < updated_before`;
- `updated_on` selects one UTC calendar day;
- do not combine `updated_on` with `updated_after` or `updated_before`.

Post time filters use `source_time_after`, `source_time_before`, or `source_on`.

#### Multi-value filters

Multi-value RPC fields are JSON arrays:

```json
{
  "source_types": ["x", "telegram"],
  "post_languages": ["en", "uk"]
}
```

Values inside one field are combined with **OR**. Different fields are combined with **AND**.

For example:

```json
{
  "source_types": ["telegram"],
  "post_languages": ["ar"]
}
```

requires a matching Arabic Telegram Post. A Telegram Post and a different Arabic Post in the same Story do not satisfy that combination.

#### Verified Sources

A Source is `verified` when it carries the source platform's verification signal:

- Telegram: official Telegram verification badge;
- X: blue, gold, or grey verification check;
- RSS: not considered verified.

`verified_only: true` restricts matching to verified Sources. Verification reflects the source platform's status and is not an Echonews trust score or endorsement.

#### Pagination and cursors

Paginated operations are:

- `stories.list`;
- `stories.top`;
- `stories.search`;
- `stories.media.list`;
- `posts.list`.

`limit` must be from `1` through `100`. It is a page-size maximum, not a guarantee that every successful page contains exactly that many items.

| Operation | Default `limit` |
|---|---:|
| `stories.list` | 50 |
| `stories.top` | 20 |
| `stories.search` | 25 |
| `stories.media.list` | 50 |
| `posts.list` | 50 |

Treat `next_cursor` as opaque. Do not decode, edit, or construct it.

A continuation request must be identical to the input that produced the cursor, except for adding `cursor`. This includes path IDs, filters, query, mode, sort, `order` where applicable, and `limit`.

First page:

```json
{
  "operation": "stories.list",
  "categories": ["world"],
  "order": "desc",
  "limit": 20
}
```

Next page:

```json
{
  "operation": "stories.list",
  "categories": ["world"],
  "order": "desc",
  "limit": 20,
  "cursor": "eyJ2IjoxLC..."
}
```

Reference operations such as `sources.list`, `locations.list`, `categories.list`, and `subcategories.list` are not paginated.

### Story filters

`stories.list` and `stories.top` accept the following fields at the top level. `stories.search` accepts the same fields inside `filters`.

| Field | Type | Meaning |
|---|---|---|
| `updated_after` | string | Inclusive Story update timestamp lower bound. |
| `updated_before` | string | Exclusive Story update timestamp upper bound. |
| `updated_on` | string | UTC day in `YYYY-MM-DD`; mutually exclusive with the timestamp bounds. |
| `categories` | string\[] | Category slugs. |
| `subcategories` | string\[] | Subcategory slugs. |
| `source_ids` | integer\[] | Match Stories containing Posts from these Sources. |
| `source_types` | string\[] | Any of `x`, `telegram`, `rss`. |
| `source_location_ids` | integer\[] | Coverage Location IDs of the Post's Source. |
| `post_languages` | string\[] | Languages of matching individual Posts. |
| `verified_only` | boolean | Restrict matching Posts to verified Sources; backend default is `false`. |

### Story API

#### `stories.list` - latest and historical Stories

Returns a chronological Story feed with structural filtering. This operation does not perform text search.

Additional fields:

| Field | Required | Meaning |
|---|---:|---|
| `order` | No | `asc` or `desc`; default `desc`. |
| `limit` | No | Page size `1-100`; default `50`. |
| `cursor` | No | Opaque continuation cursor. |

Example:

```json
{
  "operation": "stories.list",
  "updated_after": "2026-08-18T00:00:00Z",
  "updated_before": "2026-09-06T00:00:00Z",
  "categories": ["world"],
  "source_types": ["x", "telegram"],
  "order": "desc",
  "limit": 20
}
```

#### `stories.get` - one detailed Story

Required field: `story_id`.

```json
{
  "operation": "stories.get",
  "story_id": 19621038
}
```

Detailed Story responses can include mention counts by Source type and media counts in addition to the normal Story fields.

#### `stories.top` - popularity-ranked Stories

Returns Stories ordered by Echonews popularity ranking. The ranking formula is not part of the public contract.

Accepts all common Story filters plus `limit` and `cursor`. `order` is not accepted. Default `limit` is `20`.

```json
{
  "operation": "stories.top",
  "updated_on": "2026-09-04",
  "post_languages": ["en"],
  "limit": 20
}
```

#### `stories.search` - keyword, semantic, and hybrid news search

Searches English-normalized Story text. The query must be **English** and must contain at least one Latin letter.

| Field | Required | Allowed values / meaning |
|---|---:|---|
| `query` | Yes | English-language query. |
| `mode` | Yes | `keyword`, `semantic`, or `hybrid`. |
| `sort` | Yes | `relevance` or `updated_at`. |
| `filters` | No | Common Story filters, nested inside this object. |
| `limit` | No | Page size `1-100`; default `25`. |
| `cursor` | No | Opaque continuation cursor. |

`order` is not accepted by Story Search.

Search modes:

| Mode | Best for | Matching behavior |
|---|---|---|
| `keyword` | Names, phrases, explicit wording | Lexical matching. |
| `semantic` | Concepts and meaning | Semantic similarity. |
| `hybrid` | General-purpose discovery | Union of keyword and semantic results, deduplicated by Story ID. |

Sorts:

| Sort | Behavior |
|---|---|
| `relevance` | Best matches first. |
| `updated_at` | Newest matching Stories first. |

Example:

```json
{
  "operation": "stories.search",
  "query": "global energy market",
  "mode": "hybrid",
  "sort": "updated_at",
  "filters": {
    "updated_after": "2026-08-18T00:00:00Z",
    "post_languages": ["en"]
  },
  "limit": 20
}
```

Semantic matching currently covers approximately the latest **35 days**, regardless of sort. This applies to `semantic` mode and to the semantic branch of `hybrid` mode. Keyword matching is not subject to that semantic time range, so hybrid results can still include older keyword matches.

Semantic and hybrid requests may take tens of seconds. If semantic processing is temporarily unavailable, `semantic` can fail with a backend internal error; `hybrid` can continue with keyword results.

### Source and reference API

#### `sources.list`

Lists matching Sources. It is not paginated.

Optional fields:

- `q` - search Source name, handle, feed URL, or description;
- `types` - `x`, `telegram`, `rss`;
- `location_ids` - Source coverage Location IDs;
- `verified_only` - restrict to verified Sources.

```json
{
  "operation": "sources.list",
  "q": "Reuters",
  "types": ["x", "rss"],
  "location_ids": [1, 24],
  "verified_only": false
}
```

#### `sources.get`

Required field: `source_id`.

```json
{
  "operation": "sources.get",
  "source_id": 11
}
```

#### `locations.list`

Lists the flat Source coverage Location dataset. Optional fields are `q` and `levels`.

Allowed `levels`:

```text
global
country
region
city
other
```

```json
{
  "operation": "locations.list",
  "levels": ["country", "region"]
}
```

#### `categories.list`

Accepts only the operation itself and returns the complete top-level Category collection.

```json
{
  "operation": "categories.list"
}
```

#### `subcategories.list`

Optional field: `q`. Omit `q` to return all Subcategories.

```json
{
  "operation": "subcategories.list",
  "q": "nuclear"
}
```

### Post API and source evidence

#### `posts.list`

Returns individual source Posts. Use it either to inspect the source evidence behind one Story or as a structurally filtered Post feed.

It does **not** perform keyword or semantic Post search.

| Field | Meaning |
|---|---|
| `story_id` | Parent Story ID. |
| `categories` | Parent Story Category slugs. |
| `subcategories` | Parent Story Subcategory slugs. |
| `source_ids` | Source IDs. |
| `source_types` | `x`, `telegram`, `rss`. |
| `source_location_ids` | Coverage Location IDs of each Post's Source. |
| `post_languages` | Language of each returned Post. |
| `source_time_after` | Source timestamp lower bound. |
| `source_time_before` | Source timestamp upper bound. |
| `source_on` | Source UTC day in `YYYY-MM-DD`. |
| `verified_only` | Restrict to verified Sources. |
| `has_media` | Filter by media presence. |
| `is_duplicate` | Filter by Post deduplication status. |
| `order` | `asc` or `desc`; default `desc`. |
| `limit` | Page size `1-100`; default `50`. |
| `cursor` | Pagination cursor. |

Story-level filters apply to the parent Story. Post-level filters apply to each returned Post itself.

For example, this returns Posts that are themselves Arabic Telegram Posts:

```json
{
  "operation": "posts.list",
  "source_types": ["telegram"],
  "post_languages": ["ar"],
  "limit": 50
}
```

Get the source Posts behind one Story:

```json
{
  "operation": "posts.list",
  "story_id": 19595872,
  "limit": 50
}
```

#### `posts.get`

Required field: `post_id`.

```json
{
  "operation": "posts.get",
  "post_id": 44195867
}
```

Returns one detailed Post with text, deduplication information, Source metadata, and media when available.

### News media API

#### `stories.media.list`

Returns a paginated flattened collection of media from the Posts associated with one Story.

| Field | Required | Meaning |
|---|---:|---|
| `story_id` | Yes | Story ID. |
| `types` | No | `image`, `video`, `animation`. |
| `source_types` | No | `x`, `telegram`, `rss`. |
| `availability` | No | `ready`, `on_demand`. |
| `limit` | No | Page size `1-100`; default `50`. |
| `cursor` | No | Pagination cursor. |

```json
{
  "operation": "stories.media.list",
  "story_id": 19595872,
  "types": ["image", "video", "animation"],
  "source_types": ["x", "telegram"],
  "availability": ["ready", "on_demand"],
  "limit": 20
}
```

#### `posts.media.resolve`

Synchronously materializes one supported on-demand Telegram photo. Telegram videos and animations are not supported by this operation.

Required fields: `post_id` and `media_id`.

```json
{
  "operation": "posts.media.resolve",
  "post_id": 44195867,
  "media_id": "44195867:0"
}
```

A successful materialization returns a ready media object with a temporary URL and `expires_at` when available.

This operation can enqueue or materialize media and is not a pure read. It can take approximately 30 seconds.

### Native REST links in Actor output

The Actor preserves successful backend JSON unchanged. Some returned objects therefore contain native Echonews REST references such as:

```json
{
  "links": {
    "self": "/v1/stories/19620906",
    "posts": "/v1/posts?story_id=19620906",
    "media": "/v1/stories/19620906/media"
  }
}
```

Fields such as `links` and `resolve_url` are relative references to the native Echonews REST API. They are preserved as part of the data but are **not Apify RPC URLs**.

When using the Actor, use the corresponding RPC operation instead:

| Native response reference | Apify RPC equivalent |
|---|---|
| Story `links.self` | `stories.get` with `story_id` |
| Story `links.posts` | `posts.list` with `story_id` |
| Story `links.media` | `stories.media.list` with `story_id` |
| Post `links.self` | `posts.get` with `post_id` |
| Media `resolve_url` | `posts.media.resolve` with `post_id` and `media_id` |

Do not construct Apify requests by prepending an Apify hostname to these REST paths.

### Output, no-result behavior, and errors

#### Successful output

For every successful JSON response, the Actor returns the Echonews backend JSON unchanged. There is no extra Actor wrapper.

If the backend response already contains `data`, `next_cursor`, or `has_more`, those are native response fields and are preserved as-is.

#### Valid 204 no-result

`posts.media.resolve` can validly produce a backend `204 No Content` when a Telegram photo cannot be materialized.

The Standard Actor cannot proxy that native HTTP status through `run-sync`. Instead, it omits the `OUTPUT` record and exits successfully. `run-sync` therefore returns no record data.

This valid no-result outcome is **never charged**.

#### Error envelope

Actor-facing errors use one JSON shape:

```json
{
  "error": {
    "status": 422,
    "code": "invalid_request",
    "message": "limit must be at most 100."
  }
}
```

For documented backend errors, the Actor preserves the backend status, code, and message and adds `status` inside the error object.

Common errors:

| Code | Status | Meaning |
|---|---:|---|
| `invalid_request` | 400 or 422 | Backend validation error or Actor RPC validation error. |
| `invalid_cursor` | 400 | Corrupted, modified, or incompatible cursor. |
| `not_found` | 404 | Resource does not exist or is unavailable publicly. |
| `payment_limit_reached` | 402 | The successful result could not be charged within the run spending limit. |
| `billing_unavailable` | 402 | The required charge could not be recorded. |
| `backend_unavailable` | 502 | The Actor could not connect to Echonews. |
| `invalid_backend_response` | 502 | The backend returned an invalid success response. |
| `service_unavailable` | 503 | Temporary backend or required-dependency outage. |
| `backend_timeout` | 504 | Echonews did not respond before the Actor deadline. |
| `internal_error` | 500 | Backend or unexpected Actor failure. |

The `status` inside the JSON error describes the backend or Actor outcome. The outer Apify `run-sync` response does not proxy the native Echonews HTTP status.

### Pricing and Pay Per Event billing

This Actor uses Apify **Pay Per Event** pricing. A chargeable successful JSON result maps to one `api-request` event. The event price is shown in the Actor's **Pricing** tab and is not hard-coded in the API contract.

The following outcomes are not charged:

- Actor input or validation errors;
- backend `204` no-result outcomes;
- backend `4xx`, `5xx`, or future `429` errors;
- backend connection failures, malformed responses, and timeouts;
- results blocked by the Apify run spending limit.

Free and paid Apify users currently have the same API functionality and follow the same charge flow. There is no active free-user rate limit at this time.

### Timeouts and synchronous execution

This is a lightweight **Standard Apify Actor**. Each run handles one RPC operation and exits.

Apify's synchronous endpoint waits at most **300 seconds**. The Actor's backend timeout is **240 seconds**, leaving time for startup, response processing, billing, output persistence, and shutdown.

Semantic and hybrid searches may take tens of seconds. Supported Telegram photo materialization can take approximately 30 seconds. Clients and intermediaries should allow a sufficiently long timeout.

### AI agents and MCP

The Actor's contract is described by its Apify input schema and is available through Apify's API integrations. Public Actors can also be exposed to AI clients through the **Apify MCP server**; use the MCP tab on the Actor's API page for the generated configuration.

For agents choosing operations programmatically:

- use `stories.search` for topic or meaning-based discovery;
- use `stories.list` for chronological Story feeds;
- use `stories.top` for popularity-ranked Stories;
- use `posts.list` for source evidence or raw source-feed filtering;
- use `posts.get` for original text, translations, and Post media;
- use `stories.media.list` for Story-level media;
- use `sources.list`, `locations.list`, `categories.list`, and `subcategories.list` to discover valid reference data.

Do not invent backend paths or HTTP methods. Always call one documented RPC `operation`.

### Current limitations

The current Actor and backend do not provide:

- streaming, WebSocket, SSE, or webhook delivery;
- asynchronous search or bulk jobs;
- keyword or semantic search over raw Posts;
- entity, ticker, claim, or sentiment extraction and filtering;
- Story correction or revision history;
- machine-readable downstream rights metadata;
- Telegram video or animation materialization.

Historical access is available through date filters and cursor pagination. Semantic matching has the separate approximate 35-day window described under `stories.search`.

### Support

Echonews website: <https://echonews.io>\
Support: <support@echonews.io>

When contacting support about an Actor run, include the Apify run ID when possible. Do not send API tokens or other secrets.

# Actor input Schema

## `operation` (type: `string`):

The allow-listed Echonews RPC operation to execute. Only fields supported by the selected operation are accepted.

## `query` (type: `string`):

Required English-language Story search query for stories.search. Must contain at least one Latin letter.

## `mode` (type: `string`):

Required by stories.search. Keyword matches explicit wording, semantic matches meaning, and hybrid combines both result sets.

## `sort` (type: `string`):

Required by stories.search. Relevance returns best matches first; updated\_at returns newest matching Stories first.

## `filters` (type: `object`):

Optional common Story filters for stories.search. These fields must be nested here, not passed at the top level. Do not combine updated\_on with updated\_after or updated\_before.

## `source_id` (type: `integer`):

Required by sources.get.

## `story_id` (type: `integer`):

Required by stories.get and stories.media.list; optional parent Story filter for posts.list.

## `post_id` (type: `integer`):

Required by posts.get and posts.media.resolve.

## `media_id` (type: `string`):

Required by posts.media.resolve. Use the media ID returned by Post detail or Story media, for example 41919169:0.

## `updated_after` (type: `string`):

Inclusive Story updated\_at lower bound as an ISO-8601 UTC timestamp. Used by stories.list and stories.top.

## `updated_before` (type: `string`):

Exclusive Story updated\_at upper bound as an ISO-8601 UTC timestamp. Used by stories.list and stories.top.

## `updated_on` (type: `string`):

Story UTC calendar day. Used by stories.list and stories.top. Do not combine with updated\_after or updated\_before.

## `categories` (type: `array`):

Category slugs. Used by stories.list, stories.top, and as a parent Story filter for posts.list. Values are ORed.

## `subcategories` (type: `array`):

Subcategory slugs. Used by stories.list, stories.top, and as a parent Story filter for posts.list. Values are ORed.

## `source_ids` (type: `array`):

Source IDs. Used by stories.list, stories.top, and posts.list. Values are ORed.

## `source_types` (type: `array`):

Source platform types. Used by stories.list, stories.top, stories.media.list, and posts.list. Values are ORed.

## `source_location_ids` (type: `array`):

Source coverage Location IDs used by Story or Post filters. This is Source coverage, not the location where a Story happened.

## `post_languages` (type: `array`):

Languages of individual Posts. Used by Story filters and posts.list. Values are ORed.

## `verified_only` (type: `boolean`):

Restrict matching to verified X or Telegram Sources where supported. RSS Sources are not considered verified. Backend default is false.

## `source_time_after` (type: `string`):

Post source\_time lower bound as an ISO-8601 UTC timestamp.

## `source_time_before` (type: `string`):

Post source\_time upper bound as an ISO-8601 UTC timestamp.

## `source_on` (type: `string`):

Post source UTC calendar day in YYYY-MM-DD format.

## `has_media` (type: `boolean`):

Filter posts.list by whether each returned Post has media.

## `is_duplicate` (type: `boolean`):

Filter posts.list by Post deduplication status.

## `q` (type: `string`):

Optional text filter for sources.list, locations.list, or subcategories.list.

## `location_ids` (type: `array`):

Source coverage Location IDs for sources.list.

## `levels` (type: `array`):

Location levels for locations.list.

## `types` (type: `array`):

For sources.list: x, telegram, rss. For stories.media.list: image, video, animation. The Actor applies operation-specific validation.

## `availability` (type: `array`):

Availability filter for stories.media.list.

## `order` (type: `string`):

Chronological direction for stories.list (updated\_at) or posts.list (source\_time). Not accepted by stories.top or stories.search.

## `limit` (type: `integer`):

Items per page, from 1 to 100. Backend defaults: stories.list 50, stories.top 20, stories.search 25, stories.media.list 50, posts.list 50.

## `cursor` (type: `string`):

Opaque next\_cursor from the previous page. Repeat the original request unchanged and add only this cursor.

## Actor input object example

```json
{
  "operation": "stories.search",
  "query": "energy market",
  "mode": "hybrid",
  "sort": "relevance",
  "filters": {
    "post_languages": [
      "en"
    ]
  },
  "limit": 20
}
```

# Actor output Schema

## `response` (type: `string`):

The JSON response stored in the default key-value store under OUTPUT. Its shape depends on the selected RPC operation.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("echonews.io/news-intelligence-api").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("echonews.io/news-intelligence-api").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{}' |
apify call echonews.io/news-intelligence-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,echonews.io/news-intelligence-api"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/gNsH2l9kDpvZeqjxO/builds/bo2ObGD2etaDbEXR0/openapi.json
