CRM API — Referência de endpoints
Arc OS — The Orchestration System for AI Teams
Informações gerais
| Parâmetro | Valor |
|---|---|
| Base URL | https://arc-os.co/api/crm |
| Autorização | Authorization: Bearer <JWT> ou ?token=<JWT> (para SSE/WebSocket) |
| Content-Type | application/json |
| Algoritmo JWT | HMAC-SHA256 |
| TTL do JWT | 24 horas |
Autenticação
Todos os endpoints (exceto /docs/*) exigem token JWT no header Authorization: Bearer <token>.
Para conexões SSE e WebSocket, o token é passado via query param ?token=<JWT>.
Erros de autorização
| Código | Descrição |
|---|---|
| 401 | Token ausente ou inválido |
| 403 | Sem acesso ao projeto (multi-tenancy) |
Endpoints por categoria
Conta e configurações
| Método | Caminho | Descrição |
|---|---|---|
| GET | /account/settings |
Obter configurações da conta |
| PUT | /account/settings |
Atualizar configurações da conta |
Onboarding + Trial Credits (Phase 50.1)
| Método | Caminho | Descrição |
|---|---|---|
| POST | /onboarding/setup |
Crie o primeiro projeto. Body multipart: config (JSON) + files. O campo anthropicKey agora é opcional — se vazio + usuário com email_verified + sem período gratuito anterior, o projeto é criado com trial_mode=1 e 100K tokens gratuitos. Response: { ok, project, trial_activated }. Phase 51: retorna 402 com {error:"plan_limit_reached", reason, current, limit, plan} quando o usuário excede o limite de projetos do plano. |
| GET | /account/trial-status |
Status do período gratuito para banner no UI. Response: { email, email_verified, trial_granted, has_trial_active, total_remaining, total_granted, projects: [...] } |
Onboarding Checklist (Phase 54.1, issue #56)
Checklist de engajamento pós-wizard com 5 etapas. Cada etapa (workers, cli, skill, bot, issue) aceita os status completed ou skipped. As mutações são idempotentes: um POST idêntico repetido retorna o mesmo state sem gravar duplicata no activity_log. O replay não zera o state, apenas limpa dismissed_at — o UI exibe o painel novamente com o mesmo progresso.
| Método | Caminho | Descrição |
|---|---|---|
| GET | /onboarding/progress |
Estado atual do usuário autenticado. Response: { steps:["workers","cli","skill","bot","issue"], state:{<step>:<status>}, completed_count, total_steps:5, completed_at, dismissed_at, source, started_at, updated_at }. Usuário sem interações → zeros/null sem criação de linha. |
| POST | /onboarding/event |
Registrar transição de etapa. Body: { step: "workers"|"cli"|"skill"|"bot"|"issue", status: "completed"|"skipped", source?: "web"|"cli" }. Validação por whitelist → 400 para step/status desconhecido. Response: mesmo shape do GET. Emite onboarding_step_completed/onboarding_step_skipped no activity_log apenas quando houve changed; ao atingir 5/5 emite adicionalmente onboarding_completed com duration_ms. |
| POST | /onboarding/dismiss |
Fechar o painel (dismissed_at = now). Idempotente. Emite onboarding_dismissed na primeira chamada com payload {completed_count}. |
| POST | /onboarding/replay |
Reabrir painel fechado (dismissed_at = NULL). O state das etapas não é afetado. Emite onboarding_replayed no clear-event. |
| POST | /projects/:name/active-issue |
Issue #115. Vincular a sessão web atual a um issue. Body: { issue_id: number, title?: string }. Grava evento session_active_issue no activity_log (source=web). |
| GET | /projects/:name/active-issue |
Issue #115. Último issue vinculado por este owner nos últimos 7d. Response: { active_issue_id, title, ts }. |
| GET | /onboarding/cli-status |
Phase 54.3 (issue #58). O usuário fez login via arc login nos últimos 30 dias? Response: { installed: boolean, last_cli_at: string|null }. SSOT — linhas em activity_log com event_type='cli_invocation' e actor=chatId. O checklist de onboarding no frontend faz polling neste endpoint a cada 10s enquanto a etapa CLI estiver pendente; quando installed=true — marca a etapa cli como completed automaticamente. |
| GET | /analytics/onboarding-funnel |
Phase 54.6 (issue #61). Estatísticas agregadas do funil em janela contínua. Query: hours=168 (1-720, padrão 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 — eventos onboarding_step_* + onboarding_completed + cli_invocation no activity_log. TTFC = time-to-first-arc (delta julianday da primeira etapa de onboarding até o primeiro cli_invocation por actor). |
O SSOT para métricas de funil (Phase 54.6 / issue #61) são os eventos em activity_log (event_type LIKE 'onboarding_%'). A tabela onboarding_progress é um cache derivado: o UI renderiza com uma única query em vez de agregar por eventos.
Beta Feedback (Phase 53.3)
| Método | Caminho | Descrição |
|---|---|---|
| POST | /feedback |
Enviar feedback beta. Body: {type: "bug"|"feature"|"other", title, description, project?, browser?}. Registra em activity_log (event_type=feedback_report) e notifica o CEO no Telegram. |
| GET | /admin/feedback |
Lista dos últimos envios (somente admin). Query: limit=50 (máx 500). Response: {items: [...], count}. |
POST /feedback — validação do body: type ∈ {bug,feature,other}, title ≤ 200 chars, description ≤ 5000 chars. Sucesso → {ok: true, type, title}. O ping no Telegram é formatado como 🐞/💡/📝 New <type> feedback ... From: <user> Title: <title> + primeiros 400 caracteres da descrição.
O widget flutuante em
FeedbackWidget.jsx(dashboard do CRM) passa automaticamentebrowser(UA + viewport + locale) eproject(technical_name do projeto ativo).
Beta Invites (Phase 52.1, somente admin)
| Método | Caminho | Descrição |
|---|---|---|
| GET | /admin/invites |
Lista todos os códigos de convite + contagens (total_active, total_used). Somente admin. |
| POST | /admin/invites |
Gerar N códigos. Body: {count: N, note?: string}. Somente admin. Response: {ok, codes, count}. |
| DELETE | /admin/invites/:code |
Revogar código de convite não utilizado. |
Atualização do fluxo de auth: POST /api/auth/register agora exige o campo invite_code (Phase 52.1 closed beta). Sem código → 403 {error: "invite_required"}. Código inválido/usado → 403 {error: "invalid_invite"}.
Billing (Phase 51)
| Método | Caminho | Descrição |
|---|---|---|
| GET | /account/usage |
Histórico de uso de tokens para o usuário autorizado (Phase 63, #148). Response: { rows: [ { project_name, worker_id, input_tokens, output_tokens, cache_tokens, total_tokens, created_at } × até 200 ], totals: { total, input, output } }. Lê token_usage_log por owner_id. Exibido no UserDropdown (UsageCard) e BillingPage (seção Token Usage). |
| GET | /billing/status |
Plano atual, limites, uso e features. Cria linha Free automaticamente na primeira chamada. Response: { plan, status, current_period_end, limits, usage, features, pricing, can_upgrade, stripe_ready } |
| POST | /billing/checkout-session |
Stripe Checkout (Stage 2 — retorna 501 até o Stripe SDK ser integrado) |
Limites por plano (semântica OR):
- Free: 1 projeto E 5 workers
- Min ($4.99/mês): 5 projetos OU 25 workers no total
- Max ($11.99/mês): 20 projetos OU 150 workers no total
Resposta 402 em POST /onboarding/setup ou POST /projects/:name/workers quando o limite é excedido: { error: "plan_limit_reached", reason: "projects_limit"|"workers_limit", current, limit, plan, message }
Usuários admin (
role=admin) ignoram completamente a verificação de limites do plano — são operadores, não tenants pagantes.
Beta testers (
subscriptions.plan='beta', Phase 52 F&F) também ignoram — projetos e workers ilimitados mais todas as features do plano Max. Atribuído manualmente:UPDATE subscriptions SET plan='beta' WHERE user_id=?.
Bugfix (issue #25):
POST /projects/create(Quick Start, Phase 50.2) anteriormente falhava comownerChatId is not definedpor typo — corrigido; o actor de auditoria agora é registrado corretamente.
Bugfix (issue #26):
allocatePort()para novos projetos agora verifica bindings TCP reais (ss -tln), não apenas o registry. Anteriormente podia retornar uma porta ocupada por serviço externo ao registry (NotebookLM bridge :19213, internal bridges) → workspace bot falhava com EADDRINUSE.
Fluxo de auth (Phase 50.1): /api/auth/register e /api/auth/login agora retornam JWT mesmo para email não verificado + flag needs_verification: true. Ações sensíveis (trial grant, billing, invites) verificam email_verified separadamente. Rate limit no signup: 3 / IP / 24h.
Projetos (9 endpoints)
| Método | Caminho | Descrição |
|---|---|---|
| GET | /projects |
Listar projetos do usuário |
| POST | /projects/create |
Crie um projeto |
| GET | /projects/:name |
Detalhes do projeto |
| GET | /projects/:name/config |
Configuração do projeto |
| PUT | /projects/:name/config |
Atualizar configuração |
| GET | /projects/:name/protocol |
Protocolo do projeto |
| PUT | /projects/:name/protocol |
Atualizar protocolo |
| GET | /projects/:name/logs |
Logs do projeto |
| GET | /projects/:name/metrics |
Métricas do projeto |
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
Workers (11 endpoints)
| Método | Caminho | Descrição |
|---|---|---|
| GET | /projects/:name/workers |
Listar workers |
| POST | /projects/:name/workers |
Crie um worker |
| POST | /projects/:name/workers/reorder |
Phase 53.8 — reordenar workers. Body: {order: [id1, id2, ...]}. Reescreve atomicamente o workers_registry.json. Workers ausentes em order são adicionados ao final (proteção contra perda). Response: {ok, count, order}. |
| PUT | /projects/:name/workers/:id |
Atualizar worker |
| DELETE | /projects/:name/workers/:id |
Excluir worker |
| POST | /projects/:name/workers/generate-prompt |
Gerar prompt de sistema |
| GET | /projects/:name/workers/:id/telegram-token |
Obter token do Telegram |
| POST | /projects/:name/workers/:id/telegram-token |
Phase 53.4 — valida o token via Telegram getMe, salva bot_username no vault, rejeita se o mesmo bot já estiver vinculado a outro worker (409). Response: {ok, started, bot_username}. |
| DELETE | /projects/:name/workers/:id/telegram-token |
Excluir token do Telegram |
| POST | /projects/:name/workers/:id/notify |
Phase 53.2 — enviar ping de evento TG ({event?, text, buttons?}). No-op silencioso se o token não estiver vinculado ou CRM_DISABLE_TG_NOTIFY=1. |
| POST | /projects/:name/workers/:id/suggest-bot-username |
53.11.1 (issue #48) — retorna 5 candidatos de username TG para o wizard de criação de bot no formato <project>_<worker>_bot + fallbacks numerados. Slugify remove hífens, trunca em 32 chars (a parte do worker é truncada primeiro). Response: {candidates: string[]}. |
| POST | /metrics/wizard |
53.11.1 (issue #48) — sink de telemetria para o wizard de criação de bot. Body: {action, duration_ms?, attempts?, success?, project?, worker_id?}. Grava em activity_log (event_type=wizard_metric), best-effort. |
| GET | /analytics/wizard-metrics?hours=168 |
53.11.1 (issue #48) — resumo do funil: {starts, completions, abandons, success_rate, avg_duration_ms_completed, avg_attempts_completed, by_action}. Padrão 7 dias, clamp 1-720h. |
| POST | /projects/:name/restart |
Reiniciar worker |
| GET | /projects/:name/active-role |
Papel ativo atual |
| POST | /projects/:name/active-role |
Alterar papel ativo |
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_turnstem padrão20(anteriormente era5, causando erro "Reached max turns" em diálogos com múltiplas etapas e tool calls).
POST /projects/:name/restart — query: worker_id
Arquivos e armazenamento (8 endpoints)
| Método | Caminho | Descrição |
|---|---|---|
| GET | /projects/:name/files |
Árvore de arquivos |
| POST | /projects/:name/files/upload |
Fazer upload de arquivo (multipart, máx 100MB) |
| POST | /projects/:name/files/mkdir |
Criar diretório |
| POST | /projects/:name/files/create |
Criar arquivo |
| GET | /projects/:name/files/read |
Ler arquivo |
| PUT | /projects/:name/files/save |
Salve arquivo |
| DELETE | /projects/:name/files/delete |
Excluir arquivo |
| POST | /projects/:name/files/clone |
Git clone de repositório |
GET /projects/:name/files — query: path
GET /projects/:name/files/read — query: path, raw
Skills (18 endpoints)
Skills do projeto
| Método | Caminho | Descrição |
|---|---|---|
| GET | /projects/:name/skills |
Listar skills do projeto |
| POST | /projects/:name/skills |
Crie uma skill |
| PUT | /projects/:name/skills/:id |
Atualizar skill |
| DELETE | /projects/:name/skills/:id |
Excluir skill |
Marketplace global
| Método | Caminho | Descrição |
|---|---|---|
| GET | /skills |
Listar skills globais |
| POST | /skills |
Publicar skill |
| GET | /skills/:id |
Detalhes da skill |
| PUT | /skills/:id |
Atualizar skill |
| DELETE | /skills/:id |
Excluir skill |
Evolução e atualizações
| Método | Caminho | Descrição |
|---|---|---|
| GET | /skills/:id/evolution |
Histórico de evolução da skill |
| GET | /skill-updates |
Lista de atualizações disponíveis |
| POST | /skill-updates/:id/approve |
Aprovar atualização |
| POST | /skill-updates/:id/reject |
Rejeitar atualização |
Forks de skills
| Método | Caminho | Descrição |
|---|---|---|
| GET | /projects/:name/skill-forks |
Listar forks |
| POST | /projects/:name/skill-forks |
Criar fork |
| PUT | /projects/:name/skill-forks/:id |
Atualizar fork |
| DELETE | /projects/:name/skill-forks/:id |
Excluir fork |
Chat e mensagens
| Método | Caminho | Descrição |
|---|---|---|
| POST | /projects/:name/chat |
Enviar mensagem no chat |
| GET | /projects/:name/chat/history |
Histórico do chat |
| POST | /projects/:name/message |
Enviar mensagem ao worker (Phase 48.6: acorda automaticamente worker idle-killed, ~2-4s cold start; Phase 48.6.1: o wake-up também funciona em projetos single-mode, não apenas parallel) |
| GET | /projects/:name/pins |
Listar notas (pins) |
| POST | /projects/:name/pins |
Criar nota |
| DELETE | /projects/:name/pins/:id |
Excluir nota |
Wiki (4 endpoints)
| Método | Caminho | Descrição |
|---|---|---|
| GET | /projects/:name/wiki/tree |
Árvore de páginas da wiki |
| GET | /projects/:name/wiki/file |
Ler página da wiki |
| PUT | /projects/:name/wiki/save |
Salve página da wiki |
| GET | /projects/:name/wiki/download |
Baixar wiki como arquivo ZIP |
Analytics (4 endpoints)
| Método | Caminho | Descrição |
|---|---|---|
| GET | /analytics/activity |
Feed de atividade |
| GET | /analytics/sidebar |
Dados para o painel lateral |
| GET | /analytics/phases |
Lista de fases do projeto |
| POST | /analytics/phases |
Atualizar fases do projeto |
Marketplace e Sage (8 endpoints)
| Método | Caminho | Descrição |
|---|---|---|
| GET | /sage/scout/categories |
Categorias do marketplace |
| POST | /sage/scout |
Buscar skills |
| POST | /sage/scout/quick-scan |
Varredura rápida |
| POST | /sage/scout/analyze |
Análise aprofundada de skill |
| POST | /sage/scout/install |
Instalar skill |
| POST | /sage/analyze |
Análise pelo Sage |
| GET | /sage/status |
Status do serviço Sage |
| POST | /sage/benchmark |
Executar benchmark |
Memória e Knowledge
| Método | Caminho | Descrição |
|---|---|---|
| POST | /projects/:name/memory/refresh |
Atualizar memória neural |
| POST | /projects/:name/memory/fetch-artifact |
Baixar artefato |
| GET | /projects/:name/learnings |
Listar learnings |
| POST | /projects/:name/learnings |
Adicionar learning |
| GET | /projects/:name/knowledge-graph |
Grafo de conhecimento do projeto |
Documentação (global, sem auth)
| Método | Caminho | Descrição |
|---|---|---|
| GET | /docs/tree?lang=<lang> |
Árvore de documentação; lang opcional (en/uk), padrão en |
| GET | /docs/file?path=<p>&lang=<lang> |
Ler arquivo de documentação com language fallback |
GET /docs/tree — query: lang (opcional)
- Busca primeiro
docs/public/<lang>/index.md, fallback paradocs/public/index.md - Response inclui:
sections,files,served_lang,is_fallback,requested_lang
GET /docs/file — query: path (obrigatório), lang (opcional)
- Ordem de resolução:
docs/public/<lang>/<path>→docs/public/<path>(fallback EN) - Response inclui:
path,content,size,modified,served_lang,is_fallback,requested_lang - 403 em path traversal, 404 em arquivo não encontrado
- Phase 52.1.3 — parâmetro
langadicionado para tradução UK
Sistema
| Método | Caminho | Descrição |
|---|---|---|
| GET | /system/configs |
Obter configurações do sistema |
| PUT | /system/configs |
Atualizar configurações do sistema |
Códigos de erro
| Código | Significado |
|---|---|
| 200 | Sucesso |
| 201 | Criado |
| 400 | Requisição inválida |
| 401 | Não autorizado |
| 403 | Proibido (multi-tenancy) |
| 404 | Não encontrado |
| 409 | Conflito (duplicata) |
| 429 | Muitas requisições |
| 500 | Erro no servidor |
GitHub Integration (Phase 49.3)
| Endpoint | Método | Descrição |
|---|---|---|
/api/crm/projects/:name/github |
GET | Listar repositórios GitHub vinculados ao projeto |
/api/crm/projects/:name/github |
POST | Vincular repositório (body: {owner, repo}) — retorna webhook URL + secret + instruções de configuração |
/api/crm/projects/:name/github/:id |
DELETE | Desvincular repositório |
/api/crm/projects/:name/github/events |
GET | Listar eventos GitHub recentes (Phase 49.3.1, query: ?limit=50) |
/api/webhooks/github |
POST | Receptor público de webhooks (validado por HMAC-SHA256, rate-limit 100/min) |
Eventos suportados: push, pull_request, workflow_run, issues. Notificações roteadas para o Telegram do owner do projeto.
Account Security (Phase 45.4)
| Endpoint | Método | Descrição |
|---|---|---|
/api/crm/account/recovery |
GET | Listar chaves de recuperação ativas |
/api/crm/account/recovery |
POST | Criar chave de recuperação (body: encryptedKey, keyHint) |
/api/crm/account/recovery |
DELETE | Revogar chave(s) de recuperação (body: { id } ou {} para todas) |
/api/crm/account/recovery/restore |
GET | Obter master key criptografada para recuperação |
Segurança
- Multi-tenancy: cada endpoint com
:nameverifica ownership viachatIddo JWT - Validação do nome do projeto:
^[a-zA-Z0-9][a-zA-Z0-9_-]*$(máx 64 caracteres) - Proteção contra path traversal:
safePath()em todos os caminhos controlados pelo usuário - Upload de arquivos: máx 100MB, extensões bloqueadas (
.exe,.bat,.sh) - CORS: whitelist de origins via
CRM_ALLOWED_ORIGINS - Proteção contra SSRF: allowlist em
handleScoutAnalyze— somente HTTPS + hosts permitidos - Endpoints internos: rejeitam requisições com headers de proxy (
X-Forwarded-For,X-Real-IP) - Criptografia em repouso (Phase 45): chaves de API e mensagens de chat criptografadas com AES-256-GCM
- Headers de segurança:
Content-Security-Policy,X-Frame-Options: DENY,X-Content-Type-Options: nosniff - Sanitização de PII: emails, chaves de API e JWTs são automaticamente removidos dos logs JSONL
Phase 53.13 — baseline de type-safety (2026-05-10)
Sem alteração no comportamento dos endpoints — apenas tipos internos. tsc --noEmit agora bloqueia push/CI:
- Interface
ChildBotconsolidada emshared/routes/_utils.ts(3 duplicatas unificadas).bot_username,heartbeat_file,health_endpoint,statustornados opcionais — refletem runtime-state (entradas workspace com DB-enriched frequentemente sem eles). requireAdmin()emshared/routes/system.tsagora retornaResponse | { userId }em vez de{ ok, ... }— narrowing mais simples viainstanceof Response. Comportamento externo (códigos 401/403, corpos de resposta) inalterado.workers.tsDEFAULT_WORKERS perdeuas const(para compatibilidade com callsites mutáveis); parsing do body paratools/focus_dirsagora é estritamente viaArray.isArrayem vez de fallback com||.
Phase 53.15 — Sentinel Sprint 1 (2026-05-10)
Mudanças de comportamento em endpoints de auth + admin (correções P0 do audit Sentinel):
POST /api/auth/login— quandorequires2fa=true, a resposta agora é{requires2fa: true, challenge_token}em vez de{requires2fa: true, userId}. O frontend deve passarchallenge_tokenna próxima etapa.POST /api/auth/2fa/login— shape do body:{challenge_token, code}em vez de{userId, code}. O token é de uso único, TTL de 5 min. Sem token válido o endpoint retorna401 "Invalid or expired challenge — restart login". Rate-limit por userId: 5 tentativas / 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— somente admin. Não-admin → 403Forbidden — admin only. Sem auth → 401.- Rate-limit Nginx em
/api/auth/*— 5 req/min/IP (burst=10 nodelay → 429). O mesmo em/api/webhooks/github(30 req/min/IP, burst=20). - HSTS — header
Strict-Transport-Security: max-age=31536000; includeSubDomains; preloadagora é enviado em toda resposta HTTPS. Requisições HTTP → redirect 301 para HTTPS. X-Frame-Options: DENYem vez deSAMEORIGIN.
Phase 53.21 — Sentinel P2 batch 2 (2026-05-12)
POST /api/crm/feedback— agora exige que o caller tenha acesso aobody.projectindicado (verificação canAccessProject). Owner não autorizado no projeto → 403"Project not accessible".projectvazio/ausente ainda é permitido (feedback global).POST /api/internal/trial/consume— shape do body alterado:{project, owner_id, tokens}em vez de{project, tokens}.owner_idé obrigatório e verificado contraprojects.owner_idno DB. 404 para projeto desconhecido, 403 para owner mismatch. O caller (child-bot/claude-runner.ts) propagaARC_TRIAL_OWNERenv injetado porworker-spawn.ts.
Phase 53.18 — correção de vazamento de segredos no tmux (2026-05-11)
Sem alteração no comportamento dos endpoints — apenas refactor de caminhos internos de spawn.
POST /api/crm/onboarding/setup(viashared/routes/onboarding.ts:startWorkspaceBot) — o método de inicialização do child-bot em workspace-mode foi alterado debash -c "export X='val'; bun run bot.ts"paratmux -e VAR=val ... bun run bot.ts. Valores de tokens não aparecem mais em/proc/PID/cmdline. Externamente: 0 mudanças (body de resposta, status codes e comportamento idênticos).
Phase 53.16 — Sentinel Sprint 2 (2026-05-10)
Mudanças de comportamento dos endpoints após hardening de 13 × P1:
- Callback OAuth — a URL de redirect agora usa o fragment
#token=em vez da query?token=(Sentinel P1-8). O frontend lê dewindow.location.hash(com fallback em?token=por um ciclo de deploy). /api/crm/analytics/activity+/api/crm/analytics/sidebar— query agora com escopo porowner_iddo usuário logado. Não-admin vê apenas seus próprios projetos. Anteriormente vazavam os primeiros 80 chars de cada assistant message + nomes de projetos + worker IDs de todos os tenants (Sentinel P1-4).PUT /api/crm/projects/:name/files/save— adicionada verificaçãoisProtectedPath()..env/CLAUDE.md/.git/*/.claude/*agora retornam 403"Protected path"(antes podiam ser sobrescritos) (Sentinel P1-3).POST /api/crm/projects/:name/files/mkdir+/files/create—body.namecom..,.,/,\→ 400. Re-executasafePath()apósjoin()(Sentinel P1-2)./ws/local-bridge—chatIddo JWT é preservado no upgrade. Mensagem init comproject_nameque não pertence ao usuário → close 1008Forbidden — project not accessible. Anteriormente qualquer usuário podia init um bridge em projeto alheio (Sentinel P1-5).- CSP — o HTML do frontend (via docker/nginx.conf) agora envia CSP restrito:
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'. CSP JSON da API perdeu'unsafe-inline'(Sentinel P1-10). - Helper interno
extractChatId— agora verifica a assinatura com verifyToken antes de decodificar (Sentinel P1-6, defense-in-depth para futuras rotas skipAuth). - Formato criptografado da chave de recuperação — novas chaves são armazenadas como
v2:<base64-salt>:<payload>(salt aleatório de 16 bytes por chave). As antigas (sem prefixov2:) funcionam via legacy fallback (Sentinel P1-13). - CEO_CHAT_ID — agora env-first (com fallback por warning no bot_registry). O valor hardcoded 474903718 foi removido de 6 arquivos (Sentinel P1-14).
- Nginx X-Forwarded-For — sobrescreve em vez de acrescentar em todos os 17 callsites (Sentinel P1-11). O helper
clientIplê o ÚLTIMO segmento do XFF (Sentinel P1-7).
Phase 55 — Cosmic Editorial login (2026-05-13)
Novos endpoints para sign-in via magic-link:
POST /api/auth/magic-link/request— body{ email }. Gera token de uso único com TTL de 10 min emephemeral_tokens(tipomagic_link), envia o linkhttps://<host>/?magic_token=<token>via provedor de email. Anti-enumeração: sempre retorna 200 OK com{ ok: true, message: "If the account exists, a magic link has been sent" }(mesmo que o email não exista). Rate-limit: 3/min por (IP+email) + 5/10min por email — mesmo contrato deforgot-password. O caminho de falha passa por timing pad.POST /api/auth/magic-link/verify— body{ token }. Consome o token de uso único, retorna{ ok: true, token: <jwt>, userId }no sucesso ou 401"Invalid or expired magic link". Efeito colateral:user.email_verified = true+ atualizalast_login(prova de inbox = verificação).
O union EphemeralTokenType foi ampliado: agora inclui "magic_link" junto aos tipos existentes oauth_state / password_reset / email_verification / tfa_challenge.
O frontend (CosmicCard.jsx) gerencia o state magic (contagem regressiva de 60s para reenvio) e o parâmetro de URL ?magic_token= (auto-consume → login → animação de sucesso).
Phase 56 — AI Interop / Project Context Export (2026-05-13)
Exportação owner-only de snapshot sanitizado do projeto como .md para compartilhar com AI externo (Gemini / ChatGPT / Perplexity / Claude.ai).
GET /api/crm/projects/:name/context-export— params:include=section1,section2,...(seções:identity / workers / architecture / issues / activity / commits / learnings; padrão = todas as 7),scanOnly=true|false,activityHours=N(1-720, padrão 168),commitLimit=N(1-200, padrão 20),issueStatus=open|closed|all. Somente owner — role admin NÃO ignora a verificação (por design). CEO bypass funciona. Retorna{ project, exportedAt, filename: "<project>-context-YYYY-MM-DD.md", scanOnly, sections, markdown, findings, stats, alertFired, preferences }. Redige automaticamente findings críticos, salvo quandopreferences.auto_redact_critical = false. Execuções não-scanOnlygravam emexport_audit_log.GET /api/crm/projects/:name/exports— lista de auditoria (somente owner). Params:limit=N(1-200, padrão 50). Retorna{ project, exports: [{ id, owner_id, exported_at, sections[], findings_critical/high/medium/low, bytes }] }.GET /api/crm/projects/:name/settings/export— ler preferências (somente owner). Retorna{ project_name, always_include_emails, auto_redact_critical, notify_on_export, updated_at }.PATCH /api/crm/projects/:name/settings/export— atualizar preferências (somente owner). Body aceita qualquer subconjunto de{ always_include_emails, auto_redact_critical, notify_on_export }(booleanos). Retorna as preferências atualizadas.GET /api/crm/analytics/exports— estatísticas agregadas (requer auth, sem gate de owner — analytics card). Param:hours=N(1-720, padrão 168). Retorna{ total, byProject: [{ project_name, n, last }], severitySums: { critical, high, medium, low } }.
Alerta: quando o owner excede 3 exportações em 24h E prefs.notify_on_export = true (padrão OFF) — logActivity("export_alert", ...) passa pelo pipeline de notificação TG existente da Phase 53.10 (alertFired: true no body da resposta).
Scanner multi-tier (shared/secret-scanner.ts) — Tier 1 regex (PATTERN_REGISTRY do sanitizador de PII), Tier 2 entropia de Shannon ≥ 4.5 bits/char em sequências ≥ 20 chars, Tier 3 heurísticas de contexto (key=/token:/secret=/password=). Whitelist: UUID / git SHA / SHA-256 / chars repetidos / hex curto / base58 de baixa entropia. Tiers de severidade (critical/high/medium/low). Performance: < 500 ms / 1 MB.
DB migration 024 — tabelas export_audit_log + export_preferences.
Phase 57 — Platform Settings (follow-up Sentinel #103, 2026-05-15)
Gerenciamento de segredos super-admin via UI do CRM em vez de ssh/editar-.env/colar-no-chat. Backend MVP (Stage 1 de 4 stages). Todos os endpoints são protegidos por requireAdmin (Phase 53.15) — retornam 403 Forbidden — admin only para não-admin e 401 Unauthorized sem JWT.
GET /api/crm/platform/settings— retorna{ items: [{ name, label, description, testable, restartTargets[], set, preview, length, lastRotated, lastRotatedBy }] }. Allowlist de 9 chaves (ANTHROPIC_API_KEY,PLATFORM_ANTHROPIC_KEY,GITHUB_CLIENT_ID/SECRET,GOOGLE_CLIENT_ID/SECRET,MASTER_BOT_TOKEN,CITADEL_BOT_TOKEN,RESEND_API_KEY). Preview redacted:prefix(12)…suffix(4)+ comprimento. O valor completo nunca sai do servidor.PUT /api/crm/platform/settings/:name— body{ value: string ≥ 8 chars }. Grava atomicamente no vault viastoreSecret(name, value)+ linha de auditoria. 400 se name não estiver na allowlist; 400 se value < 8 chars; 500 em falha de escrita no vault.POST /api/crm/platform/settings/:name/test— verificar contra API SaaS. Anthropic →GET /v1/modelscomx-api-key; TG →getMe; Resend →/api-keys. Client secrets OAuth standalone não são testáveis → 501. Retorna{ ok: bool, reason?: string, detail?: string }. Timeout de 8s viaAbortController.POST /api/crm/platform/settings/:name/restart—Bun.spawn(["nohup", "bash", "-c", "sleep 1 && tmux kill-session ... && bash start-*.sh"], { detach: true })nas sessões tmux vinculadas. Detached para que o restart do master não interrompa a resposta em andamento. Retorna{ ok: true, restarted: [sessions], note }.GET /api/crm/platform/audit?limit=50&key=ANTHROPIC_API_KEY— entradas recentes do log de auditoria, mais recentes primeiro (limit capped em 500). Filtro por chave opcional.
Lista de exclusão rígida NEVER_EXPOSE: CRM_SECRET (assinatura JWT) + SECRET_ENCRYPTION_KEY (meta-chave do vault) — mesmo requisição admin com token válido retorna 400 "not managed". Log de auditoria append-only (sem handler de UPDATE/DELETE); cada ação (incluindo falhas) grava uma linha com IP + UA + email.
DB migration 026 — tabela platform_audit_log. Stage 2 (frontend PlatformSettings.jsx) — publicado em 2026-05-15 (cbc8bac): grade de cards somente admin + modal de rotação (<input type="password"> + confirmação de redigitação) + drawer de auditoria; entrada na sidebar filtrada por userRole === "admin" obtido de /api/auth/me.
Polish (2026-05-15, commit 56191b0) — Reestruturação da UI de Platform Settings. Itens da resposta de GET /api/crm/platform/settings ganham 5 novos campos: category (anthropic|oauth|telegram|email), usedIn (string[] — arquivos/fluxos que consomem a chave), getFromUrl (onde obter um valor atualizado), effectAfterRotate, riskIfLeaked. Usados pelo frontend para renderizar 4 grupos de cards por seção + painel de ajuda expansível por card com contexto estruturado (Used in / Get from / Effect / Risk). Sem mudança comportamental nos endpoints mutadores (PUT/POST/restart/test).
Refactor (2026-05-16) — limpeza interna em shared/routes/platform.ts. 39 linhas removidas (16 adicionadas), sem mudança na superfície pública da API. Assinaturas e respostas dos endpoints PUT/POST/restart/test/audit inalteradas. Documentado aqui apenas porque o gate do pre-push hook de cobertura de docs dispara em qualquer diff em shared/routes/*.ts.
Atividade backdated (#117, 2026-05-16) — POST /api/mcp/issues/:project/:id/log agora aceita campo opcional ts (string ISO-8601). Usado por arc retro para que entradas históricas reconstruídas sejam gravadas com seus timestamps originais. Valores com data futura são silenciosamente limitados ao momento atual dentro de addActivity() (defesa contra erros de digitação). ISO inválido → 400.
Stage 3 (2026-05-15) — hot-reload de segredos OAuth + Resend sem restart. shared/auth.ts loadOAuthConfig() agora lê getSecret("GITHUB_CLIENT_ID/SECRET" | "GOOGLE_CLIENT_ID/SECRET") a cada chamada em vez de process.env. Os callsites em master-bot/routes/auth.ts já invocavam getOAuthConfig() por requisição → 0 mudanças em callsites. RESEND_API_KEY já fazia hot-reload via shared/email.ts:47. Mudança comportamental: PUT /api/crm/platform/settings/{GITHUB_CLIENT_ID|GITHUB_CLIENT_SECRET|GOOGLE_CLIENT_ID|GOOGLE_CLIENT_SECRET|RESEND_API_KEY} agora entra em vigor na próxima requisição, sem exigir restart. restartTargets para essas 5 chaves está vazio → botão Restart fica oculto no UI. Caso extremo: fluxo OAuth com state-token emitido antes da rotação pode receber 400 no callback durante code-exchange — um retry do usuário resolve. ANTHROPIC_API_KEY, PLATFORM_ANTHROPIC_KEY, MASTER_BOT_TOKEN, CITADEL_BOT_TOKEN continuam exigindo restart (lidos no spawn do child-bot / init do TG long-poll).
Limpeza Phase 57.3.5 (2026-05-16) — allowlist MANAGED_KEYS reduzida de 9 para 6. Removidos: ANTHROPIC_API_KEY (operadores agora usam apenas PLATFORM_ANTHROPIC_KEY tanto para trial-credits quanto para inferência da plataforma; fallback via .env ainda funciona para caminhos de código legados até Sage/Karpathy migrarem), CITADEL_BOT_TOKEN (bot por projeto pertence às entradas de vault child:<name>:token, gerenciado pelo fluxo de onboarding de workers — não ao Platform Settings). MASTER_BOT_TOKEN teve o label atualizado para "Telegram — System Monitor Bot" e a descrição para "Server health alerts + on-demand status probes (admin-only, not a chat bot)". A Phase 58 adicionará o loop de monitoramento (alertas push para crash de worker / disco / RAM / brute-force SSH / bypass CF + comandos /status, /health, /errors, /restart). Conjunto final: PLATFORM_ANTHROPIC_KEY + GITHUB×2 + GOOGLE×2 + MASTER_BOT_TOKEN + RESEND_API_KEY (refs #103).
Phase 63 — Consolidação UI/UX + Rastreamento de Uso de Tokens (2026-05-21, #148)
Novo endpoint:
POST /api/internal/usage/log(somente loopback) — grava uma linha emtoken_usage_log. Body:{ project_name, owner_id, worker_id?, input_tokens, output_tokens, cache_tokens, total_tokens }. Chamado dechild-bot/bot.tscomo fire-and-forget após cada chamada ao Claude (callClaudeOnce+callWorkertext path). Não requer header de auth —/api/internal/*só é acessível a partir do localhost e bloqueado pelo nginx para requisições externas.GET /api/crm/account/usage— histórico de uso de tokens para o usuário autorizado (descrito na tabela Onboarding acima).
Mudanças em claude-runner.ts:
callClaudeOnce+callWorkertext path: agora sempre--output-format json(antestextpara non-trial). O parse JSON extrairesultcomo texto de saída eusagepara logging. O fluxo trial consume permanece inalterado.- Novo dep
logUsage?emClaudeRunnerDeps— callback(workerId, { input, output, cache }) => void.
Mudanças de UI (não API):
UserDropdown: componenteUsageCardcom total de tokens + "Details →" ao abrir; ponto de aviso no avatar quando o saldo trial < 20%.BillingPage: seção Token Usage com barra de totais + tabela de 50 linhas. Plano Enterprise (em desenvolvimento). Toggledetailsem cada card.OnboardingProgressPill: redesenhado como dropdown inline no header (não é mais wizard modal).WorkerSelector: variáveis CSS semânticas--worker-{role}em vez de tokens Tailwind chart.