# DealGraph API — full documentation (v1.4.0) > Complete agent-facing documentation for the DealGraph REST API in one file. > DealGraph is a multi-tenant portfolio management platform for VC funds and family offices. > Machine-readable contract: https://dealgraph.vc/api/v1/openapi.json ## Base URL & auth - Base URL: `https://dealgraph.vc/api/v1` - Auth: `Authorization: Bearer dg_live_...` (org-scoped API key; created by an org admin under Settings → API Keys). Scope is `read-only` or `read_write`; read-only keys get 403 `forbidden_readonly` on writes. - Public demo: `Authorization: Bearer dg_demo` serves a fictional read-only org (Aurora Ventures). Reads work fully; writes only as `?dryRun=true` simulations. - Only `/api/v1` accepts bearer keys. ## Conventions - Success envelope: `{ "data": }`. Error envelope: `{ "error": { "code", "message", "field"?, "details"? } }`. - IDs are UUIDs. Dates are `YYYY-MM-DD`; timestamps are ISO 8601. - **All monetary values are absolute EUR** (decimal string or number, e.g. `"12500000.00"` = €12.5M). There is no €M representation. Valuations are stored at €10,000 granularity (writes round to the nearest €10k; re-read to confirm). - Unknown values: omit the key (preferred) or send `null`; `""` coerces to `null`. - No pagination on list endpoints by design at current scale: they return all org rows (`/search` caps at 50, `/changes` at 4000, `/audit` at 1000). Use `?updatedSince` / `GET /changes` for incremental pulls instead of re-listing. ## Rate limits 120 requests/minute per API key (best effort). Every response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` (unix seconds). Exceeding the limit returns `429 rate_limited` with `Retry-After` (seconds). Retry 5xx/429 with exponential backoff (1s → 2s → 4s, ~3 tries) and pair every retried POST with the same `Idempotency-Key`. ## Write safety (what makes this API agent-native) - **Dry-run:** any write accepts `?dryRun=true` (or header `Prefer: validate`). The write runs with full validation inside a transaction that is rolled back; you get the would-be response plus header `X-Dry-Run: true`. Nothing persists. Use it to show a human the diff first. - **Idempotency:** send `Idempotency-Key: ` on POST creates. A retry with the same key replays the original response (header `Idempotent-Replay: true`); the same key with a different body returns `409 idempotency_conflict`. Claims expire after 24h. - **Audit:** every API-key write is recorded. `GET /audit` returns who/what/when, including the key name and the optional `X-Actor` request header. Stamp `source`/`notes` fields too. - **Soft delete:** DELETE sets `deletedAt` and hides the row; `POST //{id}/restore` undoes it. Permanent deletion requires explicit `?hard=true`. ## Entities ### Enumerations (send exact values) | Enum | Values | | --- | --- | | portfolio.category | direct · fund · other | | company.status | active · watch · exited · lost | | fundingRound.roundType | cla · safe · equity | | investment.instrumentType | cla · safe · equity · debt · grant · other | | metric.metricKey | arr_eur · headcount · runway_months · traction_score · moat_score · valuation_eur_m | `fundingRound.stage` is free text (Pre-Seed, Seed, Series A, ...), separate from `roundType`. ### Company Writable: `name` (required, unique per org), `externalRef` (cross-system id, e.g. `iec:312`), `sector`, `website` (http(s) URL), `description`, `geoHqCountry`, `geoHqCity`, `foundedYear`, `status`, `reportingStatus`. Read-only cached fields (`arrEurLatest`, `valuationLatestEur`, `*ScoreLatest`) are auto-maintained from metrics/rounds — never write them. ### Funding round (under a company) `roundDate` (required), `roundType`, `valuationPreEur`, `valuationPostEur`, `amountRaisedEur`, `stage`, `source`, `notes`. Rules: `equity` requires `valuationPostEur`; `cla`/`safe` take an optional `valuationPreEur` (the cap; discount/MFN-only convertibles have none) and no `valuationPostEur`. At least one of amount/pre/post is required. ### Metric (reporting datapoint, under a company) `metricKey` (required), `metricDate` (required), `valueNumeric` (required; `arr_eur` and `valuation_eur_m` in absolute EUR; scores 1-10; others ≥ 0), `source`, `notes`. Writing a metric auto-updates the company's cached latest fields. When PATCHing a `valuation_eur_m` value, include `metricKey` in the body so the unit is read as absolute EUR. ### Investment (links a company to a portfolio) `companyId` + `portfolioId` (required), `externalRef`, `instrumentType` (auto-filled from a linked `fundingRoundId`), `fundingRoundId`, `conversionFundingRoundId`, `ticketEur`, `entryValuationEur`, `ownershipPct` (0-100), `vehicle`, `valuationSource`, `entryStage`, `notes`, `investmentDate`, `proRata`. ### Portfolio `name` (required), `category`, `type`, `color`, `description`. Fund-only: `vintageYear`, `fundManager`, `totalFundSizeEur`, `committedCapitalEur`, `ownershipPct` (auto-derived if both given). Fund extras: drawdown notices and GP-reported marks (below). ## Endpoints ### Read (any key) | Endpoint | Returns / filters | | --- | --- | | GET /dashboard | org-level KPIs + MOIC (`?portfolioId` to scope) | | GET /portfolios | all portfolios | | GET /portfolios/{id} | portfolio + investments | | GET /portfolios/{id}/stats | KPIs + MOIC for one portfolio | | GET /portfolios/{id}/drawdowns | drawdown notices (fund) | | GET /portfolios/{id}/reported-marks | GP-reported marks (fund) | | GET /companies | `?portfolioId ?status ?sector ?geoHqCountry ?externalRef ?updatedSince` | | GET /companies/{id} | company fields flat at `data` + `data.investments[]`, plus company-level `performance{}` and `positions[]` | | ↳ `positions[]` | **one entry per portfolio holding the company** (`portfolioId`, `portfolioName`, `portfolioCategory`, `cumulativePaidIn`, `currentFairValue`, `moic`, `ownershipPct`, `investmentCount`) | | GET /companies/{id}/funding-rounds | rounds | | GET /companies/{id}/metrics | `?latest=true ?metricKey ?dateFrom ?dateTo ?updatedSince` | | GET /investments | `?portfolioId ?companyId ?externalRef ?updatedSince` | | GET /search?q=&limit= | companies + investments + portfolios (q ≥ 2 chars, limit ≤ 50) | | GET /changes?since= | guaranteed change feed (created/updated/deleted) | | GET /audit | `?entityId ?entityType ?since ?limit` — audit trail of API writes | | GET /webhooks | registered webhook endpoints (admin) | ### Write (read_write key) | Endpoint | Methods | | --- | --- | | /portfolios · /portfolios/{id} | POST · PATCH · DELETE | | /companies · /companies/{id} | POST · PATCH · DELETE | | /companies/{id}/funding-rounds · /funding-rounds/{id} | POST · PATCH · DELETE | | /companies/{id}/metrics · /metrics/{id} | POST · PATCH · DELETE | | /investments · /investments/{id} | POST · PATCH · DELETE | | /portfolios/{id}/drawdowns · /drawdowns/{id} | POST · PATCH · DELETE | | /portfolios/{id}/reported-marks · /reported-marks/{id} | POST · DELETE (re-POST an asOfDate to update) | | /webhooks · /webhooks/{id} | POST · PATCH · DELETE (admin) | | //{id}/restore | POST — undo a soft delete | Webhook deliveries are HMAC-signed (`X-DealGraph-Signature`) and best-effort; reconcile with `GET /changes`. ## Errors | code | HTTP | Meaning | | --- | --- | --- | | validation_error | 400 | bad input; `field` names the offender when known | | unauthorized | 401 | missing/invalid/expired/revoked key | | forbidden_readonly | 403 | read-only key attempted a write | | forbidden | 403 | not allowed (e.g. cross-org) | | not_found | 404 | resource not in your org | | unique_conflict | 409 | duplicate; company name conflicts return `details.existingId` | | idempotency_conflict | 409 | Idempotency-Key reused with different body / still in progress | | rate_limited | 429 | too many requests; honor `Retry-After` | | internal | 500 | server error; retry with backoff + same Idempotency-Key | ## Examples ```bash # Dashboard (works with the demo key) curl -s https://dealgraph.vc/api/v1/dashboard -H "Authorization: Bearer dg_demo" # Resolve an external id to a company curl -s "https://dealgraph.vc/api/v1/companies?externalRef=iec:312" \ -H "Authorization: Bearer $DEALGRAPH_API_KEY" | jq '.data.companies[0].id' # Dry-run a metric write, then commit it idempotently curl -s -X POST "https://dealgraph.vc/api/v1/companies//metrics?dryRun=true" \ -H "Authorization: Bearer $DEALGRAPH_API_KEY" -H "Content-Type: application/json" \ -d '{"metricKey":"arr_eur","metricDate":"2026-06-30","valueNumeric":"1200000","source":"agent:me"}' curl -s -X POST "https://dealgraph.vc/api/v1/companies//metrics" \ -H "Authorization: Bearer $DEALGRAPH_API_KEY" -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" -H "X-Actor: my-agent (operator)" \ -d '{"metricKey":"arr_eur","metricDate":"2026-06-30","valueNumeric":"1200000","source":"agent:me"}' # Incremental sync curl -s "https://dealgraph.vc/api/v1/changes?since=2026-08-01T00:00:00Z" \ -H "Authorization: Bearer $DEALGRAPH_API_KEY" ``` ## MCP server Hosted, stateless, streamable HTTP: `https://dealgraph.vc/api/mcp`. Bearer auth with the same API keys; no key = demo dataset. Tools mirror the endpoints above; the `push_metric` tool defaults to dry-run. Setup guide: https://dealgraph.vc/docs/agents.md ## More - Agent index: https://dealgraph.vc/llms.txt - Quickstart: https://dealgraph.vc/docs/quickstart.md - Claude skill: https://dealgraph.vc/docs/skill.md - OpenAPI 3.1: https://dealgraph.vc/api/v1/openapi.json