CRM API — довідник ендпоінтів

Arc OS — Система оркестрації для AI-команд

Загальна інформація

Параметр Значення
Base URL https://arc-os.co/api/crm
Авторизація Authorization: Bearer <JWT> або ?token=<JWT> (для SSE/WebSocket)
Content-Type application/json
Алгоритм JWT HMAC-SHA256
JWT TTL 24 години

Автентифікація

Усі ендпоінти (крім /docs/*) потребують JWT-токена в заголовку Authorization: Bearer <token>.

Для з'єднань SSE та WebSocket токен передається через query-параметр ?token=<JWT>.

Помилки авторизації

Код Опис
401 Відсутній або невалідний токен
403 Немає доступу до проєкту (multi-tenancy)

Ендпоінти за категоріями

Акаунт і налаштування

Метод Шлях Опис
GET /account/settings Отримати налаштування акаунта
PUT /account/settings Оновити налаштування акаунта

Онбординг + тріал-кредити (Phase 50.1)

Метод Шлях Опис
POST /onboarding/setup Створити перший проєкт. Body multipart: config (JSON) + files. Поле anthropicKey тепер необов'язкове — якщо порожнє + у користувача email_verified + він раніше не отримував тріалу, проєкт створюється в trial_mode=1 зі 100K безкоштовних токенів. Відповідь: { ok, project, trial_activated }. Phase 51: повертає 402 з {error:"plan_limit_reached", reason, current, limit, plan}, коли користувач перевищив ліміт проєктів для свого плану.
GET /account/trial-status Статус тріалу для банера UI. Відповідь: { email, email_verified, trial_granted, has_trial_active, total_remaining, total_granted, projects: [...] }
GET /account/usage Історія використання токенів для автентифікованого користувача (Phase 63, #148). Відповідь: { rows: [ { project_name, worker_id, input_tokens, output_tokens, cache_tokens, total_tokens, created_at } × up to 200 ], totals: { total, input, output } }. Читає token_usage_log за owner_id. Показується в UserDropdown (UsageCard) і BillingPage (розділ Token Usage).
GET /account/billing-summary Консолідований підсумок білінгу (#309). Відповідь: { arc: { plan, status, tokens_this_month, tokens_input, tokens_output, renewal_date }, anthropic: { connected, key_prefix, credit_balance_usd, spend_month_usd, tokens_this_week } }. Розділ Anthropic заповнюється на боці сервера через Anthropic API з використанням account_settings.anthropic_key користувача (fallback → PLATFORM_ANTHROPIC_KEY). Показується в UsageCard UserDropdown.

Чекліст онбордингу (Phase 54.1, issue #56)

Post-wizard 5-кроковий чекліст залучення. Кожен крок (workers, cli, skill, bot, issue) приймає статус completed або skipped. Мутації ідемпотентні: повторний ідентичний POST повертає той самий стан і не пише дублікат в activity_log. Replay не скидає стан, лише очищає dismissed_at — UI показує панель знову з тим самим прогресом.

Метод Шлях Опис
GET /onboarding/progress Поточний стан для автентифікованого користувача. Відповідь: { steps:["workers","cli","skill","bot","issue"], state:{<step>:<status>}, completed_count, total_steps:5, completed_at, dismissed_at, source, started_at, updated_at }. Незачеплений користувач → нулі/null без створення рядка.
POST /onboarding/event Записати перехід кроку. Body: { step: "workers"|"cli"|"skill"|"bot"|"issue", status: "completed"|"skipped", source?: "web"|"cli" }. Валідація за whitelist → 400 на невідомий step/status. Відповідь: та сама форма, що й GET. Емітить onboarding_step_completed/onboarding_step_skipped в activity_log лише коли changed; на переході до 5/5 додатково емітить onboarding_completed з duration_ms.
POST /onboarding/dismiss Закрити панель (dismissed_at = now). Ідемпотентний. Емітить onboarding_dismissed на першому виклику з payload {completed_count}.
POST /onboarding/replay Знову відкрити закриту панель (dismissed_at = NULL). Стан кроків не чіпається. Емітить onboarding_replayed на clear-event.
POST /onboarding/reset Повний self-scoped reset для повторного тестування flow нового користувача (#594): очищає onboarding_progress викликача + tours (user_tours/tour_events) + users.heard_source. Неруйнівний — проєкти зберігаються. Емітить onboarding_reset. Повертає {ok:true}.
GET /onboarding/videos Посилання на онбординг-відео (#603). Будь-який автентифікований користувач (фронтенд рендерить посилання "▶ Watch"). Відповідь: { videos: { <slot>: { url, seconds, title } } } для слотів overview/plan/workers/issue/bot/cli/notes/bridge, злитих поверх дефолтів. Зберігається в system_configs ключ ONBOARDING_VIDEOS.
PUT /onboarding/videos Лише admin (requireAdmin). Body: { videos: { <slot>: { url, seconds, title } } }. Зберігає відомі слоти (санітизовані) в system_configs; емітить onboarding_videos_updated. Повертає {ok:true, videos}. Без rebuild — посилання оновлюються наживо.
POST /projects/:name/active-issue Issue #115. Прив'язати поточну веб-сесію до задачі. Body: { issue_id: number, title?: string }. Пише подію session_active_issue в activity_log (source=web).
GET /projects/:name/active-issue Issue #115. Остання прив'язана задача для цього власника за 7д. Відповідь: { active_issue_id, title, ts }.
GET /onboarding/cli-status Phase 54.3 (issue #58). Чи заходив користувач через arc login за останні 30 днів? Відповідь: { installed: boolean, last_cli_at: string|null }. SSOT — рядки в activity_log з event_type='cli_invocation' і actor=chatId. Чекліст онбордингу фронтенду опитує цей ендпоінт кожні 10с, поки крок CLI очікується; коли installed=true — крок cli автоматично позначається як completed.
GET /cli/devices #627 (Phase B of #617). Прив'язані інсталяції arc CLI викликача з cli_devices. Відповідь: { devices: [{ device_id, hostname, platform, arc_version, last_project, first_seen, last_seen }] } (макс. 20, найновіші першими). Заповнюється POST /api/cli/heartbeat — автентифікований fire-and-forget check-in, який CLI (≥1.0.14) надсилає при логіні й на кожному старті сесії проєкту з { device_id, hostname, platform, version, project? }; перший пристрій на користувача також логує cli_invocation в activity_log.
GET /projects/:name/cli-sessions #630 (Phase E of #617). Події start/end CLI-сесій для read-only сторінки Sessions, з timeline_events (id LIKE 'cli-%', label LIKE 'CLI session%'). Query: limit (default 120, max 500). Відповідь: { events: [{ id, worker_id, label, timestamp, duration, metadata }], transcriptSessionIds: string[] } — фронтенд парує starts/ends у рядки сесій. #624 E.2: metadata end-event несе багатий summary { first_prompt, issue_id, issue_title, session_id, message_count } (записаний arc ≥1.0.15 в кінці сесії; старіші сесії рендеряться як прості рядки). #632: transcriptSessionIds перелічує (owner-scoped), які сесії мають завантажений транскрипт → сторінка Sessions показує посилання "View transcript". Owner-gated через стандартну перевірку доступу до проєкту.
POST /api/cli/session-end/:project/:mode #204 + #624 E.2. Викликається arc CLI, коли сесія завершується. Body: { duration_ms, first_prompt?, issue_id?, issue_title?, session_id?, message_count? }. Пише timeline-подію CLI session ended; необов'язкові summary-поля (best-effort, читаються з локального транскрипту ~/.claude) потрапляють у metadata події для сторінки Sessions. first_prompt whitespace-collapsed + обрізається до 280 символів на боці сервера.
POST /api/cli/transcript/:project/:mode #632. Opt-in завантаження повного транскрипту CLI-сесії для cross-device continuity. Auth: Bearer (owner = subject токена). Body: { session_id, device_id?, message_count?, first_prompt?, size_bytes?, gzip_b64 }gzip_b64 це base64 gzip-стиснутого сирого jsonl. Upserts cli_transcripts на (owner, session_id). Стиснутий payload обмежений 4 MB (413 понад). Надсилається arc ≥1.0.16 лише коли прапорець акаунта upload_transcripts увімкнено (читається з settings у /api/cli/init).
GET /projects/:name/cli-transcript?session=:id #632. Owner-gated переглядач транскриптів. Розпаковує збережений jsonl і повертає { session_id, worker_id, first_prompt, message_count, size_bytes, updated_at, messages: [{ role, text, ts }] } (лише текст user/assistant). 401 якщо неавтентифіковано, 404 якщо немає транскрипту для цього owner+project+session.
GET /api/cli/transcripts/:project #634. CLI-орієнтований список завантажених транскриптів власника для проєкту (лише метадані: { transcripts: [{ session_id, worker_id, message_count, first_prompt, size_bytes, updated_at }] }). Auto-tenancy-gated (власник має мати доступ до проєкту). Живить arc sessions <project> --from-server.
GET /api/cli/transcript/:project/:session #634. CLI-орієнтоване завантаження сирого jsonl для реконструкції сесії на іншій машині. Повертає { session_id, worker_id, message_count, size_bytes, updated_at, jsonl }jsonl це розпакований оригінальний транскрипт, який CLI пише в ~/.claude/projects/<cwd>/<session>.jsonl, щоб claude --resume міг під'єднатися. 404 якщо не власника.
GET /analytics/onboarding-funnel Phase 54.6 (issue #61). Лише admin (#497 — платформенна статистика, 403 для звичайних користувачів). Агрегована статистика воронки за rolling-вікном. Query: hours=168 (1-720, default 7д). Відповідь: { hours, total_steps:5, started_users, completed_users, completion_rate, per_step: [{step, completed, skipped}…], duration_p50_ms, duration_p90_ms, ttfc_p50_ms, ttfc_sample_size }. SSOT — події activity_log onboarding_step_* + onboarding_completed + cli_invocation. TTFC = time-to-first-arc (дельта julianday від першого кроку онбордингу до першого cli_invocation на actor).

SSOT для метрик воронки (Phase 54.6 / issue #61) — події в activity_log (event_type LIKE 'onboarding_%'). Таблиця onboarding_progress — похідний кеш: UI рендерить одним запитом замість агрегування подій.

| GET | /analytics/lifecycle-funnel | #519 Part A. Лише admin. Lifecycle-воронка по signup-когорті: signup → email_verified → first_project → first_worker → first_message → first_response → return_day2. Query: days=30 (1-365). Відповідь: { windowDays, cohortSize, steps: [{key, count, pctOfCohort, pctOfPrev, medianSecondsFromPrev}…], segments: {web|tg|cli: {cohort, steps}}, acceptance: {medianSignupToFirstResponseSec, targetSec:120, sampleSize} }. Джерела: users (signup/verified), projects.owner_id (перший проєкт), activity_log worker_created (+ preset-seeded project_created fallback), chat_messages через owner join (message/response), auth_events login ≥24г після signup (day-2). Сегменти: cli = device_code_approve/cli_invocation evidence; tg = telegram project_channels або числовий TG-born id; інакше web. | | GET | /analytics/response-latency | #560. Лише admin. Latency пайплайну першої відповіді воркера, по стадіях, з chat_messages.metadata.latency, записаного child-ботом для CRM-originated відповідей. Query: days=7 (1-90). Відповідь: { windowDays, sampleSize, stages: {queue_ms|prep_ms|gen_ms|total_ms: {p50, p90, n}} }. Стадії: queue = POST→dequeue з inbox; prep = dequeue→spawn claude; gen = wall time claude; total = POST→відповідь збережено. Перцентилі — nearest-rank. | | GET | /analytics/cascade | #562 S5. Лише admin. Spec-gated телеметрія model-каскаду. Query: days=14 (1-90). Відповідь: { windowDays, applied, escalated, escalationReasons: {class: n}, byModel: [{model, turns, total_tokens}] }. Джерела: події activity_log cascade_applied/cascade_escalated + token_usage_log.model (migration 064). Класи причин бакетують префікс перед : (наприклад eval_failure). |

Бета-фідбек (Phase 53.3)

Метод Шлях Опис
POST /feedback Надіслати бета-фідбек. Body: {type: "bug"|"feature"|"other", title, description, project?, browser?}. Пише в activity_log (event_type=feedback_report) і пінгує CEO в Telegram.
GET /admin/feedback Список недавніх заявок (лише admin). Query: limit=50 (max 500). Відповідь: {items: [...], count}.
POST /feedback/translation Надіслати проблему перекладу (Phase 59.4). Body: {locale, msgid, suggestion, severity: "minor"|"major"|"wrong", current_translation?, page_url?}. Зберігає в translation_feedback.
GET /admin/translations Список фідбеку перекладів (admin). Query: locale, status=open|accepted|rejected|all, limit. Відповідь: {items, count}.
GET /admin/translations/stats Per-locale статистика здоров'я (admin). Відповідь: {stats: [{locale, total, open_count, accepted, rejected, critical_open}]}.
POST /admin/translations/:id/accept Прийняти пропозицію — патчить файл .po на диску. Body: {note?}. Відповідь: {ok, po_patched, glossary_suggestion}.
POST /admin/translations/:id/reject Відхилити пропозицію. Body: {note?}. Відповідь: {ok}.

POST /feedback/translation — валідує: locale ∈ {uk,de,es,fr,pl,pt-BR,ru}, msgid ≤1000, suggestion ≤2000, severity ∈ {minor,major,wrong}. Після 3+ прийнятих пропозицій для того самого msgid → glossary_suggestion: true у відповіді accept.

Плаваючий віджет у FeedbackWidget.jsx тепер має 4-й тип, "Translation" — авто-заповнює locale з i18n.locale, захоплює msgid + suggestion + severity.

Arc Help AI Chat (Phase 61, #147)

Метод Шлях Опис
POST /help/chat In-app AI Q&A. Body: {message, history: [{role,text}]}. Відповідь: {reply, sources: string[], remaining, limit}. Rate limit: 30/день/користувача.
GET /help/usage Поточний ліміт. Відповідь: {remaining, limit, used}.

POST /help/chat — пайплайн: (1) перевірка rate-limit (429 при перевищенні), (2) RAG через shared/rag.ts (Cohere + sqlite-vec, Phase 71), що зливає збіги скілів project + _global_ → fallback keyword-пошук по docs/public/, (3) Claude Haiku із system prompt + doc-контекстом + історією. message ≤2000 символів. Відповідає мовою запиту.

Бета-інвайти (Phase 52.1, лише admin)

Метод Шлях Опис
GET /admin/dashboard System Dashboard (Phase 60.9, #145). Лише admin. Повертає: CPU/RAM/Disk з /proc, користувачі за планом, флот контейнерів, останні 50 подій активності, статистика waitlist + проєктів + задач.
GET /admin/wipe-metrics Дашборд телеметрії WIP-E (#308). Лише admin. Повертає: {render: {count, avg_ms, p50_ms, p95_ms, max_ms}, interaction: {count, avg_per_session, p95_per_session, max_per_session, sessions_zero}, by_worker: [{worker_id, render_count, avg_render_ms, session_count, avg_interactions}], daily: [{date, renders, interactions, avg_render_ms}], recent: [...]}.
GET /admin/waitlist Список усіх заявок waitlist. Лише admin. Відповідь: {entries: [{id, email, message, status, created_at}]}.
POST /admin/waitlist/:id/approve Схвалити заявку — генерує інвайт-код (arc-XXXX-XXXX), надсилає лист із кодом, оновлює status→approved. Відповідь: {ok, invite_code, email_sent}.
POST /admin/waitlist/:id/reject Відхилити заявку. Відповідь: {ok}.
GET /admin/invites Список усіх інвайт-кодів + лічильники (total_active, total_used). Лише admin.
POST /admin/invites Згенерувати N кодів. Body: {count: N, note?: string}. Лише admin. Відповідь: {ok, codes, count}.
DELETE /admin/invites/:code Відкликати невикористаний інвайт-код.
/admin/notebooklm/* Видалено в Phase 71.8 разом із NotebookLM Bridge. Семантичний пошук тепер працює через самохостингований RAG (rag-architecture.md).

Оновлення flow auth: POST /api/auth/register тепер вимагає поле invite_code (Phase 52.1 closed beta). Без коду → 403 {error: "invite_required"}. Невалідний/використаний код → 403 {error: "invalid_invite"}.

Standard Cloud — WebSocket-термінал + SSE-логи (Phase 60 #139)

Протокол Шлях Опис
WS /ws/cloud/:userId/terminal?token=<JWT> Проксі до docker exec -i <containerId> /bin/bash. IDOR: userId має збігатися з chatId з JWT. Призупинений контейнер авто-відновлюється. Вхідні WS-фрейми → stdin контейнера; stdout+stderr → WS-фрейми.
SSE /api/sse/cloud/:userId/logs docker logs -f --tail 50 для контейнера користувача. Auth: Bearer JWT. IDOR: userId === chatId. Події: data: {"line": "..."} на рядок, data: {"closed": true} на виході.
SSE /api/sse/cli-status?token=<JWT> #639 (T3). Онбординг-стрім "waiting for your terminal…". Опитує сигнал cli_invocation викликача (~1.5с) і емітить event: linked {"linked":true} у мить, коли CLI прив'язується, потім закривається; : keep-alive коментар-heartbeats; event: end {"reason":"timeout"} через 10 хв. Майстер відкриває це через EventSource і тримає свій 3с fetch-poll cli-status як fallback. Owner-scoped за subject токена.

Нотатка телеметрії (#640, T4): GET /api/cli/download/:platform тепер пише fire-and-forget рядок cli_download в activity_log ({platform}, best-effort actor коли токен присутній) — відкриває воронку download→login, показану в admin-картці "CLI Activation & TTV".

Нотатка телеметрії (#641, T5): POST /projects/:name/message (надсилання browser chat) пише fire-and-forget подію активності browser_chat_message — сигнал використання за рішенням "flip/remove browser chat", показаний (browser-chat vs CLI-користувачі) в admin-картці "CLI Activation & TTV". |

Standard Cloud (Phase 60)

Метод Шлях Опис
POST /cloud/claude-verify Верифікує claude --version у контейнері (transport-safe shell-quoted через SSH у remote-host режимі, #329). Встановлює claude_authed=true. Відповідь: { ok, output }
POST /cloud/ssh-keygen Генерує ключ ed25519 у контейнері (ідемпотентно). Відповідь: { public_key }
POST /cloud/ssh-verify ssh -T [email protected] у контейнері. Встановлює github_authed=true на успіх. Відповідь: { ok, output }
POST /cloud/provision Provision Docker-контейнера для користувача. Потребує плану cloud, інакше 402. Ідемпотентно: якщо контейнер уже існує — повертає поточний стан. Відповідь: { container_id, status, server_ip, port, claude_authed, github_authed }
GET /cloud/status Стан контейнера + жива reconciliation docker inspect. Відповідь: { container_id, status, server_ip, internal_port, claude_authed, github_authed, docker_running, last_active, created_at } або { status: "none" }
POST /cloud/deprovision Зупинити + видалити контейнер (docker stop + docker rm -f + docker network rm arc-net-{id}). Оновлює status=deleted у DB. Відповідь: { ok: true, container_id }

Статуси контейнера: provisioningreadypausedsuspended / deleted.

Безпека (SEC-60 #152, #154, #155, #156): кожен контейнер ізольований у власній мережі arc-net-{id} (запобігання lateral movement). З'єднання Contabo→Hetzner SSH використовує виділеного користувача arcapi (docker group, без root) із docker-only wrapper — не-docker команди блокуються на рівні authorized_keys. ARC_TOKEN інжектується через docker exec після старту (не видно в docker inspect). git clone обмежений timeout 60. WebSocket idle timeout: 120с. SSE docker logs обмежено --since 1h. IDOR-запобігання: усі ендпоінти верифікують container.user_id === req.userId. Прапорці безпеки на docker run: --cap-drop=ALL --security-opt=no-new-privileges --cpus=1.5 --memory=2g --pids-limit=200. Volumes: arc-{id}-workspace:/workspace, arc-{id}-claude:/home/arcuser/.claude, arc-{id}-ssh:/home/arcuser/.ssh. Життєвий цикл (#141): GET /cloud/status завжди оновлює last_active. Idle 30 хв → docker pause (cron кожні 5 хв, scripts/cloud-lifecycle-cron.ts). Пробудження: повідомлення CRM, повідомлення TG, WS upgrade → docker unpause автоматично.

Waitlist (#134):

Метод Шлях Опис
POST /cloud/waitlist Приєднатися до черги. Ідемпотентно. Відповідь: { position, status, joined_at, message }. 409 якщо вже на плані cloud або контейнер уже існує.
GET /cloud/waitlist/status Власний статус у черзі. Відповідь: { position, status, joined_at, invited_at } або { status: "not_joined" }.
GET /cloud/waitlist Лише admin. Повний список + статистика. Відповідь: { stats: { total, waiting, invited, activated }, list: [...] }.
POST /cloud/waitlist/invite Лише admin. Запросити користувача. Body: { user_id }. Встановлює status=invited + автоматично апгрейдить план до cloud. Відповідь: { ok, user_id, position }.

Білінг (Phase 51 → #202 Plata by mono)

Phase #202: Stripe замінено на Plata by mono (інтернет-еквайринг monobank). Періодичні підписки через токенізацію (картка зберігається при першому платежі).

Метод Шлях Опис
GET /billing/status Поточний план, ліміти, використання, фічі. Відповідь: { plan, status, current_period_end, next_billing_date, plata_masked_pan, limits, usage, features, pricing, can_upgrade, plata_ready }
POST /billing/checkout-session Створює інвойс Plata з токенізацією. Body: { plan: "min"|"cloud", success_url?, cancel_url? }. Відповідь: { url, invoice_id, plan, amount_uah }. 503 якщо PLATA_MERCHANT_TOKEN немає у vault.
POST /billing/webhook Callback Plata (БЕЗ CRM-auth — верифікується заголовком X-Token). Статуси: success (активує план + зберігає cardToken), failure/expired (інкрементує billing_failures, 3+ → downgrade до free). Ідемпотентно через таблицю plata_events.
POST /billing/cancel Скасувати підписку (downgrade до free). Призупиняє Docker-контейнер для плану cloud. Відповідь: { ok, plan: "free" }.

#205 (2026-05-26): Застарілий маршрут /billing/portal-session видалено разом із мертвим кодом Stripe. Використовуй /billing/cancel для скасування підписки.

Ліміти планів (OR-семантика):

Відповідь 402 на POST /onboarding/setup або POST /projects/:name/workers, коли ліміт перевищено: { error: "plan_limit_reached", reason: "projects_limit"|"workers_limit", current, limit, plan, message }

Admin-користувачі (role=admin) повністю обходять перевірку ліміту плану — вони оператори, а не платні tenant'и.

Бета-тестери (subscriptions.plan='beta', Phase 52 F&F) теж обходять її — необмежені проєкти/воркери плюс усі фічі Max. Призначається вручну: UPDATE subscriptions SET plan='beta' WHERE user_id=?.

Bugfix (issue #25): POST /projects/create (Quick Start, Phase 50.2) раніше падав з ownerChatId is not defined через одруківку — виправлено, actor аудиту тепер записується коректно.

Bugfix (issue #26): allocatePort() для нових проєктів тепер зондує реальні TCP-прив'язки (ss -tln), а не лише реєстр. Раніше він міг видати порт, зайнятий не-registry сервісом (NotebookLM bridge :19213, внутрішні bridge) → workspace-бот падав з EADDRINUSE. |

Flow auth (Phase 50.1): /api/auth/register та /api/auth/login тепер повертають JWT навіть для непідтвердженого email + прапорець needs_verification: true. Чутливі дії (grant тріалу, білінг, інвайти) перевіряють email_verified окремо. Rate limit на signup: 3 / IP / 24г.


Проєкти (9 ендпоінтів)

Метод Шлях Опис
GET /projects Список проєктів користувача
POST /projects/create Створити проєкт — body: {displayName, projectName, niche?, teamPreset?}; для тріал-користувачів автоматично встановлює trial_mode=1 та інжектує PLATFORM_ANTHROPIC_KEY
POST /projects/create-with-team Атомарне створення проєкту + воркерів + (опціонально) TG-бота в одному запиті — body: {project, workers[], telegram?}; rollback на помилку. #517: telegram.token тепер зберігається у project:<name>:telegram:bot_token (unified channel bots, Phase 76) — раніше писався у legacy child:<name>:token, який unified-боти не читають.
GET /projects/suggest-preset Пропозиція пресету за нішею — query: niche=<text>; повертає {preset_id} на основі keyword-мапи
GET /projects/:name Деталі проєкту
GET /projects/:name/config Конфігурація проєкту
PUT /projects/:name/config Оновити конфігурацію
POST /projects/:name/upload-icon Завантажити PNG/GIF-іконку проєкту
POST /projects/:name/workers/:id/upload-icon Завантажити PNG/GIF-іконку воркера
GET /projects/:name/protocol Протокол проєкту
PUT /projects/:name/protocol Оновити протокол
GET /projects/:name/logs Логи проєкту
GET /projects/:name/metrics Метрики проєкту

POST /projects/create — body:

{
  "technical_name": "string",
  "displayName": "string",
  "description": "string",
  "icon": "string",
  "color": "string"
}

GET /projects/:name/logs — query: category, lines

GET /projects/:name/metrics — query: since, until


Воркери (11 ендпоінтів)

Метод Шлях Опис
GET /workers #304 Phase A — усі воркери в усіх проєктах поточного користувача. Відповідь: { workers: [{ id, label, icon, type, model, tools, context_assets, project_name }] }. Фільтрується за owner_id (multi-tenancy). CEO бачить усі проєкти.
GET /workers/presets #228 — глобальна бібліотека пресетів (project-agnostic). Повертає 13 воркерів із канонічного config/workers_registry.json: { presets: [{ id, label, icon, type, model, max_turns, tools, system_prompt, context_assets, focus_dirs, prompt_style }] }. Використовується WorkerCreationWizard для Кроку 1.
GET /model-tiers #518 — конфіг tiering моделей з config/model_tiers.json. Повертає { tiers: [{ key, label, stage, model, hint }], roleDefaults: { <roleId>: <tierKey> }, fallbackTier }. Живить per-message пікер моделі в композері (Auto + stage-мітки Opus/Sonnet/Haiku) і дефолт role→tier, застосований при створенні воркера. #520 резолюція на spawn-time дотримується per-project model_mode: per-message model override > (strict → per-worker сконфігурована модель · optimizedefaultModelForRole(role)). Режим перемикається через PUT /projects/:name/config { modelMode: "strict"|"optimize" } (default strict).
GET /workers/templates #304 Phase I — шаблони поточного користувача. Відповідь: { templates: [{ id, name, description, config, is_public, created_at }] }.
POST /workers/templates #304 Phase I — зберегти/оновити шаблон. Body: { name, description?, config }. Відповідь: { ok, id }.
DELETE /workers/templates/:id #304 Phase I — видалити шаблон (лише власник). Відповідь: { ok }.
GET /projects/:name/workers Список воркерів
POST /projects/:name/workers Створити воркера
POST /projects/:name/workers/reorder Phase 53.8 — переупорядкувати воркерів. Body: {order: [id1, id2, ...]}. Атомарно перезаписує workers_registry.json. Воркери, відсутні в order, дописуються в кінець (захист від втрати). Відповідь: {ok, count, order}.
PUT /projects/:name/workers/:id Оновити воркера
DELETE /projects/:name/workers/:id Видалити воркера
POST /projects/:name/workers/generate-prompt Згенерувати system prompt
GET /projects/:name/workers/:id/telegram-token Отримати Telegram-токен
POST /projects/:name/workers/:id/telegram-token Phase 53.4 — валідує токен через Telegram getMe, зберігає bot_username у vault, відмовляє, якщо той самий бот уже прив'язаний до іншого воркера (409). Відповідь: {ok, started, bot_username}.
DELETE /projects/:name/workers/:id/telegram-token Видалити Telegram-токен
POST /projects/:name/workers/:id/avatar #304 Phase D — завантажити аватар (multipart file, JPEG/PNG/WebP, max 2 MB). Перевірка magic-byte. Зберігає в data/worker-avatars/, пише в worker_avatars (migration 043). Відповідь: { ok, url }.
GET /projects/:name/workers/:id/avatar #304 Phase D — отримати аватар як binary (Content-Type відповідно до MIME). 404, якщо аватар не завантажено.
DELETE /projects/:name/workers/:id/avatar #304 Phase D — видалити аватар, скинути avatar_pack='role' у JSON воркера.
GET /projects/:name/workers/:id/activity #306 — стрічка активності воркера (останні 50 подій). Злито: activity_log (actor=workerId) + project_issues.activity (author=workerId) + token_usage_log (щоденні снапшоти). Відповідь: { events: [{ type, title, detail, when }] }. Типи: git_commit, skill_loaded, skill_unloaded, issue_pick, issue_close, issue_log, token_budget, session_start.
GET /projects/:name/workers/:id/runtime #306 — runtime-стан воркера. Відповідь: { status: 'working'|'idle', status_started_at, tokens_today, tokens_pct, tokens_cap, current_skill }. Читає спершу з workers_runtime_state (migration 045); staleness fallback: status='working' + tmux dead + updated_at > 10 хв → idle (виявлення краху). Plan-based денний cap через lookup subscriptions.plan: free=100K, starter=400K, starter_cloud=2M, beta=unmetered (повертає tokens_cap: null, tokens_pct: 0). Інтервал опитування 15с.
POST /projects/:name/workers/:id/notify Phase 53.2 — надіслати TG event-пінг ({event?, text, buttons?}). Тихий no-op, якщо токен не прив'язано або CRM_DISABLE_TG_NOTIFY=1.
POST /projects/:name/workers/:id/suggest-bot-username 53.11.1 (issue #48) — повертає 5 кандидатів TG username для майстра створення бота у форматі <project>_<worker>_bot + numbered fallbacks. Slugify прибирає дефіси, обрізає до 32 символів (частина worker обрізається першою). Відповідь: {candidates: string[]}.
POST /metrics/wizard 53.11.1 (issue #48) — sink телеметрії для майстра створення бота. Body: {action, duration_ms?, attempts?, success?, project?, worker_id?, locale?} (#124: події locale_active/locale_switch). Пише в activity_log (event_type=wizard_metric), best-effort.
GET /analytics/wizard-metrics?hours=168 53.11.1 (issue #48) — лише admin (#497) підсумок воронки: {starts, completions, abandons, success_rate, avg_duration_ms_completed, avg_attempts_completed, by_action}. Default 7 днів, clamp 1-720г.
POST /projects/:name/restart Перезапустити воркера
GET /projects/:name/active-role Поточна активна роль
POST /projects/:name/active-role Змінити активну роль

POST /projects/:name/workers — body:

{
  "label": "string",
  "icon": "string",
  "type": "terminal | telegram",
  "model": "string",
  "max_turns": 20,
  "tools": ["Read", "Write", "Bash"],
  "system_prompt": "string",
  "focus_dirs": ["src/", "docs/"],
  "auto_approve": false
}

max_turns за замовчуванням 20 (раніше 5, що спричиняло помилку "Reached max turns" у багатокрокових діалогах із викликами інструментів).

auto_approve (#596, default false) — коли true, spawn Claude воркера отримує --permission-mode acceptEdits (інтерактивна сесія arc CLI, /api/cli/init повертає прапорець, і виклики claude -p child-бота), тож воркер не зупиняється, щоб підтвердити редагування файлів. Bash та інші дії все одно запитують. Також приймається PUT /projects/:name/workers/:id.

POST /projects/:name/restart — query: worker_id


Файли та сховище (8 ендпоінтів)

Метод Шлях Опис
GET /projects/:name/files Дерево файлів
POST /projects/:name/files/upload Завантажити файл (multipart, max 100MB)
POST /projects/:name/files/mkdir Створити каталог
POST /projects/:name/files/create Створити файл
GET /projects/:name/files/read Прочитати файл
PUT /projects/:name/files/save Зберегти файл
DELETE /projects/:name/files/delete Видалити файл
POST /projects/:name/files/clone Git clone репозиторію

GET /projects/:name/files — query: path

GET /projects/:name/files/read — query: path, raw


Скіли (18 ендпоінтів)

Скіли проєкту

Метод Шлях Опис
GET /projects/:name/skills Список скілів проєкту. Повертає глобальні скіли (owner_project=NULL) + скіли цього проєкту (owner_project=name). Скіли інших проєктів не включаються (#157).
POST /projects/:name/skills Створити скіл. Зберігається з owner_project=name, видимий лише цьому проєкту.
PUT /projects/:name/skills/:id Оновити скіл
DELETE /projects/:name/skills/:id Видалити скіл

#210 (2026-05-26): DB (skills_global) тепер SSOT-writer. Збереження з UI йдуть спершу в DB; .claude/skills/<name>/SKILL.md записується як артефакт, щоб Claude Code CLI авто-виявляв скіли. Застарілі записи skills/<name>.md видалено — наявні файли більше не читаються й не підтримуються. Migration helper: scripts/migrate-skills-to-db.ts.

Глобальний marketplace

Метод Шлях Опис
GET /skills Список глобальних скілів
POST /skills Опублікувати скіл
GET /skills/:id Деталі скілу
PUT /skills/:id Оновити скіл
DELETE /skills/:id Видалити скіл

Еволюція та оновлення

Метод Шлях Опис
GET /skills/:id/evolution Історія еволюції скілу
GET /skill-updates Список доступних оновлень
POST /skill-updates/:id/approve Схвалити оновлення
POST /skill-updates/:id/reject Відхилити оновлення

Форки скілів

Метод Шлях Опис
GET /projects/:name/skill-forks Список форків
POST /projects/:name/skill-forks Створити форк
PUT /projects/:name/skill-forks/:id Оновити форк
DELETE /projects/:name/skill-forks/:id Видалити форк

Чат і повідомлення

Метод Шлях Опис
POST /projects/:name/chat Надіслати повідомлення в чат
GET /projects/:name/chat/history Історія чату
POST /projects/:name/message Надіслати повідомлення воркеру (Phase 48.6: автоматично будить idle-killed воркера, ~2-4с cold start; Phase 48.6.1: пробудження тепер працює й у single-mode проєктах, не лише в parallel)
GET /projects/:name/pins Список нотаток (pins)
POST /projects/:name/pins Створити нотатку
DELETE /projects/:name/pins/:id Видалити нотатку

Вікі (4 ендпоінти)

Метод Шлях Опис
GET /projects/:name/wiki/tree Дерево сторінок вікі
GET /projects/:name/wiki/file Прочитати сторінку вікі
PUT /projects/:name/wiki/save Зберегти сторінку вікі. Phase 71.5: запускає syncWiki → re-embed через Cohere (fire-and-forget; збої логуються, запис не падає).
GET /projects/:name/wiki/download Завантажити вікі як ZIP-архів

Аналітика (4 ендпоінти)

Метод Шлях Опис
GET /analytics/activity Стрічка активності
GET /api/crm/activity-feed #638 (T2). Хребет Home/Activity — курована, owner-scoped, reverse-chron стрічка по всіх проєктах користувача, що зливає activity_log (шумні типи подій відфільтровано) з github_events (коміти/PR). Query: limit (default 40, max 100), before (ISO-курсор). Відповідь: { items: [{ src, id, project_name, actor, event_type, title, metadata, ts }], nextCursor }. Owner scope = власник проєкту OR actor = user. Живить сторінку Home.
GET /api/crm/activity-feed/unread?since=:iso #644 (T8). Кількість курованих подій Home-стрічки новіших за since (той самий owner-scope + фільтр шуму, що й /activity-feed), обмежена 99. Живить дзвіночок сповіщень у хедері. Відповідь: { count }.
GET /api/cli/download/:platform #300 — неавтентифіковане завантаження бінарника (linux-x64 / darwin-arm64 / darwin-x64 / windows-x64), що віддається з dist/arc-<platform>; 404 з підказкою, якщо не зібрано. Обгортається https://arc-os.co/install.sh + install.ps1 (статичні, frontend/public). Хендлер: master-bot/routes/cli.ts.
POST /sage/mcp/add #531 — додає Smithery MCP-сервер у .mcp.json проєкту ({projectName,namespace}{type:http,url:mcp.smithery.run/<ns>}, зберігає інші сервери). Потребує canAccessProject + наявний cwd. Підхоплюється воркером при наступному spawn.
GET /sage/scout/sources #529 — список доступних джерел discovery ({id,label}): claudemarketplaces (HTML), anthropic (official marketplace.json). POST /sage/scout приймає sources:[id] (порожньо=всі), fan-out + dedup by repo+path, stale прапорець.
GET /team-presets #517 — команди-бандли з config/team-presets.json (повні конфіги воркерів: model, system_prompt, tools). Живить Composer wizard; /workers/presets — то окремі ролі.
GET /models #575 — курований список моделей для всіх дропдаунів вибору моделі воркера. Гібрид: концертні версії резолвяться LIVE з Anthropic /v1/models (newest-per-tier, 1h cache), курація (tiers/labels/hints) — єдине рукотворне місце; FALLBACK якщо API недоступний. Returns { models: [{id,label,hint,tier,recommended}], live }. Пресети зберігають tier-аліаси ('sonnet'/'opus'), що резолвяться при створенні воркера.
GET /analytics/overview #497 S4 — дані для Dashboard-карток, owner-scoped: { tokens: {week, prev_week}, issues_by_priority: {P0..P3}, per_project: [{name,p0,p1,open_total}], last_activity: [{name,ts}] }. Tokens з token_usage_log (7д vs попередні 7д), issues зі статусами open/in_progress/blocked.
GET /analytics/sidebar Дані для сайдбара. #497: hotProjects рахує activity_log + chat_messages за 24г (раніше лише чат — показувало "no activity" поруч зі свіжими подіями).
GET /analytics/phases Список фаз проєкту
POST /analytics/phases Оновити фази проєкту

Marketplace та Sage (8 ендпоінтів)

Метод Шлях Опис
GET /sage/scout/categories Категорії marketplace
POST /sage/scout Пошук скілів
POST /sage/scout/quick-scan Швидкий скан
POST /sage/scout/analyze Глибокий аналіз скілу
POST /sage/scout/install Встановити скіл
POST /sage/analyze Аналіз Sage
GET /sage/status Статус сервісу Sage
POST /sage/benchmark Запустити бенчмарк

Пам'ять і знання

Метод Шлях Опис
GET /projects/:name/rag/search?q=...&k=6&include_global=true&doc_types=wiki,issue,skill,transcript Phase 71.7 (#364): семантичний пошук по embeddings + embeddings_vec (Cohere + sqlite-vec). Параметри: q (текст запиту), k (1-25, default 6), include_global (default true — зливає з namespace скілів _global_), doc_types (subset через кому; Phase 73.6 додатковий тип: transcript). Відповідь: `{ query, project, hits: [{rank, doc_type, doc_id, chunk_ix, distance, scope: 'project'
POST /projects/:name/memory/refresh Phase 71.8 (#365): re-embed MANIFEST + ROADMAP + ключових файлів у RAG-сховище (раніше — sync у NotebookLM). Той самий ендпоінт, нова семантика.
POST /projects/:name/memory/fetch-artifact Видалено в Phase 71.8 (аудіо-огляд не має RAG-еквівалента) — повертає 410 Gone.
GET /projects/:name/learnings Список learnings
POST /projects/:name/learnings Додати learning
GET /projects/:name/knowledge-graph Knowledge graph проєкту

Документація (глобально, без auth)

Метод Шлях Опис
GET /docs/tree?lang=<lang> Дерево документації; lang необов'язковий (en/uk), default en
GET /docs/file?path=<p>&lang=<lang> Прочитати файл документації з fallback за мовою

GET /docs/tree — query: lang (необов'язковий)

GET /docs/file — query: path (обов'язковий), lang (необов'язковий)


Система

Метод Шлях Опис
GET /system/configs Отримати системні конфігурації
PUT /system/configs Оновити системні конфігурації

Коди помилок

Код Значення
200 Успіх
201 Створено
400 Невалідний запит
401 Unauthorized
403 Forbidden (multi-tenancy)
404 Не знайдено
409 Конфлікт (дублікат)
429 Забагато запитів
500 Помилка сервера

Інтеграція з GitHub (Phase 49.3)

Ендпоінт Метод Опис
/api/crm/projects/:name/github GET Список GitHub-репозиторіїв, прив'язаних до проєкту
/api/crm/projects/:name/github POST Прив'язати репозиторій (body: {owner, repo}) — повертає webhook URL + secret + інструкції з налаштування
/api/crm/projects/:name/github/:id DELETE Відв'язати репозиторій
/api/crm/projects/:name/github/events GET Список недавніх подій GitHub (Phase 49.3.1, query: ?limit=50)
/api/webhooks/github POST Публічний приймач вебхуків (валідований HMAC-SHA256, rate-limit 100/хв)

Підтримувані події: push, pull_request, workflow_run, issues. Сповіщення маршрутизуються в Telegram власника проєкту.

CRM-конектори (#521)

Per-project інтеграція з CRM (один конектор на проєкт: RemOnline або Odoo). Воркер claude отримує read-інструменти конектора mcp__<provider>__*; записи ставляться в чергу на затвердження власником (ніколи не виконуються напряму). Облікові дані зберігаються зашифрованими у vault і ніколи не повертаються клієнту. Шар конекторів provider-pluggable, а окремі конектори можуть бути gated на акаунт.

Ендпоінт Метод Опис
/api/crm/projects/:name/crm GET Поточний провайдер + які набори облікових даних сконфігуровано
/api/crm/projects/:name/crm PUT Встановити провайдера + зберегти облікові дані у vault. Hot-reload конектора через respawn child (відповідь: { ok, provider, reloaded }). Body: { provider: "remonline"|"odoo"|"none", remonline?: { api_key }, odoo?: { url, db, login, api_key } }
/api/crm/projects/:name/crm/test POST Протестувати з'єднання (збережені або надані creds). Read-only зонд (RemOnline GET /contacts/people; Odoo JSON-RPC authenticate)
/api/crm/projects/:name/crm-writes GET Список запитів на запис у черзі/вирішених для проєкту
/api/crm/projects/:name/crm-writes/:id/approve POST Approve → виконує запис проти CRM з vault-creds
/api/crm/projects/:name/crm-writes/:id/reject POST Відхилити запит на запис у черзі

Облікові дані живуть лише у vault (project:<name>:<provider>:*) і інжектуються у воркера через env при spawn — ніколи не повертаються клієнту й не потрапляють у argv. Auth RemOnline — прямий Authorization: Bearer <api_key> (RO App API v2, https://api.roapp.io); auth Odoo — JSON-RPC common.authenticate.

Безпека акаунта (Phase 45.4)

Ендпоінт Метод Опис
/api/crm/account/recovery GET Список активних ключів відновлення
/api/crm/account/recovery POST Створити ключ відновлення (body: encryptedKey, keyHint)
/api/crm/account/recovery DELETE Відкликати ключ(і) відновлення (body: { id } або {} для всіх)
/api/crm/account/recovery/restore GET Отримати зашифрований master key для відновлення

Безпека


Phase 53.13 — baseline type-safety (2026-05-10)

Без зміни поведінки ендпоінтів — лише внутрішні типи. tsc --noEmit тепер блокує push/CI:


Sentinel Pentest Remediation (2026-06-10, #433–#444)

White-box pentest-спринт — зміни поведінки ендпоінтів після виправлення 3×P1 + 4×P2 + 3×P3:


Phase 53.15 — Sentinel Sprint 1 (2026-05-10)

Зміни поведінки для auth + admin ендпоінтів (виправлення P0 з аудиту Sentinel):


Phase 53.21 — Sentinel P2 batch 2 (2026-05-12)

Phase 63 — UI/UX Consolidation + Token Usage Tracking (2026-05-21, #148)

Новий ендпоінт:

Зміни в claude-runner.ts:

Зміни UI (не API):

Phase 53.18 — виправлення витоку секрету tmux (2026-05-11)

Без зміни поведінки ендпоінтів — лише рефактор внутрішніх spawn-шляхів.

Phase 53.16 — Sentinel Sprint 2 (2026-05-10)

Зміни поведінки ендпоінтів після hardening 13 × P1:

Phase 55 — Cosmic Editorial login (2026-05-13)

Нові ендпоінти для входу через magic-link:

Об'єднання EphemeralTokenType розширено: тепер містить "magic_link" поряд із наявними oauth_state / password_reset / email_verification / tfa_challenge.

Фронтенд (CosmicCard.jsx) обробляє стан magic (60-с countdown повторного надсилання) і URL-параметр ?magic_token= (auto-consume → login → анімація успіху).

Phase 56 — AI Interop / Project Context Export (2026-05-13)

Owner-only експорт санітизованого знімка проєкту як .md для передачі зовнішньому AI (Gemini / ChatGPT / Perplexity / Claude.ai).

Alert: коли власник перевищує 3 експорти за 24г AND prefs.notify_on_export = true (default OFF) — logActivity("export_alert", ...) проходить через наявний пайплайн TG-notify Phase 53.10 (alertFired: true у тілі відповіді).

Multi-tier сканер (shared/secret-scanner.ts) — Tier 1 regex (PATTERN_REGISTRY із PII-санітайзера), Tier 2 ентропія Шеннона ≥4.5 біт/символ на прогонах ≥20 символів, Tier 3 контекстні евристики (key=/token:/secret=/password=). Whitelist: UUID / git SHA / SHA-256 / повторювані символи / короткий hex / low-entropy base58. Tier'и severity (critical/high/medium/low). Продуктивність: <500 мс / 1 MB.

DB migration 024 — таблиці export_audit_log + export_preferences.


Phase 57 — Platform Settings (Sentinel #103 follow-up, 2026-05-15)

Керування секретами супер-адміном через CRM UI замість ssh/edit-.env/paste-in-chat. Backend MVP (Stage 1 з 4 стадій). Усі ендпоінти gated requireAdmin (Phase 53.15) — повертають 403 Forbidden — admin only для non-admin, 401 Unauthorized без JWT.

Жорсткий exclusion-список NEVER_EXPOSE: CRM_SECRET (підпис JWT) + SECRET_ENCRYPTION_KEY (meta-key vault) — навіть admin-запит із валідним токеном повертає 400 "not managed". Лог аудиту append-only (немає хендлера UPDATE/DELETE); кожна дія (включно з невдалими) пише рядок із IP + UA + email.

DB migration 026 — таблиця platform_audit_log. Stage 2 (фронтенд PlatformSettings.jsx) — випущено 2026-05-15 (cbc8bac): admin-only card grid + rotate-модалка (<input type="password"> + retype-confirm) + audit drawer; sidebar-запис відфільтрований за userRole === "admin", отриманим із /api/auth/me.

Polish (2026-05-15, commit 56191b0) — реструктуризація UI Platform Settings. Items відповіді GET /api/crm/platform/settings отримують 5 нових полів: category (anthropic|oauth|telegram|email), usedIn (string[] — файли/flow, що споживають ключ), getFromUrl (де взяти свіже значення), effectAfterRotate, riskIfLeaked. Використовується фронтендом для рендеру 4 sectioned card-груп + per-card згортна панель довідки зі структурованим контекстом (Used in / Get from / Effect / Risk). Без зміни поведінки mutator-ендпоінтів (PUT/POST/restart/test).

Refactor (2026-05-16) — внутрішнє прибирання shared/routes/platform.ts. Видалено 39 рядків (додано 16), без зміни публічної поверхні API. Сигнатури й відповіді ендпоінтів PUT/POST/restart/test/audit без змін. Задокументовано тут лише тому, що doc-coverage pre-push gate спрацьовує на будь-який diff shared/routes/*.ts.

Backdated activity (#117, 2026-05-16)POST /api/mcp/issues/:project/:id/log тепер приймає необов'язкове поле ts (рядок ISO-8601). Використовується реконструкцією arc retro, щоб історичні записи потрапляли на свої оригінальні timestamps. Значення в майбутньому тихо обрізаються до now всередині addActivity() (захист від fat-finger backdate). Невалідний ISO → 400.

Stage 3 (2026-05-15) — hot-reload секретів OAuth + Resend без рестарту. shared/auth.ts loadOAuthConfig() тепер читає getSecret("GITHUB_CLIENT_ID/SECRET" | "GOOGLE_CLIENT_ID/SECRET") на кожен виклик замість process.env. Callsites у master-bot/routes/auth.ts уже викликали getOAuthConfig() на кожен запит → 0 змін callsite. RESEND_API_KEY уже був hot-reloaded через shared/email.ts:47. Зміна поведінки: PUT /api/crm/platform/settings/{GITHUB_CLIENT_ID|GITHUB_CLIENT_SECRET|GOOGLE_CLIENT_ID|GOOGLE_CLIENT_SECRET|RESEND_API_KEY} тепер набуває чинності з наступного запиту, рестарт не потрібен. restartTargets для цих 5 ключів порожній → кнопка Restart в UI прихована. Edge case: OAuth-flow зі state-токеном, виданим до ротації, може отримати 400 на callback під час code exchange — повторна спроба користувача вирішує це. ANTHROPIC_API_KEY, PLATFORM_ANTHROPIC_KEY, MASTER_BOT_TOKEN, CITADEL_BOT_TOKEN залишаються restart-required (читаються при spawn child-бота / init TG long-poll).

Phase 57.3.5 cleanup (2026-05-16) — allowlist MANAGED_KEYS обрізано 9 → 6. Видалено: ANTHROPIC_API_KEY (оператори тепер використовують єдиний PLATFORM_ANTHROPIC_KEY і для trial-кредитів, і для платформенного inference; .env fallback усе ще працює для legacy code-шляхів, поки Sage/Karpathy не мігрують), CITADEL_BOT_TOKEN (per-project бот належить до vault-записів child:<name>:token, керується flow онбордингу воркера — не Platform Settings). MASTER_BOT_TOKEN перепризначено: label → "Telegram — System Monitor Bot", description → "Server health alerts + on-demand status probes (admin-only, not a chat bot)". Phase 58 додасть цикл моніторингу (push-alerts на worker crash / disk / RAM / SSH brute-force / CF bypass + команди /status, /health, /errors, /restart). Фінальний набір: PLATFORM_ANTHROPIC_KEY + GITHUB×2 + GOOGLE×2 + MASTER_BOT_TOKEN + RESEND_API_KEY (refs #103).

Arc Help (Phase 61 / #147)

Історія (Phase 61 / #153):

GDPR / Compliance (Sprint 1+2, #161–#174, 2026-05-22)

Право на стирання — DELETE /api/auth/account (#162)

Остаточно видаляє автентифікованого користувача й усі його дані (GDPR Art. 17).

Password Version / інвалідація токенів (#174)

Migration 035 додає password_version INTEGER NOT NULL DEFAULT 0 до users. При зміні пароля password_version інкрементується. JWT payload включає поле pv. crmAuthMiddleware валідує pv проти DB на кожен запит, відхиляючи токени, видані до останньої зміни пароля (401 "Token invalidated — please log in again"). Fail open, якщо DB недоступна.

Cron ретенції даних (#168)

Master-бот запускає щоденний purge на старті + кожні 24г. Ліміти ретенції: chat_messages 180 днів (за timestamp), activity_log 365 днів (за created_at), auth_events 90 днів (за ts), token_usage_log 730 днів (за created_at unixepoch), export_audit_log 365 днів (за exported_at). Non-fatal — стирання не блокує старт.

Email Compliance (#167)

Усі вихідні транзакційні листи (password reset, verification, magic-link) тепер включають:

Безпека — HIBP Breached Password Check (#171)

На POST /api/auth/register та POST /api/auth/reset-password наданий пароль перевіряється проти k-anonymity API HaveIBeenPwned перед збереженням. До HIBP надсилаються лише перші 5 hex-символів SHA-1 хешу — повний пароль ніколи не покидає сервер. Якщо пароль з'являється в будь-якій базі витоків із count > 0, запит відхиляється з HTTP 400: "This password was found in a known data breach. Please choose a different password." Fail open на таймаут/помилку HIBP (4с таймаут) — недоступний HIBP не блокує реєстрацію.

Data Portability — GET /api/auth/export (#163)

GDPR Art. 20 — право на переносимість даних. Повертає структурований JSON-файл із усіма персональними даними, які Arc OS зберігає про автентифікованого користувача.

Arc Help — Hardened System Prompt + Anti-Injection (#151)

Зміни поведінки POST /api/crm/help/chat (без зміни поверхні API):

Worker Discipline Hardening (#187, #188, #189, 2026-05-23)

Розширення статусів задач (#187)

PUT /api/mcp/issues/:project/:id тепер приймає розширені значення статусу:

Статус Значення
open Ще не почато
in_progress Активно опрацьовується (встановлюється arc issue take)
blocked Очікує зовнішньої залежності
deferred Відкладено (раніше зберігалося лише як текст)
closed Готово

Нове поле assignee: задачі тепер мають assignee: string | null. Встановлюється через arc issue take <id> або --assignee <worker_id> в arc issue update.

Migration 036: ALTER TABLE project_issues ADD COLUMN assignee TEXT (nullable, авто-застосовується на старті сервера).

CLI-команда arc issue take <id> (#187)

Скорочення для claim задачі: встановлює assignee = current_worker_id, status = in_progress, логує активність, пише session state. Еквівалент:

arc issue update <id> --status in_progress --assignee developer
arc issue log <id> "Taken by developer — status set to in_progress"

Валідація hook'а commit-msg (#187)

.githooks/commit-msg тепер валідує посилання на #N задачі проти локального issues/issues.json:

PROJECT_MANIFEST.md Bridge Injection (#188)

handleCliInit (shared/cli-routes.ts) тепер читає PROJECT_MANIFEST.md з кореня проєкту й інжектує його в блок CITADEL під ## Project Context. Ліміт: 8000 символів. Це дає bridge-воркерам (що працюють на клієнтських машинах через arc) доступ до компактної архітектури, патернів безпеки, структури файлів та ключових learnings із повного CLAUDE.md.

Розміщення: після PROJECT_RULES.md, перед списком скілів.

Поле конфігу воркера context_assets (#189)

Конфіг воркера в workers_registry.json підтримує необов'язкове context_assets: string[] — список назв скілів, які автоматично інжектуються в кожну bridge-сесію для того воркера (без потреби в arc skill <name>):

{
  "id": "developer",
  "context_assets": ["crm-api-reference", "archivist_system"]
}

Вміст кожного скілу інжектується під ### Auto-Loaded Skills → #### Skill: <name>, обрізаний до 3000 символів кожен.

Phase 62 — Голосовий ввід (#373, 2026-06-05)

Транскрипція голосу в реальному часі, проксійована через самохостингований whisper.cpp сервер (arc-whisper.service, порт 19214, модель ggml-base попередньо завантажена).

POST /api/crm/voice/transcribe (#373, Phase 62.4)

Транскрибує короткі голосові кліпи (диктування в чаті). Проксіює аудіо до локального whisper-server і повертає текст.

Auth: Bearer-токен (або query ?token=).

Body: multipart/form-data

Поле Тип Нотатки
audio Blob webm / ogg / wav. Max 25 MB.
locale string BCP-47, наприклад uk-UA, en-US. Передається як param language у whisper.

Відповідь 200:

{ "transcript": "Що ти зробив вчора?" }

Коди помилок:

Код Значення
400 Відсутнє поле audio або locale
413 Аудіо понад 25 MB
429 Досягнуто денної квоти (60 хв/користувача/день) OR сервер зайнятий (max 2 конкурентні транскрипції)
502 whisper-server повернув non-200
500 Неочікуваний збій

Rate limit: voice_usage_log (migration 051) відстежує приблизні секунди на (користувача, день), використовуючи розмір байтів завантаження як проксі (припускає voice-кодек ~32 kbps, точність ±30%). Жорсткий cap: 3600 с / день. Запити, що перевищили б cap, повертають 429 перед пробросом у whisper.

Архітектурна нотатка: whisper працює лише на Contabo (не в per-user Hetzner-контейнерах). Байти аудіо ніколи не покидають Contabo; результуючий текст — це те, що бачить cloud-chat маршрутизація Phase 70. arc-whisper.service тримає модель ggml-base попередньо завантаженою, тож вартість на виклик — чистий inference (~3.4 с warm для 11 с аудіо, 3.1× realtime на поточному 6-vCPU EPYC).


Phase 73 — Транскрипція + аналіз зустрічей (#377-#384, 2026-06-05)

Завантаж аудіо/відео зустрічі до проєкту, отримай whisper-транскрипцію + summary від Claude, опціонально ембеднуте в RAG. Усі маршрути gated canAccessProject (власник або admin).

POST /api/crm/projects/:name/transcripts/upload (#377, Phase 73.1)

Multipart-завантаження, повертає 202 з transcript_id + job_id + status:'queued'. Job підхоплюється in-process чергою (max 1 конкурентний).

Поля body:

Ліміти: 1 GB max upload, MIME allow-list (mp3/wav/m4a/aac/ogg/opus/flac + mp4/mov/webm/mkv).

Помилки: 400 (відсутнє поле / погана MIME), 401, 413 (понад cap), 500 (запис на диск).

GET /api/crm/projects/:name/transcripts (#379, Phase 73.3)

Список транскриптів проєкту, cursor-paginated. Query: ?limit=20&cursor=<id>. Повертає {items: TranscriptSummary[], next_cursor: number|null}.

GET /api/crm/projects/:name/transcripts/:id (#379)

Повний рядок, включно з transcript_text, summary_json (розпарсено в об'єкт) і frames_json (розпарсено, коли Phase 73.4 виходить).

GET /api/crm/projects/:name/transcripts/job/:jobId/progress (#379)

SSE-стрім прогресу job. Пушить event: progress з {status, progress_pct, step_label, error}, коли будь-яке поле змінюється, плюс : keep-alive коментар-heartbeats кожну 1с, щоб 10с idleTimeout Bun не вбивав довгі whisper-прогони. Закривається event: end, щойно статус термінальний.

Auth: браузерний EventSource дописує ?token=<bearer> (не може встановити заголовок Authorization).

Термінальні статуси: done (post-Phase 73.6 RAG embed + прибирання файлу), failed. Нотатка: summarized — транзитний крок — SSE залишається відкритим крізь embeddingdone. Кнопка send фронтенду розблоковується на summarized (не чекає RAG).

State machine (Phases 73.1-73.6)

queued
  → extracting_audio    (ffmpeg → 16 kHz mono WAV)
  → transcribing        (whisper-cli -t 4)
  → (video) extracting_frames → frames_extracted   (ffmpeg scene-change)
            → vision_analyzing → vision_analyzed   (Phase 73.4 Claude vision per frame)
  → (audio) transcribed
  → summarizing         (Claude Sonnet → summary_json, receives vision frames as context)
  → summarized
  → embedding           (Phase 73.6 Cohere upsert via shared/rag.ts, skipped if embed_to_rag=0)
  → done                (source file + frames dir deleted — CEO decision D4)

Форма JSON vision-frames (Phase 73.4, #380)

Зберігається як JSON-рядок у transcripts.frames_json (розпарсовується назад в об'єкт через GET /transcripts/:id).

[
  { "ts_ms": 3000, "description": "Slide titled 'Q3 Revenue' with bar chart showing 30% growth." },
  { "ts_ms": 6000, "description": "Architecture diagram with three boxes labeled API/Worker/DB." }
]

Жорсткий cap MAX_FRAMES=50 на транскрипт (~$0.15 у гіршому разі за типовими цінами Sonnet vision). Frames понад cap тихо відкидаються, останній збережений description отримує суфікс [+N more frames dropped]. Per-frame збої стають рядками [vision failed: <msg>] — вони не переривають прохід. Frames, описані як "No informational content", — це лише webcam або декоративні.

Форма Summary JSON (Phase 73.5, #381)

Зберігається як JSON-рядок у transcripts.summary_json. Розпарсовується назад в об'єкт через GET /transcripts/:id.

{
  "tldr": "1-2 sentence executive summary",
  "key_points": ["..."],
  "action_items": [{"task": "...", "owner": "name or null"}],
  "decisions": ["..."],
  "topics": ["..."],
  "model": "claude-sonnet-4-5",
  "generated_at": "2026-06-05T20:04:15.573Z"
}

Резолюція ключа Anthropic дзеркалить shared/worker-spawn.ts: BYOK account_settings.anthropic_key (розшифровано, якщо зашифровано), fallback PLATFORM_ANTHROPIC_KEY для власників у trial-режимі. Збої summary non-fatal — transcript_text залишається цілим, статус відкочується до transcribed/frames_extracted, щоб користувач міг повторити після виправлення свого ключа.

Phase 78 — Notes: Knowledge Collections (#394–#404, 2026-06-08)

Per-project нотатки в стилі NotebookLM. Кожна нотатка — це колекція джерел (video, audio, YouTube, web, PDF, DOCX, TXT, image) зі спільним RAG-індексом і чатом.

GET /api/crm/projects/:name/notes

Повертає всі нотатки проєкту. Потрібен auth + canAccessProject.

Відповідь 200:

[{ "id": 1, "title": "Sprint planning", "description": null, "created_at": "...", "source_count": 3 }]

POST /api/crm/projects/:name/notes

Створити нову нотатку.

Body: { "title": "string", "description": "string?" }
Відповідь 201: { "id": 1, "title": "Sprint planning" }

GET /api/crm/projects/:name/notes/:id

Отримати деталі нотатки з джерелами, зв'язками задач та історією чату.

Відповідь 200:

{
  "id": 1, "title": "Sprint planning",
  "sources": [{ "id": 1, "source_type": "youtube", "title": "My video", "url": "...", "status": "done", "duration_seconds": 3600 }],
  "issue_links": [{ "issue_id": 42, "title": "Issue title" }],
  "chats": [{ "role": "user", "content": "Summarize", "created_at": "..." }]
}

DELETE /api/crm/projects/:name/notes/:id

Видалити нотатку й усі джерела/чати. Каскадує до note_sources, note_chats, note_issue_links.

POST /api/crm/projects/:name/notes/:id/sources

Додати джерело (завантаження файлу, URL або файл проєкту).

Content-Type: multipart/form-data OR application/json

Відповідь 201: { "source_id": 5, "status": "queued" }

Обробка асинхронна. Опитуй GET /notes/:id, поки source.status === "done".

POST /api/crm/projects/:name/notes/:id/sources/chunk-init

Почати chunked-завантаження файлу (#547). Cloudflare відхиляє тіла запитів понад ~100 MB, тож файли більші за це надсилаються chunk'ами; web-UI перемикається автоматично на 90 MB.

Body: { "filename": "meeting.mp4", "mime": "video/mp4", "size": 262144000 }
Відповідь 201: { "upload_id": "<uuid>", "chunk_size": 20971520 }

Тип і per-type ліміти розміру валідуються наперед (ті самі правила, що й для прямого upload). Сесії закінчуються після 30 хвилин неактивності; max 10 конкурентних сесій (429 понад це).

POST /api/crm/projects/:name/notes/:id/sources/chunks/:uploadId

Дописати один chunk до сесії завантаження.

Content-Type: application/octet-stream — сирі байти chunk'а (≤ chunk_size)
Header: X-Chunk-Index — 0-based, строго послідовний (409 на mismatch)

Відповідь 200 (проміжна): { "received_bytes": 41943040 }
Відповідь 201 (фінальний chunk, коли отримані байти == заявлений розмір): { "done": true, "source_id": 5, "status": "queued" } — джерело тоді йде нормальним шляхом обробки.

PATCH /api/crm/projects/:name/notes/:id/sources/:sourceId

Перейменувати джерело (inline-редагування заголовка).

Body: { "title": "New name" }
Відповідь 200: {}
Передай порожній рядок або null для скидання до дефолту filename/URL.

DELETE /api/crm/projects/:name/notes/:id/sources/:sourceId

Прибрати джерело і його вміст.

GET /api/crm/projects/:name/notes/:id/sources/:sourceId/progress

SSE-стрім прогресу обробки джерела.

Events: progress { "status": "processing"|"done"|"error", "message": "..." }

POST /api/crm/projects/:name/notes/:id/chat

Надіслати повідомлення в чат нотатки. Відповідь SSE-стрім.

Body:

{
  "message": "Summarize all sources",
  "selectedSourceIds": [1, 3]
}

selectedSourceIds необов'язковий — пропусти, щоб включити всі джерела.

SSE events:

RAG-стратегія: sqlite-vec пошук по ембедингах note_source → fallback пряма інжекція content_text (max 80 K символів), коли векторний пошук недоступний або немає результатів. Anti-hallucination system prompt guard інжектується, коли включено необроблені джерела.

Tool use — create_issue: Claude може створювати задачі проєкту з чату. Multi-turn: turn 1 стрімить до виклику інструмента, backend виконує (issueQueries.nextId + issueQueries.insert), turn 2 відновлює стрімінг з інжектованим tool result.

State machine статусу джерела

queued → processing → done
                    ↘ error

Значення поля status джерела:

Стратегія YouTube-транскрипту (Phase 78.3)

  1. youtube-transcript npm: каскад мов ["en", "en-US", "en-GB"] → fallback будь-яка
  2. Supadata.ai API: GET https://api.supadata.ai/v1/youtube/transcript?url=...&text=true&lang=en → fallback без param lang
  3. yt-dlp + Whisper: фінальний fallback для відео без субтитрів

Пріоритет: віддавати перевагу англійським субтитрам, щоб уникнути авто-перекладених арабських/іншомовних транскриптів.