Документация
Cleantify ищет в интернете через Яндекс и читает страницы в чистый Markdown — так, чтобы результат сразу можно было отдать AI-агенту. Подключается как MCP-сервер или вызывается через REST API, одним и тем же API-ключом.
- MCP
https://cleantify.ru/mcp- REST
https://cleantify.ru/api/v1
Содержание
Обзор
Четыре инструмента закрывают три задачи: найти источники, прочитать страницу и найти картинки. Поиск и чтение учитываются отдельно — у них свои лимиты.
| Инструмент | Когда использовать | REST | Расход |
|---|---|---|---|
web_search | Нужно найти страницы по теме и сразу получить их текст. | POST /search-and-fetch | 1 поиск + до limit чтений |
web_fetch | Адрес уже известен — прочитать одну страницу. | POST /fetch | 1 чтение, из кэша — 0 |
web_results | Нужны только ссылки, заголовки и сниппеты, чтобы выбрать, что читать. | POST /search | 1 поиск |
image_search | Нужны адреса картинок и страниц, где они найдены. Файлы не скачиваются. | POST /images | 1 поиск |
Типичный путь агента: web_search для вопроса «найди и расскажи»; web_results → web_fetch, когда сначала нужно выбрать источники и не тратить чтения на лишние страницы.
Быстрый старт
1. Создайте API-ключ
В кабинете нажмите «Создать API-ключ». Ключ вида ct_… показывается один раз — сохраните его сразу. Срок действия — 90 дней, отозвать можно в любой момент.
2. Подключите MCP
Claude Code — одной командой, сервер станет доступен во всех проектах:
claude mcp add --transport http --scope user cleantify https://cleantify.ru/mcp \
--header "Authorization: Bearer <API-ключ>"Проверить: claude mcp list. Уже открытый сеанс новый сервер не видит — начните новый.
Cursor — файл ~/.cursor/mcp.json. Если там уже есть серверы, добавьте ключ cleantify к остальным:
{
"mcpServers": {
"cleantify": {
"url": "https://cleantify.ru/mcp",
"headers": { "Authorization": "Bearer ${env:CLEANTIFY_API_KEY}" }
}
}
}Ключ лучше не вписывать в файл: Cursor подставит его из переменной окружения CLEANTIFY_API_KEY (синтаксис ${env:…} из документации Cursor).
Другие клиенты с поддержкой удаленного MCP (Streamable HTTP) настраиваются так же: адрес и заголовок Authorization. Имена полей зависят от клиента — сверьтесь с его документацией.
{
"mcpServers": {
"cleantify": {
"type": "http",
"url": "https://cleantify.ru/mcp",
"headers": { "Authorization": "Bearer <API-ключ>" }
}
}
}3. Первый вызов
Попросите агента обычными словами — он сам выберет инструмент:
Найди через cleantify три свежие страницы о выходе PostgreSQL 18 и перескажи главное со ссылками на источники.Проверить ключ без расхода лимитов можно запросом остатка квоты:
curl https://cleantify.ru/api/v1/usage \
-H "Authorization: Bearer <API-ключ>"Аутентификация
Каждый запрос к /mcp и /api/v1 передает ключ в заголовке. Другие способы (параметр в URL, cookie, OAuth) не поддерживаются.
Authorization: Bearer <API-ключ>- Ключ —
ct_и 43 символа. Без ключа, с отозванным или истекшим — ответ 401unauthorized. - Ключ действует 90 дней. Дата окончания видна в кабинете; заранее создайте новый и замените в клиентах.
- API рассчитан на серверы и агентов. Запрос из браузера со стороннего сайта отклоняется с 403
forbidden— не встраивайте ключ во фронтенд. - Тело POST-запроса — JSON с
Content-Type: application/json, не больше 16 КБ.
Инструменты MCP
Сервер работает по Streamable HTTP и отвечает обычным JSON. Клиент вызывает инструмент методом tools/call; результат лежит в structuredContent и продублирован JSON-строкой в content[0].text для клиентов, которые читают только текст. isError: true ставится, когда операция завершилась со статусом failed.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "web_fetch",
"arguments": { "url": "https://www.postgresql.org/about/", "max_chars": 12000 }
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [{ "type": "text", "text": "{\"request_id\":\"…\",\"status\":\"succeeded\",…}" }],
"structuredContent": { "request_id": "…", "status": "succeeded", "data": { "page": { … } }, "error": null, "usage": { … } },
"isError": false
}
}Содержимое structuredContent одинаково для MCP и REST — оно описано в разделе Формат ответа. Все инструменты только читают: ничего не меняют на сайтах и в аккаунте.
Параметры поиска
Общие для web_search и web_results. У image_search — свой набор, см. ниже. Неизвестные поля не игнорируются, а дают invalid_input.
querystring · 1–400обязательныйПоисковый запрос на языке нужных источников. Это запрос, а не адрес страницы. Поддерживаются операторы Яндекса:site:postgresql.org,lang:en,mime:pdf,url:postgresql.org/docs/*.search_type"com" | "ru" | "tr"по умолчанию"com"Поисковая база Яндекса, а не регион и не язык документов.com— международная,ru— русская,tr— турецкая. Уimage_searchпо умолчаниюru.pageinteger · 0–24по умолчанию0Номер страницы выдачи с нуля. При листании не меняйте query, limit и фильтры.family"moderate" | "strict" | "none"по умолчанию"moderate"Семейный фильтр: умеренный, строгий или выключен.
web_search
Находит страницы и читает их: в ответе выдача и Markdown каждой прочитанной страницы в порядке ранжирования. Используйте, когда агенту нужен текст источников, а не только ссылки.
Параметры
Общие параметры поиска, а также:
limitinteger · 1–5по умолчанию3Сколько результатов найти и прочитать. В MCP одно число задает и размер выдачи, и число чтений.max_charsinteger · 1000–50000по умолчанию20000Предел символов Markdown на одну страницу. Длиннее — обрезается, и в ответе будетtruncated: true, аoriginal_charsпокажет полную длину.
Пример
{
"query": "PostgreSQL 18 release notes site:postgresql.org",
"limit": 2,
"max_chars": 8000
}{
"request_id": "6f1c2a9e-3b7d-4c51-9a0e-2d8f4b6c1e73",
"status": "succeeded",
"data": {
"results": [
{
"url": "https://www.postgresql.org/docs/18/release-18.html",
"title": "PostgreSQL: Documentation: 18: E.1. Release 18",
"snippet": "Release date: 2025-09-25. Overview. PostgreSQL 18 contains many new features…"
},
{
"url": "https://www.postgresql.org/about/news/postgresql-18-released-3142/",
"title": "PostgreSQL 18 Released!",
"snippet": "The PostgreSQL Global Development Group today announced the release of PostgreSQL 18…"
}
],
"pages": [
{
"index": 0,
"status": "succeeded",
"data": {
"url": "https://www.postgresql.org/docs/18/release-18.html",
"markdown": "## E.1. Release 18\n\n**Release date:** 2025-09-25\n…",
"truncated": true,
"original_chars": 61420
}
},
{
"index": 1,
"status": "succeeded",
"data": {
"url": "https://www.postgresql.org/about/news/postgresql-18-released-3142/",
"markdown": "# PostgreSQL 18 Released!\n\nThe PostgreSQL Global Development Group…",
"truncated": false,
"original_chars": 7311,
"cached_at": "2026-10-21T08:14:02.512Z"
}
}
],
"search": { "search_type": "com", "page": 0, "family": "moderate" }
},
"error": null,
"usage": {
"search": 1,
"read": 1,
"day": { "id": "2026-10-21", "starts_at": "2026-10-20T21:00:00.000Z", "resets_at": "2026-10-21T21:00:00.000Z" },
"month": { "id": "2026-10", "starts_at": "2026-09-30T21:00:00.000Z", "resets_at": "2026-10-31T21:00:00.000Z" }
}
}- data.results
- Выдача: url, title, snippet.
- data.pages
- Чтения по порядку выдачи; index — номер результата в results.
- pages[1].cached_at
- Страница взята из кэша: чтение не списано, поэтому usage.read = 1.
- data.search
- Параметры поиска, с которыми выполнен запрос, включая значения по умолчанию.
Неудача одной страницы не скрывает остальные. Такая страница приходит без data, с адресом и кодом ошибки, а весь ответ получает status: "partial". Если не прочиталась ни одна — failed с кодом all_pages_failed. Пустая выдача — успешный ответ без страниц.
{ "index": 1, "url": "https://example.com/article", "status": "failed", "code": "page_blocked" }web_fetch
Читает одну страницу по адресу и возвращает Markdown. Ссылки на изображения остаются в тексте обычными Markdown-ссылками на своих местах. Используйте, когда адрес уже известен: из выдачи web_results, от пользователя или из другой страницы.
Параметры
urlstring · до 4096обязательныйАдрес страницы: только http или https, без логина и пароля в URL.max_charsinteger · 1000–50000по умолчанию20000Предел символов Markdown на одну страницу. Длиннее — обрезается, и в ответе будетtruncated: true, аoriginal_charsпокажет полную длину.
Пример
{
"url": "https://www.postgresql.org/about/",
"max_chars": 12000
}{
"request_id": "0b9d4e27-8a61-4f3c-b5d2-7e19c0a4f856",
"status": "succeeded",
"data": {
"page": {
"url": "https://www.postgresql.org/about/",
"markdown": "## What is PostgreSQL?\nPostgreSQL is a powerful, open source object-relational database system…",
"truncated": false,
"original_chars": 9877
}
},
"error": null,
"usage": {
"search": 0,
"read": 1,
"day": { "id": "2026-10-21", "starts_at": "2026-10-20T21:00:00.000Z", "resets_at": "2026-10-21T21:00:00.000Z" },
"month": { "id": "2026-10", "starts_at": "2026-09-30T21:00:00.000Z", "resets_at": "2026-10-31T21:00:00.000Z" }
}
}- page.markdown
- Текст страницы в Markdown, не длиннее max_chars.
- page.truncated
- true — текст обрезан по max_chars.
- page.original_chars
- Полная длина текста до обрезки.
- page.cached_at
- Есть, если страница отдана из кэша; время, когда ее прочитали.
web_results
Ищет и возвращает только ссылки, заголовки и сниппеты, не открывая страниц. Тратит один поиск и ни одного чтения. Используйте, чтобы выбрать источники, проверить, есть ли что-то по теме, или собрать список ссылок.
Параметры
Общие параметры поиска, а также:
limitinteger · 1–10по умолчанию10Сколько результатов вернуть.
Пример
{
"query": "ключевая ставка ЦБ решение совета директоров",
"limit": 3,
"search_type": "ru"
}{
"request_id": "c47e1f08-92ab-4d6e-8f35-1a0b7c9d2e64",
"status": "succeeded",
"data": {
"results": [
{
"url": "https://www.cbr.ru/press/keypr/",
"title": "Ключевая ставка | Банк России",
"snippet": "Совет директоров Банка России принял решение…"
}
],
"search": { "search_type": "ru", "page": 0, "family": "moderate" }
},
"error": null,
"usage": {
"search": 1,
"read": 0,
"day": { "id": "2026-10-21", "starts_at": "2026-10-20T21:00:00.000Z", "resets_at": "2026-10-21T21:00:00.000Z" },
"month": { "id": "2026-10", "starts_at": "2026-09-30T21:00:00.000Z", "resets_at": "2026-10-31T21:00:00.000Z" }
}
}image_search
Ищет картинки в Яндекс Картинках, в порядке их ранжирования. Сами файлы не скачиваются — в ответе адреса и размеры. Используйте, когда нужно подобрать иллюстрацию или найти, где опубликовано изображение.
Параметры
querystring · 1–400обязательныйЧто искать.limitinteger · 1–20по умолчанию10Сколько картинок вернуть.search_type"com" | "ru" | "tr"по умолчанию"ru"Поисковая база. По умолчанию ru: русская база лучше ранжирует русские запросы.pageinteger · 0–24по умолчанию0Страница выдачи с нуля.family"moderate" | "strict" | "none"по умолчанию"moderate"Семейный фильтр.size"any" | "enormous" | "large" | "medium" | "small" | "tiny" | "wallpaper"по умолчанию"any"Размер картинки.orientation"any" | "vertical" | "horizontal" | "square"по умолчанию"any"Ориентация.format"any" | "jpeg" | "png" | "gif"по умолчанию"any"Формат файла.color"any" | "color" | "grayscale" | "red" | "orange" | "yellow" | "green" | "cyan" | "blue" | "violet" | "white" | "black"по умолчанию"any"Цветность или преобладающий цвет.sitestring · до 253Только картинки с этого домена, например example.com (латиница, цифры, точки, дефисы).min_sideinteger · 0–4000по умолчанию300Отбросить картинки, у которых меньшая сторона меньше этого числа пикселей (когда Яндекс сообщает размер). 0 — не отбрасывать.
Пример
{
"query": "Московский Кремль зимой",
"limit": 5,
"orientation": "horizontal",
"min_side": 1000
}{
"request_id": "9a2f6c41-0d7e-4b83-a5c9-3e8b1f2d7a06",
"status": "succeeded",
"data": {
"images": [
{
"imageUrl": "https://img.example.ru/photos/kremlin-winter.jpg",
"alternateUrl": "https://img.example.ru/photos/1200x800/kremlin-winter.jpg",
"sourceUrl": "https://example.ru/gallery/kremlin",
"domain": "example.ru",
"thumbnailUrl": "https://avatars.mds.yandex.net/i?id=…",
"thumbnailWidth": 320,
"thumbnailHeight": 213,
"width": 2400,
"height": 1600,
"fileSize": 812344,
"format": "jpg",
"title": "Кремль зимой"
}
]
},
"error": null,
"usage": {
"search": 1,
"read": 0,
"day": { "id": "2026-10-21", "starts_at": "2026-10-20T21:00:00.000Z", "resets_at": "2026-10-21T21:00:00.000Z" },
"month": { "id": "2026-10", "starts_at": "2026-09-30T21:00:00.000Z", "resets_at": "2026-10-31T21:00:00.000Z" }
}
}- imageUrl
- Картинка в том виде, в каком ее проиндексировал Яндекс; ей соответствуют width и height.
- alternateUrl
- Есть, если страница встраивает другую копию, часто уменьшенную.
- sourceUrl, domain
- Страница и домен, где найдена картинка.
- thumbnailUrl
- HTTPS-превью Яндекса с thumbnailWidth и thumbnailHeight. Доступно, даже когда сайт запрещает прямые ссылки на файлы.
- width, height
- Размер оригинала в пикселях, если Яндекс его сообщает.
- fileSize, format
- Размер файла в байтах и формат (например jpg), если известны.
- title, snippet
- Подпись и текст рядом с картинкой, если есть.
Все поля, кроме imageUrl, необязательны: отсутствуют, если Яндекс их не вернул.
REST API
Те же операции без MCP-клиента: из своего бэкенда, скрипта или пайплайна. Базовый адрес https://cleantify.ru/api/v1, ключ — в том же заголовке Authorization. Тело и ответ — те же, что у инструментов MCP, с одним отличием у поиска с чтением.
| Метод и путь | Инструмент MCP | Тело |
|---|---|---|
POST /search | web_results | Параметры поиска, limit 1–10 (по умолчанию 10) |
POST /fetch | web_fetch | url, max_chars |
POST /search-and-fetch | web_search | Параметры поиска, limit, fetch_count, max_chars |
POST /images | image_search | Параметры image_search |
GET /usage | — | Без тела: остаток квот и история |
curl -X POST https://cleantify.ru/api/v1/fetch \
-H "Authorization: Bearer <API-ключ>" \
-H "Content-Type: application/json" \
-d '{"url": "https://www.postgresql.org/about/", "max_chars": 12000}'curl -X POST https://cleantify.ru/api/v1/search-and-fetch \
-H "Authorization: Bearer <API-ключ>" \
-H "Content-Type: application/json" \
-d '{"query": "PostgreSQL 18 release notes", "limit": 10, "fetch_count": 3}'HTTP-статус: 200 для succeeded и partial; для failed — статус по коду ошибки (таблица ниже). Неподдерживаемый метод или путь — 405 с invalid_input.
Использование
GET /usage возвращает остаток лимитов за текущие сутки и месяц и историю операций — те же данные, что в кабинете → «Использование». Вызов не тратит лимиты.
{
"updatedAt": 1792573200000,
"configured": true,
"policyVersion": 3,
"day": {
"id": "2026-10-21",
"starts_at": "2026-10-20T21:00:00.000Z",
"resets_at": "2026-10-21T21:00:00.000Z",
"search": { "limit": 50, "used": 12, "reserved": 0, "remaining": 38 },
"read": { "limit": 100, "used": 31, "reserved": 3, "remaining": 66 }
},
"month": { "id": "2026-10", "…": "то же для месяца" },
"totals": { "operations": 214, "searchAttempts": 120, "readAttempts": 305, "…": "…" },
"keys": [ … ],
"events": [ … ]
}- configured
- false — лимиты аккаунта еще не настроены, операции вернут quota_unconfigured.
- day, month
- Окно учета: id, начало, момент сброса и счетчики search и read.
- limit / used / reserved
- Лимит, израсходовано и зарезервировано идущими операциями.
- remaining
- Сколько еще можно запустить: limit − used − reserved.
- totals, keys, events
- Итоги, расход по ключам и последние операции — для отчетов.
Формат ответа
Каждая операция — MCP или REST — возвращает один и тот же конверт. Поле data заполняется по инструменту.
- request_id
- Идентификатор операции (UUID). Укажите его, если пишете о проблеме.
- status
- succeeded — все выполнено; partial — часть страниц web_search не прочитана; failed — операция не выполнена.
- data
- null при failed (кроме web_search, где остаются results и pages с причинами).
- data.results
- web_search, web_results: url, title?, snippet?.
- data.pages
- web_search: { index, status: succeeded, data } или { index, url, status: failed | unknown | skipped, code }.
- data.page
- web_fetch: url, markdown, truncated, original_chars, cached_at?.
- data.images
- image_search: список картинок.
- data.search
- web_search, web_results: примененные параметры поиска.
- error
- null или { code, message, retry_at?, limit? }. При partial здесь общий код, причины — у страниц.
- usage
- Сколько поисков и чтений запущено этой операцией и в какие окна учета (day, month) они записаны. null, если операция не началась.
Статус страницы unknown означает, что результат чтения не подтвержден (например, оборвалось соединение), skipped — чтение не запускалось.
Ошибки
Код ошибки — в error.code, человекочитаемое описание — в error.message. Ориентируйтесь на код: текст может меняться. HTTP-ответ с busy, rate_limited или quota_exceeded несет заголовок Retry-After в секундах, а retry_at в теле — тот же момент в ISO 8601.
| Код | HTTP | Что случилось и что делать |
|---|---|---|
invalid_input | 400 | Неверные параметры: лишнее поле, значение вне диапазона, не JSON, тело больше 16 КБ. Исправьте запрос. |
unauthorized | 401 | Нет ключа, ключ с ошибкой, отозван или истек. Создайте новый в кабинете. |
forbidden | 403 | Запрос пришел с чужого Origin или Host — например, из браузера на другом сайте. |
quota_exceeded | 429 | Исчерпан дневной или месячный лимит. В error.limit — какой именно, в retry_at — когда он сбросится. |
rate_limited | 429 | Слишком много запросов за минуту. Повторите после Retry-After. |
busy | 429 | Все места заняты другими операциями. Повторите через 2 с (Retry-After). |
page_blocked | 502 | Сайт показал проверку от ботов вместо страницы. Повтор обычно не помогает — возьмите другой источник. |
all_pages_failed | 502 | web_search: выдача получена, но ни одна страница не прочиталась. Смотрите code у каждой страницы. |
executor_error | 502 | Не удалось открыть или обработать страницу. В частичном ответе web_search — признак, что часть страниц не прочитана. |
executor_invalid_response | 502 | Источник вернул некорректный или пустой ответ. |
deadline_exceeded | 504 | Операция не уложилась в 240 секунд. Сузьте запрос или уменьшите limit. |
service_paused | 503 | Владелец приостановил работу сервиса. |
executor_quarantined | 503 | Чтение страниц временно остановлено до проверки. |
quota_unconfigured | 503 | Лимиты аккаунта еще не настроены владельцем. |
cancelled | 409 | Клиент отменил запрос. |
access_changed | 409 | Доступ изменился во время выполнения, например ключ отозвали. |
internal_error | 500 | Внутренняя ошибка. Повторите позже. |
{
"request_id": "5d8e0a13-6c2f-4e97-b1a4-8f3c2d7e9b50",
"status": "failed",
"data": null,
"error": {
"code": "quota_exceeded",
"message": "Лимит операций исчерпан.",
"retry_at": "2026-10-21T21:00:00.000Z",
"limit": { "scope": "user", "unit": "read", "period": "day" }
},
"usage": null
}В MCP ошибки ключа и перегрузки до начала операции (unauthorized, forbidden, rate_limited, busy) приходят HTTP-ответом с этим же конвертом, а ошибки самой операции — в structuredContent.error вместе с isError: true. Без retry_at у quota_exceeded лимит нулевой — ожиданием его не дождаться.
Квоты и кэш
Две единицы учета
- Поиск — одно обращение к Яндексу:
web_results,image_searchи поисковая частьweb_search. - Чтение — одна открытая страница:
web_fetchи каждая страница вweb_search.
У каждой единицы свой дневной и месячный лимит на пользователя — по лимитам вашего аккаунта, они видны в кабинете → «Использование» и в GET /usage. Сутки и месяц считаются по московскому времени и сбрасываются в 00:00 МСК.
Резерв перед выполнением
Перед запуском операция резервирует все, что может потратить: web_search с limit: 3 — 1 поиск и 3 чтения. Если резерва не хватает, операция сразу получает quota_exceeded, ничего не начав. Неиспользованное возвращается после завершения; usage в ответе показывает фактический расход.
Кэш страниц
- Успешно прочитанные страницы хранятся 24 часа. Повторное чтение того же адреса в этот срок не тратит чтение и приходит с
cached_at. - Адрес сравнивается без фрагмента
#…и меток вродеutm_*,gclid,yclid; остальные параметры учитываются. - Заглушки защиты от ботов в кэш не попадают — такое чтение возвращает
page_blocked. - Резерв нужен и для страницы из кэша: при исчерпанном лимите чтений запрос не выполнится.
Как читается страница
Сначала обычным HTTP-запросом — так быстрее, и большинству страниц этого хватает. Если страница собирается JavaScript или отдает пустую оболочку, ее открывает браузер. Одна операция длится не больше 240 секунд — поставьте таймаут вызова в клиенте не меньше.
Нагрузка
Число одновременных операций и запросов в минуту ограничено. При превышении приходит busy или rate_limited: подождите Retry-After и уменьшите число параллельных вызовов.
Безопасность
- Ссылайтесь на url источника, чтобы пользователь мог проверить факт.
- Проверяйте status у каждой страницы и truncated: обрезанный текст может не содержать нужного факта.
- Храните ключ в переменной окружения или менеджере секретов, а не в репозитории и общих конфигах. Если ключ утек — отзовите его в кабинете и создайте новый.
- Не передавайте ключ в браузер и сторонним сервисам: им можно тратить ваши лимиты.
- Читаются только публичные http- и https-адреса без логина и пароля в URL; адреса внутренних сетей не открываются.
- Картинки не скачиваются сервисом. Открывая imageUrl, вы обращаетесь к стороннему сайту напрямую.