Перейти к содержимому

Документация

Cleantify ищет в интернете через Яндекс и читает страницы в чистый Markdown — так, чтобы результат сразу можно было отдать AI-агенту. Подключается как MCP-сервер или вызывается через REST API, одним и тем же API-ключом.

MCP
https://cleantify.ru/mcp
REST
https://cleantify.ru/api/v1
Содержание

Обзор

Четыре инструмента закрывают три задачи: найти источники, прочитать страницу и найти картинки. Поиск и чтение учитываются отдельно — у них свои лимиты.

ИнструментКогда использоватьRESTРасход
web_searchНужно найти страницы по теме и сразу получить их текст.POST /search-and-fetch1 поиск + до limit чтений
web_fetchАдрес уже известен — прочитать одну страницу.POST /fetch1 чтение, из кэша — 0
web_resultsНужны только ссылки, заголовки и сниппеты, чтобы выбрать, что читать.POST /search1 поиск
image_searchНужны адреса картинок и страниц, где они найдены. Файлы не скачиваются.POST /images1 поиск

Типичный путь агента: web_search для вопроса «найди и расскажи»; web_results → web_fetch, когда сначала нужно выбрать источники и не тратить чтения на лишние страницы.

Быстрый старт

1. Создайте API-ключ

В кабинете нажмите «Создать API-ключ». Ключ вида ct_… показывается один раз — сохраните его сразу. Срок действия — 90 дней, отозвать можно в любой момент.

2. Подключите MCP

Claude Code — одной командой, сервер станет доступен во всех проектах:

Claude Code · shell
claude mcp add --transport http --scope user cleantify https://cleantify.ru/mcp \
  --header "Authorization: Bearer <API-ключ>"

Проверить: claude mcp list. Уже открытый сеанс новый сервер не видит — начните новый.

Cursor — файл ~/.cursor/mcp.json. Если там уже есть серверы, добавьте ключ cleantify к остальным:

Cursor · mcp.json
{
  "mcpServers": {
    "cleantify": {
      "url": "https://cleantify.ru/mcp",
      "headers": { "Authorization": "Bearer ${env:CLEANTIFY_API_KEY}" }
    }
  }
}

Ключ лучше не вписывать в файл: Cursor подставит его из переменной окружения CLEANTIFY_API_KEY (синтаксис ${env:…} из документации Cursor).

Другие клиенты с поддержкой удаленного MCP (Streamable HTTP) настраиваются так же: адрес и заголовок Authorization. Имена полей зависят от клиента — сверьтесь с его документацией.

Другой MCP-клиент · JSON
{
  "mcpServers": {
    "cleantify": {
      "type": "http",
      "url": "https://cleantify.ru/mcp",
      "headers": { "Authorization": "Bearer <API-ключ>" }
    }
  }
}

3. Первый вызов

Попросите агента обычными словами — он сам выберет инструмент:

Запрос агенту
Найди через cleantify три свежие страницы о выходе PostgreSQL 18 и перескажи главное со ссылками на источники.

Проверить ключ без расхода лимитов можно запросом остатка квоты:

curl
curl https://cleantify.ru/api/v1/usage \
  -H "Authorization: Bearer <API-ключ>"

Аутентификация

Каждый запрос к /mcp и /api/v1 передает ключ в заголовке. Другие способы (параметр в URL, cookie, OAuth) не поддерживаются.

Заголовок
Authorization: Bearer <API-ключ>
  • Ключ — ct_ и 43 символа. Без ключа, с отозванным или истекшим — ответ 401 unauthorized.
  • Ключ действует 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.

