CRM API — Endpunkt-Referenz
Arc OS — The Orchestration System für KI-Teams
Allgemeine Informationen
| Parameter | Wert |
|---|---|
| Base URL | https://arc-os.co/api/crm |
| Autorisierung | Authorization: Bearer <JWT> oder ?token=<JWT> (für SSE/WebSocket) |
| Content-Type | application/json |
| JWT-Algorithmus | HMAC-SHA256 |
| JWT TTL | 24 Stunden |
Authentifizierung
Alle Endpunkte (außer /docs/*) erfordern einen JWT-Token im Header Authorization: Bearer <token>.
Für SSE- und WebSocket-Verbindungen wird der Token über den Query-Parameter ?token=<JWT> übermittelt.
Autorisierungsfehler
| Code | Beschreibung |
|---|---|
| 401 | Fehlender oder ungültiger Token |
| 403 | Kein Zugriff auf das Projekt (Multi-Tenancy) |
Endpunkte nach Kategorien
Konto und Einstellungen
| Methode | Pfad | Beschreibung |
|---|---|---|
| GET | /account/settings |
Kontoeinstellungen abrufen |
| PUT | /account/settings |
Kontoeinstellungen aktualisieren |
| GET | /account/usage |
Token-Usage-Verlauf für den autorisierten Benutzer (Phase 63, #148). Response: { rows: [ { project_name, worker_id, input_tokens, output_tokens, cache_tokens, total_tokens, created_at } × bis zu 200 ], totals: { total, input, output } }. Liest token_usage_log nach owner_id. Angezeigt in UserDropdown (UsageCard) und BillingPage (Token-Usage-Sektion). |
Onboarding + Trial Credits (Phase 50.1)
| Methode | Pfad | Beschreibung |
|---|---|---|
| POST | /onboarding/setup |
Erstes Projekt erstellen. Body multipart: config (JSON) + files. Feld anthropicKey ist jetzt optional — wenn leer + User hat email_verified + hat noch keine Testphase erhalten, wird das Projekt im trial_mode=1 mit 100K Free Tokens erstellt. Response: { ok, project, trial_activated }. Phase 51: Gibt 402 mit {error:"plan_limit_reached", reason, current, limit, plan} zurück, wenn der User das Projekt-Limit seines Plans überschritten hat. |
| GET | /account/trial-status |
Testphasen-Status für das UI-Banner. Response: { email, email_verified, trial_granted, has_trial_active, total_remaining, total_granted, projects: [...] } |
Onboarding Checklist (Phase 54.1, Issue #56)
Post-Wizard-5-Schritt-Engagement-Checkliste. Jeder Schritt (workers, cli, skill, bot, issue) akzeptiert den Status completed oder skipped. Mutationen sind idempotent: Ein erneuter identischer POST gibt denselben State zurück, ohne einen Duplikateintrag in activity_log zu schreiben. Replay setzt den State nicht zurück, entfernt nur dismissed_at — das UI zeigt das Panel erneut mit demselben Fortschritt.
| Methode | Pfad | Beschreibung |
|---|---|---|
| GET | /onboarding/progress |
Aktueller State für den autorisierten User. Response: { steps:["workers","cli","skill","bot","issue"], state:{<step>:<status>}, completed_count, total_steps:5, completed_at, dismissed_at, source, started_at, updated_at }. Unberührter User → Nullwerte/null ohne Zeilenerstellung. |
| POST | /onboarding/event |
Schritt-Transition aufzeichnen. Body: { step: "workers"|"cli"|"skill"|"bot"|"issue", status: "completed"|"skipped", source?: "web"|"cli" }. Whitelist-Validierung → 400 bei unbekanntem step/status. Response: dieselbe Form wie GET. Emittiert onboarding_step_completed/onboarding_step_skipped im activity_log nur bei changed; bei Transition auf 5/5 wird zusätzlich onboarding_completed mit duration_ms emittiert. |
| POST | /onboarding/dismiss |
Panel schließen (dismissed_at = now). Idempotent. Emittiert onboarding_dismissed beim ersten Aufruf mit Payload {completed_count}. |
| POST | /onboarding/replay |
Geschlossenes Panel wieder öffnen (dismissed_at = NULL). Schritt-State bleibt unberührt. Emittiert onboarding_replayed beim Clear-Event. |
| POST | /projects/:name/active-issue |
Issue #115. Aktuelle Web-Sitzung an ein Issue binden. Body: { issue_id: number, title?: string }. Schreibt activity_log-Event session_active_issue (source=web). |
| GET | /projects/:name/active-issue |
Issue #115. Zuletzt gebundenes Issue dieses Owners innerhalb von 7 Tagen. Response: { active_issue_id, title, ts }. |
| GET | /onboarding/cli-status |
Phase 54.3 (Issue #58). Hat sich der User in den letzten 30 Tagen über arc login eingeloggt? Response: { installed: boolean, last_cli_at: string|null }. SSOT — Zeilen in activity_log mit event_type='cli_invocation' und actor=chatId. Das Frontend-Onboarding-Checklisten-UI pollt diesen Endpunkt alle 10s, solange der CLI-Schritt aussteht; wenn installed=true — wird der cli-Schritt automatisch als abgeschlossen markiert. |
| GET | /analytics/onboarding-funnel |
Phase 54.6 (Issue #61). Aggregierte Funnel-Statistiken über ein Rolling Window. Query: hours=168 (1-720, Standard 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 — activity_log-Events onboarding_step_* + onboarding_completed + cli_invocation. TTFC = Time-to-First-arc (julianday-Delta vom ersten Onboarding-Schritt bis zur ersten cli_invocation pro Actor). |
SSOT für Funnel-Metriken (Phase 54.6 / Issue #61) — Events in activity_log (event_type LIKE 'onboarding_%'). Tabelle onboarding_progress — Derived Cache: UI rendert mit einer Abfrage statt durch Event-Aggregation.
Beta Feedback (Phase 53.3)
| Methode | Pfad | Beschreibung |
|---|---|---|
| POST | /feedback |
Beta-Feedback senden. Body: {type: "bug"|"feature"|"other", title, description, project?, browser?}. Schreibt in activity_log (event_type=feedback_report) und pingt den CEO auf Telegram. |
| GET | /admin/feedback |
Liste der letzten Einsendungen (nur Admin). Query: limit=50 (max 500). Response: {items: [...], count}. |
POST /feedback — Body-Validierung: type ∈ {bug,feature,other}, title ≤200 Zeichen, description ≤5000 Zeichen. Erfolg → {ok: true, type, title}. Telegram-Ping wird formatiert als 🐞/💡/📝 New <type> feedback ... From: <user> Title: <title> + erste 400 Zeichen der Beschreibung.
Das Floating-Widget in
FeedbackWidget.jsx(CRM Dashboard) überträgt automatischbrowser(UA + Viewport + Locale) undproject(technical_name des aktiven Projekts).
Beta Invites (Phase 52.1, nur Admin)
| Methode | Pfad | Beschreibung |
|---|---|---|
| GET | /admin/invites |
Liste aller Einladungscodes + Zählungen (total_active, total_used). Nur Admin. |
| POST | /admin/invites |
N Codes generieren. Body: {count: N, note?: string}. Nur Admin. Response: {ok, codes, count}. |
| DELETE | /admin/invites/:code |
Ungenutzten Einladungscode widerrufen. |
Auth-Flow-Update: POST /api/auth/register erfordert nun das Feld invite_code (Phase 52.1 Closed Beta). Ohne Code → 403 {error: "invite_required"}. Ungültiger/bereits verwendeter Code → 403 {error: "invalid_invite"}.
Billing (Phase 51)
| Methode | Pfad | Beschreibung |
|---|---|---|
| GET | /billing/status |
Aktueller Plan, Limits, Usage, Features. Erstellt automatisch eine Free-Zeile beim ersten Aufruf. Response: { plan, status, current_period_end, limits, usage, features, pricing, can_upgrade, stripe_ready } |
| POST | /billing/checkout-session |
Stripe Checkout (Stage 2 — gibt 501 zurück, solange Stripe SDK nicht integriert ist) |
Plan-Limits (OR-Semantik):
- Free: 1 Projekt UND 5 Worker
- Min ($4.99/mo): 5 Projekte ODER 25 Worker gesamt
- Max ($11.99/mo): 20 Projekte ODER 150 Worker gesamt
402-Response bei POST /onboarding/setup oder POST /projects/:name/workers, wenn das Limit überschritten wird: { error: "plan_limit_reached", reason: "projects_limit"|"workers_limit", current, limit, plan, message }
Admin-User (
role=admin) umgehen die Plan-Limit-Prüfung vollständig — sie sind Operatoren, keine zahlenden Mandanten.
Beta-Tester (
subscriptions.plan='beta', Phase 52 F&F) umgehen das ebenfalls — unbegrenzte Anzahl an Projekten/Worker plus alle Max-Features. Wird manuell zugewiesen:UPDATE subscriptions SET plan='beta' WHERE user_id=?.
Bugfix (Issue #25):
POST /projects/create(Quick Start, Phase 50.2) schlug zuvor mitownerChatId is not definedwegen eines Tippfehlers fehl — behoben, der Audit-Actor wird jetzt korrekt eingetragen.
Bugfix (Issue #26):
allocatePort()für neue Projekte prüft jetzt echte TCP-Bindings (ss -tln) statt nur die Registry. Zuvor konnte ein Port zurückgegeben werden, der von einem Nicht-Registry-Dienst belegt war (NotebookLM Bridge :19213, interne Bridges) → Workspace-Bot schlug mit EADDRINUSE fehl.
Auth-Flow (Phase 50.1): /api/auth/register und /api/auth/login geben nun JWT auch für unverified E-Mails zurück + Flag needs_verification: true. Sensitive Aktionen (Trial Grant, Billing, Invites) prüfen email_verified separat. Rate-Limit bei Registrierung: 3 / IP / 24h.
Projekte (9 Endpunkte)
| Methode | Pfad | Beschreibung |
|---|---|---|
| GET | /projects |
Liste der Projekte des Users |
| POST | /projects/create |
Projekt erstellen |
| GET | /projects/:name |
Projektdetails |
| GET | /projects/:name/config |
Projektkonfiguration |
| PUT | /projects/:name/config |
Konfiguration aktualisieren |
| GET | /projects/:name/protocol |
Projektprotokoll |
| PUT | /projects/:name/protocol |
Protokoll aktualisieren |
| GET | /projects/:name/logs |
Projekt-Logs |
| GET | /projects/:name/metrics |
Projektmetriken |
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
Worker (11 Endpunkte)
| Methode | Pfad | Beschreibung |
|---|---|---|
| GET | /projects/:name/workers |
Liste der Worker |
| POST | /projects/:name/workers |
Worker erstellen |
| POST | /projects/:name/workers/reorder |
Phase 53.8 — Worker-Reihenfolge ändern. Body: {order: [id1, id2, ...]}. Überschreibt workers_registry.json atomar. Worker, die nicht in order enthalten sind, werden ans Ende angehängt (Schutz vor Datenverlust). Response: {ok, count, order}. |
| PUT | /projects/:name/workers/:id |
Worker aktualisieren |
| DELETE | /projects/:name/workers/:id |
Worker löschen |
| POST | /projects/:name/workers/generate-prompt |
System-Prompt generieren |
| GET | /projects/:name/workers/:id/telegram-token |
Telegram-Token abrufen |
| POST | /projects/:name/workers/:id/telegram-token |
Phase 53.4 — Validiert den Token über Telegram getMe, speichert bot_username im Vault, verweigert die Anfrage, wenn derselbe Bot bereits an einen anderen Worker gebunden ist (409). Response: {ok, started, bot_username}. |
| DELETE | /projects/:name/workers/:id/telegram-token |
Telegram-Token löschen |
| POST | /projects/:name/workers/:id/notify |
Phase 53.2 — TG-Event-Ping senden ({event?, text, buttons?}). Stiller No-Op, wenn kein Token gebunden oder CRM_DISABLE_TG_NOTIFY=1. |
| POST | /projects/:name/workers/:id/suggest-bot-username |
53.11.1 (Issue #48) — Gibt 5 Kandidaten für den TG-Username im Bot-Creation-Wizard zurück, im Format <project>_<worker>_bot + nummerierte Fallbacks. Slugify entfernt Bindestriche, Truncate auf 32 Zeichen (Worker-Teil wird zuerst gekürzt). Response: {candidates: string[]}. |
| POST | /metrics/wizard |
53.11.1 (Issue #48) — Telemetrie-Sink für den Bot-Creation-Wizard. Body: {action, duration_ms?, attempts?, success?, project?, worker_id?}. Schreibt in activity_log (event_type=wizard_metric), Best-Effort. |
| GET | /analytics/wizard-metrics?hours=168 |
53.11.1 (Issue #48) — Funnel-Zusammenfassung: {starts, completions, abandons, success_rate, avg_duration_ms_completed, avg_attempts_completed, by_action}. Standard 7 Tage, Clamp 1-720h. |
| POST | /projects/:name/restart |
Worker neu starten |
| GET | /projects/:name/active-role |
Aktuell aktive Rolle |
| POST | /projects/:name/active-role |
Aktive Rolle ändern |
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_turnsstandardmäßig20(zuvor5, was den Fehler "Reached max turns" bei mehrstufigen Dialogen mit Tool Calls verursachte).
POST /projects/:name/restart — Query: worker_id
Dateien und Speicher (8 Endpunkte)
| Methode | Pfad | Beschreibung |
|---|---|---|
| GET | /projects/:name/files |
Dateibaum |
| POST | /projects/:name/files/upload |
Datei hochladen (multipart, max 100MB) |
| POST | /projects/:name/files/mkdir |
Verzeichnis erstellen |
| POST | /projects/:name/files/create |
Datei erstellen |
| GET | /projects/:name/files/read |
Datei lesen |
| PUT | /projects/:name/files/save |
Datei speichern |
| DELETE | /projects/:name/files/delete |
Datei löschen |
| POST | /projects/:name/files/clone |
Git-Repository klonen |
GET /projects/:name/files — Query: path
GET /projects/:name/files/read — Query: path, raw
Skills (18 Endpunkte)
Projekt-Skills
| Methode | Pfad | Beschreibung |
|---|---|---|
| GET | /projects/:name/skills |
Liste der Projekt-Skills |
| POST | /projects/:name/skills |
Skill erstellen |
| PUT | /projects/:name/skills/:id |
Skill aktualisieren |
| DELETE | /projects/:name/skills/:id |
Skill löschen |
Globaler Marketplace
| Methode | Pfad | Beschreibung |
|---|---|---|
| GET | /skills |
Liste der globalen Skills |
| POST | /skills |
Skill veröffentlichen |
| GET | /skills/:id |
Skill-Details |
| PUT | /skills/:id |
Skill aktualisieren |
| DELETE | /skills/:id |
Skill löschen |
Evolution und Updates
| Methode | Pfad | Beschreibung |
|---|---|---|
| GET | /skills/:id/evolution |
Evolutionshistorie eines Skills |
| GET | /skill-updates |
Liste verfügbarer Updates |
| POST | /skill-updates/:id/approve |
Update annehmen |
| POST | /skill-updates/:id/reject |
Update ablehnen |
Skill-Forks
| Methode | Pfad | Beschreibung |
|---|---|---|
| GET | /projects/:name/skill-forks |
Liste der Forks |
| POST | /projects/:name/skill-forks |
Fork erstellen |
| PUT | /projects/:name/skill-forks/:id |
Fork aktualisieren |
| DELETE | /projects/:name/skill-forks/:id |
Fork löschen |
Chat und Nachrichten
| Methode | Pfad | Beschreibung |
|---|---|---|
| POST | /projects/:name/chat |
Nachricht in den Chat senden |
| GET | /projects/:name/chat/history |
Chatverlauf |
| POST | /projects/:name/message |
Nachricht an einen Worker senden (Phase 48.6: weckt automatisch einen im Idle getöteten Worker auf, ~2-4s Cold Start; Phase 48.6.1: Wake-Up funktioniert jetzt auch in Single-Mode-Projekten, nicht nur im Parallel-Modus) |
| GET | /projects/:name/pins |
Liste der Notizen (Pins) |
| POST | /projects/:name/pins |
Notiz erstellen |
| DELETE | /projects/:name/pins/:id |
Notiz löschen |
Wiki (4 Endpunkte)
| Methode | Pfad | Beschreibung |
|---|---|---|
| GET | /projects/:name/wiki/tree |
Wiki-Seitenbaum |
| GET | /projects/:name/wiki/file |
Wiki-Seite lesen |
| PUT | /projects/:name/wiki/save |
Wiki-Seite speichern |
| GET | /projects/:name/wiki/download |
Wiki als ZIP-Archiv herunterladen |
Analytik (4 Endpunkte)
| Methode | Pfad | Beschreibung |
|---|---|---|
| GET | /analytics/activity |
Aktivitäts-Feed |
| GET | /analytics/sidebar |
Daten für die Seitenleiste |
| GET | /analytics/phases |
Liste der Projektphasen |
| POST | /analytics/phases |
Projektphasen aktualisieren |
Marketplace und Sage (8 Endpunkte)
| Methode | Pfad | Beschreibung |
|---|---|---|
| GET | /sage/scout/categories |
Marketplace-Kategorien |
| POST | /sage/scout |
Skills suchen |
| POST | /sage/scout/quick-scan |
Schnell-Scan |
| POST | /sage/scout/analyze |
Tiefenanalyse eines Skills |
| POST | /sage/scout/install |
Skill installieren |
| POST | /sage/analyze |
Sage-Analyse |
| GET | /sage/status |
Sage-Dienststatus |
| POST | /sage/benchmark |
Benchmark starten |
Speicher und Knowledge
| Methode | Pfad | Beschreibung |
|---|---|---|
| POST | /projects/:name/memory/refresh |
Neuralen Speicher aktualisieren |
| POST | /projects/:name/memory/fetch-artifact |
Artefakt laden |
| GET | /projects/:name/learnings |
Liste der Learnings |
| POST | /projects/:name/learnings |
Learning hinzufügen |
| GET | /projects/:name/knowledge-graph |
Wissensgraph des Projekts |
Dokumentation (global, ohne Auth)
| Methode | Pfad | Beschreibung |
|---|---|---|
| GET | /docs/tree?lang=<lang> |
Dokumentationsbaum; lang optional (en/uk), Standard en |
| GET | /docs/file?path=<p>&lang=<lang> |
Dokumentationsdatei mit Language-Fallback lesen |
GET /docs/tree — Query: lang (optional)
- Sucht zunächst
docs/public/<lang>/index.md, Fallback aufdocs/public/index.md - Response enthält:
sections,files,served_lang,is_fallback,requested_lang
GET /docs/file — Query: path (erforderlich), lang (optional)
- Auflösungsreihenfolge:
docs/public/<lang>/<path>→docs/public/<path>(EN-Fallback) - Response enthält:
path,content,size,modified,served_lang,is_fallback,requested_lang - 403 bei Path Traversal, 404 bei fehlender Datei
- Phase 52.1.3 —
lang-Parameter für ukrainische Übersetzung hinzugefügt
System
| Methode | Pfad | Beschreibung |
|---|---|---|
| GET | /system/configs |
Systemkonfigurationen abrufen |
| PUT | /system/configs |
Systemkonfigurationen aktualisieren |
Fehlercodes
| Code | Bedeutung |
|---|---|
| 200 | Erfolg |
| 201 | Erstellt |
| 400 | Ungültige Anfrage |
| 401 | Nicht autorisiert |
| 403 | Verboten (Multi-Tenancy) |
| 404 | Nicht gefunden |
| 409 | Konflikt (Duplikat) |
| 429 | Zu viele Anfragen |
| 500 | Serverfehler |
GitHub Integration (Phase 49.3)
| Endpoint | Method | Beschreibung |
|---|---|---|
/api/crm/projects/:name/github |
GET | Liste der mit dem Projekt verknüpften GitHub-Repos |
/api/crm/projects/:name/github |
POST | Repo verknüpfen (Body: {owner, repo}) — gibt Webhook-URL + Secret + Setup-Anweisungen zurück |
/api/crm/projects/:name/github/:id |
DELETE | Repo-Verknüpfung aufheben |
/api/crm/projects/:name/github/events |
GET | Liste der letzten GitHub-Events (Phase 49.3.1, Query: ?limit=50) |
/api/webhooks/github |
POST | Öffentlicher Webhook-Receiver (HMAC-SHA256-validiert, Rate-Limit 100/min) |
Unterstützte Events: push, pull_request, workflow_run, issues. Benachrichtigungen werden an den Telegram-Account des Projekteigentümers weitergeleitet.
Account Security (Phase 45.4)
| Endpoint | Method | Beschreibung |
|---|---|---|
/api/crm/account/recovery |
GET | Liste der aktiven Recovery Keys |
/api/crm/account/recovery |
POST | Recovery Key erstellen (Body: encryptedKey, keyHint) |
/api/crm/account/recovery |
DELETE | Recovery Key(s) widerrufen (Body: { id } oder {} für alle) |
/api/crm/account/recovery/restore |
GET | Verschlüsselten Master Key zur Wiederherstellung abrufen |
Sicherheit
- Multi-Tenancy: Jeder
:name-Endpunkt prüft die Eigentümerschaft überchatIdaus dem JWT - Projektname-Validierung:
^[a-zA-Z0-9][a-zA-Z0-9_-]*$(max. 64 Zeichen) - Path-Traversal-Schutz:
safePath()auf allen user-kontrollierten Pfaden - Datei-Upload: max. 100MB, blockierte Erweiterungen (
.exe,.bat,.sh) - CORS: Origin-Whitelist über
CRM_ALLOWED_ORIGINS - SSRF-Schutz: Allowlist auf
handleScoutAnalyze— nur HTTPS + erlaubte Hosts - Interne Endpunkte: Lehnen Anfragen mit Proxy-Headern ab (
X-Forwarded-For,X-Real-IP) - At-Rest-Verschlüsselung (Phase 45): API-Keys und Chat-Nachrichten werden AES-256-GCM verschlüsselt
- Security-Header:
Content-Security-Policy,X-Frame-Options: DENY,X-Content-Type-Options: nosniff - PII-Sanitization: E-Mails, API-Keys, JWTs werden automatisch aus JSONL-Logs geschwärzt
Phase 53.13 — Type-Safety-Baseline (2026-05-10)
Keine Verhaltensänderung der Endpunkte — nur interne Typen. tsc --noEmit blockiert jetzt Push/CI:
ChildBot-Interface inshared/routes/_utils.tskonsolidiert (3× Duplikate zusammengeführt).bot_username,heartbeat_file,health_endpoint,statusals optional markiert — bilden Runtime-State ab (DB-enriched Workspace-Einträge fehlen diese oft).requireAdmin()inshared/routes/system.tsgibt jetztResponse | { userId }statt{ ok, ... }zurück — einfacheres Narrowing viainstanceof Response. Externes Verhalten (401/403-Codes, Response-Bodies) unverändert.workers.tsDEFAULT_WORKERS verloras const(für Kompatibilität mit mutablen Callsites); Body-Parsing fürtools/focus_dirsjetzt strikt überArray.isArraystatt||-Fallback.
Phase 53.15 — Sentinel Sprint 1 (2026-05-10)
Verhaltensänderungen bei Auth- und Admin-Endpunkten (Sentinel Audit P0-Fixes):
POST /api/auth/login— wennrequires2fa=true, lautet die Response jetzt{requires2fa: true, challenge_token}statt{requires2fa: true, userId}. Das Frontend musschallenge_tokenim nächsten Schritt übergeben.POST /api/auth/2fa/login— Body-Form:{challenge_token, code}statt{userId, code}. Token ist einmalig, 5-min TTL. Ohne gültigen Token gibt der Endpunkt401 "Invalid or expired challenge — restart login"zurück. Per-userId-Rate-Limit: 5 Versuche / 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— nur Admin. Kein Admin → 403Forbidden — admin only. Ohne Auth → 401.- Nginx-Rate-Limit auf
/api/auth/*— 5 req/min/IP (burst=10 nodelay → 429). Dasselbe auf/api/webhooks/github(30 req/min/IP, burst=20). - HSTS — Header
Strict-Transport-Security: max-age=31536000; includeSubDomains; preloadwird nun bei jeder HTTPS-Response gesendet. HTTP-Anfragen → 301-Redirect auf HTTPS. X-Frame-Options: DENYstattSAMEORIGIN.
Phase 53.21 — Sentinel P2 Batch 2 (2026-05-12)
POST /api/crm/feedback— erfordert jetzt, dass der Aufrufer Zugriff auf das angegebenebody.projecthat (canAccessProject-Prüfung). Nicht-Eigentümer des Projekts → 403"Project not accessible". Leeres/fehlendesprojectist weiterhin erlaubt (globales Feedback).POST /api/internal/trial/consume— Body-Form geändert:{project, owner_id, tokens}statt{project, tokens}.owner_idist Pflichtfeld, wird gegenprojects.owner_idin der DB verifiziert. 404 bei unbekanntem Projekt, 403 bei Owner-Mismatch. Der Aufrufer (child-bot/claude-runner.ts) propagiertARC_TRIAL_OWNERenv, das vonworker-spawn.tsinjiziert wird.
Phase 53.18 — tmux Secret-Leak-Fix (2026-05-11)
Keine Verhaltensänderung der Endpunkte — nur Refactoring interner Spawn-Pfade.
POST /api/crm/onboarding/setup(übershared/routes/onboarding.ts:startWorkspaceBot) — Die Methode zum Starten des Workspace-Mode Child-Bots wurde vonbash -c "export X='val'; bun run bot.ts"auftmux -e VAR=val ... bun run bot.tsumgestellt. Token-Werte landen nicht mehr in/proc/PID/cmdline. Extern: 0 Änderungen (Response-Body, Status-Codes, Verhalten identisch).
Phase 53.16 — Sentinel Sprint 2 (2026-05-10)
Verhaltensänderungen der Endpunkte nach Hardening von 13 × P1:
- OAuth-Callback — Redirect-URL verwendet jetzt
#token=Fragment statt?token=Query (Sentinel P1-8). Das Frontend liest auswindow.location.hash(mit Fallback auf?token=für einen Deploy-Zyklus). /api/crm/analytics/activity+/api/crm/analytics/sidebar— Query ist jetzt nachowner_iddes eingeloggten Users eingegrenzt. Nicht-Admins sehen nur ihre eigenen Projekte. Zuvor wurden die ersten 80 Zeichen jeder Assistant-Nachricht + Projektnamen + Worker-IDs aller Mandanten geleakt (Sentinel P1-4).PUT /api/crm/projects/:name/files/save—isProtectedPath()-Prüfung hinzugefügt..env/CLAUDE.md/.git/*/.claude/*geben jetzt 403"Protected path"zurück (zuvor konnten diese überschrieben werden) (Sentinel P1-3).POST /api/crm/projects/:name/files/mkdir+/files/create—body.namemit..,.,/,\→ 400.safePath()wird nachjoin()erneut ausgeführt (Sentinel P1-2)./ws/local-bridge— JWT chatId wird beim Upgrade gespeichert. Init-Nachricht mitproject_name, das nicht dem User gehört → close 1008Forbidden — project not accessible. Zuvor konnte jeder User eine Bridge auf ein fremdes Projekt initiieren (Sentinel P1-5).- CSP — Frontend-HTML (über docker/nginx.conf) sendet jetzt striktes 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 verlor'unsafe-inline'(Sentinel P1-10). extractChatIdinterner Helper — verifiziert jetzt die Token-Signatur vor dem Dekodieren (Sentinel P1-6, Defense-in-Depth für zukünftige skipAuth-Routen).- Recovery Key verschlüsseltes Format — neue Keys werden als
v2:<base64-salt>:<payload>gespeichert (per-Key 16-Byte zufälliger Salt). Alte Keys (ohnev2:-Präfix) funktionieren über Legacy-Fallback (Sentinel P1-13). - CEO_CHAT_ID — jetzt Env-First (mit Warning-Fallback auf bot_registry). Hardcodierter Wert 474903718 aus 6 Dateien entfernt (Sentinel P1-14).
- Nginx X-Forwarded-For — Überschreiben statt Anhängen in allen 17 Callsites (Sentinel P1-11).
clientIp-Helper liest das LETZTE XFF-Segment (Sentinel P1-7).
Phase 55 — Cosmic Editorial Login (2026-05-13)
Neue Endpunkte für Magic-Link-Sign-in:
POST /api/auth/magic-link/request— Body{ email }. Generiert einen 10-min Single-Use-Token inephemeral_tokens(Typmagic_link), sendet Linkhttps://<host>/?magic_token=<token>über den E-Mail-Anbieter. Anti-Enumeration: immer 200 OK mit Body{ ok: true, message: "If the account exists, a magic link has been sent" }(auch wenn die E-Mail nicht existiert). Rate-Limit: 3/min pro (IP+E-Mail) + 5/10min pro E-Mail — derselbe Vertrag wieforgot-password. Fehlerpfad wird mit Timing-Pad versehen.POST /api/auth/magic-link/verify— Body{ token }. Verbraucht Single-Use-Token, gibt bei Erfolg{ ok: true, token: <jwt>, userId }zurück oder 401"Invalid or expired magic link". Side Effect:user.email_verified = true+last_loginwird aktualisiert (Inbox-Proof = Verifikation).
EphemeralTokenType-Union erweitert: enthält nun "magic_link" neben den bestehenden Typen oauth_state / password_reset / email_verification / tfa_challenge.
Das Frontend (CosmicCard.jsx) verarbeitet den magic-State (60-s-Resend-Countdown) und den ?magic_token=-URL-Parameter (Auto-Consume → Login → Erfolgsanimation).
Phase 56 — AI Interop / Project Context Export (2026-05-13)
Nur-für-Eigentümer-Export eines sanitizierten Projekt-Snapshots als .md zur Übergabe an externe KI-Systeme (Gemini / ChatGPT / Perplexity / Claude.ai).
GET /api/crm/projects/:name/context-export— Params:include=section1,section2,...(Sections:identity / workers / architecture / issues / activity / commits / learnings; Standard = alle 7),scanOnly=true|false,activityHours=N(1-720, Standard 168),commitLimit=N(1-200, Standard 20),issueStatus=open|closed|all. Nur für Eigentümer — Admin-Rolle umgeht dies NICHT (by Design). CEO-Bypass funktioniert. Gibt zurück:{ project, exportedAt, filename: "<project>-context-YYYY-MM-DD.md", scanOnly, sections, markdown, findings, stats, alertFired, preferences }. Schwärzt automatisch kritische Findings, außerpreferences.auto_redact_critical = false. Nicht-scanOnly-Ausführungen schreiben inexport_audit_log.GET /api/crm/projects/:name/exports— Audit-Liste (nur Eigentümer). Params:limit=N(1-200, Standard 50). Gibt zurück:{ project, exports: [{ id, owner_id, exported_at, sections[], findings_critical/high/medium/low, bytes }] }.GET /api/crm/projects/:name/settings/export— Einstellungen lesen (nur Eigentümer). Gibt zurück:{ project_name, always_include_emails, auto_redact_critical, notify_on_export, updated_at }.PATCH /api/crm/projects/:name/settings/export— Einstellungen aktualisieren (nur Eigentümer). Body akzeptiert jede Teilmenge von{ always_include_emails, auto_redact_critical, notify_on_export }(Booleans). Gibt aktualisierte Einstellungen zurück.GET /api/crm/analytics/exports— Aggregierte Statistiken (Auth erforderlich, kein Owner-Gate — Analytics-Karte). Param:hours=N(1-720, Standard 168). Gibt zurück:{ total, byProject: [{ project_name, n, last }], severitySums: { critical, high, medium, low } }.
Alert: Wenn der Eigentümer innerhalb von 24h mehr als 3 Exporte durchführt UND prefs.notify_on_export = true (Standard OFF) — geht logActivity("export_alert", ...) über die bestehende Phase-53.10-TG-Notify-Pipeline (alertFired: true im Response-Body).
Multi-Tier-Scanner (shared/secret-scanner.ts) — Tier 1 Regex (PATTERN_REGISTRY aus PII-Sanitizer), Tier 2 Shannon-Entropie ≥4,5 Bits/Zeichen auf ≥20-Zeichen-Runs, Tier 3 Kontext-Heuristiken (key=/token:/secret=/password=). Whitelist: UUID / Git SHA / SHA-256 / wiederholte Zeichen / kurzes Hex / Low-Entropy-Base58. Severity-Tiers (critical/high/medium/low). Performance: <500 ms / 1 MB.
DB-Migration 024 — Tabellen export_audit_log + export_preferences.
Phase 57 — Platform Einstellungen (Sentinel #103 Follow-up, 2026-05-15)
Super-Admin-Secret-Management über das CRM-UI statt SSH/edit-.env/paste-in-chat. Backend-MVP (Stage 1 von 4 Stages). Alle Endpunkte sind durch requireAdmin gesichert (Phase 53.15) — gibt 403 Forbidden — admin only für Nicht-Admins zurück, 401 Unauthorized ohne JWT.
GET /api/crm/platform/settings— gibt zurück:{ items: [{ name, label, description, testable, restartTargets[], set, preview, length, lastRotated, lastRotatedBy }] }. Allowlist mit 9 Keys (ANTHROPIC_API_KEY,PLATFORM_ANTHROPIC_KEY,GITHUB_CLIENT_ID/SECRET,GOOGLE_CLIENT_ID/SECRET,MASTER_BOT_TOKEN,CITADEL_BOT_TOKEN,RESEND_API_KEY). Geschwärzter Preview:prefix(12)…suffix(4)+ Länge. Vollständiger Wert verlässt den Server nie.PUT /api/crm/platform/settings/:name— Body{ value: string ≥ 8 Zeichen }. Schreibt atomar in den Vault überstoreSecret(name, value)+ Audit-Zeile. 400 wenn Name nicht in der Allowlist; 400 wenn value < 8 Zeichen; 500 bei Vault-Schreibfehler.POST /api/crm/platform/settings/:name/test— Verifiziert gegen SaaS-API. Anthropic →GET /v1/modelsmitx-api-key; TG →getMe; Resend →/api-keys. OAuth-Client-Secrets sind standalone nicht testbar → 501. Gibt zurück:{ ok: bool, reason?: string, detail?: string }. 8-Sekunden-Timeout überAbortController.POST /api/crm/platform/settings/:name/restart—Bun.spawn(["nohup", "bash", "-c", "sleep 1 && tmux kill-session ... && bash start-*.sh"], { detach: true })auf gebundene tmux-Sitzungen. Detached, damit ein Master-Restart die In-Flight-Response nicht abbricht. Gibt zurück:{ ok: true, restarted: [sessions], note }.GET /api/crm/platform/audit?limit=50&key=ANTHROPIC_API_KEY— Aktuelle Audit-Log-Einträge, neueste zuerst (Limit auf 500 gedeckelt). Optionaler Key-Filter.
Strikte Ausschlussliste NEVER_EXPOSE: CRM_SECRET (JWT-Signing) + SECRET_ENCRYPTION_KEY (Vault-Meta-Key) — selbst eine Admin-Anfrage mit gültigem Token gibt 400 "not managed" zurück. Audit-Log append-only (kein UPDATE/DELETE-Handler), jede Aktion (inkl. fehlgeschlagene) schreibt eine Zeile mit IP + UA + E-Mail.
DB-Migration 026 — Tabelle platform_audit_log. Stage 2 (Frontend PlatformSettings.jsx) — shipped 2026-05-15 (cbc8bac): Admin-only Card-Grid + Rotate-Modal (<input type="password"> + Retype-Confirm) + Audit-Drawer; Sidebar-Eintrag gefiltert nach userRole === "admin", abgerufen von /api/auth/me.
Polish (2026-05-15, Commit 56191b0) — Platform-Einstellungen-UI-Restrukturierung. GET /api/crm/platform/settings-Response-Items erhalten 5 neue Felder: category (anthropic|oauth|telegram|email), usedIn (string[] — Dateien/Flows, die den Key verwenden), getFromUrl (wo ein frischer Wert abgerufen werden kann), effectAfterRotate, riskIfLeaked. Vom Frontend verwendet, um 4 sektionierte Card-Gruppen + kollapsierbare Hilfe-Panels pro Card mit strukturiertem Kontext zu rendern (Used in / Get from / Effect / Risk). Keine Verhaltensänderung bei den Mutator-Endpunkten (PUT/POST/restart/test).
Refactor (2026-05-16) — shared/routes/platform.ts internes Cleanup. 39 Zeilen entfernt (16 hinzugefügt), keine Änderung der öffentlichen API-Oberfläche. PUT/POST/restart/test/audit-Endpunkt-Signaturen und Responses unverändert. Hier dokumentiert, da der Doc-Coverage-Pre-Push-Gate bei jedem shared/routes/*.ts-Diff auslöst.
Rückdatierte Aktivität (#117, 2026-05-16) — POST /api/mcp/issues/:project/:id/log akzeptiert nun das optionale Feld ts (ISO-8601-String). Wird von arc retro-Rekonstruktion verwendet, damit historische Einträge mit ihren ursprünglichen Zeitstempeln landen. Zukünftig datierte Werte werden innerhalb von addActivity() stillschweigend auf jetzt gedeckelt (Schutz vor Versehen). Ungültiges ISO → 400.
Stage 3 (2026-05-15) — Hot-Reload von OAuth- und Resend-Secrets ohne Neustart. shared/auth.ts loadOAuthConfig() liest jetzt getSecret("GITHUB_CLIENT_ID/SECRET" | "GOOGLE_CLIENT_ID/SECRET") pro Aufruf statt process.env. Callsites in master-bot/routes/auth.ts riefen bereits getOAuthConfig() pro Request auf → 0 Callsite-Änderungen. RESEND_API_KEY ist bereits Hot-Reload über shared/email.ts:47. Verhaltensänderung: PUT /api/crm/platform/settings/{GITHUB_CLIENT_ID|GITHUB_CLIENT_SECRET|GOOGLE_CLIENT_ID|GOOGLE_CLIENT_SECRET|RESEND_API_KEY} tritt jetzt mit dem nächsten Request in Kraft, erfordert keinen Neustart. restartTargets für diese 5 Keys ist leer → Restart-Schaltfläche im UI ausgeblendet. Edge Case: Ein OAuth-Flow mit einem vor der Rotation ausgestellten State-Token kann beim Code-Exchange beim Callback eine 400 erhalten — User-Retry löst das. ANTHROPIC_API_KEY, PLATFORM_ANTHROPIC_KEY, MASTER_BOT_TOKEN, CITADEL_BOT_TOKEN erfordern weiterhin einen Neustart (werden beim Child-Bot-Spawn / TG-Long-Poll-Init gelesen).
Phase 57.3.5 Cleanup (2026-05-16) — MANAGED_KEYS-Allowlist von 9 auf 6 reduziert. Entfernt: ANTHROPIC_API_KEY (Operatoren verwenden jetzt den einheitlichen PLATFORM_ANTHROPIC_KEY für Trial-Credits und Platform-Inferenz; .env-Fallback funktioniert weiterhin für Legacy-Code-Pfade, bis Sage/Karpathy migrieren), CITADEL_BOT_TOKEN (Per-Projekt-Bot gehört zu child:<name>:token-Vault-Einträgen, verwaltet durch den Worker-Onboarding-Flow — nicht durch Platform Einstellungen). MASTER_BOT_TOKEN umbenannt: Label → "Telegram — System Monitor Bot", Beschreibung → "Server-Health-Alerts + On-Demand-Status-Probes (nur Admin, kein Chat-Bot)". Phase 58 wird die Monitoring-Schleife hinzufügen (Push-Alerts für Worker-Crash / Disk / RAM / SSH-Brute-Force / CF-Bypass + /status, /health, /errors, /restart-Befehle). Finales Set: PLATFORM_ANTHROPIC_KEY + GITHUB×2 + GOOGLE×2 + MASTER_BOT_TOKEN + RESEND_API_KEY (refs #103).
Phase 63 — UI/UX Konsolidierung + Token-Usage-Tracking (2026-05-21, #148)
Neuer Endpunkt:
POST /api/internal/usage/log(nur loopback) — schreibt eine Zeile intoken_usage_log. Body:{ project_name, owner_id, worker_id?, input_tokens, output_tokens, cache_tokens, total_tokens }. Wird vonchild-bot/bot.tsals fire-and-forget nach jedem Claude-Aufruf aufgerufen (callClaudeOnce+callWorkertext path). Kein Auth-Header erforderlich —/api/internal/*ist nur von localhost erreichbar und wird von nginx für externe Anfragen blockiert.GET /api/crm/account/usage— Token-Usage-Verlauf für den autorisierten Benutzer (s. Tabelle Onboarding oben).
Änderungen in claude-runner.ts:
callClaudeOnce+callWorkertext path: jetzt immer--output-format json(vorhertextfür non-trial). JSON-Parse extrahiertresultals Output-Text undusagefür das Logging. Trial-Consume-Flow unverändert.- Neuer
logUsage?Dep inClaudeRunnerDeps— Callback(workerId, { input, output, cache }) => void.
UI-Änderungen (kein API):
UserDropdown:UsageCard-Komponente mit Total-Tokens + "Details →" beim Öffnen; Warning-Dot auf Avatar wenn Trial-Balance < 20%.BillingPage: Token-Usage-Sektion mit Totals-Bar + 50-Zeilen-Tabelle. Enterprise-Plan (in Entwicklung).details-Toggle auf jeder Karte.OnboardingProgressPill: Als Inline-Header-Dropdown neu gestaltet (kein Modal-Wizard mehr).WorkerSelector: Semantische--worker-{role}CSS-Vars statt Tailwind-Chart-Tokens.