Мок-API
Apify API
Что покрыто в моке Apify API v2, откуда взята спецификация, чем лимиты Apify отличаются от остальных сервисов и как работает мок его MCP-сервера.
На этой странице · 11
Apify — платформа веб-скрейперов и автоматизаций; её единицы исполнения называются акторами. У неё два интерфейса, и APIStend мокает оба: REST API v2 и MCP-сервер, через который с платформой работают ИИ-агенты.
Откуда взята спецификация
Единственный из четырёх сервисов, для которого не пришлось ни обходить защиту,
ни искать чужой снимок. Поэтому в каталоге у его методов стоит происхождение
spec, а не mirror: это живая спецификация вендора, а не копия с чьего-то
зеркала. Дата снимка тоже не выдумана — она взята из info.version самой
спецификации, то есть датирует сборку у Apify, а не наше скачивание.
Обновить: ./scripts/vendor-apify-spec.sh, затем pnpm ingest apify.
Что покрыто
231 операция в 131 пути. Источники ответов:
Крупнейшие разделы: хранилища (38), акторы (36), задачи акторов и очереди запросов (по 15), запуски (10), вебхуки (9). Пять методов помечены устаревшими — так же, как их помечает сам Apify.
Подстановка вместо боевого адреса
Версия у Apify входит в путь, а не в хост, поэтому подмена сводится к замене одного хоста на один префикс — остальная часть адреса не меняется вовсе:
https://api.apify.com/v2/actors
https://apistend.ru/apify/v2/actors
Историческое написание /v2/acts/… работает наравне с /v2/actors/…: боевой
api.apify.com отвечает на оба, и именно acts стоит в примерах документации
Apify и в его собственных клиентах. Каталог от этого не раздваивается — путь
приводится к каноничному и попадает в тот же метод.
Авторизация
Оба способа из спецификации Apify работают, и оба принимают ключ песочницы:
# заголовком — основной способ
curl 'https://apistend.ru/apify/v2/actors' \
-H 'Authorization: Bearer stend_sk_…'
# параметром — им пользуется сам Apify, подставляя токен в адрес вебхука
curl 'https://apistend.ru/apify/v2/actors?token=stend_sk_…'
Различие двух отказов
Apify отвечает token-not-provided, когда токена нет вовсе, и invalid-token,
когда токен неверен. Мок различает эти случаи так же: клиент, который по ним
отличает «не залогинен» от «ключ протух», в песочнице увидит ту же разницу.
Заголовки: чем Apify отличается от остальных
Здесь три отличия, и каждое снято с живых ответов api.apify.com, а не выведено
из документации.
Content-Type приходит с charset — application/json; charset=utf-8,
как у Битрикс24, а не голый application/json, как у Ozon и Wildberries.
Из заголовков лимита есть только один. X-RateLimit-Limit приходит на каждом
ответе, а -Remaining и -Reset не приходят никогда. Это третий вариант
поведения, которого до Apify в стенде не было: сервис сообщает потолок, но не
остаток. Клиент, написанный на чтении остатка, в бою получит undefined —
и в моке получит его же.
Идентификатора запроса нет. Ни X-Request-Id, ни аналога: в живых ответах
его нет ни под одним именем. Свой X-APIStend-Request-Id приходит всегда, как
и у Битрикс24, у которого боевого заголовка тоже не существует.
Живой ответ мока:
HTTP/1.1 200 OK
content-type: application/json; charset=utf-8
x-ratelimit-limit: 60
x-apify-pagination-total: 1247
x-apify-pagination-offset: 0
x-apify-pagination-count: 20
x-apify-pagination-limit: 20
x-apify-pagination-desc: false
x-apistend-request-id: req_0d60cf4173
x-apistend-source: example
x-apistend-cors: native
Заголовки пагинации
Списковые ответы Apify дублируют поля конверта data в заголовки
X-Apify-Pagination-Total, -Offset, -Count, -Limit, -Desc. Это не
украшение: сам Apify перечисляет их в своём Access-Control-Expose-Headers,
то есть читать их из браузера предполагается.
Мок берёт значения из того самого тела, которое отдаёт, — заголовок разойтись с телом не может. У одиночного объекта полей пагинации нет, и заголовков тоже не будет.
CORS здесь боевой
X-APIStend-Cors у Apify приходит со значением native, а не
added-by-sandbox. Причина простая: боевой Apify отвечает
access-control-allow-origin: * и вызовы из браузера разрешает. Пометить это
как «добавлено песочницей» значило бы соврать в обратную сторону — будто в бою
из браузера ходить нельзя.
Лимиты зависят от эндпоинта
Единственный сервис в стенде, у которого лимит — свойство не сервиса, а метода:
Числа 400, 200 и 60 — из документации Apify; 90 в ней нет, оно снято с живого
ответа /v2/users/me. Мок ведёт по отдельному ведру на каждый класс, поэтому
упереться в лимит на датасетах, не тронув остальные вызовы, здесь можно так же,
как в бою.
Глобальный потолок — 250 000 запросов в минуту на пользователя — мок не воспроизводит намеренно: до него не доберётся никакая отладочная нагрузка.
Паузы Apify не подсказывает
Заголовка Retry-After в ответах нет, и в документации он не описан. Его
собственные клиенты (apify-client для Python и JS) отступают по своей
экспоненте: 500 мс, 1 с, 2 с, 4 с, до четырёх попыток. Мок подделывать чужой
заголовок не станет — рекомендуемая пауза приходит в X-APIStend-Retry-After.
Конверт ошибки
Один на все коды, из components.schemas.ErrorResponse:
{"error":{"type":"record-not-found","message":"The requested resource was not found."}}
Вебхуки
Политика Apify не похожа ни на одну из трёх остальных:
eventData и resource намеренно дублируют друг друга: документация Apify
называет это обратной совместимостью. resource — это то, что вернул бы
соответствующий метод API в момент события, поэтому шаблон вида
{{resource.status}} в песочнице разворачивается так же, как в бою.
Заголовки X-Apify-Webhook, X-Apify-Webhook-Dispatch-Id и
X-Apify-Request-Origin система выставляет сама и перезаписывает
пользовательские — мок делает то же.
События: ACTOR.RUN.CREATED, .SUCCEEDED, .FAILED, .ABORTED, .TIMED_OUT,
а также ACTOR.BUILD.SUCCEEDED и .FAILED.
Доставка может повториться
Документация Apify прямо предупреждает: «In rare cases, the webhook might be invoked more than once». Обработчик обязан быть идемпотентным, и проверить это удобнее на песочнице, чем на боевом аккаунте.
Акторы: ввод и вывод
Мок REST отвечает на «покажи список акторов». Но агент работает не с методами, а с конкретными акторами: читает их схему входа, собирает по ней вызов, запускает и разбирает поля датасета. Поэтому в стенде лежит снимок магазина со схемами входа и выхода настоящих акторов.
Выборка складывается из двух частей, и доли зарезервированы, а не остаточны:
- мировой топ по популярности — 40 % мест. Ими написана большая часть чужого кода, который приносят на отладку;
- адресный поиск по площадкам, ради которых существует APIStend — Ozon,
Wildberries, Яндекс, Avito, VK, Telegram,
auto.ru,drom,cian,hh.ruи остальной русский рынок. Таких акторов в снимке 424.
Резерв нужен именно так. В первой версии адресный поиск шёл первым и выбирал
всю квоту целиком, вытеснив даже apify/instagram-scraper — самого запускаемого
актора магазина.
Весь магазин не снимается намеренно: в нём 57 тысяч акторов, а интеграции пишут с единицами. Количество задаётся аргументом:
node scripts/vendor-apify-actors.mjs 2000
# с токеном — тот же снимок, но выше лимит запросов к Apify
APIFY_TOKEN=… node scripts/vendor-apify-actors.mjs 2000
Токен необязателен: карточки акторов, их сборки и магазин у публичных акторов открыты и анонимно — снимается ровно то, что видит любой посетитель apify.com.
Снимок опционален: без него мок REST работает полностью, а инструменты MCP, которым нужны акторы, честно скажут, что снимка нет, — вместо того чтобы выдумать акторов.