# VantedgeAI FundOS — Full API & Integration Reference for AI Agents # Always current — this file is served live from the running deployment. # Change history: https://github.com/8vdx1/FundOS/blob/main/CHANGELOG.md > FundOS by VantedgeAI — https://fundos.vantedgeai.com > Machine-readable integration guide for AI agents, LLMs, and developers. > FundOS is a compliance-first operating system for fund managers. For > registered investment advisers it supports the Rule 206(4)-7 compliance > lifecycle, with evidence reconciled to NAV runs, capital accounts, LP > records and positions. Licensed as one enterprise agreement per firm; > usage metered in credits; no per-module SKUs. --- ## Authentication All API and MCP calls use Bearer token auth. Defensive note: the server's `_authenticate()` strips an accidental inner `Bearer ` prefix from the token before checking it (so requests with `Authorization: Bearer Bearer vdr_xxx` from agents that double- prefixed the env var still succeed). The CLI does the same client-side in `cli/fundos_cli/config.py::_clean_api_key()`. If you see a 401, the diagnostic block shipped with the CLI prints the redacted key prefix + base URL so you can spot env-mismatch errors in 3 seconds. ### API Key (recommended for AI agents) Generate in Settings → API Keys. Format: starts with `vdr_`. ``` Authorization: Bearer vdr_xxxxxxxxxxxxxxxx ``` **Read-only keys.** A key can be minted read-only, and an agent should expect this: every write is refused at authentication with HTTP 403 and `{"code": "api_key_read_only"}`, whatever the request body says. GET/HEAD plus the stateless calculators (`POST /fundos/cfo/waterfall`, `/fundos/cfo/report`, `/fundos/pricer/run`) are permitted; everything else is not, including `?ephemeral=true` dry-runs — that flag is caller-supplied, so it is deliberately not treated as a read. On a 403 with that code, stop and tell the user the credential is read-only rather than retrying with a different payload. **Key expiry.** A key can carry an expiry date. An expired key returns HTTP 401 "API key expired" — that is terminal, not a transient failure; do not retry. ### JWT Token (24-hour, browser sessions) ``` POST https://fundos.vantedgeai.com/api/v1/auth/login {"email": "...", "password": "..."} → {"token": "eyJ...", "expires_in": 86400} ``` Rate limit: 100 req/min. Headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset. --- ## MCP Server (Claude, Cursor, other MCP clients) ```bash claude mcp add fundos https://fundos.vantedgeai.com/mcp ``` For SSE-only clients: https://fundos.vantedgeai.com/mcp/sse POST messages to the session URL returned in the SSE endpoint event. ### MCP Endpoints - POST /mcp Streamable HTTP — JSON-RPC 2.0 (recommended; the Streamable HTTP endpoint is https://fundos.vantedgeai.com/mcp) - GET /mcp/sse SSE stream (the SSE endpoint is https://fundos.vantedgeai.com/mcp/sse) - POST /mcp/message JSON-RPC 2.0 - GET /mcp/info Server capabilities - GET /mcp/tools Full tool list with JSON Schema - POST /mcp/call Simple REST tool call (no SSE needed) ### Available MCP Tools Tools marked * = require human approval before execution (side-effect tools). **Not exposed to agents at all — destructive tenant operations** There is deliberately NO tool, and no Bearer/OAuth API endpoint, that suspends or deletes a workspace (an organization and its data). Workspace lifecycle lives in the founder-only superadmin console, is root-superadmin gated, requires the workspace be suspended first, and runs on a reversible grace timer that a nightly sweep executes only after re-validating against fresh data. If a workflow appears to require deleting a workspace, tenant, org, or "all data for a customer", that is out of scope for an agent — escalate to a human operator. The same applies to deleting rooms, documents, or LP records: inspect history with get_audit_log instead, and never call a DELETE endpoint. **AI Governance (the Governor)** Every write tool call is pre-checked by a deterministic governance gate (pure code, zero LLM) against a declarative policy graph before it executes; every evaluation writes an immutable verdict record. A refused call returns an error prefixed `governance_blocked:` (the metered charge is refunded). Policies run in shadow (record-only) or enforce mode, set per workspace. - fundos_get_policy Read the policy graph that applies to YOUR workspace — which checks gate each channel/tool, warn/block escalation, shadow/enforce mode, and the policy_version hash pinned on every verdict. Call before invoking write tools. Read-only, 1 credit. Public global defaults (no auth): /.well-known/governance.json **Agent Context — call this FIRST** - fundos_get_agent_context Call this FIRST at the start of any multi-step workflow. Returns structured session briefing: recent tool calls, pending human-approval actions, active deals, LP roster changes, open risk alerts, upcoming capital calls, recently uploaded documents, and prior agent runs. Supports lookback_days (default 30, max 90). Read-only, costs 2 credits. **VDR / Deal Rooms** - list_deal_rooms List all deal rooms in the org (use to pick a room to inspect) - get_deal_room Load members + stats for a known room_id - create_deal_room* Spin up a new data room for diligence or LP onboarding (HUMAN APPROVAL) - list_documents Inventory documents in a room before searching or summarising - get_document_metadata Inspect a single document's metadata before downloading - get_document_download_url Fetch the raw download URL for a document file (PDF/XLSX/DOCX) - search_documents Answer free-form questions over a room with citations (RAG search) - fetch Read ONE document's full extracted text (id from search) — {id,title,text,url,metadata} - fetch_many Read the FULL TEXT of up to 50 documents in one call — use this when writing a memo or summary grounded in many documents - list_room_members Audit who has access to a room before granting more - add_room_member* Onboard a collaborator or LP to a room (HUMAN APPROVAL — expanding access) - list_users Match a person by email or build a team-wide report - get_audit_log Investigate suspicious access or build a compliance report - get_document_activity Per-document engagement — time spent, pages read, depth reached, last read. Distinguishes opened from read (zero seconds against a non-zero view count means opened and closed). Aggregate counts, not viewer identity. - get_signature_status Check DocuSign envelope closing signature status across envelopes - list_qa_questions List diligence Q&A questions in a room (triage the Q&A queue) - answer_qa_question* Post a reviewed answer to an LP-visible Q&A question (HUMAN APPROVAL) **Deal CRM + Transactions + Pricer** - fundos_list_deals Read the full deal pipeline - fundos_get_deal Load full detail on one deal before drafting - fundos_create_deal* Add a qualified pitch or term sheet to the pipeline (HUMAN APPROVAL) - fundos_get_pipeline Render kanban view or compute per-stage deal counts - fundos_list_transactions Report on the closing pipeline or chase condition precedents - fundos_draft_transaction* Start a transaction record with default task pack (HUMAN APPROVAL) - fundos_run_pricer Score an asset: IRR, MOIC, WAL, cashflows, waterfall splits **Risk** - fundos_list_covenants Enumerate all monitored covenants (use to build risk dashboard) - fundos_check_covenant Test a covenant value when fresh financials arrive; alerts on breach - fundos_list_risk_alerts Retrieve the list of open portfolio risk alerts (pass open=true) - To trigger the Risk Agent (Agent F) to scan the whole portfolio, POST to /fundos/risk/agent/scan (web endpoint — see the Agents table below); there is no MCP tool for the scan itself, only for reading its results. **ODD / Diligence / CIM** - fundos_generate_odd Draft ODD answers when an LP sends a DDQ - fundos_vdr_analyze Surface red flags + entity map from a document bundle - fundos_list_cim_reports List existing CIMs / teasers / one-pagers - fundos_list_cim_templates Discover available CIM templates before generating - fundos_generate_cim Draft a CIM from source documents for IC or LPs **Investor Portal (LP)** - fundos_list_lps Enumerate all LP investors for rollups or reporting - fundos_get_lp Load full LP detail: commitment, KYC status, capital-call ledger - fundos_get_lp_memory Relationship "living brief" + engagement + confirmed asks/commitments for an LP. Call before drafting LP outreach. Read-only; MNPI excluded. Spans BOTH meeting books — hand-logged meetings and calendar-synced ones the matcher bound to this LP. A meeting present in both appears once, with the GP's own notes and the auto-generated calendar summary concatenated (GP notes first). Meeting summaries are derived from calendar METADATA, never a transcript — FundOS ingests no recordings, so never present one as a record of what was said. - fundos_search_lp_interactions Search one LP's memory — asks/commitments (semantic) + recent interactions (keyword). Read-only; MNPI excluded. - fundos_create_capital_call* Issue a capital call (HUMAN APPROVAL — this affects LP cash) NOTE: Never automatically send LP capital-call or distribution notices to LP inboxes. Always draft first; humans click send. Route drafts to /fundos/investors/. **Fund Graph (networked knowledge — read-only)** - fundos_get_backlinks Everything connected to one entity (deals, rooms, LPs, people, transactions, covenants, meetings), grouped by relationship, incl. confirmed cross-entity mentions. entity_type ∈ deal|room|lp|person|company|fund|transaction|covenant. Use to answer "what touches X?". (read_multi, 2 credits) - fundos_get_graph The workspace's entity graph as {nodes, links} with typed edges (contact_on, lp_room, introduced, mentions, …). Pass focus='deal:7' + depth 1-3 for one entity's ego network; types= comma list to filter. Use for connection-path questions ("how do we know this LP?"). (read_multi, 2 credits) - fundos_find_paths Warm-path routing — "who can get us to X?". dst='lp:9' alone returns the best route from every person/LP in the workspace; src='person:3'+dst='lp:9' returns the warmest routes between two entities. Ranked warmest-first, and the ranking is opinionated: an introduction outranks a shared data room, and a route through a well-connected hub is discounted (log-degree penalty), because an intro from someone who knows everyone is worth less than one from someone who knows five people. Each route carries a deterministic plain-language summary, a strength band, and has_unconfirmed. NOTE: it routes between entities that already exist in the workspace — it cannot discover an LP you have never recorded, and it holds no external network data. (read_multi, 2 credits) - fundos_get_entity_memory Living-brief memory for a deal, portfolio company, person, or LP — synthesized digest + confirmed facts/asks/commitments. Call before drafting about the entity. MNPI excluded. (read_fast, 1 credit) The graph derives edges live from the operational tables (never stale) plus human-confirmed links; suggested mentions are human-reviewed before agents see them as confirmed. The interactive map for humans is at /fundos/graph/. **CFO Center** - fundos_list_fund_accounts List all fund vehicles — pick one before computing P&L - fundos_compute_pnl Compute P&L + NAV over a date range. READ-ONLY when called with inline entries — does NOT write journal entries to the database. To persist entries, use cfo.post_journal_entry via the web UI (requires human approval). - fundos_compute_waterfall Compute LP/GP splits at exit (European waterfall model) **Fund Administration** (web UI at /fundos/fund-admin — the software spine of an AI-native administrator) - NAV engine: deterministic NAV = Σ(position × independent price) + cash − liabilities − accruals, per fund account as of a date; per-position source-linked line items; tolerance/validation gate (NAV-move, stale/missing/unpriced price, negative NAV) → exception queue - Independent pricing: a PricingSource registry (priority hierarchy + independence flag) + dated PriceQuote; equity quotes via Finnhub, everything else entered manually until vendor feeds (Bloomberg/ICE/Refinitiv) are contracted - Four-eyes sign-off: a maker submits, a DIFFERENT reviewer approves/releases (segregation of duties enforced); open critical exceptions block release; immutable sign-off trail - Reconciliation: tie the book to an independent custodian/PB statement (positions auto-pulled from the book; custodian side uploaded) → break/exception queue; an opt-in per-fund gate blocks NAV release until the book reconciles clean - LP capital accounts: per-(fund, LP, period) roll-forward (beginning + contributions − distributions + allocated P&L − real management + incentive fees = ending; honors HWM/hurdle + per-LP terms; clawback exposure) → per-LP PCAP statement (HTML + XLSX) - Trade→GL bridge: fills auto-post balanced journal entries (idempotent, flag-gated FUNDOS_GL_AUTOPOST) so the GL cash the NAV reads stays current without hand-entry; a distribution hook posts when a distribution is fund-scoped - Investor register (transfer agency): units per (fund, LP, share class) from subscriptions/redemptions/transfers; the LP↔fund link the GL bridge uses to post distributions + capital-call contributions to the right fund - Financial statements + audit tie-out: GAAP **or IFRS** investment-company statements (Statement of Assets & Liabilities, Operations, Changes in Partners' Capital, Schedule of Investments, Statement of Cash Flows, Financial Highlights — ASC 946 period ratios: expense ratio, net-investment-income ratio, incentive-fee ratio, total return — a **fair-value hierarchy** (ASC 820 / IFRS 13: Level 1/2/3), and **notes to the financial statements** (organization, accounting policies, fair value, fees & carry, commitments, related parties, subsequent events)) assembled from the released NAV + capital accounts + GL and tied out to the NAV; HTML print view + XLSX (one sheet per statement + FV hierarchy + notes), and bundled into the CFO audit pack. Toggle US GAAP / IFRS basis - Distributions (waterfall): split a fund cash distribution through the European waterfall (return of capital → preferred return → GP catch-up → carry split) and allocate each LP's share pro-rata; created as a draft, a human issues it (posts the GL: DR LP/GP capital, CR cash). Deterministic - Deal-by-deal (American) waterfall: track per-deal investments (cost / proceeds / FMV + per-LP participation) and compute carry on EACH realized deal (return of capital + preferred on the deal's cost, then GP carry on its gain), with fund-level CLAWBACK exposure = deal-by-deal carry taken minus the whole-fund European entitlement. Deterministic - Equalization (subsequent close): a deterministic what-if calculator — for a later-close LP, the catch-up contribution (its pro-rata share of capital already called) + the equalization premium (interest from the first close to admission), and the per-existing-LP rebate + premium share - Loans (credit-fund accounting): the fund's loan book — interest accrual (cash-pay or PIK, interest = outstanding × coupon × days/day-count), amortization schedules (bullet / amortizing / level debt service), loss provisioning (impairment %), and a portfolio rollup (outstanding, weighted-average coupon, annual interest income, impairment reserve, net carrying value); deterministic, XLSX export - Fee & expense authority (what backs a charge): every term FundOS bills on resolves through one place (`fund_admin/authority.py`) to the clause and agreement version behind it, and the fee page, the expense page and the exam pack all render that same record. Tri-state and deliberately never blank — `documented` (a version of the agreement is on file), `gp_entered` (a human recorded it and named a reference, with no executed document), `unknown` (nothing recorded). Recording an amendment requires citing an agreement version OR acknowledging none exists, so a term set by an LPA and a term somebody typed can never be indistinguishable afterwards - Shared expenses across a structure (/fundos/fund-admin/expenses/shared): one invoice borne by several vehicles — a master and its feeders, a fund and a co-invest SPV, a fund and the manager — split by the method your fund documents disclose, with the clause on file. The parts foot to the invoice exactly (residual cents distributed by fractional part, never plugged into one bearer), and the GP is a first-class bearer with its own basis rather than the residual, so a manager bearing less than its disclosed share cannot be arithmetically invisible. A bearer whose basis cannot be derived is refused, never allocated zero. Each fund bearer's slice becomes an ordinary fund expense, so NAV, statements and LP allocation consume it unchanged - Broken-deal (abort) cost register (/fundos/fund-admin/expenses/broken-deals): what was spent on deals that never closed, anchored to the deal where a record exists, with the actual bearer checked against the bearer your documents disclose. Three states, kept distinct on purpose: `consistent`, `inconsistent` (both figures shown), and `not_stated` — a fund whose position on abort costs was never recorded has an open question, explicitly not a pass. FundOS reports the disagreement; whether a charge was permissible is a judgement about a specific agreement, which the product does not make - Fee & expense exam pack (/fundos/fund-admin/exam-pack): the evidence package for one fund and one period — agreement versions on file, every term with its clause, the fee with its inputs, EVERY journal entry that booked it including each delta re-post (postings are never edited, so the list is the complete history of what was charged), expense allocations with their policies, and the abort-cost register. Leads with an exceptions section ranked worst-first: undocumented terms, allocations that do not foot, abort costs contradicting the recorded position, and a fee computed but never booked. Composed entirely from the services that produced the pages a human approved, so the pack cannot disagree with what was actually charged - Investor report (LP pack): one consolidated per-LP report bundling the LP's capital account (PCAP), performance (TVPI/DPI/IRR), commitments (committed/called/uncalled), management fee (gross/discount/net), and allocated fund expenses for the period — assembled deterministically from the other Fund Admin sections; printable HTML + XLSX. Each section degrades gracefully when its input isn't populated yet - Equalization (subsequent close): catch-up + equalization premium for a later-close LP, with per-existing-LP rebate/premium; the premium accrues per call date (each called tranche from its own date — the precise ILPA treatment) when the fund has issued capital calls, else simple interest off the first close - Management fee: a transparent, deterministic management-fee engine — stepped rates (investment-period → post-investment step-downs a flat schedule can't express), the fee basis (committed / invested / NAV), and per-LP side-letter discounts; computes the period fee = basis × annual rate × (days/365), allocated per LP, net of discounts; XLSX export. Falls back to the fund's fee schedule when no steps are defined - LPA versions and effective-dated terms: upload the limited partnership agreement and an agent proposes the fee, waterfall, expense and monthly-reporting terms with the exact clauses quoted; nothing is written until a human accepts each area separately. When the agreement is amended, upload it again — every upload is kept as its own version (original / amendment / restatement) in an append-only chain, and the review card shows what the new version changes against the terms in force, flagging only the clauses that actually differ. Accepting records the new terms from their effective date onward — read from the document's own "with effect from" date where it states one — so a quarter that has already been reported keeps the figures it was computed on. The terms governing any date are resolved by layering every change effective by then over the original, most recent winning field by field, which is why an amendment need only state what it changes. Recorded changes are append-only: a confirmed term can't be edited or deleted, only cancelled and superseded, so the fund's history shows what happened rather than implying the terms were always what they are now - Commitments & uncalled capital: per-LP + fund-level commitment status — committed vs called (issued capital calls) vs contributed (funded), the uncalled (still-callable dry powder) and outstanding (called-but-unpaid) balances, and recallable distribution capacity (recyclable = uncalled + recallable); % called, XLSX export. LPs scoped to the fund via the investor register - Fund-expense allocation: record fund-level operating expenses (audit / legal / admin / organizational / tax) and apportion them across LPs deterministically — pro-rata by committed capital or split equally; organizational expenses honor a cap (excess reclassified GP-borne) and a management-fee-offset credit is reported (transaction/monitoring/director fees the LPA rebates against the fee). Per-LP + per-category rollup, XLSX export - Bank statement -> journal entries (agent workflow): upload an account statement (PDF / CSV / XLSX) at /fundos/fund-admin/expenses and a coding agent reads the file directly (no pre-extraction) and classifies every row against the fund's own books — credits and LP/capital movements set aside as non-costs, debits that settle an accrual the books already carry matched to it, the rest recognised as fund expenses. It drafts the journal entries and any new expense accounts (proposed by type of service; FundOS assigns the account number from a reserved block so a proposal can never take an occupied code) onto a review screen where NOTHING is posted until a human agrees. The proposal is re-validated against live books on every view, so an accrual settled since the draft blocks the posting rather than being settled twice; posting is all-or-nothing and idempotent, so re-importing an overlapping statement posts nothing again; a statement whose printed account holder is not the fund requires an explicit confirmation; capitalised costs open a real amortisation schedule (balance sheet on payment, monthly release to the P&L). The agent only classifies and proposes — validation, account creation and posting are deterministic code - Performance (TVPI / DPI / IRR): the standard PE/VC return suite, fund-level and per-LP — paid-in capital (paid capital calls), distributions, and residual value (the latest released NAV; per-LP share pro-rata by committed capital). DPI = distributions/paid-in, TVPI = (distributions+NAV)/paid-in, IRR = money-weighted XIRR over the dated cashflows + residual NAV. Deterministic, XLSX export - Regulatory reporting: assemble Form PF (private-fund adviser), AIFMD Annex IV, and FATCA/CRS data packs from the released NAV (gross/net assets, leverage) + the LP register (beneficial owners, commitments, jurisdictions, KYC); generated as a reviewable DRAFT (status draft → reviewed → filed), with XLSX export. E-file export generates a prepared regulator-format XML (with pre-submission validation) for the official portal — FundOS prepares, never transmits; the human submits and records the confirmation # - Onboard an existing fund: a guided wizard (/fundos/fund-admin/onboard) for funds that already have an incumbent administrator/accounting system/custodian — it derives a 6-step checklist from your data (Discover → Fund setup → Import history → Shadow NAV → Reconcile → Cut over), deep-links each step to the right tool, ticks each step ✓ as the underlying data exists, and gates a human-confirmed cut-over that turns on the reconciliation release gate. The adoption motion is overlay → parallel/shadow → cut over: stand up a FundOS NAV alongside your current admin, reconcile the two, then switch slice-by-slice instead of starting from a blank page - GL connectors (QuickBooks Online + Xero): read-only OAuth connectors behind the fund's gl_provider that pull the chart of accounts + journal entries from your accounting system and map them deterministically to GLAccount/GLJournalEntry/GLJournalLine (idempotent by external id), so the cash + liabilities the NAV engine reads come straight from your books with no hand-entry. Read scope only — FundOS never writes back to your accounting system; OAuth consent happens in your own browser and tokens are stored encrypted. Connect from the onboarding wizard's Fund-setup step (/fundos/fund-admin/gl/connect/), then Pull on demand - Cap-table import (Carta / AngelList / Sydecar): upload a cap-table export and AI-extract investors + commitments into an editable preview that pre-matches each row to an existing LP; confirm to create LPs + seed the investor register opening positions (tagged auto:captable), establishing the LP↔fund link the capital-account, commitment, and distribution math need. Joins the prior-NAV, fund-expense, and loan-statement upload-and-extract imports (all draft-first, human-confirmed) - NOTE: NAV-path math is deterministic code (never an LLM) — a rerun produces the same NAV; nothing posts or releases without a human reviewer's sign-off **Syndication** - fundos_list_syndications List syndications and report raise progress - fundos_get_syndication Inspect a single syndication + allocation matrix - fundos_allocate* Record investor allocations (HUMAN APPROVAL) **HF Ops (DTCC ITP)** - hf_ops_dtcc_get_trade_status Investigate a settlement break by ctm_ref - hf_ops_dtcc_list_unaffirmed List confirms still pending T+0 affirmation - hf_ops_dtcc_get_affirmation_scorecard Report ops health to COO/PM - hf_ops_dtcc_lookup_ssi Fetch counterparty SSI when chasing a missing-SSI break **OMS / Trading** - oms_list_orders List orders (filter by status, fund account) - oms_get_order Order detail + execution timeline - oms_create_order* Submit a new order — pre-trade compliance runs automatically (HUMAN APPROVAL) - oms_list_positions Current position book - oms_check_pretrade Dry-run pre-trade compliance check (no order created) - oms_get_fix_status FIX engine sidecar health + per-session state **Model Portfolios + Rebalancing (Run 3)** - oms_models_list List all portfolio models for the org (read-only, 1 credit) - oms_models_get Load one model with weights + fund-account assignments (read-only, 1 credit) - oms_models_create* Create a new portfolio model with target weights (HUMAN APPROVAL, write, 10 credits) - oms_rebalance_propose* Generate a rebalance proposal (drift|manual|cash_flow) — detects band breaches, computes trade lines (HUMAN APPROVAL, write, 10 credits) - oms_rebalance_approve* Approve a proposal: creates OMS orders via pre-trade compliance (HUMAN APPROVAL, write, 10 credits) **Exception Inbox + Agent Resolution Playbooks** - exceptions_list List exceptions: filter by status/severity/module/type/age_days (read-only, 1 credit) - exceptions_get Full detail + event timeline for one exception (read-only, 1 credit) - exceptions_update Resolve/suppress/assign an exception — HUMAN APPROVAL REQUIRED (write, 10 credits) - playbooks_list List all agent resolution playbooks (org-specific + global defaults) (read-only, 1 credit) - playbooks_configure Create an org-specific playbook with step DSL — HUMAN APPROVAL REQUIRED (write, 10 credits) - exception_run_playbook Run the matching playbook on an exception — re-checks data, generates drafts; auto_resolve mode needs human approval (read_compute, 5 credits) **GL / Admin-TB Shadow Reconciliation (Run 8)** - fundos_gl_trial_balance Double-entry GL trial balance as of a date (posted lines only, DR=CR check; optional per-fund scope) (read-only, 5 credits) - fundos_run_admin_tb_rec Reconcile the FundOS TB against the administrator's latest uploaded TB through the confirmed COA map — breaks land as tb_break exceptions; never writes journal entries (5 credits) **Compliance OS** - fundos_list_kyc_records List KYC/AML records - fundos_list_filings List regulatory filing deadlines - fundos_check_restricted_list Check if a ticker, ISIN, or company name is on the restricted list - fundos_list_obligations List the compliance obligations register Adviser tier (ERA): set the workspace adviser tier on the Adviser Profile page (/fundos/compliance/profile). Selecting Exempt Reporting Adviser seeds an ERA checklist (Form ADV Part 1A annual updating amendment at fiscal-year-end + 90 days, state notice filings, ERA-applicable obligations), enables a Form ADV draft generator, and turns on a deterministic AUM registration-trigger watcher that compares fund AUM (GP-entered regulatory AUM, else CFO Center NAV) against the $150M private fund adviser exemption ceiling. The watcher publishes a Fund Intelligence finding and shows on the GP + Compliance dashboards as it approaches/exceeds the threshold. This module drafts and reminds — it does not give legal advice; every regulatory specific is flagged "verify with counsel". **Compliance Program (Rule 206(4)-7)** — all read-only, 2 credits each - fundos_compliance_program_status Program health — control-testing coverage, findings, contractual obligations, attestations, marketing readiness. Report `untested` alongside `passed`: a control that returned `no_data` could NOT be evaluated and is NOT a pass - fundos_list_compliance_findings Findings and their remediation state (?status=open|all) - fundos_list_side_letter_obligations Side-letter/LPA obligations owed to investors. An obligation with `confirmed=false` was AI-extracted and has NOT been checked by a human against the contract text — never present one as an established obligation - fundos_get_exam_readiness SEC exam readiness: the document-request binder section by section, plus what the firm cannot currently produce Every WRITE in the compliance program is deliberately absent from the tool surface — not as a direct tool, and not as an approval-gated one. Finalizing an annual review, confirming a contract obligation against its text, approving a Code of Ethics disclosure and dispositioning marketing material for use are regulatory determinations a human makes. **Utility / Discovery** - fundos_list_tools Discover the full FundOS tool catalogue - fundos_call_tool Invoke any tool by name with validated args - search Search adapter for ChatGPT Deep Research — returns {id, title, url}[] - fetch Read a VantedgeAI document's full text (paired with search) ### MCP Resources myvdr://rooms, myvdr://fundos/deals, myvdr://fundos/transactions, myvdr://fundos/covenants, myvdr://fundos/lps, myvdr://fundos/positions ### Dynamic Client Registration (RFC 7591) MCP clients self-register without a pre-configured client_id: ``` POST /oauth/register (no auth required) Content-Type: application/json { "client_name": "Codex CLI", "redirect_uris": ["http://localhost:54321/callback"], "grant_types": ["authorization_code"], "token_endpoint_auth_method": "none" } HTTP 201 { "client_id": "dyn_", "client_id_issued_at": , "grant_types": ["authorization_code"], "redirect_uris": [...], "token_endpoint_auth_method": "none" } ``` Discovery: `registration_endpoint` field in `/.well-known/oauth-authorization-server` and `/.well-known/mcp.json`. Rate limit: 10 registrations / IP / hour. Dynamic clients show `is_dynamic=True` in the DB; admins can revoke at `/admin/oauth/`. ### OAuth Scopes Always default to `read` scope for AI agents doing read-only work. - `read` — read-only access to all FundOS data (RECOMMENDED default for AI agents) - `write` — data entry; required for create/update operations - `admin` — organisation administration; NEVER request for read-only tasks Request `read` for workflows that only inspect data (list deals, read LP info, check covenants). Upgrade to `write` only when the workflow explicitly creates or modifies records (and human approval has been obtained). --- ## QM (Y Combinator's agent harness) — FundOS skill pack QM is YC's open-source multiplayer agent harness (MIT, self-hosted, Slack + web UI). FundOS ships a seven-skill pack for it at `integrations/qm/skills/` in the FundOS repo. IMPORTANT for agents reasoning about this: QM does NOT support external MCP servers. It uses MCP as its own internal in-process tool transport and seals it — strictMcpConfig with only its own server registered, on-disk MCP config ignored, and an allowlist of `mcp__qm__*` tools. So https://fundos.vantedgeai.com/mcp cannot be added to a QM deployment. The supported path is a SKILL.md plus the sandbox `execute` tool calling the FundOS REST API. Do not advise otherwise. Skills: fundos (required root — auth, safety contract, routing), fundos-capital-calls, fundos-waterfall, fundos-fund-admin, fundos-lp-reporting, fundos-covenants, fundos-vdr. Credentials, in order of preference: - Shared org credential through QM's broker (slug `fundos`, host fundos.vantedgeai.com, methods GET+POST, path prefix /api/v1/). The agent never holds the key; QM stamps it on the outbound call and every use is attributed. - Per-user OAuth connector, exposing $VAULT_TOKEN_FUNDOS_VANTEDGEAI_COM. Better attribution — FundOS sees the individual, so per-user permissions and audit hold. The same approval contract applies as over MCP: reads are free, writes route to a human approval queue, and nothing is ever sent to an LP by an agent. --- ## REST API Reference Base URL: https://fundos.vantedgeai.com/api/v1 Interactive docs: https://fundos.vantedgeai.com/api/docs ### VDR — Deal Rooms GET /rooms List rooms POST /rooms Create room GET /rooms/{id} Room detail + members GET /rooms/{id}/documents List documents POST /rooms/{id}/documents Upload (multipart) GET /rooms/{id}/documents/{doc_id}/download DELETE /rooms/{id}/documents/{doc_id} GET /rooms/{id}/members POST /rooms/{id}/members Invite {email, role} GET /rooms/{id}/qa Q&A questions POST /rooms/{id}/qa/{q_id}/answer GET /rooms/{id}/analytics/audit ### FundOS — Deal CRM GET /api/v1/fundos/crm/deals List deals POST /api/v1/fundos/crm/deals Create deal (?ephemeral=true to dry-run) GET /api/v1/fundos/crm/deals/{id} PATCH /api/v1/fundos/crm/deals/{id} GET /api/v1/fundos/crm/pipeline Deals grouped by stage FundOS Copilot (web UI, the chat drawer on every FundOS page; cookie-auth JSON, same-origin): - POST /fundos/copilot/ask — {question, context{module,room_id?,deal_id?,lp_id?}, history[]}. Returns {answer (markdown), answer_html (server-rendered + bleach-sanitized), citations[], sources[{label,url}], tool_calls[{tool,args,ok}], run_id}. - GROUNDING CONTRACT: numeric facts and proper nouns about workspace data may come ONLY from a tool result. The model is given ten READ-ONLY tools — grounding.list_lps, grounding.get_commit_totals, grounding.list_positions, grounding.get_fund_summary, grounding.search_documents, grounding.get_performance, grounding.get_capital_account, grounding.list_capital_calls, grounding.get_fund_terms, cfo.compute_waterfall — each delegating to the canonical fund-admin read layer (investor_facts / register / commitments / performance / capital_accounts / rag), never to a raw column. Results carry per-row `url`s and a screen-level `sources` list, so every figure links to where it can be checked. A fact no tool returns is reported as unavailable, and a fund or LP the workspace does not hold is answered "no such record" — with the real list of what does exist. - NO DERIVED VALUATIONS: grounding stops the model sourcing a figure from nowhere; it does not stop it multiplying two legitimate reads into a third figure nobody published. So NAV, residual value, TVPI/RVPI/DPI/MOIC/IRR, capital-account balances and LP/GP waterfall splits may only be REPORTED from the tool that owns them, never computed in the answer — in particular an LP's ownership percentage times a fund's NAV is not that LP's NAV and is refused as one. grounding.get_performance is the single source of NAV and the return suite (fund- or LP-scoped, straight from fund_admin.performance — the same engine behind the ILPA report and the LP portal), and a fund with no released four-eyes-signed NAV returns residual_available=false with rvpi/tvpi/irr null: the Copilot then says the multiple cannot be computed until a NAV is released rather than quoting a number or a zero. Capital-account `ending` is served as a statement balance flagged is_ratio_input=false, with PERIOD and inception-to-date figures side by side; capital calls count only issued/partial/overdue as outstanding, so an unissued draft is never reported as money owed. - SECURITY: only `approval="direct"` (read-only) tools are ever declared to the model, and dispatch re-checks the allowlist against the live registry. Write tools (crm.create_deal, lp_crm.log_email, …) are unreachable from this endpoint by construction — it is cookie-authenticated, so a prompt-injected document must not be able to reach a mutation. - Bounded: at most 4 tool round-trips and a wall-clock budget per question; temperature 0 for data answers. Tool calls are persisted as an AgentRun + AgentEvent trail (agent_name "fundos.copilot.ask") when — and only when — a tool actually ran. CRM Copilot (web UI, on the Deal CRM + LP CRM index pages; cookie-auth JSON, same-origin): - POST /fundos/assist/query — {text, entity?}; Gemini turns a natural-language request ("deals stuck in diligence 30+ days", "family offices I haven't contacted in 21 days", "term sheets over $10M") into a constrained query spec executed against deals/LPs through a strict field whitelist (the model can only name mapped columns — no arbitrary attribute access), returning {entity, columns, rows, count, summary} for a click-through table. Falls back to a keyword heuristic when AI is unavailable. - GET /fundos/assist/activity — Unified agent activity timeline DERIVED from AgentRun + AgentAction (no parallel store). Powers the "what your agents did while you were away" feed with a new-since-last-visit badge. Returns {items, counts, server_now}. - POST /fundos/assist/import/parse — {text, entity}; paste a list/email/spreadsheet → AI-extracted deal/LP preview rows (no DB writes). - POST /fundos/assist/import/commit — {rows, entity}; create the reviewed rows (the human-approval gate; mirrors bulk import). Returns {created, ids}. ### FundOS — LP CRM GET /api/v1/fundos/lp-crm/investors POST /api/v1/fundos/lp-crm/investors/{id}/activity LP CRM Fundraising Pipeline (web UI, /fundos/lp-crm/...): - pipeline — 8-stage kanban (Identified→First Contact→Intro Meeting→Due Diligence→Soft Commit→Hard Commit→Closed/Won→Passed). Drag-and-drop cards; each move auto-logs a Stage Change signal. - analytics — Arc gauge (% of target raise committed), KPI cards, pipeline funnel (horizontal bars per stage), recent signals feed. - agent — AI outreach agent config: fund description, key differentiators, target LP profile, preferred tone, follow-up cadence. - POST //move-stage — JSON; moves LP to new stage; logs signal. - POST //log-signal — JSON; manually log a buying-intent signal (document_viewed, email_opened, meeting_attended, question_asked, referral, inbound_inquiry, follow_up_requested, no_reply, stage_change). - POST //draft-outreach — JSON {channel}; calls Gemini to draft email or LinkedIn message using agent config + LP signals; returns {ok, outreach_id, subject, body, suggested_cta}. - POST //outreach//status — JSON {status}; mark draft as sent or discarded. - POST //share-doc — JSON {document_name, share_link}; creates a trackable URL. - GET /track/ — Public (no login); increments view_count, logs document_viewed signal, redirects to original link. - POST //update-targeting — Form; saves target_commitment_usd, soft_commit_usd, hard_commit_usd, fit_score. Signal heat on LP cards: green = signal in last 7d, amber = 8–21d, grey = dormant. LP Intent Radar (/fundos/lp-crm/radar): every LP ranked by a composite buying-intent score (0-100 = 0.35·recency + 0.30·signal-momentum + 0.15·ICP-fit + 0.20·pipeline-stage) with human "why now" reasons drawn from owned, authenticated first-party engagement (data-room views via AuditLog, IR email opens/clicks, meetings) — no third-party ad de-anonymization, no general solicitation. Pure-Python scoring (no AI cost to rank). One-click intent-aware outreach Draft (review-only LPDevOutreach). LPs with a PROMISED follow-up now due sort ABOVE the scored list (a tier, not a score bonus — the score keeps meaning signal recency/momentum/fit/stage) and carry the LP's own words as reason #1. The LP Autopilot (Agent A) scans promise-first, then in intent order. Meeting transcripts (app/services/meeting_transcripts.py; ships DARK behind FUNDOS_MEETING_TRANSCRIPTS): what was actually SAID, as opposed to the calendar metadata the meeting summariser has always read. Three ways in — a dispatched Recall.ai bot, a signed webhook from a notetaker the firm already runs (POST /webhooks/transcripts/, HMAC-SHA256, fail-closed), or a VTT/SRT/TXT upload that needs no integration. Ingesting an existing notetaker is preferred over dispatching a second bot into an LP call. No transcript is stored without a recorded consent basis — that is a refusal, not a default — and a bot is never dispatched automatically. Asks, commitments and promised follow-up dates are extracted from the VERBATIM text rather than the summary, since a short summary drops exactly the clause that carries a date. Verbatim speech expires on a retention clock while the record that a recording happened, and on what basis, is kept. LP promises (/fundos/lp-crm/, right panel; app/services/lp_promises.py): "come back to us in the fall" recorded as a queryable date, not just searchable prose. Extraction stamps a memory item with revisit_at + precision (day/week/month/quarter/season) + the LP's verbatim phrase, resolving relative expressions against the date the promise was MADE rather than the date extraction ran. Lead time scales with precision, so a vague promise surfaces ~2 weeks early and a named day 3 days early. Only human-CONFIRMED promises drive outreach; a promise auto-closes once an outbound email or a meeting is logged in its window, so it fires once. Exposed on the living brief and via fundos_get_lp_memory (promises_due / promises_upcoming; MNPI excluded). LP-portal data-room VIEW/DOWNLOADs auto-become document_viewed signals via a cron sweep (high-precision: only the LP's own portal user counts, never GP/staff views). The Autopilot's post-meeting rule reads BOTH meeting books — meetings the GP logged by hand and meetings synced from a connected calendar and matched to the LP — so a meeting nobody logged still produces a follow-up draft. Where the same meeting is in both, the draft is written from the two summaries concatenated, with the GP's own notes leading and treated as authoritative. Calendar matches the system inferred heuristically are surfaced but flagged "unconfirmed" on the approval card; a match a human explicitly rejected is never used. Nothing auto-sends — every draft is a proposed AgentAction a GP approves. ### FundOS — Portfolio & Pipeline News A cron-refreshed news feed for the companies in the deal book (active + pipeline deals; never passed/closed-lost or exited). Surfaced on the GP dashboard (1 latest headline per company + "N more") and a grouped feed page at /fundos/news/, newest first, link-out to source in a new tab. - Sources: Finnhub company-news for tickered/public names; Google News RSS (free, no key) for private companies, keyed by name. - Relevance: an LLM filter (one Gemini call per Google-News batch, thinkingBudget 0) keeps only headlines about THIS company using its context — "what it does" seeded from the Deal plus the company's own homepage description fetched from its domain — so a startup named "Sherpa" drops news about Nepal mountain guides. Best-effort (degrades to unfiltered if AI unavailable). Finnhub (symbol-keyed) skips the filter. - Per-org: Organization.news_feed_enabled (admin toggle at /fundos/news/settings); env NEWS_FEED_ENABLED is an optional global force-on. Ships dark (default off, every org). - /fundos/news/cron/refresh (Bearer CRON_SECRET) — new-only on the cron; the settings "Refresh now" dispatches a background deep job that also re-filters existing headlines. Read-only; nothing auto-sends. LP IR Communications (segmented distribution lists + email campaigns, /fundos/lp-crm/...): - segments — Distribution lists. Static (hand-picked LPs) or dynamic (saved filter over pipeline stage, entity type, priority, KYC status, committed capital, consultant flag — auto-updates). - segments/new, / (preview resolved members + sendable/no-email counts), //delete. - campaigns — IR campaign list with per-campaign sent/open/click rollups. - campaigns/new, / (compose + recipients + stats + send controls). - POST /campaigns//draft-ai — JSON {topic}; Gemini drafts subject+body grounded on the agent config + the fund's Answer Bank. - POST /campaigns//send-test — Send a one-off test to yourself before sending for real. - POST /campaigns//send — Human send gate: materializes recipients from the segment, drains the first batch; the rest deliver automatically. - POST /campaigns//send-batch — Drain one batch (client-driven continue loop). /pause, /delete. - GET /ir/settings (admin) — Per-org send provider: native SMTP, or SendGrid/Mailchimp with an encrypted API key + from/reply-to. - GET /ir/open/.gif — Public open pixel; logs an email_opened signal on the LP. - GET /ir/click/ — Public click redirect; logs a follow_up_requested signal, then 302s to the target. Nothing sends automatically — a human clicks Send on each campaign. Opens and clicks feed buying-intent signals back into the pipeline + analytics. LP Prospecting Connectors (seed the pipeline from external sources, /fundos/lp-crm/...): - sources — Connector list + recent import runs + review-queue count. - sources/new (admin) — Add an Apollo.io connector (API key stored encrypted) with a people-search config (org keyword, titles, locations); or a custom REST endpoint. - POST /sources//run — Synchronous pull → dedupe/upsert candidates → review queue. - import/csv — Upload or paste a CSV (Dakota Marketplace export, LinkedIn Sales Navigator export, or any CSV); columns auto-map (override per field). Rows land in the review queue. - sources/review — Triage imported prospects (needs_review=True). Accept → pipeline at Identified stage; Drop → delete. - POST /sources/cron/pull — Bearer CRON_SECRET; runs every enabled connector daily. Dakota's API is partner-gated and LinkedIn has no usable prospecting API — both flow through CSV export import. ### FundOS — Pricer POST /api/v1/fundos/pricer IRR/MOIC/WAL/waterfall — ?ephemeral=true safe ### FundOS — Risk GET /api/v1/fundos/risk/covenants POST /api/v1/fundos/risk/covenants Create a covenant + run its first check. `deal_id` OR `vi_deal_id` (auto-imports the VcDeal) OR neither (unattached). ?ephemeral=true validates + echoes with zero writes (skips deal resolution) POST /api/v1/fundos/risk/covenants/{id}/check GET /api/v1/fundos/risk/alerts POST /api/v1/fundos/risk/alerts/{id}/resolve Mark resolved; stamps resolved_at ### FundOS — Credit Facilities + Borrowing Base GET /api/v1/fundos/risk/facilities Facilities with commitment/outstanding GET /api/v1/fundos/risk/facilities/{id} Terms + rules + rates + limits + latest run POST /api/v1/fundos/risk/facilities/{id}/bb/run Run borrowing base — body {"tape_id"?: N, "ephemeral": true} for a what-if that persists nothing (use before proposing a draw) GET /api/v1/fundos/risk/facilities/{id}/bb/{run} Run detail incl. per-loan eligibility verdicts Web: /fundos/risk/facilities (workspace), /fundos/risk/facilities/{id}/certificate (+.xlsx), /fundos/risk/facilities/{id}/lender-pack.zip Nightly computed-covenant scan (deterministic re-derivation — NOT the risk agent): GET /fundos/risk/cron/covenant-scan (Bearer CRON_SECRET, 23:00 UTC weekdays). To trigger the risk_agent (Agent F) portfolio scan that drafts lender notices, POST /fundos/risk/agent/scan. ### FundOS — Remittances, Stress Test, Treasury, Slack Q&A Web: /fundos/pricer/remittances (servicer collection reports → per-loan reconciliation vs the tape series; breaks → Exception Inbox) Web: /contracts/{doc_id}/stress-test (proposed term-sheet terms vs the live loan book; deterministic verdict + optional AI counter draft, display-only) Web: /fundos/treasury (facility outstanding + availability; read-only Plaid bank balances when PLAID_CLIENT_ID/SECRET are configured — ships dark, no payments ever) Slack two-way Q&A: mention the FundOS bot or DM it ("what's our exposure to consumer loans?") — org-scoped answers via the Copilot; configure at /admin/integrations/slack-qa (Slack v0 signature-verified receiver at POST /webhooks/slack/events; one Slack team maps to exactly one organization) Daily bank-balance refresh: GET /fundos/treasury/cron/refresh (Bearer CRON_SECRET; no-ops while Plaid is unconfigured) ### FundOS — Headless ingest + loan-ledger bridge + GL accrual POST /api/v1/fundos/pricer/tapes Push a loan tape (base64 body). Auto-commits ONLY when a human confirmed this source's column mapping once; first uploads land as drafts with a review URL POST /api/v1/fundos/pricer/remittances Push a servicer remittance — auto-commits + reconciles under the same trust rule; breaks land in the Exception Inbox Email-inbound ingest: servicers can EMAIL tapes/remittances (CSV/XLSX attachments) to the org's inbound address — admins allow-list each servicer's sending address at /fundos/pricer/ingest-routes (two factors must match: the org's secret inbound address AND the allow-listed sender); the same mapping trust rule applies (first file from a new source drafts; auto-commit only after a human confirmed the mapping once), and routed senders skip deal-pitch triage entirely Inbound pitch decks (same address, different pipeline): POST /webhooks/inbound-email accepts a forwarded email (raw MIME, SendGrid multipart, or JSON with base64 attachments) addressed to a workspace's per-user pitch address ({prefix}@vantedgeai.com, set at /fundos/crm/ → "My inbound address"). A PDF/PPTX/DOCX attachment is text-extracted (pypdf/markitdown, with a Gemini-vision fallback for scanned image-only decks) and routed to the Deal Screener, which screens it against the workspace's investment thesis (/fundos/crm/thesis) and proposes a Deal carrying a memo, a 0-100 thesis-fit score and a pass/proceed/strong_proceed recommendation. Mail with no readable deck falls through to intent triage, which itself hands a net-new opportunity to the screener rather than proposing a memo-less deal. Only the extracted TEXT is stored, never the deck file. Nothing enters the CRM until a human approves the proposal; an allow-listed servicer sender still takes the tape/remittance path above instead. Web: /fundos/pricer/tapes/{id}/sync-fa — preview→commit bridge from the tape book into Fund Admin's loan ledger (accruals/NAV; human-gated) Fund setting "Accrue loan interest to the GL daily" (default off): idempotent per-loan daily JEs — Dr receivable / Cr income, PIK capitalizes, defaulted loans are non-accrual MCP (hosted /mcp): fundos_list_deals, fundos_get_deal, fundos_get_pipeline, fundos_list_lps, fundos_get_lp, fundos_list_covenants, fundos_check_covenant (ephemeral or stored), fundos_list_risk_alerts, fundos_run_pricer, fundos_compute_pnl, fundos_compute_waterfall, fundos_list_fund_accounts, fundos_list_facilities, fundos_run_borrowing_base (EPHEMERAL by default — run the what-if before proposing a draw) ### FundOS — Investor Portal GET /api/v1/fundos/investors/lps GET /api/v1/fundos/investors/lps/{id} Includes capital-call ledger POST /api/v1/fundos/investors/lps/{id}/capital-calls ### FundOS — CFO Center GET /api/v1/fundos/cfo/accounts Fund account list (bare array) GET /api/v1/fundos/cfo/chart-of-accounts Same accounts + a computed debit/credit balance each + org totals: {accounts, totals, currency, count} POST /api/v1/fundos/cfo/report P&L + NAV computation POST /api/v1/fundos/cfo/waterfall European waterfall splits CFO Center v2 — Fund Performance + Period Close (web UI, /fundos/cfo/accounts//...): - dashboard — 6-tile live overview (MTD/QTD/YTD P&L, Risk Metrics, Top Movers, Period Close, LP Waterfall, Benchmark sparkline) - performance — MTD/QTD/YTD P&L tabs + Chart.js fund vs SPY/QQQ benchmark line chart - risk — Sharpe ratio (12-month), fund leverage (gross/net), bond portfolio DV01 - movers — Top 10 P&L contributors by position (DAILY/MTD/QTD/YTD) - period-close — Run month/quarter/year-end closes; locks P&L; runs LP waterfall (HF: on realized+unrealized; PE/VC/PC: on realized only) - period-close — GL LOCK (book-of-record control): while a period is closed, journal entries dated on/before its period end are rejected from every surface (manual JE, REST API, auto-posting, QBO/Xero sync); posted entries + lines are append-only — corrections are dated reversing entries in the open period - period-close//reopen — POST, workspace-admin only: audited reopen (reason required, kept on the trail) that unlocks GL posting for the period - accruals/true-up — POST: management-fee TRUE-UP — compares booked (crystallized accruals) vs the actual invoice for a period and drafts the adjusting JE (under-accrual books expense, over-accrual releases it; draft-first, never auto-posted; dated in the open period when the target period is locked) - capital-call receivable flow: issuing a call posts DR Capital Call Receivable / CR LP Partner Capital; the paid event relieves the receivable (DR Cash / CR Receivable) — NAV cash moves only when money lands; legacy calls without an issued entry keep DR Cash / CR Partner Capital - four-eyes beyond NAV: manual journal entries always save as DRAFTS (UI + REST API — is_posted in the body is ignored) and a DIFFERENT team member posts them (maker ≠ poster enforced); distribution issuance requires an issuer different from the drafter; fee-schedule changes are workspace-admin-only - external-auditor role (User.is_auditor, admin-assigned on /admin/users): a READ-ONLY scope — the account reaches only audit packs, the trial balance, and the GL journal (GETs); everything else 403s; every allowed view is written to the immutable audit log (AUDITOR_VIEW) so the fund can show exactly what its auditor saw and when; admin/superadmin flags override - ILPA quarterly report (G2): /fundos/fund-admin/lp-report -> "Generate ILPA report" (one LP) or "All LPs" (whole register) renders the quarterly report in the ILPA Reporting Template layout (capital account statement with fees gross->offsets->net, commitment schedule, since-inception performance incl. net XIRR) as XLSX and FILES it into the LP's document vault (category "Quarterly Report (ILPA)"); deterministic Layer-1 composition of PCAP + performance + commitments + fee + expense engines — never an LLM - LP portal Statements (G2): /portal/lp/statements now live — the LP sees their own published statements (ILPA quarterly reports, capital-account statements, K-1s, notices) with downloads, plus their capital-account roll-forward history; strictly scoped to the logged-in LP's own record and vault - LP portal returns: /portal/lp/performance reports TVPI / DPI / RVPI / net IRR from `fund_admin.performance.lp_performance` — the SAME engine the ILPA quarterly report the LP receives is built from, so the portal and the document cannot disagree. Residual value is the LP's ownership share of the latest RELEASED (four-eyes-signed) NavRun, never an estimate. A fund with no released NAV is excluded from the ratio basis and NAMED in the UI rather than scored as zero residual (which would understate TVPI); its cash still appears in the contributed/distributed totals, which need no valuation. With nothing valued anywhere the multiples are withheld with an explanation instead of shown as 0.00x. tvpi == dpi + rvpi always. Distributions count only at status "issued" — a drafted distribution has not been paid - LP document release: a statement-type document (K-1, capital account, quarterly report, notice) is filed into the investor's vault the moment it is produced and stays PRIVATE until a workspace admin releases it — generating a K-1 does not show it to the LP, because a freshly generated K-1 is an unreviewed draft. Release one document from the LP page (POST /fundos/investors//documents//release), or a whole tax year from the fund's K-1 page (POST /fundos/cfo//k1/release-to-portals, refuses the in-progress year, idempotent). Withdraw reverses it (.../unrelease) without deleting the document — the vault stays the system of record. Every release and withdrawal writes an AuditLog row. Ownership is checked against the LP's OWN room, not just the workspace, so one investor's document cannot be published to another's portal. Not agent-callable by design: no MCP tool, no action handler — expanding who can see fund data is human-only. Non-statement documents (side letters, onboarding packs) need no release; they are LP-visible as soon as they are filed - K-1 tax data package (G3): /fundos/cfo//k1/package?tax_year=YYYY downloads the CPA workbook — every LP's DERIVED K-1 boxes (distribution character from typed distribution rows, Box 1 = the fund's posted GL income x the FUND-scoped register share, Box 13W = stored fee lines net of side-letter discounts), Item L GAAP capital + a mechanical tax-basis ending (GAAP minus the LP's share of unrealized P&L from period closes), fund totals that foot, 1099-INT data for credit funds, and an assumptions sheet ("FundOS assembles, your CPA reviews and files" — never tax advice); also fixes the single-K-1 generator to use the fund-scoped ownership share (was diluting across the whole org) - Multi-entity structures (G4): fund accounts link into structures via a parent pointer + relation_type (feeder | parallel | spv | blocker) with an ownership % — /fundos/cfo/accounts//master-feeder manages links (admin-only, cycle-guarded); intercompany P&L-allocation entries post per-entity account pairs (master DR 4900 P&L-allocated-to-feeders / CR 2850 due-to-feeders; feeder DR 1350 due-from-master / CR 4800 allocated income; loss flips legs; idempotent per period; GL period lock applies); CONSOLIDATED NAV is a reporting view over each entity's latest released NAV with intercompany eliminations (matched due-from/due-to pairs net out, unmatched investment-in-master assets stripped) and minority interest for partially-owned SPVs/blockers — never persisted as a NAV run; feeder LPs' reports carry a look-through line (LP share of feeder x feeder %% of master = effective master exposure) - Shadow NAV (G5a): a nightly cron (mon-fri 22:00 UTC, /fundos/fund-admin/cron/shadow-nav) computes an ADVISORY draft NAV per NAV-module fund with the same deterministic engine + validation checks as the official close (period_type=shadow, one per fund per day, 14-day retention); the NAV page shows a shadow strip (today's NAV, delta vs prior, anomaly badges, Refresh-now); shadow runs can NEVER be submitted or signed — month-end becomes review-and-sign ("T+1 NAV" vs a traditional administrator's T+10-15) - Auditor Q&A agent (G5b): POST /fundos/cfo/accounts//audit-qa — grounded, citation-only answers over the fund's audit surfaces (trial balance, capital accounts, fee schedule, released NAVs); answers strictly from that context with a section citation per figure, declines + names the artifact to request when the context lacks the answer; question text is fenced as untrusted input; read-only and available to the external-auditor role - Unmarked-portco guard + NAV staleness notice (G5c): an active portfolio company with NO mark is a named warning on the NAV run (it used to be silently valued at 0); the NAV page warns when portfolio valuation changes were recorded AFTER the last released NAV (the official number may be stale) - Add-investor doc filing (G5d): the new-LP form takes optional onboarding documents (subscription/KYC/side letter) filed straight into the investor's vault at creation - period-close/ — LP waterfall allocation detail; HF incentive fee shown as accrual - lp-terms — Per-LP hurdle/carry/preferred return overrides (blank = fund default) - chart-data — JSON endpoint for Chart.js benchmark chart data (SPY, QQQ, Fund NAV) CFO Center Audit Pack (web UI, /fundos/cfo): - Generate a ZIP audit pack (trial balance, LP capital roll-forward, mgmt fee, waterfall, cover memo) for any fund account with a custom as-of date. - View the contents of any generated pack (individual file list with per-file download buttons). - Delete any audit pack (including errored ones) via the trash icon. - Per-account persistent configuration via AuditPackConfig: toggle each of the 5 standard artifacts on/off and attach extra VDR documents to include in the pack. Config is shared across all CFO users in the workspace. - Downloads are proxied through Flask (authenticated); direct DO Spaces URLs are not exposed. OMS Multi-Asset Order Entry (June 2026): - New Order modal supports 8 instrument types: EQUITY, ETF, BOND, HY_BOND, PREFERRED, OPTION, FUTURE, SWAP - Selecting a type dynamically reveals type-specific fields (CUSIP/coupon/maturity/face_value/YTM for bonds; underlying/strike/expiry/option_type for options; contract_code/multiplier/tick_size for futures; swap_type/notional/fixed_rate/floating_index for swaps) - InstrumentSpec rows created alongside orders; position book shows DV01 + Duration columns for bond positions - Benchmark prices (SPY, QQQ) fetched nightly from Finnhub at 9pm UTC via cron /fundos/cfo/cron/benchmark-refresh OMS Tax Lot Tracking (June 2026): - Every buy creates a TaxLot row (FIFO — IRS default lot method) - Every sell matches against open lots oldest-first; realized G/L recorded per lot - Position book: click any position row to expand individual lots with ST/LT badge + unrealized G/L - Short-term: held < 365 days (ordinary income rates); long-term: held ≥ 365 days (preferential 0/15/20%) - Tax Summary page (/fundos/oms/tax-summary): annual realized ST/LT gains/losses + open lot unrealized exposure - Wash-sale detection: flagged when a repurchase occurs within 30 days of a loss close - All figures are informational; consult your tax advisor for 1099-B reconciliation and filing OMS Trade-Fill Import (August 2026): - GET/POST /fundos/oms/fills/import — two-step upload (validate & preview -> import) for broker fill files - Required columns: trade_date, settle_date, side (BUY|SELL), instrument_type (FUT|SPOT), symbol, quantity, price, fees, broker, account_ref, broker_trade_id. Optional: vintage - Common broker aliases auto-mapped (tradedate, b/s, sectype, ticker, qty, px, commission, executing_broker, trade_id, ...); date formats YYYY-MM-DD, MM/DD/YYYY, DD/MM/YYYY, YYYY/MM/DD, DD-Mon-YYYY, YYYYMMDD - Quantity is in CONTRACTS (lots for futures) and must be positive — a sale is side=SELL, never a negative quantity - Idempotency: a fill is keyed on (broker, broker_trade_id); the synthesized Execution.exec_id carries a database UNIQUE index, so a re-upload imports 0 rows through any batch, session or request. The preview reports duplicates BEFORE the commit - Inventory gate: a SELL larger than the quantity held at that point in the file is refused with the arithmetic (v1 books no short inventory). The check is cumulative down the file - Unknown symbol: prompted, not silently booked — map it to an instrument already held, or create one (name, contract size, currency). The answer is remembered per workspace - Commit books each fill as an Order (FILLED) + Execution, then applies it through the shared position fold, appends an IborEvent, and posts the GL cash leg - Result page /fundos/oms/fills/import/: per-symbol position deltas (quantity, WAC, realized P&L, exposure before/after), the NAV move with per-row arithmetic (quantity x contract size x (mark - price), less fees), and the tri-state limit register - Demo file shipped in the repo at scripts/demo/gtc_fills.csv OMS Marks (August 2026): - GET/POST /fundos/oms/marks — record a dated manual mark per instrument (with a fair-value rationale) or import a settlement-price CSV (symbol, date, settle_price) - Marks are stored as dated PriceQuote rows — the prices the NAV engine reads — and stamped onto the position, which is revalued at its contract multiplier - Re-importing the same instrument and date replaces the price rather than adding a second one OMS Historical Lot Import (June 2026): - GET/POST /fundos/oms/lots/import — two-step upload (preview → confirm) for bulk CSV lot import - Accepts any broker or spreadsheet export; auto-maps column aliases (symbol→ticker, shares→quantity, price→cost_basis, date→acquired_date, fund→fund_account, ccy→currency, etc.) - Columns: required: ticker, acquired_date, quantity, cost_basis; optional: instrument_type, fund_account, currency - Date formats supported: YYYY-MM-DD, MM/DD/YYYY, DD/MM/YYYY, YYYY/MM/DD; BOM-stripped UTF-8 - Preview step groups lots by ticker+account, shows total shares and total cost; error rows listed separately and skipped - Commit step creates TaxLot rows (buy_order_id=None, currency=USD default or per-row) and WAC-merges into Position (no existing positions overwritten) - "Import Lots" button added to /fundos/oms/positions top-right action bar CFO Master-Feeder Fund Accounting — Gap #5 (June 2026): - FundAccount model: self-referential FK parent_fund_account_id (INTEGER REFERENCES fundos_fund_accounts(id)), allocation_pct (NUMERIC 8,4), feeder_funds dynamic relationship, is_master/is_feeder properties - GET /fundos/cfo/accounts//master-feeder — master fund card + feeder table + YTD consolidated P&L + link/unlink forms - POST /fundos/cfo/accounts//master-feeder/allocate — post intercompany GL entries for a period (YYYY-MM); idempotent by reference key mf_alloc::: - POST /fundos/cfo/accounts//master-feeder/link-feeder — designate an existing fund account as feeder + set allocation_pct; entity_type auto-set to "feeder" - POST /fundos/cfo/accounts//master-feeder/unlink-feeder/ — remove feeder relationship - app/services/master_feeder.py: get_structure(), compute_consolidated_pnl(), post_allocation_entries(), validate_allocations() - GL entries: master side DR 1300 Due-from-Feeder / CR 4900 Intercompany Allocation Income; feeder side DR 4800 Allocated Income from Master / CR 2900 Due to Master Fund - Allocation pcts need not sum to 100%; master absorbs residual (common in GP participation structures) - Migration: ALTER TABLE fundos_fund_accounts ADD COLUMN IF NOT EXISTS parent_fund_account_id + allocation_pct + index - New Account form updated with parent fund selector (auto-filters to non-feeder accounts); "Master-Feeder" link in CFO subnav HF Price Reconciliation — 3rd Recon Break Type (June 2026): - 5th file upload slot "PB prices" on /fundos/ops/recons/new — prime broker closing price CSV (columns: symbol, price/current_price/mark, currency, price_date; auto-mapped) - HFPriceRow model (hf_price_rows table): stores parsed PB price rows per recon run (source_type='pb'); recon engine derives internal prices from existing position rows (market_value / abs(quantity)) - _reconcile_prices() in app/services/hf_ops/recon.py: compares internal per-share price vs PB closing price per (canonical_symbol, currency); tolerances: 1% (PRICE_TOL_PCT) with $0.05 floor (PRICE_TOL_MIN); critical if deviation >= 3% (PRICE_CRIT_PCT) - break_type='price', classification='price_break' (both sides present, deviation exceeds threshold) or 'price_missing' (PB price exists but no internal position) - HFReconRun new columns: pb_prices_blob, price_total, price_matched, price_break_count; new price_match_pct property - recons_detail.html: price stat cards (matched/total + %) and price breaks count card shown only when price_total > 0; PB prices download link - Migration: hf_price_rows CREATE TABLE IF NOT EXISTS; ALTER TABLE hf_recon_runs ADD COLUMN IF NOT EXISTS (pb_prices_blob, price_total, price_matched, price_break_count) - Flash message on commit includes price break count when PB price file was uploaded CFO NAV Accruals — Daily Management Fee Engine (June 2026): - GET /fundos/cfo/accounts//accruals — daily management-fee accrual dashboard - POST /fundos/cfo/accounts//accruals/run — manually trigger today's accrual entry - POST /fundos/cfo/accounts//accruals/crystallize — crystallize a period (posts GL journal entry) - GET /fundos/cfo/cron/daily-accruals — cron: runs all fund accounts every weekday at 09:30 ET - NavAccrualEntry model (fundos_nav_accruals table): one row per (fund_account, date); stores nav_basis, rate_annual, days_divisor (252 HF / 365 other), accrual_amount, cumulative_mtd, period_label, status - Formula: daily_fee = nav_basis × (annual_rate / days_divisor); divisor = 252 for HF (trading-day), 365 for PE/VC/PC (calendar-day) - Status lifecycle: accrued → crystallized (GL entry posted) → reversed (manual adjustment) - Crystallization posts DR Management Fee Expense 5100 / CR Management Fee Payable 2100; GL entry linked from each row - YTD bar chart shows per-month accruals; green = crystallized, blue = open - Idempotent: re-running an accrual for an existing date is a no-op (skipped with info flash) - Migration: CREATE TABLE IF NOT EXISTS fundos_nav_accruals (...) OMS Multi-Currency FX Rates (June 2026): - GET /fundos/oms/fx-rates — daily FX rate dashboard; shows 1 foreign = X USD for all tracked currencies + 1 USD = X foreign inverse - GET /fundos/oms/cron/fx-refresh — Finnhub /forex/rates?base=USD fetch + upsert; runs weekdays 9PM UTC via Vercel cron - FXRate model (fundos_fx_rates table): from_currency, to_currency, rate (1 foreign = rate USD), trade_date, source; UNIQUE (from_currency, to_currency, trade_date) - app/services/fx_rates.py: fetch_and_store_rates(), get_rate(), convert_to_usd(), latest_rates() (DISTINCT ON from_currency for latest snapshot) - Finnhub rate inversion: Finnhub returns 1 USD = N foreign; we store 1/N so rate always means "1 foreign = X USD" - TaxLot.currency column (VARCHAR 3, default USD) allows per-lot currency tagging for multi-currency books - CCY badge displayed on positions table for non-USD holdings; USD positions show plain "USD" in grey - Migration: ALTER TABLE fundos_tax_lots ADD COLUMN IF NOT EXISTS currency VARCHAR(3) NOT NULL DEFAULT 'USD'; CREATE TABLE IF NOT EXISTS fundos_fx_rates (...) AI FDE — Fund Onboarding Automation (June 2026): The AI FDE automates fund client onboarding end-to-end. An admin creates an onboarding at /fundos/fde/, which generates a shareable link (/onboard/). The fund's COO/CFO chooses between two discovery paths: DISCOVERY PATH A — Document upload (recommended): 1. Client uploads PPM, pitch deck, or any combination of up to 3 PDFs (≤5 MB each). 2. POST /onboard//upload (multipart/form-data, field name "docs") → Gemini multimodal extracts Fund Profile from the documents → Sets discovery_mode="doc_upload" on the FundOnboarding record → Seeds a gap-fill opening message confirming what was extracted → Returns {success, profile, extraction_summary, opening_message} 3. A short 4–6 question follow-up fills in gaps the documents didn't cover (tech stack, pain points, priorities, integration constraints). 4. When follow-up is complete, discovery_complete=true. DISCOVERY PATH B — Q&A interview (fallback): Clients who don't want to share documents or don't have them yet complete a ~30-question async conversation. The opening screen shows 6 fund-type pill buttons; each subsequent turn shows [OPTIONS: A | B | C] pills for finite- answer questions. All state persists so clients can close and return later. Both paths produce the same structured Fund Profile JSON. When complete, the admin clicks Configure to set up the FundAccount, pipeline stages, and LP placeholder. DISCOVERY SCOPE (both paths): fund type (hedge_fund/vc_fund/pe_fund/private_credit/ family_office), AUM, investment strategy, team size, domicile, GP location, launch timeline, regulatory status (SEC/CFTC/AIFMD/FCA), existing systems (prime broker, fund admin, compliance system, PM system, data provider), trading instruments, reporting requirements, pain points, priority deliverables, integration requirements. MODULE SELECTOR RULES: - cfo_center: always enabled - gp_admin_dashboard: always enabled - lp_portal: hedge_fund / pe_fund / vc_fund / private_credit - oms: any trading instruments present - trading_fix_engine: equities / fixed_income / options / futures / fx in instruments - compliance_os: SEC or CFTC registered - deal_room: vc_fund or pe_fund - lp_crm: pe_fund / vc_fund / private_credit Admin routes (login required): GET/POST /fundos/fde/, GET /fundos/fde/runs/, POST /fundos/fde/runs//configure Public routes (token auth): GET /onboard/, POST /onboard//message, POST /onboard//upload, GET /onboard//status The /onboard//message response shape: {reply, choices, status, question_count, discovery_complete} `choices` is a string[] of clickable options stripped from Gemini's [OPTIONS: A|B|C] block. An empty array means the question is free-text. The /onboard//upload response shape: {success: true, profile: {...}, extraction_summary: "...", opening_message: "..."} On failure: {error: "extraction_failed", message: "..."} — client falls back to Q&A path. DB columns on fund_onboardings: discovery_mode ("qa" | "doc_upload"), doc_extraction_json (raw extracted profile + summary + file_count stored as JSON). Model: gemini-2.5-pro by default (override via GEMINI_FDE_MODEL env var without redeploy). Chat turns use thinkingBudget=0; profile extraction uses thinkingBudget=2048. Document extraction uses Gemini multimodal with inline_data base64 PDF parts. Database tables: fund_onboardings, fund_discovery_sessions, fund_impl_documents, fund_impl_tasks, fund_change_requests All discovery turns are billed via gemini_call(feature=fundos.fde.discovery) and linked to AgentRun for observability. Phases 2-5 (planning docs, config orchestration, UAT, change requests) are planned for future builds. ### FundOS — OMS / Trading GET /api/v1/fundos/oms/orders List orders (status, fa_id filters) POST /api/v1/fundos/oms/orders Submit order (runs pre-trade compliance) GET /api/v1/fundos/oms/orders/{id} Order + executions GET /api/v1/fundos/oms/positions Position book POST /api/v1/fundos/oms/pretrade Dry-run compliance check (no order created) GET /api/v1/fundos/oms/fix-status FIX sidecar health + session state ### FundOS — Model Portfolios + Rebalancing GET /api/v1/fundos/oms/models List portfolio models GET /api/v1/fundos/oms/models/{id} Model detail + weights + assignments POST /api/v1/fundos/oms/models Create model with weights GET /api/v1/fundos/oms/rebalance List rebalance proposals POST /api/v1/fundos/oms/rebalance/propose Generate proposal (drift|manual|cash_flow) POST /api/v1/fundos/oms/rebalance/{id}/approve Approve proposal → creates OMS orders Web UI routes (module flag: rebalancing): GET /fundos/oms/models Portfolio models grid GET /fundos/oms/models/new Create model form GET /fundos/oms/models/{id} Model detail (weights, drift table, assignments) GET /fundos/oms/models/{id}/edit Edit model settings POST /fundos/oms/models/{id}/weights Add a weight row POST /fundos/oms/models/{id}/weights/{w}/delete Delete a weight row POST /fundos/oms/models/{id}/assign Assign model to fund account POST /fundos/oms/models/{id}/unassign/{a} Unassign GET /fundos/oms/proposals Proposals list (filter by status) POST /fundos/oms/proposals/generate Generate proposal (form submit) GET /fundos/oms/proposals/{id} Proposal detail + trade lines POST /fundos/oms/proposals/{id}/approve Approve → OMS orders POST /fundos/oms/proposals/{id}/cancel Cancel ### FundOS — Syndication GET /api/v1/fundos/syndication POST /api/v1/fundos/syndication/{id}/allocate ### Stateless Document API — no FundOS account required Developers, law firms, fund admins, and LPs can call these endpoints with only a `doc_` API key. All data is supplied inline. No fund account, no pre-loaded data. Outcome-based billing — charged per successful document at rates set in the caller's enterprise agreement (contact https://fundos.vantedgeai.com/contact for access). POST /api/v1/documents/k1 — Schedule K-1 Form 1065 (per partner) POST /api/v1/documents/odd — ODD/DDQ completion POST /api/v1/documents/analyze — Document red-flag + entity-map POST /api/v1/documents/waterfall — European waterfall POST /api/v1/documents/pricer — IRR/MOIC/WAL pricer Auth for all: `Authorization: Bearer doc_` K-1 minimal body: { "fund_name": "Eight Capital Fund II", # required "tax_year": 2025, # required "partner_name": "Aksia LP", # required "lp_share_pct": 0.15, # optional "capital_account_start": 1000000, # optional "contributions_year": 0, # optional "box_1_ordinary_income": 45000, # income boxes (all optional, default 0) "box_9a_lt_gain": 105000, "box_13_deductions": 2500, "distributions": 0, # OR dist_roc/dist_income/dist_lt/dist_st/dist_recallable "format": "html" # only format supported today } Returns: { "html": "...", "filename": "k1_2025_aksia_lp.html", "charged_usd": 5.00, "charge_reason": "charged" } ODD minimal body: { "fund_info": {"fund_name": "...", "strategy": "VC pre-seed", "aum": 20000000}, "questions": ["Describe your investment process.", "..."] } Returns: { "answers": {...}, "charged_usd": 25.00, "token_usage": {...} } Analyze body: { "documents": [{"name": "cim.pdf", "text": "...full text..."}, ...] } Returns: { "summary": "...", "red_flags": [...], "entity_map": {...}, "charged_usd": 3.00 } Waterfall body: { "distributable": 1000000, "committed": 1000000, "hurdle_pct": 8, "carry_pct": 20 } Returns: { "lp_total": 916000, "gp_total": 84000, "tiers": [...], "charged_usd": 0 } All endpoints: accept `vdr_` OR `doc_` keys. Developer orgs (plan=developer): usage billed in credits per agreement. Developer dashboard: https://fundos.vantedgeai.com/developers/dashboard — key table + monthly usage. --- ## Triggering AI Agents FundOS has 9 AI agents (A–I). They are triggered via POST, stream events via SSE, and propose AgentAction rows that humans approve before execution (the CFO Flux agent is read-only commentary and proposes nothing). | Agent | What it does | Trigger endpoint | |-------|-------------|-----------------| | A — LP Fundraising Autopilot | LP follow-up emails, meeting prep | POST /fundos/lp-crm/ → "Run Autopilot" button | | B — Deal Screening | Screens inbound pitch decks against the fund thesis | POST /fundos/crm/screen (paste text or upload a deck), or email a deck to the workspace pitch address | | C — Diligence Agent | VDR red-flag analysis | POST /fundos/vdr/room//diligence/run | | D — Closing Coordinator | Extracts CPs from term sheets | POST /fundos/transactions//extract-cps | | E — CFO Reminder Agent | Capital call reminder ladder | POST /fundos/investors/calls//schedule-reminders | | F — Risk Agent | Covenant + alert portfolio scan | POST /fundos/risk/agent/scan | | G — Deal Team Orchestrator | Multi-agent natural-language | /fundos/team | | H — Compliance Agent | KYC/AML + filing scan | POST /fundos/compliance/agent/scan | ### Approving / rejecting agent actions All proposed actions go through human review: POST /fundos/agent/actions//approve — GP approves and executes the action POST /fundos/agent/actions//reject — GP rejects the action Stream a run's events: GET /fundos/agent/runs//stream — SSE stream (?since= to resume) GET /fundos/agent/runs//events.json — polling fallback when SSE is blocked Nothing auto-executes. Every state-changing action requires a human to click Approve. --- ## Common Agent Workflows ### 1. Due Diligence on an inbound deck 1. fundos_create_deal (ephemeral=true) — validate 2. Human confirms → fundos_create_deal (persist) 3. create_deal_room → spin up diligence room 4. fundos_vdr_analyze → red flags + entity map 5. search_documents for specific terms ### 2. LP fundraising follow-up 1. fundos_list_lps → find target LP 2. fundos_get_lp → commitment + capital-call ledger 3. search_documents (LP room) → last meeting notes 4. Draft follow-up — STOP, ask human to send ### 3. Pre-trade compliance check (OMS) 1. oms_check_pretrade (ticker, side, qty, limit_price) → passed/blocked + notes 2. If passed → oms_create_order (HUMAN APPROVAL required) 3. oms_list_positions → confirm position updated after fills ### 4. Capital call ILPA notice 1. fundos_get_lp → confirm LP and lp_room_id 2. Human creates CapitalCall row in /fundos/investors/{id} 3. GP clicks ILPA button → notice generated into LP room 4. Agent NEVER auto-sends LP notices ### 5. T+0 affirmation chase (HF Ops) 1. hf_ops_dtcc_list_unaffirmed → pending confirms 2. hf_ops_dtcc_get_trade_status → live state per trade 3. hf_ops_dtcc_lookup_ssi → check SSI if mismatch suspected 4. hf_ops_dtcc_get_affirmation_scorecard → COO summary --- ## Webhooks Register an HTTPS endpoint to receive real-time FundOS events. ```bash POST /api/v1/webhooks/ {"url": "https://your-server/hooks/fundos", "event_types": ["action.approval_required", "job.completed"]} ``` Events: credit.low, credit.exhausted, credit.granted, action.approval_required, action.approved, action.rejected, action.expired, job.completed, job.failed, module.enabled, module.disabled Payload signed with HMAC-SHA256 in X-FundOS-Signature header. Retry schedule: immediate → 1min → 5min → 30min → 2hr → 8hr (6 total). delivery_id is stable across retries — use it to deduplicate. Verify the signature in Python: ```python import hmac, hashlib def verify_fundos_signature(raw_body: bytes, header: str, secret: str) -> bool: expected = "sha256=" + hmac.new( secret.encode(), raw_body, hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected.encode(), header.encode()) # In your endpoint handler: # raw = request.get_data() # sig = request.headers.get("X-FundOS-Signature", "") # if not verify_fundos_signature(raw, sig, MY_WEBHOOK_SECRET): # abort(401) ``` Webhook management endpoints: POST /api/v1/webhooks/ Register (returns secret — save it) GET /api/v1/webhooks/ List registered webhooks PATCH /api/v1/webhooks/ Update (url/name/event_types/is_active) DELETE /api/v1/webhooks/ Deactivate POST /api/v1/webhooks//rotate-secret Rotate signing secret POST /api/v1/webhooks//test Send a ping.test delivery GET /api/v1/webhooks//deliveries List delivery attempts --- ## Blog (fundos.vantedgeai.com/blog) Published articles covering fund operations, compliance, and product: - /blog/fund-graph-networked-knowledge — "Your fund is a network. Your software should finally act like one." — The Fund Graph: FundOS's networked-knowledge layer. Unified Person identity (one node per human across deal/LP/introducer records, matched by email, default-closed on ambiguity); a Connections/backlinks panel on every deal/LP/person answering "what touches this?"; human-confirmed mentions mined from notes/meeting summaries (exact-name, zero-LLM); generalized entity memory (living briefs for deals/companies/people, not just LPs); and a full-screen interactive Graph Lens. Connections are derived live from the underlying records (never a stale copy) and are readable by the AI agents via fundos_get_backlinks / fundos_get_graph / fundos_get_entity_memory. Two guarantees: MNPI never leaves via API/agents, and mined links are proposed-not-assumed until a human confirms. - /blog/shadow-reconciliation-fund-administrators — "Your books shouldn't wait for your administrator to tell you they're right." — Admin-TB Shadow Reconciliation: FundOS computes its own daily trial balance from the double-entry GL and reconciles it account-by-account against whatever TB the fund's administrator delivers, through a human-confirmed chart-of-accounts mapping (AI proposes, a person confirms once). Every break is classified deterministically (accrual timing / missing expense / FX translation / mapping gap) and lands in the exceptions inbox. The administrator stays the official book — this is an independent, always-current second opinion, with a no-rip-and-replace onboarding path from a signed opening trial balance. - /blog/who-to-call-what-to-know — "Who to call today — and what your companies did overnight." — Turning a fund's own first-party signal into a daily answer. The LP Intent Radar ranks which LP is warm this week from owned engagement (data-room views, email opens/clicks, meetings) with plain-English "why now" reasons + one-click outreach — deliberately NOT third-party reverse-IP de-anonymization (wrong tool under Reg D; authenticated data-room signal is cleaner). Portfolio & Pipeline News surfaces what active/pipeline companies did overnight, relevance-filtered against each company's own website description so a startup named "Sherpa" drops mountain-guide noise. Thesis: a fund's edge is attention on owned signal, not more data. - /blog/deterministic-ai-governance — "The model can be wrong. The decision it feeds can't be." — Deterministic AI governance for financial decision-making; the Governor runtime enforcement gate (pure-code policy checks, zero LLM in the gate) that validates every AI output before it reaches the decision-maker; immutable verdict evidence; the public policy graph at /.well-known/governance.json; observe-then-enforce rollout. Why "the enforcement is deterministic" is the defensible claim, not "deterministic AI", and why the real risk is a subtly-wrong input flowing silently into a decision a human then owns. - /blog/fund-intelligence-proactive-monitoring — "The questions that never get asked" — Fund Intelligence proactive monitoring; how the Monitor→Investigate→Judge→Publish pipeline works; dual-judge quality gate; finding specificity; the role of memory in fund operations monitoring. - /blog/ai-fde-fund-onboarding — "Your fund was supposed to go live six weeks ago" — AI FDE async onboarding; 30-question discovery interview; Fund Profile extraction; one-click configuration; why implementation takes months and how the AI FDE compresses it to hours. - /blog/fund-brain — "Your fund needs a brain, not more connectors" — FundOS thesis; why data access ≠ understanding; the difference between connectors and resolution. - /blog/auditor-data-room — "Your annual audit shouldn't take three weeks of email" — Auditor Data Room; ten folders auditors expect; one-click audit pack. - /blog/valuation-policy-template — "Nobody writes a valuation policy until an examiner asks for it" — SEC valuation policy requirements for private funds. - /blog/sec-exam-readiness-checklist — "You already know what the SEC will ask for" — SEC exam preparation for registered investment advisers. --- ## Error Codes 400 Bad Request — Invalid parameters 401 Unauthorized — Missing or invalid API key 403 Forbidden — Insufficient permissions 404 Not Found — Resource does not exist 422 Unprocessable — Pre-trade compliance block or validation failure 429 Too Many Requests — Rate limit (100 req/min); retry after X-RateLimit-Reset 500 Internal Error — Server error; retry with exponential backoff {"error": true, "message": "...", "code": "ERROR_CODE"} --- ## Key Facts for AI Agents - ?ephemeral=true on any POST: validates and returns shape, zero DB writes - AI processing on uploaded documents: automatic, ~30–120s; poll status - No AI model trains on or retains document content - FundOS agents propose actions; humans approve — nothing auto-executes - BYOD MCP connectors: Google Drive, SharePoint, Egnyte, QuickBooks, Salesforce, custom - OMS FIX engine: QuickFIX/n sidecar on DigitalOcean, connecting to BTIG prime brokerage - Tiered Google Gemini 3.x powers AI generation, routed per task: a fast default (gemini-3.5-flash), a cheap tier for extraction/classification (gemini-3.1-flash-lite), and an escalation tier for complex determinations (gemini-3.1-pro). Reasoning is pinned to its cheapest valid setting per model; the model behind each tier is configurable without a redeploy --- ## What's new — July 2026 ### Fund Graph — the networked-knowledge layer (2026-07-24) One unified Person identity per human, so a deal contact, an LP contact and an introducer who share an email are the same node (`/fundos/people`). Connections / backlinks panels on deal, LP and person pages, including human-confirmed "mentioned with" suggestions mined from notes and meeting summaries by exact-name matching — pure Python, zero LLM calls. Living-brief entity memory for deals, companies and people. An interactive graph lens at `/fundos/graph`. Agents read it with `fundos_get_backlinks` ("what touches X?"), `fundos_get_graph` (connection paths — "how do we know this LP?", with `focus='deal:7'` + depth for an ego network) and `fundos_get_entity_memory` (living-brief digest + confirmed facts). All read-only, all MNPI-excluded. **Call `fundos_get_entity_memory` before drafting anything about a deal, company, person or LP** — it is the difference between a generic draft and one that knows what was already promised. ### The Governor — deterministic AI governance (2026-07-23) Every AI output and every governed money write passes a deterministic gate (`app/services/governor.py`) that runs **zero LLM calls** and writes an immutable verdict record. The policy graph is data, not prose: the human-approval surface list is encoded in `governor_validators.APPROVAL_SURFACE_KINDS`, and `execute_action()` refuses those kinds without an explicit approved status. For agent builders this matters in two ways. `fundos_get_policy` returns the policy contract, so an agent can discover what will be refused **before** it proposes it. And the public contract at `/.well-known/governance.json` publishes the global policy defaults. Aggregate evidence — policy, validator and golden-fixture counts plus 30-day evaluation volume — is published at `/trust`. The gate ships in shadow mode until a policy is explicitly promoted, so today it is evidence rather than enforcement; the approval rules above bind regardless, because they are enforced in `execute_action()`. ### Distribution tax character, equalization, GP carry (2026-07-26 → 2026-07-29) A distribution reads two ways and they are not the same book. The **waterfall** reading counts every payout against capital, because that is what drives the hurdle and the carry split. The **accounting** reading splits each payout into return of capital vs income, because the LP is taxed on the income portion and it must appear that way on the capital account, the financial statements and the K-1. Implementing only the first understates taxable income. FundOS now carries both. Character is derived from per-deal realized economics drawn on pools **net of prior issued distributions**, flows into the capital account, the K-1 and all six LP statements, and falls back to a single total line whenever the parts do not foot to the total — so a statement never publishes a split that does not add up. Also shipped: LPA-driven **equalization** for subsequent-close investors on the terms actually on file, with per-asset-class rates; and a **GP carry ledger**, so the waterfall's GP leg lands in a real account against designated carry recipients. ### Agent Experience (AX) benchmark — real two-arm results (2026-07-29) The `/ax` page now publishes measured results from a runnable harness rather than claims: an agent given FundOS's docs and MCP tools scored **20/20 tasks, 40/40 points, 0 hallucinations, 4/4 approval gates respected**, against a **13/20, 26/40** baseline arm working from a conventional REST surface. The harness is open source and the benchmark re-runs monthly. Read the numbers there rather than from any prose that quotes them — including this file. ## What's new — May 2026 ### Stateless Document API + Developer Registration (2026-05-31) A second access model for FundOS — no account setup, no fund data to configure. Five REST endpoints that accept all data inline and return completed documents. Access is provisioned with your FundOS agreement — book a walkthrough at https://fundos.vantedgeai.com/contact. Get a `doc_` prefixed API key. Dashboard at https://fundos.vantedgeai.com/developers/dashboard. Endpoints (all at /api/v1/documents/, Bearer doc_; usage billed in credits): POST /k1 — Schedule K-1 (Form 1065) HTML — per-call fee (per agreement) POST /odd — ODD/DDQ completion — per-call fee (per agreement) POST /analyze — Document red-flag + entity-map — per-call fee (per agreement) POST /waterfall — European waterfall — free POST /pricer — IRR/MOIC/WAL — free Implementation: `app/routes/documents_api.py` + `app/services/k1_renderer.py` (pure-function K-1 renderer, no DB). Developer orgs: plan="developer", excluded from platform billing; usage is billed in credits per agreement. ### Compliance OS (11th module) — /fundos/compliance/ Models: ComplianceKYCRecord, FilingDeadline, RestrictedSecurity, PreClearanceRequest, ComplianceObligation, KYCNameScreenResult. Routes: - GET /fundos/compliance/ Compliance dashboard - GET /fundos/compliance/kyc/ KYC/AML records - GET /fundos/compliance/filings/ Regulatory filings - GET /fundos/compliance/trade/ Restricted securities + pre-clearance - GET /fundos/compliance/obligations/ Obligations register - POST /fundos/compliance/kyc//screen Gemini-powered name screen - POST /fundos/compliance/agent/scan Run Agent H MCP tools: fundos_list_kyc_records, fundos_list_filings, fundos_check_restricted_list (by ticker/ISIN/company name), fundos_list_obligations. ### Compliance Program (Rule 206(4)-7) — /fundos/compliance/program/ The CCO's system of record, layered on top of Compliance OS. Compliance OS tracks the operational artefacts (KYC records, filings, a restricted list); the Compliance Program is the Advisers Act Rule 206(4)-7 program those artefacts are evidence FOR — a written manual, a risk register, controls, testing, findings and an annual review that an SEC examiner asks to see. WHOSE PROGRAM IT IS. Rule 206(4)-7 is an ADVISER-level obligation, so the program belongs to the management company, never to one fund — its evidence aggregates across every fund in the workspace, and the dashboard and exam binder carry an "Adviser-level" badge naming the firm and the funds the evidence draws from (also in-band in the exam JSON payload as `scope`). Compliance renders as one module in the nav, but its evidence engine reads across the rest of FundOS — fund administration, trading, the data rooms, the LP register and the performance engine — which is why a workspace with no fund account connected cannot produce fund-level evidence (the binder says so rather than reading as mysteriously empty). Models (app/models/compliance_program.py): CompliancePolicy, ComplianceRisk, ComplianceControl, ComplianceTestRun, ComplianceFinding, ComplianceAnnualReview, AttestationCampaign, AttestationAssignment, CodeOfEthicsDisclosure, SideLetterObligation, MarketingReview, MarketingReviewFinding, MarketingPerformanceClaim. Engine (app/services/compliance_program/): evidence.py (23 collectors), seed.py (21 policies / 25 risks / 38 controls), testing.py, review.py, register.py, side_letters.py, coe.py, pay_to_play.py, attestations.py, marketing.py, exam.py. THE ORGANIZING IDEA. A fund manager's compliance obligations come from exactly three sources, and most products cover one: regulation Form ADV, 206(4)-7, 206(4)-5, the Marketing Rule contract LPA covenants, side-letter reporting, MFN elections mandate concentration limits, leverage caps, the restricted list FundOS registers all three in one obligation register (/program/register) and tests them against ONE set of books — the same NAV runs, capital accounts, LP register, GL and position book the rest of the product runs on. A compliance tool that reads only documents or only communications can check that a disclosure footnote exists; it cannot check whether the number is true. FOUR OUTCOMES, AND no_data IS NOT A PASS. Every control test returns passed | exception | no_data | error. `no_data` means the control could not be evaluated because the records it needs are absent — it leaves the control UNTESTED, is excluded from the tested set, and counts as a gap in the annual review's own coverage. Coverage is measured against ALL active controls, never against the subset that produced a verdict, so a firm that tested 4 of 38 controls reads as 10.5% covered rather than "100% passing". EVIDENCE COLLECTORS (23) read real workspace data: NAV released on time, four-eyes preparer/reviewer segregation honoured, valuation marks current, capital-account roll-forward chain unbroken, capital-call notices documented, LP KYC currency, screening interval, restricted list enforced against the LIVE position book, pre-clearance turnaround, mandate rules configured, filing deadlines met, policy review currency, finding remediation timeliness, attestation completion, CoE disclosure review turnaround, pay-to-play clear, side-letter obligations current, LP reporting delivered, extraction review, negotiated fee terms honored in the GL, restricted-list checks complete, marketing reviewed before use, marketing claims verified. SIDE LETTERS + LPA (/program/side-letters) — the contractual source nobody else serves. Point it at a data-room document and it extracts proposed obligations across 12 categories, each stored with the VERBATIM contract text and a clause reference. An extracted obligation lands unconfirmed: it drives no compliance determination, is never given a due date, and cannot raise an overdue alert until a human has read it against the source and confirmed it. Extraction drops any clause whose quoted text is not actually present in the document. /side-letters/mfn benchmarks each MFN holder against comparable terms granted to other investors in the same fund. A confirmed obligation can then be MONITORED against the books rather than merely tracked. `fee_terms_honored` tests a negotiated fee rebate end to end: the rate the clause promises, the discount the fee engine applies, the charge on that investor's capital account, and the management fee actually posted to the general ledger for the period. The four outcomes hold — a clause whose promised rate was never recorded is UNTESTED rather than clean, and a period with no fee posted to the GL is `no_data` (plenty of firms bill fees outside FundOS), never a breach inferred from a record nobody kept. The ledger evidence is fund-level by nature, so the result says so explicitly instead of implying the GL names the investor. CODE OF ETHICS + PAY-TO-PLAY (/program/coe) — Rule 204A-1 disclosures (gifts, entertainment, political contributions, outside business activities, personal trades, private placements) with a Rule 206(4)-5 screen that cross-references a political contribution against the firm's OWN LP register for government-entity investors and computes the two-year time-out from actual commitment dates. The time-out is the later of the calendar two-year anniversary and 730 days, so a 29 February contribution is handled correctly. Output is a flag with its basis and confidence for a human — never a legal conclusion. MARKETING RULE (/program/marketing) — Rule 206(4)-1 review across 14 checks, plus track-record verification: every performance claim (IRR, TVPI, DPI, RVPI, MOIC, NAV) is extracted and RECONCILED against the fund's actual NAV runs and capital accounts through fund_admin/performance.py. Result is match | mismatch | unverifiable, and `unverifiable` is never rendered as verified. Dispositioning a piece as approved for use is human-only and is refused over an unexplained mismatch or a blocking finding. EXAM BINDER (/program/exam) — assembles the standard SEC document-request list section by section (13 sections), each ready | partial | empty WITH what is missing. A binder that silently omitted its empty sections would read as complete, so gaps are the headline rather than a footnote. Routes (~47, all under /fundos/compliance/program/, on their own `compliance_program` blueprint): / (dashboard), /policies, /risks, /controls, /findings, /reviews, /register, /side-letters, /side-letters/mfn, /coe, /attestations, /marketing, /exam. Every write is admin-only. Nothing in the module auto-executes: no auto-filing, no auto-sending, no auto-attesting, no auto-approval. REST (Bearer, read-only): - GET /api/v1/fundos/compliance/program/status - GET /api/v1/fundos/compliance/program/findings - GET /api/v1/fundos/compliance/program/side-letters - GET /api/v1/fundos/compliance/program/exam-readiness MCP tools: fundos_compliance_program_status, fundos_list_compliance_findings, fundos_list_side_letter_obligations, fundos_get_exam_readiness. This module produces evidence and flags. It is not legal advice, and it never makes a regulatory determination on the firm's behalf. ### Compliance Dashboard — /fundos/dashboard/compliance A third executive dashboard alongside GP (/fundos/dashboard/gp) and Admin (/fundos/dashboard/admin). KYC pipeline counts, expiring records in 60 days, recent name screens, upcoming filings, overdue filings list, restricted-list count, pre-clearance queue, obligations, recent Agent H runs. Safe-queries every column so a freshly-deployed DB shows zeros + a "schema not provisioned" banner instead of 500s. ### Per-user dashboard customization (persistent) Every executive dashboard supports: - Toggle widgets on/off + drag-reorder via the Customize modal (top right). - Set this dashboard as my post-login landing page (overrides /fundos/). Schema: users.allowed_dashboards CSV, users.default_dashboard, users. dashboard_prefs_json. Helpers: User.allowed_dashboards_set(), User.can_view_dashboard(key), User.dashboard_prefs(key), User.set_dashboard_prefs(key, hidden, order). Endpoints: - POST /fundos/dashboard//prefs Save widget hidden/order - POST /fundos/dashboard//default Mark as post-login landing - GET /admin/dashboard-access Admin grants per-user access ### Schema-deployment safety net Schema is Alembic-owned: a pre-rollout DOKS Job runs `alembic upgrade head` and blocks until it succeeds, before new pods roll, on every push to stage or main. A failed migration aborts the deploy, so schema and code never drift in normal operation. app/utils/schema_guard.py remains as a backstop — it catches missing-table / missing-column errors anywhere in the app and renders a friendly page instead of a 500. There is NO manual migration trigger anymore: the browser-based superadmin "Run migrations now" button was retired in the cutover, and the CLI route (`POST /api/cli/db/migrate`) plus the legacy `_migrate_db()` function itself were retired/deleted 2026-07-11/12 (see the retirement note at app/routes/cli_api.py). The fix for a failed migration is to check the deploy pipeline's Alembic job, not to trigger anything by hand. ### Sanity portfolio sync GET /superadmin/sanity-sync (dry-run preview) and POST /superadmin/sanity-sync (apply) push the FundOS portfolio for org=SANITY_SYNC_ORG_ID into the public Sanity CMS that backs eight-capital.com. The sync collapses VI follow-on rows ("Foo (FO)") to a single canonical company, only patches fields it owns (name, status, batch), and never touches hand-curated Sanity fields (description, logo, founder bios). Re-running is safe (createIfNotExists + targeted patches); deletes and renames are never automatic — orphans on the site are surfaced for manual reconciliation in Sanity Studio. Implementation: app/services/sanity_sync.py. ### Per-page "How to use" help Every FundOS page now has a small ? button in the top-right corner that opens a modal with page-specific help (also accessible via the ? key). Coverage is seeded for ~20 high-traffic pages. Registry lives in app/utils/page_help.py — adding help for a new page is one tuple entry. ### Admin module access bypass @module_required now lets admins / superadmins bypass both gates (the per-user enabled_modules layer AND the org-subscription layer). Non- admin users still hit both. Rationale: admins make billing decisions and shouldn't be blocked from clicking into a module they're about to subscribe to. ### Fund-type-aware UI (module presets + metric visibility per strategy) Organizations get fund-type default module presets derived from their strategy_type (Venture Capital / Private Credit / Structured Credit / Private Equity / Hedge Fund / Multi-Strategy Hedge Fund) — e.g. a Structured Credit workspace gets deal-test covenants (OC/IC, delinquency triggers, CPR/CDR bounds, WAL limits) and an Asset Book; a Hedge Fund workspace enables Trading/OMS, HF Ops, Risk, Compliance and Fund Admin and drops the deal-pipeline modules; terminology and risk covenant catalogs adapt per type (Borrower vs Portfolio Company; VaR/exposure limits vs DSCR/LTV). Set at onboarding or via the Fund strategy card on /billing/modules. Workspaces without a strategy keep all modules. Agents reading module availability should expect the enabled set to vary by fund type. The strategy also controls PERFORMANCE-METRIC VISIBILITY: hedge-fund and multi-strat workspaces hide the closed-end multiple suite (TVPI/DPI/ MOIC — meaningless for open-end vehicles) while keeping IRR, Sharpe, leverage and drawdown; Structured Credit hides the multiples but keeps yield/WAL/IRR; PE/VC keep the full suite. The CFO dashboard JSON twin (/api/v1/fundos/cfo//dashboard) returns a `metrics_visible` list — API consumers SHOULD respect it when rendering. Admins can override everything (metric tiles, terminology, portfolio columns, covenant catalog) per workspace at /billing/workspace-profile; saving a strategy applies its module preset automatically on fresh workspaces and offers a one-click +/− diff banner on customized ones. Platform-wide per-strategy defaults are superadmin-tunable at /superadmin/fund-profiles (DB-backed, no redeploy). ### Operator CLI — `fundos` (May 2026) Standalone Python CLI for operator workflows. Lives in cli/ in the repo. pip install -e ./cli fundos login # prompts for API key + base URL fundos whoami # identity check Command tree: fundos org list / get / create / enable-vertical / cleanup-spam fundos credits balance / history / add fundos fix status / config / connect / test-order / fills fundos db status / migrate / seed fundos mcp tools / call # read-only tools only fundos logs audit / agents / errors (all --org-id filter) The CLI talks HTTP only — no DB access, no app/ imports. Backend at /api/cli/* (18 routes). Same Bearer-token auth as /api/v1/*. Both the CLI and the server defensively strip an accidental "Bearer " prefix from pasted tokens, so the most common copy-paste 401 doesn't happen. On a 401, the CLI prints the redacted key prefix + base URL so users can spot env-mismatches (dev key against prod, etc.). Env vars: FUNDOS_API_KEY, FUNDOS_BASE_URL (default https://www.kela.com; canonical https://fundos.vantedgeai.com — both work). ### Try-before-you-pay billing (auth-then-capture, May 2026) For packaged AI tasks (ODD, CIM, audit pack, K-1 generation, Level 3 valuation memos), credits are reserved when the task runs but not charged until the customer uses the output. Promise to the customer: "View any AI-generated artifact for free. We only charge if you download it, accept it, or feed it into another tool. If 7 days pass without confirmation, the hold expires and you're not charged." State machine on CreditLedger.hold_status: pending — task done, output viewable, NOT counted toward balance confirmed — customer used the output (download / explicit confirm / chained tool call); charge stands refunded — customer rejected the output; no charge expired — 7 days elapsed with no action; no charge REST API (Bearer auth): GET /api/v1/tasks/holds/ list pending for org GET /api/v1/tasks/holds/ read a hold's state POST /api/v1/tasks/holds//confirm customer accepts the charge POST /api/v1/tasks/holds//refund customer rejects the output POST /api/v1/tasks/holds/cron/expire hourly cron (CRON_SECRET) Document download gate: any Document whose billing_hold_id points at a pending hold auto-confirms on download; a refunded or expired hold returns 402 with structured JSON `{error, hold_id, hold_status}`. Schema columns (auto-migrated unconditionally on every cold start — not gated by RUN_MIGRATIONS, because the ORM SELECTs these on every Document query and missing them would 500 every page that loads docs): credit_ledger.hold_status VARCHAR(20) credit_ledger.hold_expires_at TIMESTAMP documents.billing_hold_id INTEGER (FK credit_ledger.id) ### Anti-spam signup guards (May 2026; signup itself CLOSED 2026-07) Self-serve signup is closed — the guards below now protect only the invite-link self-registration path (and remain importable for the spam sweep). Historical shape at app/utils/signup_guard.py: 1. Disposable-email blocklist (17 domains: mailinator, guerrillamail, tempmail, sharklasers, dispostable, maildrop, …). yopmail.com is explicitly whitelisted in ALLOWED_TEST_DOMAINS for internal QA. 2. Random-name detector — two complementary heuristics: - vowel ratio < 0.20 on the cleaned (letters-only) string - ≥ 3 lower→upper case transitions The second heuristic catches high-vowel bot strings like "HrzIIXAEjlmnTIuDjgAu" (40% vowels) that vowel-ratio alone can't. Tested against the full Eight Capital / VantedgeAI / Bristol Myers Squibb / PricewaterhouseCoopers corpus — zero false positives. 3. IP rate limit — 5 attempts per IP per hour, Postgres-backed, fails open on DB hiccups so legit signups are never blocked by infra. Historically wired into POST /signup, POST /create-org, and the Google OAuth new-user branch — all closed 2026-07 (they render a sales page and create nothing). Cleanup of existing spam: POST /api/cli/admin/cleanup-spam-orgs (or `fundos org cleanup-spam` from the CLI). Defaults to dry-run. Six nested safety guards: - PROTECTED_ORG_IDS (= {1, 2, 17, 30}) never touched - PROTECTED_EMAIL_DOMAINS (yopmail.com) never touched - already-suspended orgs not re-suspended - paid plans not touched (only free / starter / NULL) - orgs < 24h old not touched - orgs with any Document / DealRoom / Deal rows not touched (real work) Then matches at least one spam signal (random-string name, dotted-bot name, disposable-email owner) before flipping status='suspended' and writing an AuditLog row. ### Billing engine — metering, add-ons, invoicing Metering: every REST (/api/v1/tools//call) and MCP Bearer tool call deducts credits from the org's CreditLedger; insufficient balance returns a 402 with required/balance/topup_url. Suspended orgs are blocked at the meter. Packaged deliverables (ODD, CIM, Diligence Agent, valuation memo, valuation policy, K-1, audit pack, contract abstraction, redaction) bill purely by usage credits — there are no separate per-deliverable success fees. Platform pricing is set in each org's enterprise agreement and is not published. There are no per-module SKUs: the whole platform ships with every agreement, and modules are per-strategy configuration (a hedge fund and a VC receive differently configured software, not different price lists). Monthly crons (CRON_SECRET bearer, defined in vercel.json): 06:00 1st /billing/cron/push-stripe-monthly — negotiated platform charges + overage to Stripe 07:00 1st /billing/cron/grant-monthly-credits — monthly CreditLedger grant 08:00 1st /billing/cron/generate-invoices — paper invoices (invoice-pay orgs) 08:00 dly /billing/cron/send-reminders — overdue reminders 09:00 dly /billing/cron/dunning — 30d past_due / 60d suspend / 90d terminate Annual prepay: org admins self-serve "Switch to Annual" from /billing/ (default 20% off / 2 months free); superadmin can issue prepay invoices. Tax/VAT: per-org tax_rate_pct on paper invoices; Stripe Tax (automatic_tax) on all Stripe Checkout flows. Credit notes + manual wire/ACH/check payment recording on invoices. Invoice PDFs render domestic + international-USD + FX-via-intermediary wire instructions, ACH, and check details from the superadmin-editable BillingSettings (/superadmin/billing/payment-settings). Internal team reference: /superadmin/billing/reference. VentureInsights billing integration: VI uses the same FundOS ledger, Stripe account, and webhook settlement. Token-auth JSON twins under /api/v1/fundos/credits/* return Stripe URLs for the VI SPA: GET /credits/topup/packages, POST /credits/topup/checkout, POST /credits/subscribe, and POST /credits/portal. They accept VI return URLs, honor the X-Target-Vc-Firm-Id workspace override for org-1 service calls, and settle through the existing /billing/webhook handlers (no Stripe SDK or webhook in VI). ## Fund Intelligence — Proactive AI Monitoring (June 2026) Proactive monitoring system that automatically surfaces compliance deadlines, overdue capital calls, KYC expirations, cash shortfalls, and LP communication gaps before they become crises — without requiring any manual input from the GP. Dashboard: GET /fundos/monitoring/ — module health grid, 7-day trend, last run status. Daily brief: GET /fundos/monitoring/brief/ — all findings for that date. PIPELINE: Monitor → Investigate → Judge → Publish runs daily at 6am UTC. 1. Monitor: SQL queries against live fund tables for 5 signal types: - compliance_filing_deadline: filings due within 14 days - compliance_kyc_expiry: KYC records expiring within 30 days - lp_overdue_call: capital calls past due_date (critical >7d, high >3d) - lp_communication_gap: LPs with no email for 90+ days - cfo_cash_low: fund cash balance < 110% of minimum 2. Investigate: Gemini root-causes each candidate, queries prior memory (has this entity been flagged before? was it actioned?), returns {title, severity, summary, root_cause, recommendation, affected_entities, confidence} 3. Judge: Two independent Gemini passes; finding passes only if BOTH approve AND combined confidence >= 0.65. Filters noise before reaching the GP. 4. Publish: Approved findings write FundMonitoringPublished row + update FundDailyBrief. Critical/HIGH trigger admin email. FINDING CARD: title, severity badge, summary, collapsible root_cause + recommendation + affected_entities list. Action buttons: "Mark resolved" + "Snooze 2 days". MEMORY: fund_finding_memory tracks per-entity flag count, first/last seen, resolution status. Investigation prompt includes this so Gemini adjusts confidence for recurrent unresolved issues. API routes (all Bearer auth): GET /api/v1/monitoring/brief/today Today's brief JSON GET /api/v1/monitoring/brief/ Brief for a specific date GET /api/v1/monitoring/findings All findings (filters: status, severity, module) GET /api/v1/monitoring/findings/ Single finding detail POST /api/v1/monitoring/findings//action Mark as actioned/resolved POST /api/v1/monitoring/findings//snooze Snooze for N days GET /api/v1/monitoring/watches Custom watches POST /api/v1/monitoring/watches Create a watch DELETE /api/v1/monitoring/watches/ Delete a watch POST /api/v1/monitoring/run Trigger a manual run (admin) GET /api/v1/monitoring/run//status Poll background run status SCHEDULER (non-Vercel only): monitoring_daily: cron 6am UTC — full pipeline for all orgs monitoring_critical: interval 30min — critical signal types only monitoring_baselines: cron Sunday 2am UTC — recalculate baselines DB TABLES: fund_monitoring_runs, fund_monitoring_watches, fund_monitoring_candidates, fund_monitoring_findings, fund_monitoring_judgments, fund_monitoring_published, fund_daily_briefs, fund_monitoring_baselines, fund_finding_memory POST-DEPLOY: the 9 tables auto-create via unconditional boot-time DDL in create_app() (app/__init__.py) on every pod start — not an Alembic revision — no manual step needed. ## Links Homepage: https://fundos.vantedgeai.com Developer API: https://fundos.vantedgeai.com/developers (stateless API landing — no login required) Developer Register: https://fundos.vantedgeai.com/developers/register (get a doc_ key in 60 seconds) Developer Platform: https://fundos.vantedgeai.com/developer (logged-in power-tools hub, plan-gated) Developer Quickstart: https://fundos.vantedgeai.com/developers/quickstart (build a deal+VDR+webhook pipeline in 15 min) Module Guides: https://fundos.vantedgeai.com/developers/modules/ — per-module reference (HTML) Modules: crm, lp-crm, vdr, transactions, investors, cfo Plain text for AI agents: append .txt (e.g. /developers/modules/crm.txt) Module Contract: https://fundos.vantedgeai.com/.well-known/fundos-modules.json — JSON manifest of all 12 modules (canonical IDs, cross-module links, webhook events, ephemeral support) API Changelog: https://fundos.vantedgeai.com/api/changelog.json — machine-readable CalVer changelog API Docs: https://fundos.vantedgeai.com/api/docs MCP Info: https://fundos.vantedgeai.com/mcp/info CLAUDE.md: https://fundos.vantedgeai.com/CLAUDE.md llms.txt: https://fundos.vantedgeai.com/llms.txt llms-full.txt: https://fundos.vantedgeai.com/llms-full.txt (this document) FAQ (JSON-LD): https://fundos.vantedgeai.com/faq.json (12 Q&As, schema.org FAQPage) FAQ (HTML): https://fundos.vantedgeai.com/faq AX Benchmark: https://fundos.vantedgeai.com/ax (agent-readability results, updated monthly) Privacy: https://fundos.vantedgeai.com/privacy Trust: https://vantedgeai.trust.site/ fundos-mcp repo: https://github.com/8vdx1/fundos-mcp ## Agent Experience (AX) Benchmark FundOS publishes a self-hosted, reproducible benchmark that measures whether an AI agent given ONLY the FundOS documentation can correctly identify the right MCP tool, avoid hallucinating non-existent tools, and flag write actions as needing human approval. Run: 20 tasks × 8 categories (MCP/Auth, Deal CRM, LP/Investors, VDR/Documents, Risk, CFO/Finance, Agents, Webhooks) × 2 conditions (curated AX docs vs raw API reference baseline) Live score: https://fundos.vantedgeai.com/ax — regenerated on every re-run; numbers are not repeated here so this file cannot drift against the data Model: one provider per run, identical on both conditions (no truncation) — recorded in the published run meta; supports claude-sonnet-4-5 (Anthropic) and gemini-2.5-flash (Google) Public results: https://fundos.vantedgeai.com/ax Open source harness: https://github.com/8vdx1/fundos-mcp (benchmarks/ax/) Run yourself: pip install pyyaml && GEMINI_API_KEY=... python benchmarks/ax/runner.py Scoring: 2pts = correct answer + approval gate mentioned (or not required) 1pt = correct tool but missed the human-approval warning 0pts = wrong answer or hallucinated tool/endpoint Reproducibility: every task definition, scoring function, and result file is in the public repo. The benchmark is intentionally self-authored — it is FundOS's measure of its own documentation quality, not a third-party evaluation.