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: [...] }
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 /account/billing-summary Resumo consolidado de billing (#309). Response: { arc: { plan, status, tokens_this_month, tokens_input, tokens_output, renewal_date }, anthropic: { connected, key_prefix, credit_balance_usd, spend_month_usd, tokens_this_week } }. A seção Anthropic é preenchida server-side via Anthropic API com a account_settings.anthropic_key do usuário (fallback → PLATFORM_ANTHROPIC_KEY). Exibido no UsageCard do UserDropdown.

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 automaticamente browser (UA + viewport + locale) e project (technical_name do projeto ativo).

Arc Help AI Chat (Phase 61, #147)

Método Caminho Descrição
POST /help/chat Q&A com IA dentro do app. Body: {message, history: [{role,text}]}. Response: {reply, sources: string[], remaining, limit}. Rate limit: 30/dia/usuário.
GET /help/usage Limite atual. Response: {remaining, limit, used}.

POST /help/chat — pipeline: (1) verificação de rate limit (429 se excedido), (2) RAG via shared/rag.ts (Cohere + sqlite-vec, Phase 71) mesclando hits do projeto + skills _global_ → fallback de busca por palavras-chave em docs/public/, (3) Claude Haiku com system prompt + contexto de docs + histórico. message ≤2000 chars. Responde no idioma da pergunta.

Beta Invites (Phase 52.1, somente admin)

Método Caminho Descrição
GET /admin/dashboard System Dashboard (Phase 60.9, #145). Somente admin. Retorna: CPU/RAM/Disco de /proc, usuários por plano, frota de containers, últimos 50 eventos de atividade, estatísticas de waitlist + projetos + issues.
GET /admin/wipe-metrics Dashboard de telemetria WIP-E (#308). Somente admin. Retorna: {render: {count, avg_ms, p50_ms, p95_ms, max_ms}, interaction: {count, avg_per_session, p95_per_session, max_per_session, sessions_zero}, by_worker: [{worker_id, render_count, avg_render_ms, session_count, avg_interactions}], daily: [{date, renders, interactions, avg_render_ms}], recent: [...]}.
GET /admin/waitlist Lista de todas as inscrições da waitlist. Somente admin. Response: {entries: [{id, email, message, status, created_at}]}.
POST /admin/waitlist/:id/approve Aprovar inscrição — gera invite code (arc-XXXX-XXXX), envia email com o código, atualiza status→approved. Response: {ok, invite_code, email_sent}.
POST /admin/waitlist/:id/reject Rejeitar inscrição. Response: {ok}.
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.
/admin/notebooklm/* Removidos na Phase 71.8 junto com o NotebookLM Bridge. A busca semântica agora funciona via RAG self-hosted (rag-architecture.md).

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"}.

Standard Cloud — WebSocket Terminal + SSE Logs (Phase 60 #139)

Protocolo Caminho Descrição
WS /ws/cloud/:userId/terminal?token=<JWT> Proxy para docker exec -i <containerId> /bin/bash. IDOR: userId precisa coincidir com o chatId do JWT. Container pausado é retomado automaticamente. Frames WS de entrada → stdin do container; stdout+stderr → frames WS.
SSE /api/sse/cloud/:userId/logs docker logs -f --tail 50 para o container do usuário. Auth: Bearer JWT. IDOR: userId === chatId. Eventos: data: {"line": "..."} por linha, data: {"closed": true} ao encerrar.

Standard Cloud (Phase 60)

Método Caminho Descrição
POST /cloud/claude-verify Verifica claude --version no container (shell-quoted de forma transport-safe via SSH no modo remote-host, #329). Define claude_authed=true. Response: { ok, output }
POST /cloud/ssh-keygen Gera chave ed25519 no container (idempotente). Response: { public_key }
POST /cloud/ssh-verify ssh -T [email protected] no container. Define github_authed=true em caso de sucesso. Response: { ok, output }
POST /cloud/provision Provisionamento de container Docker para o usuário. Exige plano cloud, 402 caso contrário. Idempotente: se o container já existe — retorna o estado atual. Response: { container_id, status, server_ip, port, claude_authed, github_authed }
GET /cloud/status Estado do container + reconciliação live via docker inspect. Response: { container_id, status, server_ip, internal_port, claude_authed, github_authed, docker_running, last_active, created_at } ou { status: "none" }
POST /cloud/deprovision Parar + remover o container (docker stop + docker rm -f + docker network rm arc-net-{id}). Atualiza status=deleted no DB. Response: { ok: true, container_id }

Status do container: provisioningreadypausedsuspended / deleted.

Segurança (SEC-60 #152, #154, #155, #156): cada container é isolado na própria rede arc-net-{id} (prevenção de lateral movement). Conexão SSH Contabo→Hetzner via usuário dedicado arcapi (grupo docker, sem root) com wrapper docker-only — comandos não-docker são bloqueados no nível do authorized_keys. ARC_TOKEN é injetado via docker exec após o start (não aparece em docker inspect). git clone limitado por timeout 60. Idle timeout do WebSocket: 120s. SSE docker logs limitado a --since 1h. Prevenção de IDOR: todos os endpoints conferem container.user_id === req.userId. Flags de segurança no docker run: --cap-drop=ALL --security-opt=no-new-privileges --cpus=1.5 --memory=2g --pids-limit=200. Volumes: arc-{id}-workspace:/workspace, arc-{id}-claude:/home/arcuser/.claude, arc-{id}-ssh:/home/arcuser/.ssh. Lifecycle (#141): GET /cloud/status sempre atualiza last_active. Idle por 30 min → docker pause (cron a cada 5 min, scripts/cloud-lifecycle-cron.ts). Wake: mensagem no CRM, mensagem no TG, upgrade WS → docker unpause automaticamente.

Waitlist (#134):

Método Caminho Descrição
POST /cloud/waitlist Entrar na fila. Idempotente. Response: { position, status, joined_at, message }. 409 se já estiver no plano cloud ou já tiver um container.
GET /cloud/waitlist/status Status próprio na fila. Response: { position, status, joined_at, invited_at } ou { status: "not_joined" }.
GET /cloud/waitlist Somente admin. Lista completa + stats. Response: { stats: { total, waiting, invited, activated }, list: [...] }.
POST /cloud/waitlist/invite Somente admin. Convidar usuário. Body: { user_id }. Define status=invited + faz upgrade automático do plano para cloud. Response: { ok, user_id, position }.

Billing (Phase 51 → #202 Plata by mono)

Phase #202: o Stripe foi substituído pelo Plata by mono (internet acquiring do monobank). Assinaturas recorrentes via tokenization (o cartão é salvo no primeiro pagamento).

Método Caminho Descrição
GET /billing/status Plano atual, limites, uso e features. Response: { plan, status, current_period_end, next_billing_date, plata_masked_pan, limits, usage, features, pricing, can_upgrade, plata_ready }
POST /billing/checkout-session Cria invoice Plata com tokenization. Body: { plan: "min"|"cloud", success_url?, cancel_url? }. Response: { url, invoice_id, plan, amount_uah }. 503 se PLATA_MERCHANT_TOKEN não estiver no vault.
POST /billing/webhook Callback do Plata (SEM auth do CRM — verificado pelo header X-Token). Status: success (ativa o plano + salva o cardToken), failure/expired (incrementa billing_failures, 3+ → downgrade para free). Idempotente via tabela plata_events.
POST /billing/cancel Cancelar assinatura (downgrade para free). Pausa o container Docker do plano cloud. Response: { ok, plan: "free" }.

#205 (2026-05-26): a rota legada /billing/portal-session foi removida junto com o código morto do Stripe. Use /billing/cancel para cancelar uma assinatura.

Limites por plano (semântica OR):

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 com ownerChatId is not defined por 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
POST /projects/create-with-team Criação atômica de projeto + workers + (opc.) bot TG em uma única requisição — body: {project, workers[], telegram?}; rollback em caso de erro
GET /projects/suggest-preset Sugestão de preset por nicho — query: niche=<text>; retorna {preset_id} com base em keyword map
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 /workers #304 Phase A — todos os workers de todos os projetos do usuário atual. Response: { workers: [{ id, label, icon, type, model, tools, context_assets, project_name }] }. Filtragem por owner_id (multi-tenancy). O CEO vê todos os projetos.
GET /workers/presets #228 — preset library global (project-agnostic). Retorna 13 workers do config/workers_registry.json canônico: { presets: [{ id, label, icon, type, model, max_turns, tools, system_prompt, context_assets, focus_dirs, prompt_style }] }. Usado pelo WorkerCreationWizard no Step 1.
GET /workers/templates #304 Phase I — templates do usuário atual. Response: { templates: [{ id, name, description, config, is_public, created_at }] }.
POST /workers/templates #304 Phase I — salvar/atualizar template. Body: { name, description?, config }. Response: { ok, id }.
DELETE /workers/templates/:id #304 Phase I — excluir template (somente o dono). Response: { ok }.
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/avatar #304 Phase D — fazer upload de avatar (multipart file, JPEG/PNG/WebP, máx 2 MB). Verificação de magic bytes. Salva em data/worker-avatars/, grava em worker_avatars (migration 043). Response: { ok, url }.
GET /projects/:name/workers/:id/avatar #304 Phase D — obter o avatar binário (Content-Type conforme o MIME). 404 se nenhum avatar foi enviado.
DELETE /projects/:name/workers/:id/avatar #304 Phase D — excluir avatar, redefinir avatar_pack='role' no JSON do worker.
GET /projects/:name/workers/:id/activity #306 — feed de atividade do worker (últimos 50 eventos). Merge de: activity_log (actor=workerId) + project_issues.activity (author=workerId) + token_usage_log (snapshots diários). Response: { events: [{ type, title, detail, when }] }. Types: git_commit, skill_loaded, skill_unloaded, issue_pick, issue_close, issue_log, token_budget, session_start.
GET /projects/:name/workers/:id/runtime #306 — runtime state do worker. Response: { status: 'working'|'idle', status_started_at, tokens_today, tokens_pct, tokens_cap, current_skill }. Lê primeiro de workers_runtime_state (migration 045); fallback de staleness: status='working' + tmux morto + updated_at > 10 min → idle (detecção de crash). Cap diário baseado no plano via lookup em subscriptions.plan: free=100K, starter=400K, starter_cloud=2M, beta=sem medição (retorna tokens_cap: null, tokens_pct: 0). Intervalo de polling 15s.
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?, locale?} (#124: eventos locale_active/locale_switch). 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_turns tem padrão 20 (anteriormente era 5, 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

#210 (2026-05-26): o DB (skills_global) agora é o writer SSOT. Salvamentos via UI vão primeiro para o DB; .claude/skills/<name>/SKILL.md é gravado como artefato para que o Claude Code CLI descubra skills automaticamente. As gravações legadas em skills/<name>.md foram removidas — os arquivos existentes não são mais lidos nem mantidos. Helper de migração: scripts/migrate-skills-to-db.ts.

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. Phase 71.5: dispara syncWiki → re-embed Cohere (fire-and-forget; falhas são logadas, o write não quebra).
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
GET /projects/:name/rag/search?q=...&k=6&include_global=true&doc_types=wiki,issue,skill,transcript Phase 71.7 (#364): busca semântica sobre embeddings + embeddings_vec (Cohere + sqlite-vec). Parâmetros: q (texto da consulta), k (1-25, padrão 6), include_global (padrão true — merge com o namespace de skills _global_), doc_types (subconjunto separado por vírgulas; Phase 73.6 adiciona o type transcript). Response: `{ query, project, hits: [{rank, doc_type, doc_id, chunk_ix, distance, scope: 'project'
POST /projects/:name/memory/refresh Phase 71.8 (#365): re-embed do MANIFEST + ROADMAP + arquivos-chave no RAG store (antes — sync para o NotebookLM). Mesmo endpoint, nova semântica.
POST /projects/:name/memory/fetch-artifact Removido na Phase 71.8 (audio overview não tem equivalente RAG) — retorna 410 Gone.
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)

GET /docs/file — query: path (obrigatório), lang (opcional)


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


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:


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

Sprint de pentest white-box — mudanças de comportamento dos endpoints após o fix de 3×P1 + 4×P2 + 3×P3:


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

Mudanças de comportamento em endpoints de auth + admin (correções P0 do audit Sentinel):


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

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.

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

Mudanças de comportamento dos endpoints após hardening de 13 × P1:

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

Novos endpoints para sign-in via magic-link:

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).

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.

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:

Mudanças em claude-runner.ts:

Mudanças de UI (não API):

Arc Help (Phase 61 / #147)

Histórico (Phase 61 / #153):

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

Right to Erasure — DELETE /api/auth/account (#162)

Exclui permanentemente o usuário autenticado e todos os seus dados (GDPR Art. 17).

Password Version / Invalidação de Tokens (#174)

A migration 035 adiciona password_version INTEGER NOT NULL DEFAULT 0 em users. Na troca de senha, password_version é incrementado. O payload do JWT inclui o campo pv. O crmAuthMiddleware valida pv contra o DB a cada requisição, rejeitando tokens emitidos antes da última troca de senha (401 "Token invalidated — please log in again"). Fail-open se o DB estiver indisponível.

Cron de Retenção de Dados (#168)

O master bot executa uma limpeza diária no startup + a cada 24h. Limites de retenção: chat_messages 180 dias (por timestamp), activity_log 365 dias (por created_at), auth_events 90 dias (por ts), token_usage_log 730 dias (por created_at unixepoch), export_audit_log 365 dias (por exported_at). Não-fatal — a limpeza não bloqueia o startup.

Compliance de Email (#167)

Todos os emails transacionais de saída (reset de senha, verificação, magic-link) agora incluem:

Segurança — Verificação de Senha Vazada via HIBP (#171)

Em POST /api/auth/register e POST /api/auth/reset-password, a senha enviada é verificada contra a API de k-anonimato do HaveIBeenPwned antes de ser armazenada. Apenas os 5 primeiros caracteres hex do hash SHA-1 são enviados ao HIBP — a senha completa nunca sai do servidor. Se a senha aparece em qualquer banco de vazamentos com count > 0, a requisição é rejeitada com HTTP 400: "This password was found in a known data breach. Please choose a different password." Fail-open em timeout/erro do HIBP (timeout de 4s) — um HIBP fora do ar não bloqueia o cadastro.

Portabilidade de Dados — GET /api/auth/export (#163)

GDPR Art. 20 — Right to Data Portability. Retorna um arquivo JSON estruturado contendo todos os dados pessoais que o Arc OS mantém sobre o usuário autenticado.

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

Mudanças de comportamento em POST /api/crm/help/chat (sem mudança na superfície da API):

Endurecimento da Disciplina de Workers (#187, #188, #189, 2026-05-23)

Expansão de Status de Issues (#187)

PUT /api/mcp/issues/:project/:id agora aceita valores de status estendidos:

Status Significado
open Ainda não iniciado
in_progress Em trabalho ativo (definido por arc issue take)
blocked Aguardando dependência externa
deferred Adiado (antes era armazenado apenas como texto)
closed Concluído

Novo campo assignee: issues agora têm assignee: string | null. Definido via arc issue take <id> ou --assignee <worker_id> em arc issue update.

Migration 036: ALTER TABLE project_issues ADD COLUMN assignee TEXT (nullable, aplicada automaticamente no start do servidor).

Comando CLI arc issue take <id> (#187)

Atalho para assumir um issue: define assignee = current_worker_id, status = in_progress, registra a atividade, grava o estado da sessão. Equivalente a:

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

Validação do Hook commit-msg (#187)

.githooks/commit-msg agora valida os issues #N referenciados contra o issues/issues.json local:

Injeção do PROJECT_MANIFEST.md no Bridge (#188)

handleCliInit (shared/cli-routes.ts) agora lê PROJECT_MANIFEST.md da raiz do projeto e o injeta no bloco CITADEL sob ## Project Context. Limite: 8000 chars. Isso dá aos bridge workers (rodando em máquinas de clientes via arc) acesso compacto à arquitetura, padrões de segurança, estrutura de arquivos e learnings-chave do CLAUDE.md completo.

Posicionamento: depois de PROJECT_RULES.md, antes da lista de skills.

Campo de Config de Worker context_assets (#189)

A config de worker em workers_registry.json suporta context_assets: string[] opcional — lista de nomes de skills que são injetadas automaticamente em cada sessão bridge daquele worker (sem exigir arc skill <name>):

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

O conteúdo de cada skill é injetado sob ### Auto-Loaded Skills → #### Skill: <name>, truncado em 3000 chars cada.

Phase 62 — Voice Input (#373, 2026-06-05)

Transcrição de voz em tempo real via proxy para o servidor whisper.cpp self-hosted (arc-whisper.service, porta 19214, modelo ggml-base pré-carregado).

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

Transcreve clipes de voz curtos (ditado no chat). Faz proxy do áudio para o whisper-server local e retorna o texto.

Auth: Bearer token (ou query ?token=).

Body: multipart/form-data

Campo Tipo Notas
audio Blob webm / ogg / wav. Máx 25 MB.
locale string BCP-47, ex.: uk-UA, en-US. Passado como param language ao whisper.

Response 200:

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

Códigos de erro:

Código Significado
400 Campo audio ou locale ausente
413 Áudio acima de 25 MB
429 Cota diária atingida (60 min/usuário/dia) OU servidor ocupado (máx 2 transcrições concorrentes)
502 whisper-server retornou non-200
500 Falha inesperada

Rate limit: voice_usage_log (migration 051) rastreia segundos aproximados por (usuário, dia) usando o tamanho em bytes do upload como proxy (assume codec de voz ~32 kbps, precisão ±30%). Hard cap: 3600 s / dia. Requisições que excederiam o cap retornam 429 antes de encaminhar ao whisper.

Nota de arquitetura: o whisper roda apenas no Contabo (não dentro dos containers Hetzner por usuário). Os bytes de áudio nunca saem do Contabo; o texto resultante é o que o roteamento de cloud-chat da Phase 70 vê. O arc-whisper.service mantém o modelo ggml-base pré-carregado, então o custo por chamada é inferência pura (~3,4 s warm para 11 s de áudio, 3,1× realtime na atual box EPYC de 6 vCPU).


Phase 73 — Transcrição + Análise de Reuniões (#377-#384, 2026-06-05)

Faça upload de áudio/vídeo de reunião em um projeto, obtenha transcrição whisper + resumo do Claude, opcionalmente embedados no RAG. Todas as rotas passam por canAccessProject (owner ou admin).

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

Upload multipart, retorna 202 com transcript_id + job_id + status:'queued'. O job é coletado pela fila in-process (máx 1 concorrente).

Campos do body:

Limites: upload máx de 1 GB, allow-list de MIME (mp3/wav/m4a/aac/ogg/opus/flac + mp4/mov/webm/mkv).

Erros: 400 (campo ausente / MIME inválido), 401, 413 (acima do cap), 500 (escrita em disco).

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

Lista transcrições do projeto, com paginação por cursor. Query: ?limit=20&cursor=<id>. Retorna {items: TranscriptSummary[], next_cursor: number|null}.

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

Linha completa incluindo transcript_text, summary_json (parseado para objeto) e frames_json (parseado quando a Phase 73.4 estiver no ar).

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

Stream SSE do progresso do job. Envia event: progress com {status, progress_pct, step_label, error} sempre que qualquer campo muda, mais heartbeats de comentário : keep-alive a cada 1s para que o idleTimeout de 10s do Bun não mate execuções longas do whisper. Fecha com event: end quando o status é terminal.

Auth: o EventSource do browser anexa ?token=<bearer> (não consegue definir header Authorization).

Status terminais: done (após Phase 73.6: embed no RAG + limpeza de arquivos), failed. Nota: summarized é uma etapa transitória — o SSE permanece aberto por embeddingdone. O botão de envio no frontend é liberado em summarized (não espera o RAG).

Máquina de estados (Phases 73.1-73.6)

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

Shape do JSON de vision frames (Phase 73.4, #380)

Armazenado como string JSON em transcripts.frames_json (parseado de volta para objeto pelo GET /transcripts/:id).

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

Hard cap MAX_FRAMES=50 por transcrição (~$0,15 no pior caso ao preço típico de vision do Sonnet). Frames acima do cap são descartados silenciosamente; a última descrição mantida recebe o sufixo [+N more frames dropped]. Falhas por frame viram strings [vision failed: <msg>] — não abortam o passe. Frames descritos como "No informational content" são apenas webcam ou decorativos.

Shape do JSON de resumo (Phase 73.5, #381)

Armazenado como string JSON em transcripts.summary_json. Parseado de volta para objeto pelo GET /transcripts/:id.

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

A resolução da chave Anthropic espelha shared/worker-spawn.ts: BYOK account_settings.anthropic_key (descriptografada se criptografada), fallback PLATFORM_ANTHROPIC_KEY para owners em trial-mode. Falhas de resumo são não-fatais — o transcript_text permanece intacto, o status volta para transcribed/frames_extracted para que o usuário possa tentar de novo após corrigir a chave.

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

Notas por projeto no estilo NotebookLM. Cada nota é uma coleção de fontes (vídeo, áudio, YouTube, web, PDF, DOCX, TXT, imagem) com índice RAG compartilhado e chat.

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

Retorna todas as notas do projeto. Auth obrigatória + canAccessProject.

Response 200:

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

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

Criar nova nota.

Body: { "title": "string", "description": "string?" }
Response 201: { "id": 1, "title": "Sprint planning" }

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

Detalhe da nota com fontes, vínculos a issues e histórico de chat.

Response 200:

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

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

Exclui a nota e todas as fontes/chats. Cascateia para note_sources, note_chats, note_issue_links.

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

Adicionar uma fonte (upload de arquivo ou URL).

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

Response 201: { "source_id": 5, "status": "queued" }

O processamento é assíncrono. Faça polling em GET /notes/:id até source.status === "done".

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

Renomear uma fonte (edição inline do título).

Body: { "title": "New name" }
Response 200: {}
Passe string vazia ou null para resetar ao padrão (nome de arquivo/URL).

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

Remove uma fonte e seu conteúdo.

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

Stream SSE do progresso de processamento da fonte.

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

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

Enviar mensagem ao chat da nota. Resposta em stream SSE.

Body:

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

selectedSourceIds é opcional — omita para incluir todas as fontes.

Eventos SSE:

Estratégia de RAG: busca sqlite-vec nos embeddings de note_source → fallback de injeção direta do content_text (máx 80 K chars) quando a busca vetorial está indisponível ou sem resultados. Guard anti-alucinação injetado no system prompt quando fontes não processadas são incluídas.

Tool use — create_issue: o Claude pode criar issues do projeto a partir do chat. Multi-turn: o turno 1 faz streaming até a tool call, o backend executa (issueQueries.nextId + issueQueries.insert), o turno 2 retoma o streaming com o resultado da tool injetado.

Máquina de estados do status da fonte

queued → processing → done
                    ↘ error

Valores do campo status da fonte:

Estratégia de transcript do YouTube (Phase 78.3)

  1. npm youtube-transcript: cascata de idiomas ["en", "en-US", "en-GB"] → fallback qualquer
  2. API Supadata.ai: GET https://api.supadata.ai/v1/youtube/transcript?url=...&text=true&lang=en → fallback sem o param lang
  3. yt-dlp + Whisper: fallback final para vídeos sem legendas

Prioridade: preferir legendas em inglês para evitar transcripts auto-traduzidos em árabe/outros idiomas.