CRM API — Dokumentacja endpointów
Arc OS — The Orchestration System for AI Teams
Informacje ogólne
| Parametr | Wartość |
|---|---|
| Base URL | https://arc-os.co/api/crm |
| Autoryzacja | Authorization: Bearer <JWT> lub ?token=<JWT> (dla SSE/WebSocket) |
| Content-Type | application/json |
| Algorytm JWT | HMAC-SHA256 |
| TTL JWT | 24 godziny |
Uwierzytelnianie
Wszystkie endpointy (poza /docs/*) wymagają tokenu JWT w nagłówku Authorization: Bearer <token>.
Dla połączeń SSE i WebSocket token przekazywany jest przez parametr query ?token=<JWT>.
Błędy autoryzacji
| Kod | Opis |
|---|---|
| 401 | Brak lub nieprawidłowy token |
| 403 | Brak dostępu do projektu (multi-tenancy) |
Endpointy według kategorii
Konto i ustawienia
| Metoda | Ścieżka | Opis |
|---|---|---|
| GET | /account/settings |
Pobierz ustawienia konta |
| PUT | /account/settings |
Zaktualizuj ustawienia konta |
Onboarding + Trial Credits (Phase 50.1)
| Metoda | Ścieżka | Opis |
|---|---|---|
| POST | /onboarding/setup |
Utwórz pierwszy projekt. Body multipart: config (JSON) + files. Pole anthropicKey jest teraz opcjonalne — jeśli puste + użytkownik ma zweryfikowany email + nie otrzymał wcześniej wersji próbnej, projekt tworzony jest w trial_mode=1 ze 100K free tokens. Response: { ok, project, trial_activated }. Phase 51: zwraca 402 z {error:"plan_limit_reached", reason, current, limit, plan} gdy użytkownik przekroczył limit projektów dla planu. |
| GET | /account/trial-status |
Status wersji próbnej dla bannera UI. Response: { email, email_verified, trial_granted, has_trial_active, total_remaining, total_granted, projects: [...] } |
Onboarding Checklist (Phase 54.1, issue #56)
Post-wizard 5-krokowa lista kontrolna zaangażowania. Każdy krok (workers, cli, skill, bot, issue) przyjmuje status completed lub skipped. Mutacje są idempotentne: ponowny identyczny POST zwraca ten sam stan, nie zapisuje duplikatu w activity_log. Replay nie resetuje stanu, jedynie usuwa dismissed_at — UI ponownie wyświetla panel z tym samym postępem.
| Metoda | Ścieżka | Opis |
|---|---|---|
| GET | /onboarding/progress |
Aktualny stan dla zalogowanego użytkownika. Response: { steps:["workers","cli","skill","bot","issue"], state:{<step>:<status>}, completed_count, total_steps:5, completed_at, dismissed_at, source, started_at, updated_at }. Nienaruszony użytkownik → zera/null bez tworzenia wiersza. |
| POST | /onboarding/event |
Zapisz przejście kroku. Body: { step: "workers"|"cli"|"skill"|"bot"|"issue", status: "completed"|"skipped", source?: "web"|"cli" }. Walidacja allowlisty → 400 na nieznany krok/status. Response: ten sam kształt co GET. Emituje onboarding_step_completed/onboarding_step_skipped w activity_log tylko przy changed; przy przejściu do 5/5 dodatkowo emituje onboarding_completed z duration_ms. |
| POST | /onboarding/dismiss |
Zamknij panel (dismissed_at = now). Idempotentnie. Emituje onboarding_dismissed przy pierwszym wywołaniu z payload {completed_count}. |
| POST | /onboarding/replay |
Ponownie otwórz zamknięty panel (dismissed_at = NULL). Stan kroków nie jest dotykany. Emituje onboarding_replayed przy clear-event. |
| POST | /projects/:name/active-issue |
Issue #115. Powiąż bieżącą sesję webową z issue. Body: { issue_id: number, title?: string }. Zapisuje zdarzenie session_active_issue w activity_log (source=web). |
| GET | /projects/:name/active-issue |
Issue #115. Ostatnio powiązane issue dla tego właściciela w ciągu 7 dni. Response: { active_issue_id, title, ts }. |
| GET | /onboarding/cli-status |
Phase 54.3 (issue #58). Czy użytkownik logował się przez arc login w ciągu ostatnich 30 dni? Response: { installed: boolean, last_cli_at: string|null }. SSOT — wiersze w activity_log z event_type='cli_invocation' i actor=chatId. Frontend onboarding-checklist odpytuje ten endpoint co 10s dopóki krok CLI jest pending; gdy installed=true — automatycznie oznacza krok cli jako completed. |
| GET | /analytics/onboarding-funnel |
Phase 54.6 (issue #61). Zagregowane statystyki lejka w kroczącym oknie czasowym. Query: hours=168 (1-720, domyślnie 7d). Response: { 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 — zdarzenia onboarding_step_* + onboarding_completed + cli_invocation w activity_log. TTFC = czas do pierwszego arc (delta julianday od pierwszego kroku onboardingu do pierwszego cli_invocation per aktor). |
SSOT dla metryk lejka (Phase 54.6 / issue #61) — zdarzenia w activity_log (event_type LIKE 'onboarding_%'). Tabela onboarding_progress — derived cache: UI renderuje się jednym zapytaniem zamiast agregacji po zdarzeniach.
Beta Feedback (Phase 53.3)
| Metoda | Ścieżka | Opis |
|---|---|---|
| POST | /feedback |
Wyślij beta feedback. Body: {type: "bug"|"feature"|"other", title, description, project?, browser?}. Zapisuje do activity_log (event_type=feedback_report) i pinguje CEO na Telegram. |
| GET | /admin/feedback |
Lista ostatnich zgłoszeń (tylko admin). Query: limit=50 (max 500). Response: {items: [...], count}. |
POST /feedback — walidacja body: type ∈ {bug,feature,other}, title ≤200 znaków, description ≤5000 znaków. Sukces → {ok: true, type, title}. Ping Telegram formatowany jako 🐞/💡/📝 New <type> feedback ... From: <user> Title: <title> + pierwsze 400 znaków opisu.
Pływający widget w
FeedbackWidget.jsx(dashboard CRM) automatycznie przekazujebrowser(UA + viewport + locale) iproject(technical_name aktywnego projektu).
Beta Invites (Phase 52.1, tylko admin)
| Metoda | Ścieżka | Opis |
|---|---|---|
| GET | /admin/invites |
Lista wszystkich kodów zaproszenia + liczniki (total_active, total_used). Tylko admin. |
| POST | /admin/invites |
Wygeneruj N kodów. Body: {count: N, note?: string}. Tylko admin. Response: {ok, codes, count}. |
| DELETE | /admin/invites/:code |
Unieważnij nieużyty kod zaproszenia. |
Aktualizacja flow auth: POST /api/auth/register wymaga teraz pola invite_code (zamknięta beta Phase 52.1). Bez kodu → 403 {error: "invite_required"}. Nieprawidłowy/użyty kod → 403 {error: "invalid_invite"}.
Billing (Phase 51)
| Metoda | Ścieżka | Opis |
|---|---|---|
| GET | /billing/status |
Aktualny plan, limity, użycie, funkcje. Automatycznie tworzy wiersz Free przy pierwszym wywołaniu. Response: { plan, status, current_period_end, limits, usage, features, pricing, can_upgrade, stripe_ready } |
| POST | /billing/checkout-session |
Stripe Checkout (Stage 2 — zwraca 501 dopóki Stripe SDK nie jest zintegrowane) |
| GET | /account/usage |
Historia zużycia tokenów dla autoryzowanego użytkownika (Phase 63, #148). Response: { rows: [ { project_name, worker_id, input_tokens, output_tokens, cache_tokens, total_tokens, created_at } × do 200 ], totals: { total, input, output } }. Odczytuje token_usage_log po owner_id. Wyświetlane w UserDropdown (UsageCard) i BillingPage (sekcja Token Usage). |
Limity planu (semantyka OR):
- Free: 1 projekt AND 5 workerów
- Min ($4.99/mies.): 5 projektów OR 25 workerów łącznie
- Max ($11.99/mies.): 20 projektów OR 150 workerów łącznie
Odpowiedź 402 na POST /onboarding/setup lub POST /projects/:name/workers gdy limit przekroczony: { error: "plan_limit_reached", reason: "projects_limit"|"workers_limit", current, limit, plan, message }
Użytkownicy admin (
role=admin) całkowicie pomijają sprawdzanie limitu planu — są operatorami, nie płatnymi tenantami.
Testerzy beta (
subscriptions.plan='beta', Phase 52 F&F) również pomijają — nieograniczona liczba projektów/workerów plus wszystkie funkcje Max. Przypisywane ręcznie:UPDATE subscriptions SET plan='beta' WHERE user_id=?.
Bugfix (issue #25):
POST /projects/create(Quick Start, Phase 50.2) wcześniej rzucał błądownerChatId is not definedprzez literówkę — naprawione, aktor audytu jest teraz poprawnie zapisywany.
Bugfix (issue #26): allocatePort() dla nowych projektów sprawdza teraz rzeczywiste powiązania TCP (
ss -tln), a nie tylko registry. Wcześniej mógł zwrócić port zajęty przez serwis spoza registry (NotebookLM bridge :19213, internal bridges) → workspace bot padał na EADDRINUSE.
Flow auth (Phase 50.1): /api/auth/register i /api/auth/login zwracają teraz JWT nawet dla niezweryfikowanego emaila + flaga needs_verification: true. Wrażliwe operacje (przyznanie wersji próbnej, billing, kody zaproszenia) sprawdzają email_verified osobno. Rate limit na rejestrację: 3 / IP / 24h.
Projekty (9 endpointów)
| Metoda | Ścieżka | Opis |
|---|---|---|
| GET | /projects |
Lista projektów użytkownika |
| POST | /projects/create |
Utwórz projekt |
| GET | /projects/:name |
Szczegóły projektu |
| GET | /projects/:name/config |
Konfiguracja projektu |
| PUT | /projects/:name/config |
Zaktualizuj konfigurację |
| GET | /projects/:name/protocol |
Protokół projektu |
| PUT | /projects/:name/protocol |
Zaktualizuj protokół |
| GET | /projects/:name/logs |
Logi projektu |
| GET | /projects/:name/metrics |
Metryki projektu |
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
Workery (11 endpointów)
| Metoda | Ścieżka | Opis |
|---|---|---|
| GET | /projects/:name/workers |
Lista workerów |
| POST | /projects/:name/workers |
Utwórz workera |
| POST | /projects/:name/workers/reorder |
Phase 53.8 — zmień kolejność workerów. Body: {order: [id1, id2, ...]}. Atomowo nadpisuje workers_registry.json. Workery nieobecne w order dodawane są na końcu (zabezpieczenie przed utratą). Response: {ok, count, order}. |
| PUT | /projects/:name/workers/:id |
Zaktualizuj workera |
| DELETE | /projects/:name/workers/:id |
Usuń workera |
| POST | /projects/:name/workers/generate-prompt |
Wygeneruj systemowy prompt |
| GET | /projects/:name/workers/:id/telegram-token |
Pobierz token Telegram |
| POST | /projects/:name/workers/:id/telegram-token |
Phase 53.4 — waliduje token przez Telegram getMe, zapisuje bot_username w vault, odmawia jeśli ten sam bot jest już powiązany z innym workerem (409). Response: {ok, started, bot_username}. |
| DELETE | /projects/:name/workers/:id/telegram-token |
Usuń token Telegram |
| POST | /projects/:name/workers/:id/notify |
Phase 53.2 — wyślij ping zdarzenia TG ({event?, text, buttons?}). Silent no-op jeśli token nie jest powiązany lub CRM_DISABLE_TG_NOTIFY=1. |
| POST | /projects/:name/workers/:id/suggest-bot-username |
53.11.1 (issue #48) — zwraca 5 kandydatów TG username dla wizarda tworzenia bota w formacie <project>_<worker>_bot + ponumerowane fallbacki. Slugify usuwa myślniki, obcięcie do 32 znaków (część worker obcinana pierwsza). Response: {candidates: string[]}. |
| POST | /metrics/wizard |
53.11.1 (issue #48) — sink telemetrii dla wizarda tworzenia bota. Body: {action, duration_ms?, attempts?, success?, project?, worker_id?}. Zapisuje do activity_log (event_type=wizard_metric), best-effort. |
| GET | /analytics/wizard-metrics?hours=168 |
53.11.1 (issue #48) — podsumowanie lejka: {starts, completions, abandons, success_rate, avg_duration_ms_completed, avg_attempts_completed, by_action}. Domyślnie 7 dni, clamp 1-720h. |
| POST | /projects/:name/restart |
Uruchom ponownie workera |
| GET | /projects/:name/active-role |
Aktualnie aktywna rola |
| POST | /projects/:name/active-role |
Zmień aktywną rolę |
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/"]
}
max_turnsdomyślnie20(wcześniej było5, powodowało błąd "Reached max turns" w wieloetapowych dialogach z wywołaniami narzędzi).
POST /projects/:name/restart — query: worker_id
Pliki i przechowywanie (8 endpointów)
| Metoda | Ścieżka | Opis |
|---|---|---|
| GET | /projects/:name/files |
Drzewo plików |
| POST | /projects/:name/files/upload |
Prześlij plik (multipart, max 100MB) |
| POST | /projects/:name/files/mkdir |
Utwórz katalog |
| POST | /projects/:name/files/create |
Utwórz plik |
| GET | /projects/:name/files/read |
Odczytaj plik |
| PUT | /projects/:name/files/save |
Zapisz plik |
| DELETE | /projects/:name/files/delete |
Usuń plik |
| POST | /projects/:name/files/clone |
Git clone repozytorium |
GET /projects/:name/files — query: path
GET /projects/:name/files/read — query: path, raw
Skille (18 endpointów)
Skille projektu
| Metoda | Ścieżka | Opis |
|---|---|---|
| GET | /projects/:name/skills |
Lista skilli projektu |
| POST | /projects/:name/skills |
Utwórz skill |
| PUT | /projects/:name/skills/:id |
Zaktualizuj skill |
| DELETE | /projects/:name/skills/:id |
Usuń skill |
Globalny marketplace
| Metoda | Ścieżka | Opis |
|---|---|---|
| GET | /skills |
Lista globalnych skilli |
| POST | /skills |
Opublikuj skill |
| GET | /skills/:id |
Szczegóły skilla |
| PUT | /skills/:id |
Zaktualizuj skill |
| DELETE | /skills/:id |
Usuń skill |
Ewolucja i aktualizacje
| Metoda | Ścieżka | Opis |
|---|---|---|
| GET | /skills/:id/evolution |
Historia ewolucji skilla |
| GET | /skill-updates |
Lista dostępnych aktualizacji |
| POST | /skill-updates/:id/approve |
Zatwierdź aktualizację |
| POST | /skill-updates/:id/reject |
Odrzuć aktualizację |
Forki skilli
| Metoda | Ścieżka | Opis |
|---|---|---|
| GET | /projects/:name/skill-forks |
Lista forków |
| POST | /projects/:name/skill-forks |
Utwórz fork |
| PUT | /projects/:name/skill-forks/:id |
Zaktualizuj fork |
| DELETE | /projects/:name/skill-forks/:id |
Usuń fork |
Chat i wiadomości
| Metoda | Ścieżka | Opis |
|---|---|---|
| POST | /projects/:name/chat |
Wyślij wiadomość na chat |
| GET | /projects/:name/chat/history |
Historia chatu |
| POST | /projects/:name/message |
Wyślij wiadomość do workera (Phase 48.6: automatycznie budzi uśpionego workera, ~2-4s cold start; Phase 48.6.1: budzenie działa teraz również w projektach single-mode, nie tylko parallel) |
| GET | /projects/:name/pins |
Lista notatek (pins) |
| POST | /projects/:name/pins |
Utwórz notatkę |
| DELETE | /projects/:name/pins/:id |
Usuń notatkę |
Wiki (4 endpointy)
| Metoda | Ścieżka | Opis |
|---|---|---|
| GET | /projects/:name/wiki/tree |
Drzewo stron wiki |
| GET | /projects/:name/wiki/file |
Odczytaj stronę wiki |
| PUT | /projects/:name/wiki/save |
Zapisz stronę wiki |
| GET | /projects/:name/wiki/download |
Pobierz wiki jako archiwum ZIP |
Analityka (4 endpointy)
| Metoda | Ścieżka | Opis |
|---|---|---|
| GET | /analytics/activity |
Feed aktywności |
| GET | /analytics/sidebar |
Dane dla panelu bocznego |
| GET | /analytics/phases |
Lista faz projektu |
| POST | /analytics/phases |
Zaktualizuj fazy projektu |
Marketplace i Sage (8 endpointów)
| Metoda | Ścieżka | Opis |
|---|---|---|
| GET | /sage/scout/categories |
Kategorie marketplace |
| POST | /sage/scout |
Wyszukaj skille |
| POST | /sage/scout/quick-scan |
Szybkie skanowanie |
| POST | /sage/scout/analyze |
Dogłębna analiza skilla |
| POST | /sage/scout/install |
Zainstaluj skill |
| POST | /sage/analyze |
Analiza Sage |
| GET | /sage/status |
Status serwisu Sage |
| POST | /sage/benchmark |
Uruchom benchmark |
Pamięć i wiedza
| Metoda | Ścieżka | Opis |
|---|---|---|
| POST | /projects/:name/memory/refresh |
Odśwież pamięć neuronową |
| POST | /projects/:name/memory/fetch-artifact |
Pobierz artefakt |
| GET | /projects/:name/learnings |
Lista learnings |
| POST | /projects/:name/learnings |
Dodaj learning |
| GET | /projects/:name/knowledge-graph |
Graf wiedzy projektu |
Dokumentacja (globalna, bez auth)
| Metoda | Ścieżka | Opis |
|---|---|---|
| GET | /docs/tree?lang=<lang> |
Drzewo dokumentacji; lang opcjonalny (en/uk), domyślnie en |
| GET | /docs/file?path=<p>&lang=<lang> |
Odczytaj plik dokumentacji z language fallback |
GET /docs/tree — query: lang (opcjonalny)
- Najpierw szuka
docs/public/<lang>/index.md, fallback nadocs/public/index.md - Response zawiera:
sections,files,served_lang,is_fallback,requested_lang
GET /docs/file — query: path (wymagany), lang (opcjonalny)
- Kolejność rozwiązywania:
docs/public/<lang>/<path>→docs/public/<path>(EN fallback) - Response zawiera:
path,content,size,modified,served_lang,is_fallback,requested_lang - 403 na path traversal, 404 na brakujący plik
- Phase 52.1.3 — dodano parametr
langdla tłumaczenia UK
System
| Metoda | Ścieżka | Opis |
|---|---|---|
| GET | /system/configs |
Pobierz konfiguracje systemowe |
| PUT | /system/configs |
Zaktualizuj konfiguracje systemowe |
Kody błędów
| Kod | Znaczenie |
|---|---|
| 200 | Sukces |
| 201 | Utworzono |
| 400 | Nieprawidłowe żądanie |
| 401 | Nieautoryzowany |
| 403 | Zabronione (multi-tenancy) |
| 404 | Nie znaleziono |
| 409 | Konflikt (duplikat) |
| 429 | Zbyt wiele żądań |
| 500 | Błąd serwera |
GitHub Integration (Phase 49.3)
| Endpoint | Method | Opis |
|---|---|---|
/api/crm/projects/:name/github |
GET | Lista repos GitHub powiązanych z projektem |
/api/crm/projects/:name/github |
POST | Powiąż repo (body: {owner, repo}) — zwraca webhook URL + secret + instrukcje konfiguracji |
/api/crm/projects/:name/github/:id |
DELETE | Odepnij repo |
/api/crm/projects/:name/github/events |
GET | Lista ostatnich zdarzeń GitHub (Phase 49.3.1, query: ?limit=50) |
/api/webhooks/github |
POST | Publiczny odbiornik webhook (walidowany HMAC-SHA256, rate-limit 100/min) |
Obsługiwane zdarzenia: push, pull_request, workflow_run, issues. Powiadomienia kierowane do Telegram właściciela projektu.
Account Security (Phase 45.4)
| Endpoint | Method | Opis |
|---|---|---|
/api/crm/account/recovery |
GET | Lista aktywnych kluczy odzyskiwania |
/api/crm/account/recovery |
POST | Utwórz klucz odzyskiwania (body: encryptedKey, keyHint) |
/api/crm/account/recovery |
DELETE | Unieważnij klucz(-e) odzyskiwania (body: { id } lub {} dla wszystkich) |
/api/crm/account/recovery/restore |
GET | Pobierz zaszyfrowany klucz główny do odtworzenia |
Bezpieczeństwo
- Multi-tenancy: każdy endpoint
:namesprawdza własność przezchatIdz JWT - Walidacja nazwy projektu:
^[a-zA-Z0-9][a-zA-Z0-9_-]*$(max 64 znaki) - Ochrona przed path traversal:
safePath()na wszystkich ścieżkach kontrolowanych przez użytkownika - Przesyłanie plików: max 100MB, zablokowane rozszerzenia (
.exe,.bat,.sh) - CORS: allowlist origins przez
CRM_ALLOWED_ORIGINS - Ochrona SSRF: allowlist na
handleScoutAnalyze— tylko HTTPS + dozwolone hosty - Endpointy wewnętrzne: odrzucają żądania z nagłówkami proxy (
X-Forwarded-For,X-Real-IP) - Szyfrowanie at-rest (Phase 45): klucze API i wiadomości czatu zaszyfrowane AES-256-GCM
- Nagłówki bezpieczeństwa:
Content-Security-Policy,X-Frame-Options: DENY,X-Content-Type-Options: nosniff - Sanityzacja PII: emaile, klucze API, JWT są automatycznie redagowane z logów JSONL
Phase 53.13 — type-safety baseline (2026-05-10)
Brak zmiany zachowania endpointów — tylko typy wewnętrzne. tsc --noEmit blokuje teraz push/CI:
- Interfejs
ChildBotskonsolidowany wshared/routes/_utils.ts(3× duplikaty połączone).bot_username,heartbeat_file,health_endpoint,statusstały się opcjonalne — odzwierciedlają runtime-state (wpisy workspace wzbogacone przez DB często ich nie mają). requireAdmin()wshared/routes/system.tszwraca terazResponse | { userId }zamiast{ ok, ... }— prostsze zawężanie przezinstanceof Response. Zewnętrzne zachowanie (kody 401/403, treści odpowiedzi) niezmienione.workers.tsDEFAULT_WORKERS straciłas const(dla zgodności z mutable callsites); parsowanie body dlatools/focus_dirsteraz ściśle przezArray.isArrayzamiast||-fallback.
Phase 53.15 — Sentinel Sprint 1 (2026-05-10)
Zmiany zachowania endpointów auth + admin (Sentinel audit P0 fixes):
POST /api/auth/login— gdyrequires2fa=true, odpowiedź to teraz{requires2fa: true, challenge_token}zamiast{requires2fa: true, userId}. Frontend musi przekazywaćchallenge_tokenw kolejnym kroku.POST /api/auth/2fa/login— kształt body:{challenge_token, code}zamiast{userId, code}. Token jednorazowy, TTL 5 min. Bez prawidłowego tokenu endpoint zwraca401 "Invalid or expired challenge — restart login". Rate-limit per userId 5 prób / 15 min → 429.POST /api/crm/skills+PUT /api/crm/skills/:id+DELETE /api/crm/skills/:id+POST /api/crm/skill-updates/:id/approve+POST /api/crm/skill-updates/:id/reject— tylko admin. Nie-admin → 403Forbidden — admin only. Bez auth → 401.- Rate-limit Nginx na
/api/auth/*— 5 req/min/IP (burst=10 nodelay → 429). Tak samo na/api/webhooks/github(30 req/min/IP, burst=20). - HSTS — nagłówek
Strict-Transport-Security: max-age=31536000; includeSubDomains; preloadwysyłany jest teraz przy każdej odpowiedzi HTTPS. Żądania HTTP → 301 redirect na HTTPS. X-Frame-Options: DENYzamiastSAMEORIGIN.
Phase 53.21 — Sentinel P2 batch 2 (2026-05-12)
POST /api/crm/feedback— teraz wymaga, aby wywołujący miał dostęp dobody.project(sprawdzenie canAccessProject). Nie-właściciel projektu → 403"Project not accessible". Puste/brakująceprojectjest nadal dozwolone (globalny feedback).POST /api/internal/trial/consume— zmieniony kształt body:{project, owner_id, tokens}zamiast{project, tokens}.owner_idwymagany, weryfikowany względemprojects.owner_idw DB. 404 na nieznany projekt, 403 na niezgodność właściciela. Wywołujący (child-bot/claude-runner.ts) propagujeARC_TRIAL_OWNERenv wstrzyknięty przezworker-spawn.ts.
Phase 53.18 — tmux secret-leak fix (2026-05-11)
Brak zmiany zachowania endpointów — tylko refactor wewnętrznych ścieżek spawn.
POST /api/crm/onboarding/setup(przezshared/routes/onboarding.ts:startWorkspaceBot) — sposób uruchamiania workspace-mode child-bota zmieniony zbash -c "export X='val'; bun run bot.ts"natmux -e VAR=val ... bun run bot.ts. Wartości tokenów nie trafiają już do/proc/PID/cmdline. Zewnętrznie: 0 zmian (treść odpowiedzi, kody statusu, zachowanie identyczne).
Phase 53.16 — Sentinel Sprint 2 (2026-05-10)
Zmiany zachowania endpointów po hardeningu 13 × P1:
- OAuth callback — Redirect URL używa teraz fragmentu
#token=zamiast query?token=(Sentinel P1-8). Frontend czyta zwindow.location.hash(z fallback na?token=przez jeden cykl deploy). /api/crm/analytics/activity+/api/crm/analytics/sidebar— query teraz jest ograniczone doowner_idzalogowanego użytkownika. Nie-admin widzi tylko swoje projekty. Wcześniej wyciekały pierwsze 80 znaków każdej wiadomości asystenta + nazwy projektów + IDs workerów wszystkich tenantów (Sentinel P1-4).PUT /api/crm/projects/:name/files/save— dodano sprawdzenieisProtectedPath()..env/CLAUDE.md/.git/*/.claude/*zwracają teraz 403"Protected path"(wcześniej można było nadpisać) (Sentinel P1-3).POST /api/crm/projects/:name/files/mkdir+/files/create— body.name zawierające..,.,/,\→ 400. Ponowne wykonaniesafePath()pojoin()(Sentinel P1-2)./ws/local-bridge— chatId z JWT zachowywane przy upgrade. Wiadomość init zproject_namenienależącym do użytkownika → close 1008Forbidden — project not accessible. Wcześniej dowolny użytkownik mógł zainicjować bridge na cudzy projekt (Sentinel P1-5).- CSP — frontend HTML (przez docker/nginx.conf) wysyła teraz ścisły CSP:
default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; connect-src 'self' https://arc-os.co wss://arc-os.co; frame-ancestors 'none'; base-uri 'self'; form-action 'self'. API JSON CSP stracił'unsafe-inline'(Sentinel P1-10). - Wewnętrzny helper
extractChatId— teraz weryfikuje podpis przez verifyToken przed dekodowaniem (Sentinel P1-6, defense-in-depth dla przyszłych tras skipAuth). - Zaszyfrowany format klucza odzyskiwania — nowe klucze przechowywane jako
v2:<base64-salt>:<payload>(losowy 16-bajtowy salt per klucz). Stare (bez prefiksuv2:) działają przez legacy fallback (Sentinel P1-13). - CEO_CHAT_ID — teraz env-first (z ostrzeżeniem fallback na bot_registry). Hardcoded 474903718 usunięty z 6 plików (Sentinel P1-14).
- Nginx X-Forwarded-For — nadpisywanie zamiast dołączania we wszystkich 17 callsites (Sentinel P1-11). Helper
clientIpczyta OSTATNI segment XFF (Sentinel P1-7).
Phase 55 — Cosmic Editorial login (2026-05-13)
Nowe endpointy dla logowania magic-link:
POST /api/auth/magic-link/request— body{ email }. Generuje jednorazowy token ważny 10 minut wephemeral_tokens(typmagic_link), wysyła linkhttps://<host>/?magic_token=<token>przez dostawcę email. Anti-enumeration: zawsze 200 OK z treścią{ ok: true, message: "If the account exists, a magic link has been sent" }(nawet jeśli email nie istnieje). Rate-limit: 3/min per (IP+email) + 5/10min per email — ten sam kontrakt coforgot-password. Nieudana ścieżka przechodzi timing pad.POST /api/auth/magic-link/verify— body{ token }. Konsumuje jednorazowy token, zwraca{ ok: true, token: <jwt>, userId }przy sukcesie lub 401"Invalid or expired magic link". Efekt uboczny:user.email_verified = true+ aktualizacjalast_login(dowód odbioru w skrzynce = weryfikacja).
Union EphemeralTokenType rozszerzony: zawiera teraz "magic_link" obok istniejących oauth_state / password_reset / email_verification / tfa_challenge.
Frontend (CosmicCard.jsx) obsługuje stan magic (60-sekundowy countdown do ponownego wysłania) oraz parametr URL ?magic_token= (auto-consume → login → animacja sukcesu).
Phase 56 — AI Interop / Project Context Export (2026-05-13)
Eksport dostępny tylko dla właściciela — sanitizowany snapshot projektu jako .md do przekazania zewnętrznemu AI (Gemini / ChatGPT / Perplexity / Claude.ai).
GET /api/crm/projects/:name/context-export— parametry:include=section1,section2,...(sekcje:identity / workers / architecture / issues / activity / commits / learnings; domyślnie wszystkie 7),scanOnly=true|false,activityHours=N(1-720, domyślnie 168),commitLimit=N(1-200, domyślnie 20),issueStatus=open|closed|all. Tylko właściciel — rola admin NIE omija (zgodnie z projektem). Bypass CEO działa. Zwraca{ project, exportedAt, filename: "<project>-context-YYYY-MM-DD.md", scanOnly, sections, markdown, findings, stats, alertFired, preferences }. Auto-redaguje krytyczne findings chyba żepreferences.auto_redact_critical = false. Wywołania nie-scanOnlyzapisują doexport_audit_log.GET /api/crm/projects/:name/exports— lista audytu (tylko właściciel). Parametry:limit=N(1-200, domyślnie 50). Zwraca{ project, exports: [{ id, owner_id, exported_at, sections[], findings_critical/high/medium/low, bytes }] }.GET /api/crm/projects/:name/settings/export— odczyt preferencji (tylko właściciel). Zwraca{ project_name, always_include_emails, auto_redact_critical, notify_on_export, updated_at }.PATCH /api/crm/projects/:name/settings/export— aktualizacja preferencji (tylko właściciel). Body przyjmuje dowolny podzbiór{ always_include_emails, auto_redact_critical, notify_on_export }(wartości boolowskie). Zwraca zaktualizowane preferencje.GET /api/crm/analytics/exports— zagregowane statystyki (wymagana autoryzacja, brak blokady właściciela — karta analityczna). Parametr:hours=N(1-720, domyślnie 168). Zwraca{ total, byProject: [{ project_name, n, last }], severitySums: { critical, high, medium, low } }.
Alert: gdy właściciel przekroczy 3 eksporty w ciągu 24h AND prefs.notify_on_export = true (domyślnie OFF) — logActivity("export_alert", ...) przechodzi przez istniejący pipeline powiadomień TG Phase 53.10 (alertFired: true w treści odpowiedzi).
Multi-tier scanner (shared/secret-scanner.ts) — Tier 1 regex (PATTERN_REGISTRY z sanitizerem PII), Tier 2 entropia Shannona ≥4,5 bitów/znak na ciągach ≥20 znaków, Tier 3 heurystyki kontekstowe (key=/token:/secret=/password=). Allowlist: UUID / git SHA / SHA-256 / powtarzające się znaki / krótki hex / base58 o niskiej entropii. Poziomy ważności (critical/high/medium/low). Wydajność: <500 ms / 1 MB.
Migracja DB 024 — tabele export_audit_log + export_preferences.
Phase 57 — Platform Settings (Sentinel #103 follow-up, 2026-05-15)
Zarządzanie sekretami super-admina przez UI CRM zamiast ssh/edycji-.env/wklejania-na-chacie. Backend MVP (Stage 1 z 4 stages). Wszystkie endpointy bramkowane przez requireAdmin (Phase 53.15) — zwracają 403 Forbidden — admin only dla nie-admina, 401 Unauthorized bez JWT.
GET /api/crm/platform/settings— zwraca{ items: [{ name, label, description, testable, restartTargets[], set, preview, length, lastRotated, lastRotatedBy }] }. Allowlist 9 kluczy (ANTHROPIC_API_KEY,PLATFORM_ANTHROPIC_KEY,GITHUB_CLIENT_ID/SECRET,GOOGLE_CLIENT_ID/SECRET,MASTER_BOT_TOKEN,CITADEL_BOT_TOKEN,RESEND_API_KEY). Redacted preview:prefix(12)…suffix(4)+ długość. Pełna wartość nigdy nie opuszcza serwera.PUT /api/crm/platform/settings/:name— body{ value: string ≥ 8 chars }. Atomowo zapisuje do vault przezstoreSecret(name, value)+ wiersz audytu. 400 jeśli name nie jest na allowliście; 400 jeśli value < 8 znaków; 500 przy błędzie zapisu vault.POST /api/crm/platform/settings/:name/test— weryfikacja względem SaaS API. Anthropic →GET /v1/modelszx-api-key; TG →getMe; Resend →/api-keys. Samodzielne sekrety klientów OAuth nie są testowalne → 501. Zwraca{ ok: bool, reason?: string, detail?: string }. Timeout 8 sekund przezAbortController.POST /api/crm/platform/settings/:name/restart—Bun.spawn(["nohup", "bash", "-c", "sleep 1 && tmux kill-session ... && bash start-*.sh"], { detach: true })na powiązanych sesjach tmux. Detached aby restart mastera nie zabił odpowiedzi w locie. Zwraca{ ok: true, restarted: [sessions], note }.GET /api/crm/platform/audit?limit=50&key=ANTHROPIC_API_KEY— ostatnie wpisy logu audytu od najnowszego (limit max 500). Opcjonalny filtr po kluczu.
Lista twardych wykluczeń NEVER_EXPOSE: CRM_SECRET (podpisywanie JWT) + SECRET_ENCRYPTION_KEY (meta-klucz vault) — nawet żądanie admina z prawidłowym tokenem zwraca 400 "not managed". Log audytu tylko do dołączania (brak handlera UPDATE/DELETE), każda akcja (wliczając nieudane) zapisuje wiersz z IP + UA + email.
Migracja DB 026 — tabela platform_audit_log. Stage 2 (frontend PlatformSettings.jsx) — dostarczony 2026-05-15 (cbc8bac): siatka kart tylko dla adminów + modal rotacji (<input type="password"> + potwierdzenie przez powtórzenie) + szuflada audytu; wpis w sidebarze filtrowany przez userRole === "admin" pobierany z /api/auth/me.
Polish (2026-05-15, commit 56191b0) — Restrukturyzacja UI Platform Settings. Elementy odpowiedzi GET /api/crm/platform/settings zyskują 5 nowych pól: category (anthropic|oauth|telegram|email), usedIn (string[] — pliki/przepływy korzystające z klucza), getFromUrl (skąd pobrać nową wartość), effectAfterRotate, riskIfLeaked. Używane przez frontend do renderowania 4 pogrupowanych sekcji kart + zwijany panel pomocy per karta ze strukturalnym kontekstem (Used in / Get from / Effect / Risk). Brak zmian behawioralnych endpointów mutujących (PUT/POST/restart/test).
Refactor (2026-05-16) — wewnętrzne porządki shared/routes/platform.ts. Usunięto 39 linii (dodano 16), brak zmian w publicznym API. Sygnatury i odpowiedzi endpointów PUT/POST/restart/test/audit niezmienione. Udokumentowane tutaj tylko dlatego, że pre-push gate doc-coverage wyzwala na każdym diffie shared/routes/*.ts.
Backdated activity (#117, 2026-05-16) — POST /api/mcp/issues/:project/:id/log akceptuje teraz opcjonalne pole ts (ciąg ISO-8601). Używane przez arc retro do rekonstrukcji historycznych wpisów z ich oryginalnymi znacznikami czasu. Wartości z przyszłości są po cichu obcinane do teraz wewnątrz addActivity() (ochrona przed grubymi palcami). Nieprawidłowy ISO → 400.
Stage 3 (2026-05-15) — hot-reload sekretów OAuth + Resend bez restartu. shared/auth.ts loadOAuthConfig() teraz czyta getSecret("GITHUB_CLIENT_ID/SECRET" | "GOOGLE_CLIENT_ID/SECRET") per wywołanie zamiast process.env. Callsites w master-bot/routes/auth.ts już wywoływały getOAuthConfig() per żądanie → 0 zmian callsites. RESEND_API_KEY już hot-reload przez shared/email.ts:47. Zmiana behawioralna: PUT /api/crm/platform/settings/{GITHUB_CLIENT_ID|GITHUB_CLIENT_SECRET|GOOGLE_CLIENT_ID|GOOGLE_CLIENT_SECRET|RESEND_API_KEY} wchodzi teraz w życie od następnego żądania, nie wymaga restartu. restartTargets dla tych 5 kluczy jest pusty → przycisk Restart w UI jest ukryty. Edge case: flow OAuth z state-tokenem wydanym przed rotacją może otrzymać 400 na callbacku przy wymianie kodu — ponowna próba użytkownika rozwiązuje problem. ANTHROPIC_API_KEY, PLATFORM_ANTHROPIC_KEY, MASTER_BOT_TOKEN, CITADEL_BOT_TOKEN pozostają wymagające restartu (czytane przy spawn child-bota / inicjalizacji TG long-poll).
Phase 57.3.5 cleanup (2026-05-16) — allowlist MANAGED_KEYS skrócony z 9 do 6. Usunięto: ANTHROPIC_API_KEY (operatorzy używają teraz jednego PLATFORM_ANTHROPIC_KEY zarówno dla trial-credits jak i platform inference; fallback .env nadal działa dla starszych ścieżek kodu dopóki Sage/Karpathy nie zmigrują), CITADEL_BOT_TOKEN (bot per-projekt należy do wpisów vault child:<name>:token, zarządzanych przez flow onboardingu workera — nie Platform Settings). MASTER_BOT_TOKEN zmienił przeznaczenie: label → "Telegram — System Monitor Bot", description → "Server health alerts + on-demand status probes (admin-only, not a chat bot)". Phase 58 doda pętlę monitorowania (alerty push dla crashu workera / dysku / RAM / brute-force SSH / bypass CF + polecenia /status, /health, /errors, /restart). Finalny zestaw: PLATFORM_ANTHROPIC_KEY + GITHUB×2 + GOOGLE×2 + MASTER_BOT_TOKEN + RESEND_API_KEY (refs #103).
Phase 63 — Konsolidacja UI/UX + Śledzenie zużycia tokenów (2026-05-21, #148)
Nowy endpoint:
POST /api/internal/usage/log(tylko loopback) — zapisuje wiersz dotoken_usage_log. Body:{ project_name, owner_id, worker_id?, input_tokens, output_tokens, cache_tokens, total_tokens }. Wywoływany zchild-bot/bot.tsjako fire-and-forget po każdym wywołaniu Claude (callClaudeOnce+callWorkertext path). Nie wymaga nagłówka auth —/api/internal/*dostępny tylko z localhost i blokowany przez nginx dla zewnętrznych żądań.GET /api/crm/account/usage— historia zużycia tokenów dla autoryzowanego użytkownika (opisana w tabeli Onboarding powyżej).
Zmiany w claude-runner.ts:
callClaudeOnce+callWorkertext path: teraz zawsze--output-format json(wcześniejtextdla non-trial). Parse JSON wyodrębniaresultjako tekst wyjściowy iusagedo logowania. Przepływ trial consume bez zmian.- Nowy dep
logUsage?wClaudeRunnerDeps— callback(workerId, { input, output, cache }) => void.
Zmiany UI (nie API):
UserDropdown: komponentUsageCardz całkowitą liczbą tokenów + „Details →" przy otwieraniu; kropka ostrzeżenia na awatarze gdy saldo trial < 20%.BillingPage: sekcja Token Usage z paskiem sumy + tabela 50 wierszy. Plan Enterprise (w opracowaniu). Toggledetailsna każdej karcie.OnboardingProgressPill: przeprojektowany jako inline dropdown w nagłówku (nie ma już wizard modal).WorkerSelector: semantyczne zmienne CSS--worker-{role}zamiast tokenów Tailwind chart.