tools/call · JSON-RPC
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "web_fetch",
    "arguments": { "url": "https://www.postgresql.org/about/", "max_chars": 12000 }
  }
}
Ответ · JSON-RPC
{
  "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"
    Семейный фильтр: умеренный, строгий или выключен.

Находит страницы и читает их: в ответе выдача и Markdown каждой прочитанной страницы в порядке ранжирования. Используйте, когда агенту нужен текст источников, а не только ссылки.

Параметры

Общие параметры поиска, а также:

  • limitinteger · 1–5по умолчанию 3
    Сколько результатов найти и прочитать. В MCP одно число задает и размер выдачи, и число чтений.
  • max_charsinteger · 1000–50000по умолчанию 20000
    Предел символов Markdown на одну страницу. Длиннее — обрезается, и в ответе будет truncated: true, а original_chars покажет полную длину.

Пример

arguments · JSON
{
  "query": "PostgreSQL 18 release notes site:postgresql.org",
  "limit": 2,
  "max_chars": 8000
}
Ответ · JSON
{
  "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. Пустая выдача — успешный ответ без страниц.

Непрочитанная страница · JSON
{ "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 покажет полную длину.

Пример

arguments · JSON
{
  "url": "https://www.postgresql.org/about/",
  "max_chars": 12000
}
Ответ · JSON
{
  "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
    Сколько результатов вернуть.

Пример

arguments · JSON
{
  "query": "ключевая ставка ЦБ решение совета директоров",
  "limit": 3,
  "search_type": "ru"
}
Ответ · JSON
{
  "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" }
  }
}

Ищет картинки в Яндекс Картинках, в порядке их ранжирования. Сами файлы не скачиваются — в ответе адреса и размеры. Используйте, когда нужно подобрать иллюстрацию или найти, где опубликовано изображение.

Параметры

  • 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 — не отбрасывать.

Пример

arguments · JSON
{
  "query": "Московский Кремль зимой",
  "limit": 5,
  "orientation": "horizontal",
  "min_side": 1000
}
Ответ · JSON
{
  "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 /searchweb_resultsПараметры поиска, limit 1–10 (по умолчанию 10)
POST /fetchweb_fetchurl, max_chars
POST /search-and-fetchweb_searchПараметры поиска, limit, fetch_count, max_chars
POST /imagesimage_searchПараметры image_search
GET /usage—Без тела: остаток квот и история
curl · fetch
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 · search-and-fetch
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 возвращает остаток лимитов за текущие сутки и месяц и историю операций — те же данные, что в кабинете → «Использование». Вызов не тратит лимиты.

Ответ · JSON (числа условные)
{
  "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_input400Неверные параметры: лишнее поле, значение вне диапазона, не JSON, тело больше 16 КБ. Исправьте запрос.
unauthorized401Нет ключа, ключ с ошибкой, отозван или истек. Создайте новый в кабинете.
forbidden403Запрос пришел с чужого Origin или Host — например, из браузера на другом сайте.
quota_exceeded429Исчерпан дневной или месячный лимит. В error.limit — какой именно, в retry_at — когда он сбросится.
rate_limited429Слишком много запросов за минуту. Повторите после Retry-After.
busy429Все места заняты другими операциями. Повторите через 2 с (Retry-After).
page_blocked502Сайт показал проверку от ботов вместо страницы. Повтор обычно не помогает — возьмите другой источник.
all_pages_failed502web_search: выдача получена, но ни одна страница не прочиталась. Смотрите code у каждой страницы.
executor_error502Не удалось открыть или обработать страницу. В частичном ответе web_search — признак, что часть страниц не прочитана.
executor_invalid_response502Источник вернул некорректный или пустой ответ.
deadline_exceeded504Операция не уложилась в 240 секунд. Сузьте запрос или уменьшите limit.
service_paused503Владелец приостановил работу сервиса.
executor_quarantined503Чтение страниц временно остановлено до проверки.
quota_unconfigured503Лимиты аккаунта еще не настроены владельцем.
cancelled409Клиент отменил запрос.
access_changed409Доступ изменился во время выполнения, например ключ отозвали.
internal_error500Внутренняя ошибка. Повторите позже.
Лимит исчерпан · JSON
{
  "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, вы обращаетесь к стороннему сайту напрямую.
Cleantifyweb_search · web_fetch · web_results · image_search