name: collect-api-endpoints description: Collect a comprehensive list of QuantPilot API endpoints (HTTP + WebSocket) across all QP repos for pentest scoping. Walks FastAPI / NestJS / Next.js / MCP / Go-oapi-codegen / OpenAPI / AsyncAPI sources with a deterministic Python extractor (no LLM guessing), produces api_endpoints.json for review, then — only on explicit user go-ahead — publishes to the security Notion database. Use when the user asks to collect/list/inventory QP endpoints, refresh the pentest scope list, or sync endpoints to the security Notion DB.
collect-api-endpoints
Purpose
Produce a reviewable inventory of every HTTP + WebSocket endpoint that QuantPilot exposes, with each endpoint tagged by a single flat auth enum: public, jwt, okta, api-key, signature, or unspecified. This mixes scope and mechanism in the way pentesters think about credentials (what to attempt against each endpoint). Output is published to a Notion database so the list stays maintained and diffable.
| Auth value | Meaning | Example triggers |
|---|---|---|
public |
no auth gate | allow_unauthenticated_paths, no @UseGuards, no middleware |
jwt |
user JWT bearer | FastAPI get_current_user, NestJS AuthGuard('jwt') |
okta |
Okta SSO (admin/staff) | FastAPI require_admin, NestJS OktaAuthGuard |
api-key |
service/integration API key header | X-Service-API-Key, AgenticApiKeyGuard, MCP x-api-key |
signature |
webhook HMAC signature check | verify_internal_secret, Stripe/Anthropic webhooks |
unspecified |
could not determine | OpenAPI/AsyncAPI rows with no implementer in scope |
- Extraction is deterministic — a Python script walks source code, spec YAML, and generated OpenAPI files. It does not ask an LLM to enumerate routes.
- Two phases, strictly separate. Phase 1 writes local files for user review. Phase 2 publishes to Notion, and only runs when the user explicitly asks for it.
Target Notion database: https://www.notion.so/3496b755601180739c4ce3c24b0708e4 (DB id 3496b755601180739c4ce3c24b0708e4).
Required inputs
This skill reads repos from Claude Code's current working-directory list (what /add-dir has attached to the session). Do not ask the user for paths or workspace roots — list what's attached and map each directory to a role.
| Role | Expected repo | Contributes | Required? |
|---|---|---|---|
| agentic-backend | 3commas-io/quantpilot-agentic-backend |
FastAPI HTTP + WS routes | recommended |
| frontend | 3commas-io/quantpilot-frontend |
NestJS (apps/api) + Next.js (apps/landing) |
recommended |
| mcps | 3commas-io/quantpilot-mcps |
MCP server HTTP + tools | recommended |
| mdm | 3commas-io/mdm |
Implements eck spec — middleware → auth level | recommended (else eck rows land as Unspecified) |
| sbm | 3commas-io/sbm |
Implements sbm + sbm-ws specs | recommended |
| ecm | 3commas-io/ecm |
Implements ecm spec + inline /health |
recommended |
| eck | 3commas-io/eck |
Dev client GET / + WS /ws only |
optional |
| common | 3commas-io/common |
OpenAPI + AsyncAPI specs (eck, ecm, sbm, sbm-backtests-ws) | required |
Out of scope (explicit): legacy 3commas Rails, app-3commas-frontend, 3commas-frontend, 3commas-ui, nextjs-docker. These are the main 3commas.io product, not QuantPilot. Do not scan them unless the user explicitly expands scope.
Behavior — run this sequence
Phase 1 — extract & verify (default; never publishes)
Enumerate working directories attached to the session. The session system prompt lists a primary working directory plus "Additional working directories";
/add-dirupdates the list. Gather all of them.Map each directory to a role. First try the directory basename against the expected-repo names. If a basename doesn't match (e.g. the user cloned into a different name), run a quick fingerprint check with
Read/Bash:common: hasopenapi/*.yamlandasyncapi/*.yamlunder the repo root.agentic-backend:src/application/__init__.pyexists.frontend:apps/api/src/andapps/landing/app/both exist.mcps:servers/<name>/andpackages/core/both exist.mdm: Go repo whosecmd/server/main.goimportsgithub.com/3commas-io/common/api/eck/v1(aliaseckapi).sbm: Go repo whosecmd/server/main.gocallsv1.HandlerFromMuxWithBaseURL(..., "/sbm/v1").ecm: Go repo whosecmd/server/main.gomountsapi.HandlerFromMuxWithBaseURL(..., "/ecm/v1").eck: Go repo withapps/marketdata+apps/kora+cmd/client/web.go.
Print a coverage table and stop for any missing recommended/required role. Example:
role status path ----------------- --------------- -------------------------------------- common OK /Users/you/3com/common agentic-backend OK /Users/you/3com/quantpilot-agentic-backend frontend OK /Users/you/3com/quantpilot-frontend mcps OK /Users/you/3com/quantpilot-mcps mdm OK /Users/you/3com/mdm sbm MISSING — ecm OK /Users/you/3com/ecm eck MISSING (opt.) —For each missing recommended/required role, tell the user exactly what to do (do not try to clone yourself):
Cannot scan: 'sbm' is not attached. Either: - /add-dir /path/to/your/sbm checkout, OR - if you don't have it cloned, run gh repo clone 3commas-io/sbm ~/3com/sbm then /add-dir ~/3com/sbm Re-invoke the skill when done.Missing
commonis a hard stop. Missingeckis a warning only — proceed with a note in the report.Invoke the extractor. The script lives at
${CLAUDE_PLUGIN_ROOT}/skills/collect-api-endpoints/scripts/collect_endpoints.py. Pass one--role key=<absolute-path>for every role that resolved; omit skipped roles entirely. Write outputs next to the current working directory so the user can inspect them:python3 "${CLAUDE_PLUGIN_ROOT}/skills/collect-api-endpoints/scripts/collect_endpoints.py" \ --role common=<abs> \ --role agentic-backend=<abs> \ --role frontend=<abs> \ --role mcps=<abs> \ --role mdm=<abs> \ --role sbm=<abs> \ --role ecm=<abs> \ [--role eck=<abs>] \ --out-json ./api_endpoints.json${CLAUDE_PLUGIN_ROOT}is set automatically when a plugin skill runs; if it isn't present (dev invocation), fall back to resolving the script via the skill's own directory.Relay the summary to the user. The script prints a summary to stdout (totals per repo, per auth, parse warnings, skipped sources). Show that summary and the path to
./api_endpoints.jsonso the user can open it in their editor. Stop here. Do not invoke any Notion tool in Phase 1.
Phase 2 — publish to Notion (only on explicit user go-ahead)
Only run when the user asks to publish (e.g. "publish to Notion", "sync the list to Notion").
Dry-run first. Print what will be created/updated without hitting Notion:
python3 "${CLAUDE_PLUGIN_ROOT}/skills/collect-api-endpoints/scripts/collect_endpoints.py" \ --publish ./api_endpoints.json \ --dry-runThe script reads the JSON from Phase 1 and prints the intended payloads (one per row). Do not write the payloads to Notion yet.
Publish. When the user confirms after seeing the dry-run, run without
--dry-run. Claude is responsible for calling the Notion MCP tools; the script produces the payloads and Claude submits them viamcp__claude_ai_Notion__*against database id3496b755601180739c4ce3c24b0708e4. Match rows on(Repository, Method, Path)and update in place; create new rows otherwise. Never mutate the DB schema silently — if a column is missing, tell the user and stop.
Safety rules
- Do not clone repos. If a repo is missing, print the
gh repo clone ...//add-dir ...hint and stop. - Do not publish to Notion in Phase 1, ever.
- Do not mutate the Notion schema. If columns are missing, surface the list and ask the user.
- Do not touch any repo listed under "Out of scope" unless the user explicitly expands scope.
- The script is the source of truth for extraction. Do not second-guess its output by reading routes yourself — if the user suspects an extractor bug, reproduce it with the script and fix the script.