Documentation
How the workspace fits together: chatting with your research, ingesting documents into the search layer, organizing private and group libraries alongside the shared corpus, and paying for usage with credits.
Getting started
Sign in from the Chat page. New registrations must use an email on the admin allowlist, and you need to verify your inbox before you can use the workspace.
For how data and research content are handled, see Privacy and Terms of use.
Preferences
Open Settings to manage your profile, API keys, and model preferences. The Models card is separate from profile fields — save each card with its own button.
Choose which Claude models power your research runs. Each dropdown offers three models —Claude Sonnet 5.5, Claude Opus 5.5, and Claude Haiku 5.5 — plus Workspace default to inherit the deployment baseline.
When you pick a specific model, the card shows Anthropic list pricing per million tokens (input, cache writes, cache hits, and output). Your workspace may apply additional platform charges on top; see Credits & billing.
POST /copilot/api/ask has no per-request model fields. API runs use the models stored on your profile; update them with PATCH /auth/profile (see Chat API: models and profile).Assistant
The chat UI is built around threads — each thread keeps its own title, message history, and uploaded attachments.
While the model works, you may see agent steps, tool activity, and downloadable artifacts (tables, plots, exports) tied to the assistant reply. Failed answers can be retried from the error state when available.
You can run several chats at the same time. Start a long answer in one thread, switch to another in the sidebar, and send a new question there — each thread keeps its own in-flight run on the server. Switching threads does not cancel the run you left behind.
Integrations
Connections links your personal accounts to Financial Research Copilot so you can pull files in, push chat output out, and wire packaged agents to external systems. Open the Connections panel from the Chat header (and from Ingestion when cloud import is available). Each link is per user — not shared across the workspace.
A connector shows Connected when your account is linked, Not connected when the server supports it but you have not signed in yet, or Unavailable when the workspace has not configured that provider (ask an admin to enable it on the backend).
.md / .markdown artifacts, use Save to Feishu Docs.Integrations
MCP (Model Context Protocol) connects the assistant to external tool servers your workspace exposes — data APIs, search, or custom backends. Admins register each server (name, URL, description, on/off) under Admin → MCP servers. Only servers marked enabled appear in chat.
In the chat composer, with the General Research Copilot agent (see Packaged agents), set Tools to:
GitLab and Synology also appear in the Tools list when enabled, but they only activate for your user after the matching Connection is set up (GitLab also requires a linked project and branch). Selecting GitLab without a linked project, or Synology without a connected NAS, will not attach working MCP tools for that server. For GitLab, the backend injects your linked project path and branch into the prompt so the model knows which repository to use.
Reuse
Skills are reusable capabilities you invoke from chat with a slash id (for example /my_skill). Manage them from the Skills page; in the message box, type / to open suggestions for enabled skills.
custom/... packages with a SKILL.md and optional scripts) are different — they mount only on the custom agent that references them. Switch to General Research Copilot if you need slash instruction or file skills on a message.Automation
Use /schedule in a chat thread to run the same agent prompt on a recurring cadence. Schedules use your browser timezone and bill credits on each run like a normal chat turn.
/schedule every friday 9am Append latest flows to the dashboard/schedule list — list tasks bound to this thread/schedule stop task_abc123 — disable a taskScheduled runs reuse the thread session when possible. The agent sees thread uploads, prior outputs, and your stored prompt each time and decides which files to continue from.
Agents
The Agent control in the composer selects which research workflow backs the thread. General Research Copilot is the default: full knowledge scope and MCP tools in Auto or Manual mode, plus slash skills.
The other options are packaged agents: curated builtins plus any custom agents you published (or that were shared to you). Custom slugs look like custom/you/agent-name. Author them in Agent Designer; browse shared ones in the gallery.
Curated builtins include:
On those builtins the scoper records the issuer's query languages and local-script names. Web and broker-research search then run in each listed language, not English only, so a name whose home-market record is not in English is not treated as if that record were silent.
For packaged agents, the UI shows Preset MCP stack instead of the workspace MCP picker. Knowledge and manual MCP selection are disabled so the agent can follow its own retrieval and data rules.
After a packaged-agent turn, the research rail can show a short version label (a git SHA on builtins, or the publish release label / definition hash on custom agents). Custom labels are not git links.
If you later want follow-ups against ingested knowledge — changing Knowledge scope, using slash skills, or choosing MCP servers manually — switch the Agent control to General Research Copilot and continue in the same thread. Prior turns stay in the transcript so you do not need a new chat.
Custom agents
Designer is a canvas for your packaged agents: an orchestrator plus specialist sub-agents, each with tools, MCP servers, and skills. The same definition Chat runs after you publish. Nothing on the canvas changes how a live turn executes until you save and publish.
read, grep, glob, write, edit, bash), builtin skills, and your custom skills. Drag onto the orchestrator or a selected sub-agent.Open Skills in the header. These are not slash skills. A Designer skill is a custom/... package: SKILL.md plus optional scripts (for example scripts/compute.py). Suggested slug: custom/you/skill-name. Visibility is private or public. Scripts ship into the agent sandbox; prompts should tell the specialist to run those scripts instead of re-deriving the math.
Beyond Markdown, a published agent can return two kinds of rich block, both drawn by the chat UI. Each has a builtin skill that documents the exact payload, and both are prompt-level conventions rather than API calls: attach the skill in Skills so the agent has the spec, and the platform strips the raw block from the draft and renders the widget in its place.
interactive-chart-blocks. The agent emits a fenced chart: block holding a small declarative JSON spec, and the answer renders a live chart (hover tooltips, legend, dual axes) instead of a static image or a Markdown table of the same numbers. Bar, line, area, composed, scatter and pie.interactive-form-gate. The agent emits an analyst_input_request block, and the answer renders a form the reader fills in; their typed answers come back to the agent as a table it parses. Used for confirm gates and any values only the reader has.Two things to know when wiring either one up. The chart and form renderers live in the frontend, so an agent can emit these blocks before the frontend that draws them has shipped, in which case they show as plain code blocks. And a builtin skill resolves from the agents service's own skill directory, so the service must already carry the skill; attaching one that the deployment is missing fails the run rather than skipping the skill.
custom/ (for example custom/you/test-agent-02) and freezes an immutable version. Optional release label (for example v1); if omitted, the definition hash is stored so Chat can show a version chip.public). Workspace agents appear in the gallery. There is no open-web share: Gallery and Chat require a signed-in workspace account.Republishing keeps the slug and increments the version. Chat's Agent control lists published custom agents next to the builtins. On a packaged-agent turn you can press Debug at the right end of the composer toolbar (next to Send) to inspect coordinator and sub-agent input, output, files, and steps when the run used the agents VM or Claude (thread owner only). The toggle is stored per agent. General Research Copilot does not show Debug.
frc_ key can author agents at /copilot/designer/... and run a published slug with POST /copilot/api/ask. See API: Designer.Custom agents
Gallery lists published custom agents shared as Workspace or Internet. Search by name or slug. Sort by Trending (default), Recent, or Most liked. Each card shows the prompt preview, sub-agent count, MCP allowlist, author, run count, thumbs up/down, and note count.
custom/you/... slug. Forking does not change the original and does not copy runs, votes, or comments.Private and group-shared agents do not appear in the gallery. Your own published private agents still show in Designer and in Chat's Agent control.
Data plane
There are several ways content enters the system — pick the one that matches your goal:
.pdf), Word (.docx), Excel (.xlsx), and PowerPoint (.pptx) — modern Office Open XML only, not legacy .doc / .xls / .ppt. Import from Synology via From Synology NAS after you connect your NAS; use the file-type filter when browsing folders to narrow the list. You must be signed in. Each file becomes its own run; use the Runs tab for live status and per-paper stats. Optional toggles include Dry run (parse without writing to the vector store) and Only ingest new papers (skip content already present). Use Sharing settings on the ingest form to target Private (only you), Group (members of selected knowledge groups), or Shared (workspace-wide shared corpus — admins only). Standard users can ingest to Private or Group for groups they belong to; they cannot publish to the shared corpus.entries array alongside the pool's settings. A company row takes name — a bare string works as shorthand — plus optional code (the Gangtise gtsCode), scope (foreign, domestic, or both; blank inherits the pool's broker toggles, which the Target column shows as a dashed badge), and keywords (extra search terms when Gangtise has no exact security). Optional comparables (also comps / peers) lists peer companies ingested in the same broker job as that row — the same idea as comps on the Broker reports tab, not a second watchlist column type. A NAS row takes path and an optional file_type. Column names are forgiving (company, ticker, folder and similar all work) since the file usually comes out of a spreadsheet. On upload each row is checked and reported back: Resolved (matched a Gangtise security), Keyword (no exact match, so it will search the knowledge base by name instead), NAS folder, or Rejected. Rejected rows are skipped; the rest import.code is the Gangtise security identifier (600519.SH, TSM.N, 00700.HK) and it is what actually drives discovery; the name is a label. Lookups are a direct Gangtise search with no AI canonicalization, and only an exacthit on a ticker or on the security's own display name counts — so supply the name Gangtise itself shows (贵州茅台, not Kweichow Moutai), or the code. A near-miss is reported with the closest candidate so you can paste its code into the row. If a row carries both and they disagree, the code wins and the row is flagged as a Mismatch — nothing is mis-ingested, but the pool would otherwise show one company while ingesting another. Rows are relabelled with the canonical Gangtise name so the list stays consistent.PUMA scores against Puma Biotechnology (PBYI.O) and Puma SE against 西南证券, both entirely different companies. That is why only exact matches become a code; everything else becomes a Keyword row that searches the knowledge base by name and never touches security resolution. Pool such names by leaving code out and listing every term worth searching under keywords — e.g. {"name": "Puma SE", "keywords": ["Puma SE", "PUMA", "Puma"]}, since the short ticker usually has better recall than the formal name and the results of both are combined.Runs. The Runs tab lists your ingestion jobs with live status. Use the status tiles (queued / running / succeeded / failed) to narrow the list, and the search box to match titles, company names, filenames, or other job text. Clear filters restores the recent-runs view. Select a row for per-document summaries and errors.
SSRN PDFs. Upload a working paper you downloaded from SSRN through Ingestion → Local documents (or attach it from chat for a single thread). The pipeline recognizes common filenames such as ssrn-4098475.pdf. In chat, mention the paper in your question or use the rag knowledge-base tool to retrieve from ingested SSRN uploads.
Library
Indexed research lives in one of three visibility tiers. Each tier controls who can retrieve a paper in chat and where it appears in the UI:
The Knowledge page is for private and group libraries only — not the shared corpus. Use it to review what you have indexed, delete papers you own (or any paper if you are an admin), and change sharing on a paper (move between private, group, or shared) when you have permission. The shared-corpus browser uses the same search controls.
Keyword vs Semantic search. Library browsers offer a Keyword | Semantic toggle:
Developers
Mint a personal API key in Settings to call programmatic routes as your account. One key covers four route families:
/ingestion/...) — index documents into knowledge libraries./copilot/api/...) — ask questions, attach files, and collect answers plus output artifacts. Includes GET /copilot/api/agents (runnable slugs), POST /copilot/api/ask with a builtin or published custom/... agent_slug, and Sweeps (/copilot/api/sweeps) for batch runs across a portfolio./copilot/designer/...) — author skills, drafts, publish, share, and fork. Synchronous JSON (not jobs)./auth/...) — read and update profile fields, including agent and composer models used by chat runs.frc_) is shown only once.401.Send the key on every request: Authorization: Bearer frc_.... The browser UI uses your session cookie instead — you do not need a key when using the web app.
Programmatic routes are served by the backend API root, not the Next.js app shell. Build full URLs as:
https://your-domain/api + route path — for example https://frcopilot.qedgeam.com/api/copilot/api/ask.
/api is the FastAPI backend mount — always include it before route paths such as /ingestion/..., /copilot/api/..., or /auth/....Curl examples below use the API_BASE placeholder for that backend root (for example https://frcopilot.qedgeam.com/api).
Instead of wiring calls by hand, download a ready-made frcopilot-api skill that teaches your own AI agent tools — Cursor, Claude Code, Codex, and other SKILL.md-compatible agents — how to use this API. In Settings, open the Agent skill card and choose Download skill (.zip).
The archive holds SKILL.md (routes, examples, and job polling) and .authorization (your key and base URL). Unzip the frcopilot-apifolder into your tool's skills directory — for example .cursor/skills/ for Cursor or .claude/skills/ for Claude Code.
The bundle is generated per deployment, so its apiBasealways points at this workspace's API — the main service or a tenant's own domain. It ships with an empty apiKey: paste a key from API keys into .authorization, or press Download skill with this key in the one-time key dialog to pre-fill it.
Ingestion uploads and chat asks are job-based: the server returns 202 with a job_id, then you poll until status is succeeded or failed. Account routes such as /auth/profile respond synchronously.
job_id.answer + artifacts).Job status values: queued → running → succeeded / failed. You only see your own jobs.
Index documents into your knowledge libraries under /ingestion.
Endpoints
POST/ingestion/uploads
Body: raw document bytes (PDF, DOCX, XLSX, or PPTX). Headers: X-Filename and the document Content-Type. Query options: visibility, group_ids, dry_run, and only_new.
POST/ingestion/youtube
Body: JSON with urls (YouTube video or playlist URLs / IDs). Optional JSON fields: visibility, group_ids, dry_run, and only_new. No X-Filename header is needed.
POST/ingestion/audio
Body: raw audio bytes. Headers: X-Filename and the audio Content-Type. Query options: visibility, group_ids, dry_run, and only_new.
POST/ingestion/broker-reports
Body: JSON with primary (company name or ticker) and optional comparables, include_foreign, include_domestic, domestic_brokers (subset of CICC, CITIC, CSC, Huatai), date_from, date_to, and limit. Query options: visibility, group_ids, dry_run, and only_new. Requires broker-research credentials to be configured (otherwise returns 503).
POST/ingestion/broker-reports/suggest-comps
Body: JSON with primary. Returns a suggested, editable set of comparable companies (name + code) to feed into /ingestion/broker-reports.
GET/POST/ingestion/pools · /ingestion/pools/{pool_id}
Admins only (standard keys get 403). Manage scheduled stock pools. Create body: name, cron_expr, timezone, window_mode (previous_month / current_month / trailing_days), trailing_days, the ingest defaults (visibility, group_ids, dry_run, only_new, include_foreign, include_domestic, domestic_brokers, limit — max reports per Gangtise company job; NAS folders ignore it), and an optional entries array. PATCH updates any of those; DELETE removes the pool and its runs.
POST/ingestion/pools/import
Create a whole pool from a JSON watchlist — the practical way to load hundreds of names. Body: the pool settings plus an entries array (a bare array is also accepted, and creates a pool with default settings). Company rows take name (a bare string works), optional code, scope (foreign / domestic / both, blank inherits the pool default), keywords, and optional comparables (peer companies for the same broker job); NAS rows take path and optional file_type. Names are resolved directly against Gangtise, falling back to a keyword search on a miss. The response reports every row's outcome (code, keyword, folder, error); rejected rows are skipped rather than failing the import. Add ?dry_run=true to get the report without creating anything. Use POST /ingestion/pools/{pool_id}/entries/import (with ?replace=true to swap the list wholesale) to load the same file into an existing pool.
POST/ingestion/pools/{pool_id}/entries
Add one target to a pool. Body: kind (gangtise_code / gangtise_keyword / synology_folder) plus code, keyword + keywords, or synology_path + synology_file_type. Optional scope (foreign / domestic / both) overrides the pool broker toggles for this row, and comparable_refs ingests peer companies alongside the primary. PATCH / DELETE on /entries/{entry_id}.
POST/ingestion/pools/{pool_id}/run · GET/runs · /preview
POST /run queues an immediate run (202 with a run_id) without moving the cron schedule, and returns 422 if the pool has no enabled entries. GET /runs returns run history including the child job_ids to poll via /ingestion/jobs/{job_id}. GET /preview resolves the current window and lists the NAS files each folder entry would match, with no writes. POST /runs/{run_id}/cancel stops a queued or running run (409 once it is terminal): names not yet started are dropped and companies already ingesting finish. Pausing a pool (PATCH enabled: false) only stops the schedule — it never cancels a run.
GET/ingestion/jobs
List recent jobs. Optional query: limit (1–100), q (case-insensitive match on query text, kind, owner, or result titles), status (queued / running / succeeded / failed), and kind (ingestion job kind). Standard users only see their own jobs.
GET/ingestion/jobs/{job_id}
Poll for status and per-document ingest summaries.
GET/ingestion/corpus/papers · /ingestion/my-corpus/papers · /ingestion/group-corpus/papers
Paginated keyword browse for the shared corpus, your private library, or group libraries. Optional filters: source, title, paper_id, abstract, doi, limit, offset. Group listing also accepts group_ids (comma-separated).
POST/ingestion/corpus/papers/search · /ingestion/my-corpus/papers/search · /ingestion/group-corpus/papers/search
Semantic (embedding) search within the same scopes. JSON body: query (required), optional source, limit (default 40, max 100). Group search may include group_ids. Returns top papers by similarity with optional score and match_excerpt — not offset-paginated.
/ingestion/uploads — raw document body; X-Filename header; optional query parameters visibility, group_ids, dry_run, and only_new./ingestion/youtube — JSON body with required urls; optional JSON fields visibility, group_ids, dry_run, and only_new./ingestion/audio — raw audio body; X-Filename header; optional query parameters visibility, group_ids, dry_run, and only_new./ingestion/broker-reports — JSON body with required primary and optional comparables, include_foreign, include_domestic, domestic_brokers, date_from, date_to, and limit; optional query parameters visibility, group_ids, dry_run, and only_new./ingestion/jobs — optional limit, q, status, and kind./papers (GET) — keyword filters and pagination; /papers/search (POST) — semantic query + optional source / limit (and group_ids for group corpus).visibility accepts private, group, or public. Omit it to get your role’s default — the shared corpus for admins, your private library for everyone else (see visibility below).group_ids is required when visibility=group. For raw upload/audio endpoints it is comma-separated in the query string; for YouTube it is a JSON list in the request body.Maps to the same tiers as the UI (see Knowledge libraries & access):
visibility=private) — only you (and admins).visibility=group + group_ids) — members of those groups. Standard users must belong to each group or get 403.visibility=public) — workspace corpus. Admins only; a standard-user key asking for it gets 403.visibilitymeans “use my role’s default”: an admin key publishes to the shared corpus, a standard-user key lands in private. Naming a destination you may not write to is an error rather than a silent redirect, so a standard-user key that passes visibility=public gets 403 and nothing is ingested. Pass visibility explicitly whenever the destination matters.You can submit many uploads concurrently, but the backend processes with limited concurrency. Collect each job_id, then poll — do not assume immediate parallel completion. Re-uploading identical content is safe (de-duplicated).
Poll responsibly: space checks 5-10 seconds apart and back off when many jobs are still pending. For large batches, sweep with GET /ingestion/jobs?limit=N once per cycle instead of hitting /ingestion/jobs/{job_id} for every outstanding job. A job stuck in queued with nothing running for a long time is likely orphaned — resubmit it rather than polling indefinitely.
Example — private upload
curl -X POST "{{API_BASE}}/ingestion/uploads?visibility=private" \
-H "Authorization: Bearer frc_..." \
-H "X-Filename: report.pdf" \
--data-binary @report.pdfExample — poll job
curl "{{API_BASE}}/ingestion/jobs/JOB_ID" \
-H "Authorization: Bearer frc_..."Example — semantic search (private library)
curl -X POST "{{API_BASE}}/ingestion/my-corpus/papers/search" \
-H "Authorization: Bearer frc_..." \
-H "Content-Type: application/json" \
-d '{"query":"gold mining cost curve","source":"upload","limit":20}'Run headless chat under /copilot/api. API chats are real threads owned by your account — they appear in the browser Chat list. Reusing a thread_id restores the agent session and any files attached to the thread.
Chat runs resolve agent and composer models from your stored profile — POST /copilot/api/ask has no per-request model fields. Update models before asking, or rely on what you saved in Settings (see also Settings & models):
Account endpoints
GET/auth/model-presets
List selectable agent and composer models, workspace defaults, and Anthropic list pricing per model.
PATCH/auth/profile
Update profile fields. Set agent_model and/or compose_model to a preset id (e.g. claude-sonnet-5-5) or null to revert to workspace default.
Example — set models for subsequent chat jobs
curl -X PATCH "{{API_BASE}}/auth/profile" \
-H "Authorization: Bearer frc_..." \
-H "Content-Type: application/json" \
-d '{"agent_model": "claude-opus-5-5", "compose_model": "claude-sonnet-5-5"}'Chat endpoints
POST/copilot/api/threads
Create a thread (optional title). Use before attaching files.
POST/copilot/api/threads/{thread_id}/uploads
Attach a file (raw body + X-Filename). Recorded on the thread; auto-attaches on every later turn.
GET/copilot/api/threads/{thread_id}
Thread sharing state, pasteable share_url, and latest_job (same poll object as /jobs/{job_id}).
PATCH/copilot/api/threads/{thread_id}/sharing
Share the thread. Body: visibility (private / group / public / internet) and group_ids when visibility=group. Returns share_url.
DELETE/copilot/api/threads/{thread_id}
Soft-archive the thread (same as the browser Chat delete). Returns 204. The row stays; it leaves the sidebar. 404 if missing, already archived, or not yours.
GET/copilot/api/skills
List slash skills visible to your account (skill_id, name, kind, enabled). Optional query: q to filter.
GET/copilot/api/agents
List packaged agents you may run via agent_slug: builtins plus your published custom agents and workspace-gallery agents (public). Optional query: q to filter. This is the run catalog — authoring lives under /copilot/designer.
POST/copilot/api/ask
Ask a question. Body: question (required; may include /skill_id tokens), thread_id, title, then optionally agent_slug, mcp_mode, mcp_servers, knowledge_scope.
GET/copilot/api/jobs/{job_id}
Poll for answer and artifacts[] (each with download_url) when succeeded.
POST/copilot/api/jobs/{job_id}/cancel
Cancel a queued or running job. Queued jobs become cancelled immediately. Running jobs become cancelling while the agent is interrupted and the run drains to idle; then the job and message are cancelled. Idempotent — a no-op on jobs that already finished.
GET/copilot/api/jobs
List your recent chat jobs. Optional thread_id filters to one thread.
GET/copilot/api/jobs/{job_id}/artifacts/{file_id}/download
Download an output artifact. Add ?inline=true to view inline.
The API-key counterparts of the browser /schedule command. Tasks are bound to a thread and freeze MCP servers, skills, and models at creation time, so recreate a task after republishing an agent.
Scheduled task endpoints
POST/copilot/api/threads/{thread_id}/schedules
Create a schedule. Body: prompt plus exactly one of cron_expr (standard 5-field cron, skips the phrase parser) or schedule_text (e.g. every friday 9am). Optional timezone (IANA, also accepted as client_timezone), human_summary, agent_slug, mcp_mode, mcp_servers, knowledge_scope.
GET/copilot/api/threads/{thread_id}/schedules
Schedules bound to one thread.
GET/copilot/api/schedules
All your schedules. Each task includes thread_title, the name of the thread it runs in.
PATCH/copilot/api/schedules/{task_id}
Body: enabled (pause or resume).
DELETE/copilot/api/schedules/{task_id}
Delete the task.
POST/copilot/api/schedules/{task_id}/run
Fire immediately without moving the cron. Returns as soon as the run is dispatched; 402 when the account has no credit, 409 when a run is already in progress.
GET/copilot/api/schedules/{task_id}/runs
Run history, newest first, each with status, error, answer, and artifact_file_ids.
Scheduled runs are not chat jobs, so they do not appear under /copilot/api/jobs; read the answer from the runs list. Each fire prepends [Scheduled run — task <id> — <ISO timestamp> — run <n>] to the prompt, so a recurring task can read its own as-of date from the envelope.
Optional POST /copilot/api/ask fields (same semantics as the browser composer). Order below reflects how you typically set them:
compounder-identifier-v2. Custom (published Designer) example: custom/you/test-agent-02. Omit for General Research Copilot. Packaged agents (builtin or custom/...) use a fixed MCP allowlist; mcp_mode / mcp_servers and slash skills are ignored. Drafts are not runnable. Unknown custom slugs fail on poll, not at submit. Use GET /copilot/api/agents to discover slugs.mcp_mode: "auto" (default) lets the router pick enabled servers from your question; mcp_mode: "manual" attaches the slugs you list (lowercase), e.g. edgar-13f, rag, firecrawl, yfinance, gitlab.rag is attached (auto or manual). Defaults to "all". Has no effect unless RAG is in the MCP set. Mirrors the composer Knowledge control (see Chat & threads).Accepted knowledge_scope values:
"all" — all tiers your account can access (shared, private, and your groups; admins see more)."public" — shared corpus only."private" — your private library only."public": true and/or "private": true to mix tiers with specific groups.Example — group-scoped knowledge_scope value
{"mode":"custom","group_ids":["GROUP_ID"]}Example — EDGAR 13F MCP
curl -X POST "{{API_BASE}}/copilot/api/ask" \
-H "Authorization: Bearer frc_..." \
-H "Content-Type: application/json" \
-d '{
"question": "Show Bill Ackman MSFT holdings in Q1 2026",
"mcp_mode": "manual",
"mcp_servers": ["edgar-13f"]
}'Example — RAG MCP with shared corpus only
curl -X POST "{{API_BASE}}/copilot/api/ask" \
-H "Authorization: Bearer frc_..." \
-H "Content-Type: application/json" \
-d '{
"question": "What do broker reports say about MSFT?",
"mcp_mode": "manual",
"mcp_servers": ["rag"],
"knowledge_scope": "public"
}'Example — published custom agent
curl "{{API_BASE}}/copilot/api/agents?q=test-agent" \
-H "Authorization: Bearer frc_..."
curl -X POST "{{API_BASE}}/copilot/api/ask" \
-H "Authorization: Bearer frc_..." \
-H "Content-Type: application/json" \
-d '{
"question": "AAPL 1y daily return, vol, Sharpe, max drawdown",
"agent_slug": "custom/you/test-agent-02"
}'The chat API supports the same slash skills as the browser composer. There is no separate skill_ids field — prefix question with /skill_id (must be followed by whitespace or end of string). Use GET /copilot/api/skills to discover ids your API key can invoke.
agent_slug values ignore slash skills (same as the UI).error on the job (validation runs after 202, not at submit time).Example — list skills, then invoke one
curl "{{API_BASE}}/copilot/api/skills" \
-H "Authorization: Bearer frc_..."
curl -X POST "{{API_BASE}}/copilot/api/ask" \
-H "Authorization: Bearer frc_..." \
-H "Content-Type: application/json" \
-d '{"question": "/summarize What changed in the latest CPI print?"}'After POST /copilot/api/ask, poll the run until it finishes, then optionally share the thread and copy a pasteable link into a note or doc. This is the same Share hub as the browser Chat page. Delete is the same soft-archive as the Chat sidebar.
GET /copilot/api/jobs/{job_id} returns queued / running / cancelling / succeeded / failed / cancelled, plus answer and artifacts when done. Cancel an in-flight job with POST /copilot/api/jobs/{job_id}/cancel. GET /copilot/api/jobs?thread_id=... lists jobs on one thread.GET /copilot/api/threads/{thread_id} returns sharing fields and latest_job (same object as job poll) when you have a thread_id but not the job_id.PATCH /copilot/api/threads/{thread_id}/sharing with visibility and, for groups, group_ids. The response share_url is the link to put in a document.DELETE /copilot/api/threads/{thread_id} soft-archives the thread (204, empty body). Same as the browser Chat delete: the row stays, it leaves the sidebar, and local attachment copies are dropped. 404 if the thread is missing, already archived, or not yours. Public/shared links stop resolving once archived.Accepted visibility values (thread sharing, not ingest):
private — owner only. share_url is null.group — members of group_ids (you must belong, unless admin). Link is /chat/shared/{thread_id} (sign-in required).public — whole workspace ("shared"). Same signed-in /chat/shared/{thread_id} link.internet — anyone with the link, no sign-in. Mints a revocable public_share_slug and returns /chat/public/{slug}. Switching away from internet clears the slug and kills the public link.Example — poll until the thread run finishes
curl "{{API_BASE}}/copilot/api/jobs/JOB_ID" \
-H "Authorization: Bearer frc_..."
curl "{{API_BASE}}/copilot/api/threads/THREAD_ID" \
-H "Authorization: Bearer frc_..."Example — share to the workspace (signed-in link)
curl -X PATCH "{{API_BASE}}/copilot/api/threads/THREAD_ID/sharing" \
-H "Authorization: Bearer frc_..." \
-H "Content-Type: application/json" \
-d '{"visibility": "public"}'Example — share to selected groups
curl -X PATCH "{{API_BASE}}/copilot/api/threads/THREAD_ID/sharing" \
-H "Authorization: Bearer frc_..." \
-H "Content-Type: application/json" \
-d '{"visibility": "group", "group_ids": ["GROUP_ID"]}'GET /copilot/api/skills for slash ids; GET /copilot/api/agents for runnable agent_slug values (builtin + published custom).ask creates a thread automatically.thread_id — thread attachments are picked up automatically.succeeded, then read answer and artifacts. GET /copilot/api/threads/{thread_id} also exposes the same job as latest_job.download_url.PATCH /copilot/api/threads/{thread_id}/sharing and copy share_url into a doc. public is the whole workspace; internet is anyone with the link; group needs group_ids.DELETE /copilot/api/threads/{thread_id} soft-archives the thread (same as the browser Chat delete).Example — one-shot ask
curl -X POST "{{API_BASE}}/copilot/api/ask" \
-H "Authorization: Bearer frc_..." \
-H "Content-Type: application/json" \
-d '{"question": "Summarize the latest CPI print"}'Example — poll chat job
curl "{{API_BASE}}/copilot/api/jobs/JOB_ID" \
-H "Authorization: Bearer frc_..."Example — cancel a running chat job
curl -X POST "{{API_BASE}}/copilot/api/jobs/JOB_ID/cancel" \
-H "Authorization: Bearer frc_..."Example — share a finished thread and copy the link
curl -X PATCH "{{API_BASE}}/copilot/api/threads/THREAD_ID/sharing" \
-H "Authorization: Bearer frc_..." \
-H "Content-Type: application/json" \
-d '{"visibility": "internet"}'Example — delete (soft-archive) a thread
curl -X DELETE "{{API_BASE}}/copilot/api/threads/THREAD_ID" \
-H "Authorization: Bearer frc_..."thread_id on later asks to continue the conversation. UI and API turns on the same thread share one agent session.failed job may mean insufficient balance — check error on the job.A sweep is a saved matrix of ordinary chat runs. Every ticker × as-of date × stage becomes one run, and each run is submitted as a new thread and a normal chat turn — so billing, Debug, artifacts, and sharing work per row with no separate execution path. Routes live under /copilot/api/sweeps and are synchronous JSON: the create call returns immediately, then a background worker drives the rows.
Stages run in order per ticker/date. Stage 2 is submitted only after stage 1 succeeded; when a stage fails permanently or is cancelled, the stages behind it become skipped. The worker submits at most one run per minute per sweep and pauses submitting while the queue is backed up, so a large sweep is a slow batch rather than a burst of chats. A sweep is capped at 2000 runs.
Sweep endpoints
POST/copilot/api/sweeps
Create a sweep and expand it into run rows. Returns 201 with sweep and total_runs.
GET/copilot/api/sweeps
List your sweeps (limit 1–200, default 50) with per-status counts.
GET/copilot/api/sweeps/{sweep_id}
One sweep: status, spec, attempt/timeout settings, counts, and timestamps.
GET/copilot/api/sweeps/{sweep_id}/runs
The run table. Optional filters status, ticker, and stage_index (0-based).
PATCH/copilot/api/sweeps/{sweep_id}
{"status": "paused" | "running"} — stop or resume submission. In-flight runs keep going; 409 once the sweep is terminal.
POST/copilot/api/sweeps/{sweep_id}/cancel
Cancel the sweep and every queued/running child job. Cancelled rows stay cancelled.
POST/copilot/api/sweeps/{sweep_id}/retry-failed
Reset exhausted failures to pending with attempt=0, and reopen the stages that were skipped behind them.
GET/copilot/api/sweeps/{sweep_id}/export.csv
Download the run table as CSV, including score columns and thread_id / job_id.
DELETE/copilot/api/sweeps/{sweep_id}
Remove the sweep from your list. Add ?archive_threads=true to also archive the chat threads its runs created. Run history, CSV, and billing are kept; 409 while the sweep is still running or paused.
Create body: name (required), stages (1–10, in execution order), and a portfolio. A ticker needs a date from either as_of_dates (shared) or portfolio[].as_of (per ticker). Use tickers for bare symbols, or portfolio for {"ticker", "name", "as_of"} rows. Optional: save_to_synology, max_attempts (1–10, default from the deployment), run_timeout_seconds (60–86400).
Each stage is {"agent_slug", "label", "prompt", "save_to_synology"}.agent_slug is any packaged assessment agent or a published custom/.... The general assistant is chat-only — it has no assessment workflow, so the API rejects it with 400 and the picker does not offer it. A stage prompt is an optional template; placeholders are {ticker}, {name}, {as_of}, {stage_label}, {stage_index} (1-based), {sweep_name}, and {attempt}. The platform always appends a short unattended sentence (auto-approve every gate, finish the run) and the Synology save instruction when enabled. It never describes an agent's output — a scorecard, a memo, or anything else — because the agent already defines that.
Deleting. DELETE is a soft delete: the sweep leaves your list and the API, while the run rows stay for export, audit, and billing. Chat threads are real work, so they are only archived when you ask for it with ?archive_threads=true — and that is the same soft archive as deleting a thread in Chat (it leaves the sidebar; share links stop resolving). Threads you already deleted are skipped and reported as threads_already_gone, so a partly tidied sweep still deletes cleanly. The response carries status: "deleted" plus threads_total, threads_archived, and threads_already_gone. Cancel a running sweep first (409 otherwise).
Sweep status: running | paused | succeeded | failed | cancelled. Run status: pending → submitting → queued → running → succeeded | failed | skipped | cancelled. attempt counts submissions (retries get a fresh thread). score is populated only for agents with a known scorecard and carries composite, coverage, coverage_met, and source (artifact or answer); agents without one leave it empty.
Example — create, watch, steer, export
# 2 tickers x 1 date x 2 ordered stages = 4 runs
curl -X POST "{{API_BASE}}/copilot/api/sweeps" \
-H "Authorization: Bearer frc_..." \
-H "Content-Type: application/json" \
-d '{
"name": "China quality - 2025 year-end",
"as_of_dates": ["2025-12-31"],
"tickers": ["600519.SH", "0700.HK"],
"stages": [
{"agent_slug": "compounder-identifier-v2", "label": "Compounder v2"},
{"agent_slug": "fallen-angel-identifier", "label": "Fallen Angel"}
],
"save_to_synology": true,
"max_attempts": 2
}'
curl "{{API_BASE}}/copilot/api/sweeps/SWEEP_ID" \
-H "Authorization: Bearer frc_..."
curl "{{API_BASE}}/copilot/api/sweeps/SWEEP_ID/runs?status=failed" \
-H "Authorization: Bearer frc_..."
curl -X PATCH "{{API_BASE}}/copilot/api/sweeps/SWEEP_ID" \
-H "Authorization: Bearer frc_..." -H "Content-Type: application/json" \
-d '{"status":"paused"}'
curl -X POST "{{API_BASE}}/copilot/api/sweeps/SWEEP_ID/retry-failed" \
-H "Authorization: Bearer frc_..."
curl -X POST "{{API_BASE}}/copilot/api/sweeps/SWEEP_ID/cancel" \
-H "Authorization: Bearer frc_..."
curl "{{API_BASE}}/copilot/api/sweeps/SWEEP_ID/export.csv" \
-H "Authorization: Bearer frc_..." -o sweep.csvthread_id and job_id, so anything the chat routes expose (answer, artifacts, sharing) applies to that one run.Author custom packaged agents with the same frc_ key. Routes are synchronous JSON (not jobs). Drafts appear in Designer; only a published slug is runnable on POST /copilot/api/ask. Skill slugs and publish slugs must start with custom/ (suggested custom/you/name). See Agent Designer for the browser flow.
Designer endpoints
GET/copilot/designer/palette
MCP servers, builtin tools, and skills you can attach on the canvas.
POST/copilot/designer/skills
Create a Designer skill. Body: slug, markdown (SKILL.md, max 12k), visibility (private / public), optional scripts[] (path + content, 200k each).
GET PUT DELETE/copilot/designer/skills
List skills; GET / PUT / DELETE /copilot/designer/skills/{id} for one skill.
POST/copilot/designer/agents
Create a draft. Body: display_name and definition (orchestrator system, mcp_allowlist, tools, skills, subagents[]). Builtin tools only: read, grep, glob, write, edit, bash. Sub-agent mcp_names are unioned into the allowlist on save.
GET PUT DELETE/copilot/designer/agents
List your drafts; /copilot/designer/agents/{id} for one agent.
POST/copilot/designer/agents/{id}/validate
Soft warnings in the JSON body; hard errors return 400.
POST/copilot/designer/agents/{id}/publish
Freeze a version. Body: slug (must start with custom/) and optional release_commit (label; defaults to the definition hash). Republish keeps the slug and increments version. 409 if another package already owns that slug.
POST/copilot/designer/agents/{id}/share
Published only. Body: visibility (private / group / public) and group_ids when visibility=group. internet is stored as Workspace.
POST/copilot/designer/agents/{id}/fork
Copy a shared published agent into a new draft you own.
GET/copilot/designer/gallery
Workspace-shared published agents (same list as Gallery). Optional query: q, sort=trending|recent|useful (default trending).
Example — create skill, draft, validate, publish
curl -X POST "{{API_BASE}}/copilot/designer/skills" \
-H "Authorization: Bearer frc_..." \
-H "Content-Type: application/json" \
-d '{"slug":"custom/you/equity-stats","markdown":"# Equity stats\n","visibility":"private","scripts":[]}'
AGENT_ID=$(curl -sS -X POST "{{API_BASE}}/copilot/designer/agents" \
-H "Authorization: Bearer frc_..." \
-H "Content-Type: application/json" \
-d @definition.json | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
curl -X POST "{{API_BASE}}/copilot/designer/agents/$AGENT_ID/validate" \
-H "Authorization: Bearer frc_..."
curl -X POST "{{API_BASE}}/copilot/designer/agents/$AGENT_ID/publish" \
-H "Authorization: Bearer frc_..." \
-H "Content-Type: application/json" \
-d '{"slug":"custom/you/test-agent-02","release_commit":"v1"}'/copilot/api/agents — that path is the run catalog. Author at /copilot/designer/..., then run the published slug with POST /copilot/api/ask.Billing
Financial Research Copilot uses a prepaid credit balance. Each assistant turn can debit your account after usage is metered; you’ll see notices in the chat sidebar when a charge completes, when metering is still pending, or when a run failed (including insufficient balance).
How charges are calculated. The expanded balance panel explains that consumption reflects platform AI processing pricing — underlying model usage, tax recovery, infrastructure, and margin — rather than a flat per-message fee. Admins can open a usage request’s detail drawer to compare base cost, tax recovery, markup, and per-component token metering for auditing.
Layout
On small viewports, the chat sidebar becomes a slide-out drawer: use the menu control in the header to open threads, billing, workspace links, and sign out. The billing panel can expand to focus on balance and purchase controls. Workspace pages wrapped in the shell use the same pattern.
Tables (for example on Knowledge) scroll horizontally when needed so columns stay readable without cramping the layout.