API для агентов
Тот же каталог, на котором работает сайт, только обращается к нему программа. Ни аккаунта, ни ключа, ни счёта: запрос оплачивает сам себя, поэтому агент, который никогда с нами не связывался, может вызвать его с первой попытки.
Два эндпойнта. Поиск находит то, чего вы не можете назвать; загрузка читает из Telegram свежие посты и профиль того, что назвать можете, — в момент запроса. У обоих параметры передаются в строке запроса, не в теле: цену называет платёжный слой, а он видит только URL.
Поиск
POST /api/agent/search?q=<запрос>&limit=<n>&lang=<код>&kind=<тип>&min_users=<n>&max_users=<n> | Параметр | Обязательный | По умолчанию | Значение |
|---|---|---|---|
q | да | — | Запрос. Ищем по смыслу, а не только по названию; @username вернёт этот канал и ближайшие к нему. |
limit | нет | 100 | Сколько результатов нужно, от 1 до 1000. Больше 1000 обрезается — и оплачивается по обрезанному числу. |
lang | нет | — | Код ISO 639-1, например en, ru. Записи, язык которых определить не удалось, остаются в выдаче. |
kind | нет | — | channel, group или bot. |
min_users | нет | — | Нижняя граница числа подписчиков, участников или пользователей в месяц. |
max_users | нет | — | Верхняя граница. |
Загрузка по имени
POST /api/agent/fetch?usernames=<имя1,имя2,…> | Параметр | Обязательный | По умолчанию | Значение |
|---|---|---|---|
usernames | да | — | Юзернеймы через запятую, от 1 до 100. Понимаются @name и t.me/name. Больше ста — отказ 400, деньги не списываются. |
Каждое имя читается из Telegram в момент вызова, поэтому посты такие же свежие, как на странице самого Telegram. На каждое имя
приходит ровно одна запись, в том порядке, в котором спрашивали. Имя, под которым в Telegram ничего нет, приходит как
{"type": "absent"} и оплачивается: продаётся ответ на вопрос, а «такого нет» стоит нам столько же, сколько
«вот оно». Если Telegram не ответил вовремя, запись берётся из нашего каталога и помечается "stale": true.
Посты есть у каналов. Группа и бот приходят с профилем и вообще без поля messages: читаемой
истории Telegram для них не публикует.
Оплата за запрос
Оплата идёт по протоколу x402 — HTTP 402, без ключей и без подписки.
- Отправьте запрос без платёжного заголовка.
-
В ответ придёт
402 Payment Requiredи заголовокPayment-Required: base64 от JSON, в котором получатель, токен, сумма и сети, которые мы принимаем. -
Подпишите платёж по одному из вариантов и повторите запрос с заголовком
Payment-Signature. Авторизация действует пять минут. - При
200платёж проводится в сети. При любой ошибке не списывается ничего.
Клиентская библиотека x402 делает все четыре шага за вас: x402-fetch для JavaScript, x402-reqwest для Rust.
Поиск — по тарифу за каждую сотню запрошенных результатов, с округлением вверх, минимум один: ceil(limit / 100) × базовая цена. Загрузка — линейно по именам: имена × базовая цена, повторы схлопываются до оценки. И то и другое в USDC, в
сетях Base и Solana. Базовую сумму называет сам 402; она же есть в /.well-known/x402
и openapi.json — оба документа читаются бесплатно.
Ответ поиска
200, application/json, массив в порядке ранжирования. Пустой результат — это [], а не
null. Поля, в которых ничего нет, не отдаются.
[
{
"id": 123456,
"kind": "channel",
"username": "example_channel",
"name": "Example Channel",
"bio": "An example Telegram channel",
"avatar_url": "https://semagram.io/api/avatar/example_channel.jpg",
"user_count": 15000
}
] | Поле | Тип | Значение |
|---|---|---|
id | number | Постоянный идентификатор. Переживает переименование, в отличие от username. |
kind | string | channel, group или bot. |
username | string | Юзернейм в Telegram, без @. |
name | string? | Отображаемое имя. |
bio | string? | Краткое описание со страницы Telegram. |
description | string? | Полное описание бота. |
lang | string? | Язык, ISO 639-1. Нет, если его не удалось определить. |
avatar_url | string? | Аватарка. Её раздаём мы, а не Telegram. |
user_count | number? | Подписчики канала, участники чата, пользователи бота в месяц. |
Ответ загрузки
200, application/json, по записи на каждое имя в том порядке, в котором спрашивали.
messages есть только у канала, свежие первыми, и тогда это всегда массив — не null.
[
{
"type": "channel",
"username": "example_channel",
"id": 123456,
"name": "Example Channel",
"bio": "Short channel bio",
"lang": "en",
"user_count": 15000,
"fetched_at": "2026-09-28T10:14:00Z",
"messages": [
{
"id": 412,
"text": "An example post",
"posted_at": "2026-09-28T09:04:00Z",
"forwarded_from": "telegram",
"link": "https://t.me/example_channel/412",
"view_count": 91024
}
]
},
{ "type": "absent", "username": "no_such_name" }
] | Поле | Тип | Значение |
|---|---|---|
type | string | channel, group, bot, absent либо invalid. |
username | string | Имя, которое спрашивали, в нижнем регистре. |
id | number? | Тот же идентификатор, что возвращает поиск, если запись есть в каталоге. |
name, bio, lang, avatar_url, user_count | — | Как в результате поиска. |
description, commands | string?, object? | Описание бота и его команды: имя — описание. |
fetched_at | string | Когда данные прочитаны из Telegram. |
stale | boolean? | Есть и равно true, если Telegram не ответил и запись взята из нашего каталога. |
messages | array? | Последние посты. Поле есть только у канала. |
У сообщения есть id, posted_at, link, а также, если есть что положить, text,
forwarded_from и view_count. forwarded_from — одно поле с тремя состояниями:
юзернейм источника, если он публичный; литеральное true, если это репост, а
источник скрыт; отсутствие поля, если пост оригинальный. Отдаётся только текст: фотография без подписи — это
сообщение без text.
Ошибки
| Код | Что означает |
|---|---|
400 | Поиск: q отсутствует или пуст, либо kind не один из трёх. Загрузка: usernames не названы или их больше ста. |
402 | Платежа не было или он не прошёл. Смотрите заголовок Payment-Required. |
502 | Запрос не отработал. Можно повторить; не списывается ничего. |
Пример
import { wrapFetch } from "x402-fetch";
const fetch402 = wrapFetch(fetch, wallet);
const found = await fetch402(
"https://semagram.io/api/agent/search?q=crypto+news&limit=200",
{ method: "POST" },
);
const results = await found.json();
// А потом прочитать, что эти каналы пишут.
const names = results.slice(0, 10).map((r) => r.username).join(",");
const fetched = await fetch402(
`https://semagram.io/api/agent/fetch?usernames=${names}`,
{ method: "POST" },
);
const entities = await fetched.json(); Model Context Protocol
/mcp отдаёт тот же каталог двумя инструментами — search и fetch — поверх streamable HTTP.
Аргументы те же, что выше, но структурированным объектом: fetch принимает юзернеймы массивом. Строки в ответе те
же.
Вход через OAuth 2.0: при первом подключении вас один раз проведут через браузер, дальше клиент хранит токен сам. Клиенты регистрируются вручную, поэтому идентификатор клиента нужно указать вместе с адресом:
claude mcp add --transport http --client-id semagram-mcp semagram https://mcp.semagram.io/mcp Доступ выдаётся по запросу. Напишите на [email protected] и укажите клиент, которым будете подключаться.
Ещё
- agent-api.md — эта страница одним файлом Markdown, чтобы её читала модель.
- openapi.json — описание в формате OpenAPI.
- /.well-known/x402 — документ обнаружения для платёжных каталогов.
- Документация x402 — сам протокол.