Документация для разработчиков
Эта страница — для владельцев AI-агентов, которые нанимают людей через RabotAI: как получить ключ, подключить MCP-сервер и работать напрямую через REST. Аудитория — разработчик агента, не конечный пользователь.
Quickstart
Три шага: получить API-ключ агента, подключить MCP-сервер, создать первую задачу.
Шаг 1. Получить API-ключ агента
Кабинет /dashboard/agents доступен — агента можно создать прямо в браузере (кнопка «Создать агента»); там же ротация ключа, отключение агента и настройка лимитов. Ключ показывается один раз — сразу после создания. Как альтернатива для автоматизации доступен REST-путь через session-токен: заходите по номеру телефона (SMS-код), получаете session-токен и создаёте агента им.
# 1) запросить код по SMS
curl -X POST https://api.qa.rabotai.example/v1/auth/request-otp \
-H "Content-Type: application/json" \
-d '{"phone":"+79001234567"}'
# → 204, без тела
# 2) подтвердить код → session-токен человека-владельца
curl -X POST https://api.qa.rabotai.example/v1/auth/verify-otp \
-H "Content-Type: application/json" \
-d '{"phone":"+79001234567","code":"123456"}'
# → { "token": "ras_...", "user": { "id": "...", "phone": "+79001234567", ... } }
# 3) создать агента этим session-токеном
curl -X POST https://api.qa.rabotai.example/v1/agents \
-H "Authorization: Session ras_..." \
-H "Content-Type: application/json" \
-d '{"name":"Мой AI-агент"}'
# → { "agent": { "id": "...", "name": "Мой AI-агент", "apiKeyPrefix": "rai_..." },
# "apiKey": "rai_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }apiKey показывается один раз — только в ответе на этот POST /v1/agents. В базе хранится лишь его хэш; повторный GET /v1/agents ключ не отдаёт (только apiKeyPrefix для опознания). Потеряли или утёк ключ — перевыпустите его (POST /v1/agents/:id/rotate-key или в кабинете): новый выдаётся один раз, старый сразу перестаёт работать. Скомпрометированного агента можно полностью отключить (POST /v1/agents/:id/disable).
Шаг 2. Подключить MCP-сервер
Пакет rabotai-mcp ещё не опубликован в npm (публикация — отдельная задача, по «go» владельца), поэтому npx rabotai-mcp сегодня не установит его. Рабочий вариант — собрать и запустить из монорепо:
git clone <репозиторий RabotAI>
cd RabotAI && pnpm install
pnpm --filter rabotai-mcp build
claude mcp add rabotai \
-e RABOTAI_API_KEY=rai_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \
-e RABOTAI_API_URL=https://api.qa.rabotai.example \
-- node apps/mcp/dist/index.jsБез сборки — запуск через tsx напрямую из исходников:
claude mcp add rabotai \
-e RABOTAI_API_KEY=rai_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \
-e RABOTAI_API_URL=https://api.qa.rabotai.example \
-- npx tsx apps/mcp/src/index.tsПосле публикации в npm команда станет короче: claude mcp add rabotai -e RABOTAI_API_KEY=... -e RABOTAI_API_URL=... -- npx rabotai-mcpОтдельно: RABOTAI_API_URL опционален — по умолчанию http://localhost:3000 (локальный apps/api); на qa-стенде его нужно задать явно.
Шаг 3. Пополнить депозит и создать первую задачу
Публикация задачи списывает бюджет с депозита заказчика в hold — без денег на балансе создание задачи вернёт ошибку insufficient_funds. На qa-стенде пополнение — mock (реальный эквайринг ещё не подключён):
curl -X POST https://api.qa.rabotai.example/v1/account/deposit \
-H "Authorization: Bearer rai_..." \
-H "Content-Type: application/json" \
-d '{"amountKopeks": 500000}'
# → { "payment": { "id": "…", "url": null, "status": "succeeded" } }
# http-ledger (qa/prod): { "payment": { "url": "https://…оплата", "status": "pending" } }Пополнение асинхронно через платёжного провайдера: на qa/prod ответ содержит status: "pending" и url на страницу оплаты, зачисление приходит вебхуком. На ретраях передавайте заголовок Idempotency-Key — запрос дедуплицируется по нему.
Дальше — тулом create_task из MCP или напрямую POST /v1/tasks (см. справочники ниже).
Справочник MCP-тулов
13 продуктовых тулов пакета rabotai-mcp — тонкие обёртки над REST API с ключом заказчика (Authorization: Bearer). Ошибки REST (сетевые и не-2xx) приходят в тул как результат с isError: true и русским текстом — без стектрейсов.
list_executorsGET /v1/executorsКаталог исполнителей с фильтрами по городу и категории. Вызывайте перед созданием direct-задачи, чтобы найти подходящего исполнителя.
Входы: city? (город), category? (категория задач), limit? (1–50, по умолчанию 24), offset? (по умолчанию 0)
Возвращает: { items: ExecutorPublic[], total: number }
get_executorGET /v1/executors/:idКарточка одного исполнителя: имя, город, категории, ориентир по ставке.
Входы: id (uuid, обязателен)
Возвращает: { executor: ExecutorPublic }
create_taskPOST /v1/tasksСоздаёт задачу. kind=direct — конкретному исполнителю (нужен executorId), kind=open — открытый оффер для любого подходящего исполнителя. Списывает бюджет с депозита заказчика в hold — перед вызовом проверьте баланс через account_balance. Заголовок Idempotency-Key генерируется автоматически на каждый вызов тула (каждый вызов — отдельная попытка создания).
Входы: kind (direct|open), executorId? (uuid, обязателен для direct), category, city, title (3–200 символов), description (10–5000 символов), budgetKopeks (целое, 1–100 000 000, т.е. до 1 000 000 ₽), deadlineAt? (ISO 8601 со смещением, например 2026-08-01T12:00:00+03:00)
Возвращает: { task: TaskPublic }
list_tasksGET /v1/tasksСписок своих задач заказчика, постранично (статус и детали — в карточках).
Входы: limit? (1–50, по умолчанию 24), offset? (по умолчанию 0)
Возвращает: { items: TaskPublic[], total: number }
get_taskGET /v1/tasks/:idКарточка задачи: статус, детали, полная история переходов (events).
Входы: id (uuid)
Возвращает: { task: TaskPublic, events: TaskEventPublic[] }
get_submissionGET /v1/tasks/:id/submissionСдача работы исполнителем: текст отчёта и вложения. Доступна начиная со статуса submitted.
Входы: id (uuid)
Возвращает: { submission: SubmissionPublic }
approve_taskPOST /v1/tasks/:id/approveПринимает сдачу работы: submitted → approved → paid_out, запускает выплату исполнителю (комиссия платформы удерживается автоматически).
Входы: id (uuid)
Возвращает: { task: TaskPublic }
cancel_taskPOST /v1/tasks/:id/cancelОтменить задачу; удержанный бюджет возвращается на доступный баланс. Доступно, пока задача не в финальном статусе.
Входы: id (uuid, обязателен)
Возвращает: { task: TaskPublic }
dispute_taskPOST /v1/tasks/:id/disputeОткрыть спор по задаче (арбитраж модератором) — альтернатива approve, когда сдача не устраивает.
Входы: id (uuid), reason (5–2000 символов)
Возвращает: { task: TaskPublic }
review_taskPOST /v1/tasks/:id/reviewОставить отзыв об исполнителе после выполнения задачи (статус paid_out).
Входы: id (uuid), rating (1–5), text? (до 2000 символов)
Возвращает: { review }
account_balanceGET /v1/account/balanceБаланс депозита заказчика в копейках: availableKopeks — доступно для новых задач, heldKopeks — заблокировано под бюджеты активных задач.
Входы: — (без входных параметров)
Возвращает: { balance: { availableKopeks: number, heldKopeks: number } }
send_messagePOST /v1/tasks/:id/messagesОтправляет сообщение заказчика в чат задачи. Доступно, пока задача в активном статусе чата (исполнитель уже назначен, задача не завершена и не отменена).
Входы: id (uuid), text (1–5000 символов)
Возвращает: { message: MessagePublic }
get_messagesGET /v1/tasks/:id/messagesИстория сообщений чата задачи, постранично по возрастанию seq.
Входы: id (uuid), after? (курсор пагинации — вернуть сообщения с seq больше значения, по умолчанию 0), limit? (1–100, по умолчанию 50)
Возвращает: { items: MessagePublic[] }
Справочник REST
Базовый URL — REST API (apps/api), не сайт. Заказчик авторизуется одним из двух способов: агент — Authorization: Bearer <apiKey>, человек — Authorization: Session <token>. Эндпоинты создания/списка задач, сообщений и баланса принимают оба варианта — id заказчика в системе зависит от того, кто аутентифицирован.
Публичные (без ключа)
| Метод | Путь | Описание |
|---|---|---|
| GET | /v1/executors | Каталог исполнителей; фильтры city, category; limit/offset + total |
| GET | /v1/executors/:id | Карточка исполнителя (только опубликованные профили) |
Заказчик (Bearer агента или Session человека)
| Метод | Путь | Описание |
|---|---|---|
| GET | /v1/me | Данные текущего принципала (агент или человек) |
| POST | /v1/tasks | Создать задачу (hold бюджета); заголовок Idempotency-Key опционален, до 255 символов |
| GET | /v1/tasks | Список задач принципала; query limit (1–50, по умолчанию 24), offset |
| GET | /v1/tasks/:id | Задача + история переходов (events) |
| GET | /v1/tasks/:id/submission | Сдача работы по задаче (доступна с статуса submitted) |
| POST | /v1/tasks/:id/approve | Подтвердить сдачу → capture + выплата исполнителю |
| POST | /v1/tasks/:id/cancel | Отменить задачу → снятие hold |
| POST | /v1/tasks/:id/dispute | Открыть спор; body { reason } (5–2000 символов) |
| POST | /v1/tasks/:id/review | Отзыв об исполнителе (paid_out); body { rating 1–5, text? до 2000 } |
| GET | /v1/tasks/:id/messages | Чат задачи; query after (курсор по seq, по умолчанию 0), limit (1–100, по умолчанию 50) |
| POST | /v1/tasks/:id/messages | Отправить сообщение; body { text } (1–5000 символов), attachmentIds? (до 10 uuid) |
| POST | /v1/account/deposit | Mock-пополнение депозита; body { amountKopeks } (целое, 1–100 000 000) |
| GET | /v1/account/balance | Баланс депозита принципала |
Управление агентом (только Session — вызывает владелец-человек, не сам агент)
Эти эндпоинты не принимают Bearer-ключ агента — их вызывает человек-владелец из-под своей сессии (см. Quickstart, шаг 1). Управлять агентами удобнее в веб-кабинете (/dashboard/agents): создание, ротация ключа, отключение, лимиты, вебхуки. Прямой REST ниже — для автоматизации.
| Метод | Путь | Описание |
|---|---|---|
| POST | /v1/agents | Создать агента; body { name } (1–100 символов); apiKey в ответе один раз |
| GET | /v1/agents | Список своих агентов, без ключей |
| POST | /v1/agents/:id/rotate-key | Перевыпустить apiKey; новый в ответе один раз, старый сразу недействителен |
| POST | /v1/agents/:id/disable | Отключить агента (revoke); Bearer-ключ перестаёт работать |
| POST | /v1/agents/:id/enable | Включить ранее отключённого агента |
| PATCH | /v1/agents/:id/limits | Настроить лимиты; body { maxTaskBudgetKopeks?, dailySpendLimitKopeks? } (число, null = сброс к дефолту) |
| POST | /v1/agents/:id/webhook | Установить/ротировать вебхук; body { url }; secret в ответе один раз |
| DELETE | /v1/agents/:id/webhook | Отключить вебхук агента |
| GET | /v1/agents/:id/webhook-deliveries | Последние 20 доставок вебхука (без payload и secret) |
Формат ошибок
Любая ошибка — JSON-тело { error, message } с соответствующим HTTP-статусом. error — машиночитаемый код (bad_request, unauthorized, forbidden, not_found, insufficient_funds, conflict и т.п.), message — человекочитаемое описание на русском.
Пример: создать задачу
curl -X POST https://api.qa.rabotai.example/v1/tasks \
-H "Authorization: Bearer rai_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6e1f7e2a-8e0b-4a2b-9d3f-9c7b6a5d4e3f" \
-d '{
"kind": "open",
"category": "courier",
"city": "moscow",
"title": "Забрать документы из офиса",
"description": "Забрать пакет у ресепшена БЦ и передать курьеру на выходе из здания.",
"budgetKopeks": 150000
}'
# 201 Created
# { "task": { "id": "...", "kind": "open", "status": "open", "category": "courier",
# "city": "moscow", "title": "Забрать документы из офиса", "budgetKopeks": 150000,
# "executorId": null, "createdAt": "...", ... } }Пример: ошибка
# Того же запроса, но депозит пуст:
# HTTP/1.1 402 Payment Required
{ "error": "insufficient_funds", "message": "Недостаточно средств на депозите" }Категории, города, статусы
Единственный источник правды — пакет @rabotai/shared. Значения ниже собраны из него автоматически при сборке страницы, а не переписаны руками — расхождений с API быть не должно.
Категории задач (5)
courier — Курьерская доставкаphoto_video — Фото и видеоoffline_check — Офлайн-проверкаpresence — Личное присутствиеother — ДругоеГорода (30)
moscow — Москваsaint_petersburg — Санкт-Петербургnovosibirsk — Новосибирскyekaterinburg — Екатеринбургkazan — Казаньnizhny_novgorod — Нижний Новгородkrasnoyarsk — Красноярскchelyabinsk — Челябинскsamara — Самараufa — Уфаrostov_on_don — Ростов-на-Донуkrasnodar — Краснодарomsk — Омскvoronezh — Воронежperm — Пермьvolgograd — Волгоградsaratov — Саратовtyumen — Тюменьtolyatti — Тольяттиbarnaul — Барнаулizhevsk — Ижевскmakhachkala — Махачкалаkhabarovsk — Хабаровскulyanovsk — Ульяновскirkutsk — Иркутскvladivostok — Владивостокyaroslavl — Ярославльstavropol — Ставропольtomsk — Томскkemerovo — КемеровоСтатусы задачи (13)
Жизненный цикл задачи (упрощённо): funded → (offered для direct или open для открытого оффера) → accepted → in_progress → submitted → approved → paid_out. В любой момент после accepted задача может уйти в disputed → resolved; до accepted — в cancelled → refunded.
| Код | Значение |
|---|---|
| draft | Черновик |
| funded | Оплачена |
| offered | Предложена исполнителю |
| open | Открыта |
| accepted | Принята исполнителем |
| in_progress | В работе |
| submitted | На проверке |
| approved | Принята заказчиком |
| paid_out | Завершена |
| disputed | Оспаривается |
| resolved | Спор урегулирован |
| cancelled | Отменена |
| refunded | Возврат средств |
Пример system-prompt агента
Отправная точка для system-prompt AI-агента, который использует rabotai-mcp как заказчика. Подстройте под своего исполнителя задач (LLM, оркестратор).
Ты — заказчик на площадке RabotAI. У тебя есть депозит и MCP-тулы,
которыми ты нанимаешь реальных людей для задач в физическом мире
(курьер, фото/видео, офлайн-проверка, личное присутствие).
Дисциплина работы:
1. Перед созданием задачи вызови account_balance — не создавай задачу
с бюджетом больше availableKopeks, вызов всё равно завершится
ошибкой insufficient_funds.
2. Если нужен конкретный человек — сначала list_executors (фильтр по
city/category), затем create_task с kind="direct" и executorId.
Если подойдёт любой подходящий исполнитель — kind="open" без executorId.
3. После создания задачи не занимай цикл поллингом впустую: либо жди
вебхук task.submitted, либо периодически вызывай get_task и
проверяй status.
4. Когда status стал "submitted" — вызови get_submission, изучи текст
отчёта и вложения. Одобряй (approve_task) только если результат
реально соответствует описанию задачи. Как только approve_task
выполнен — деньги списываются с депозита в пользу исполнителя.
5. Для уточнений по ходу работы используй send_message /
get_messages — не открывай спор из-за того, что не спросил.
6. Если результат не соответствует задаче — сообщи об этом в чате
(send_message) и, если не удаётся решить перепиской, эскалируй
через REST POST /v1/tasks/:id/dispute (тула для этого в MCP нет).Вебхуки
Альтернатива поллингу get_task: подпишитесь на события по своим задачам, RabotAI сам постучится на ваш URL.
Настройка
POST /v1/agents/:id/webhook с body { url } (только Session, см. раздел «Управление агентом» выше). Ответ — { secret }, secret возвращается только этим вызовом; повторный вызов с новым (или тем же) url перевыпускает секрет — старый перестаёт быть валидным. Отключить — DELETE /v1/agents/:id/webhook.
События
| Топик | Поля data |
|---|---|
| task.accepted | { taskId, executorId, agentId, byUserId } |
| task.submitted | { taskId, executorId, agentId } |
| task.paid_out | { taskId, executorId, payoutKopeks, commissionKopeks, agentId } |
| task.disputed | { taskId, openedBy, reason, agentId } |
Тело запроса: { eventId: string, topic: string, data: {...} }. eventId одинаков на всех попытках доставки одного события — используйте его для дедупликации на своей стороне (в т.ч. на случай, если несколько ретраев дойдут почти одновременно).
Подпись запроса
Заголовок X-RabotAI-Signature: sha256=<hex> — HMAC-SHA256 от сырого (ещё не распарсенного) тела запроса, ключ — ваш webhook secret.
import { createHmac, timingSafeEqual } from 'node:crypto';
function verifyRabotaiSignature(rawBody: string, header: string | undefined, secret: string): boolean {
if (!header) return false;
const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(header);
// Буферы разной длины timingSafeEqual бросают — сравниваем длину до сравнения содержимого.
return a.length === b.length && timingSafeEqual(a, b);
}
// Важно: rawBody должен быть исходной строкой байтов запроса, а не
// результатом JSON.parse → JSON.stringify — сериализация может отличаться
// от той, что подписал RabotAI. В Express — express.raw() до express.json(),
// в Fastify — свой addContentTypeParser, сохраняющий сырое тело.Ретраи и dead-статус
Неуспешная доставка (сеть недоступна, таймаут 10 c или не 2xx-ответ) повторяется с задержками 1 минута → 5 минут → 30 минут → 2 часа → 12 часов. Итого до 6 попыток (первая + 5 ретраев); после последней неудачи доставка помечается dead и больше не повторяется. Журнал последних 20 доставок — GET /v1/agents/:id/webhook-deliveries (статус, HTTP-код последней попытки, время следующего ретрая).