GPAgent API v1
Programmatic access to your fund data. Every endpoint returns JSON and is authenticated with a bearer API key. GPAgent is the source of truth — any client (including OpenClaw) should read and write through this API rather than keep a local mirror.
- Base URL (production):
https://www.gpagent.ai/api/v1 - Base URL (local dev):
http://localhost:3000/api/v1 - Auth header:
Authorization: Bearer gpa_... - Content type:
application/json
Create and manage keys at /app/settings/api in the web UI. Keys are per-user and can be revoked at any time.
Use the
www.host. The apexgpagent.aiissues a307redirect towww.gpagent.ai, and most HTTP clients (curl, fetch, reqwest) drop theAuthorizationheader on cross-host redirects as a security measure. Pointing atwww.gpagent.aidirectly avoids an unauthenticated retry and the resulting401.
Conventions
Identifiers
Portfolio companies are addressed by a URL-safe slug (e.g. phases, anthum-ai). If you don't supply one on create, the API derives it from name. Slugs are unique per vehicle and stable across renames.
Vehicle scoping
Every endpoint operates on a single vehicle (fund or SPV). If your API key has access to exactly one vehicle, it's selected automatically. If you have multiple, pass ?vehicleId=... on every request — the API returns 400 with the list of accessible vehicle IDs otherwise.
Soft delete
DELETE marks a record as deleted by setting deletedAt. Deleted records are excluded from list and detail reads. Re-creating via PUT with the same slug clears deletedAt and restores the record.
Idempotency
| Verb | Behavior |
|---|---|
POST /portfolio | Strict create. Returns 409 if the slug already exists. |
PUT /portfolio/{slug} | Upsert. Safe to retry. Replaces the full record at that slug. |
PATCH /portfolio/{slug} | Partial update. 404 if the slug does not exist. |
DELETE /portfolio/{slug} | Soft delete. Safe to retry (idempotent). |
For sync use-cases, prefer PUT — it's the only write verb safe to retry after a network failure without worrying about duplicate state.
Errors
All errors return JSON with at least an error field:
{ "error": "Invalid or revoked API key" }
Validation errors include a zod issues array so clients can point at the offending field.
| Status | Meaning |
|---|---|
400 | Invalid JSON, failed validation, or ambiguous vehicle scope |
401 | Missing, malformed, or revoked API key |
403 | Key is valid but does not have access to the requested resource |
404 | Resource not found (or soft-deleted) |
409 | Slug conflict on POST /portfolio |
Endpoints
GET /portfolio
List the active fund vehicle, fund model, and all portfolio companies.
curl -H "Authorization: Bearer $GPAGENT_API_KEY" \
https://www.gpagent.ai/api/v1/portfolio
Response 200
{
"vehicle": {
"id": "cmx...",
"name": "Element 14 Capital Fund I",
"type": "FUND",
"slug": "fund-i"
},
"fund": {
"targetFundSize": 1050000,
"targetCompanyCount": 15,
"maxConcentrationPct": 25,
"totalDeployed": 275000,
"deploymentPct": 26.2,
"avgCheckSize": 27500,
"avgValuation": 26100000,
"companyCount": 10
},
"companies": [
{
"id": "cmx...",
"slug": "phases",
"name": "Phases",
"website": "https://phases.so",
"sector": "Developer Tools",
"stage": "pre-seed",
"checkSize": 25000,
"postMoneyValuation": 20000000,
"investmentDate": "2025-11-26T00:00:00.000Z",
"instrumentType": "SAFE",
"status": "ACTIVE",
"notes": "YC S25. AI clinical trial recruitment.",
"logoUrl": null,
"createdAt": "2025-11-26T19:00:00.000Z",
"updatedAt": "2026-03-12T10:00:00.000Z"
}
],
"sectors": [
{ "sector": "AI/ML", "count": 3, "deployed": 85000 }
]
}
POST /portfolio
Create a new portfolio company. Strict create — if a company with the same slug already exists, returns 409. Use PUT /portfolio/{slug} instead for safe-retry upserts.
Request body (JSON)
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | yes | Display name |
slug | string | no | Lowercase alphanumeric + hyphens. Derived from name if omitted. |
website | string (URL) | no | |
sector | string | no | |
stage | string | no | e.g. pre-seed, seed, series-a |
checkSize | number | yes | USD, non-negative |
postMoneyValuation | number | no | USD, non-negative |
investmentDate | string (ISO 8601) | yes | |
instrumentType | enum | yes | SAFE | EQUITY | CONVERTIBLE |
status | enum | no | ACTIVE (default) | MARKED_UP | WRITTEN_DOWN | EXITED |
notes | string | no | |
logoUrl | string (URL) | no |
curl -X POST \
-H "Authorization: Bearer $GPAGENT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Phases",
"website": "https://phases.so",
"sector": "Developer Tools",
"stage": "pre-seed",
"checkSize": 25000,
"postMoneyValuation": 20000000,
"investmentDate": "2025-11-26",
"instrumentType": "SAFE",
"notes": "YC S25."
}' \
https://www.gpagent.ai/api/v1/portfolio
Response 201 — the created company (same shape as GET /portfolio/{slug}).
GET /portfolio/{slug}
Fetch a single portfolio company by slug.
curl -H "Authorization: Bearer $GPAGENT_API_KEY" \
https://www.gpagent.ai/api/v1/portfolio/phases
Response 200
{
"id": "cmx...",
"slug": "phases",
"name": "Phases",
"website": "https://phases.so",
"sector": "Developer Tools",
"stage": "pre-seed",
"checkSize": 25000,
"postMoneyValuation": 20000000,
"investmentDate": "2025-11-26T00:00:00.000Z",
"instrumentType": "SAFE",
"status": "ACTIVE",
"notes": "YC S25.",
"logoUrl": null,
"createdAt": "2025-11-26T19:00:00.000Z",
"updatedAt": "2026-03-12T10:00:00.000Z"
}
PUT /portfolio/{slug}
Upsert a portfolio company by slug. Full-record replace. Safe to retry — this is the recommended write verb for sync clients.
Body shape matches POST /portfolio. slug in the body, if present, must match the URL slug.
curl -X PUT \
-H "Authorization: Bearer $GPAGENT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Phases",
"website": "https://phases.so",
"sector": "Developer Tools",
"stage": "pre-seed",
"checkSize": 25000,
"postMoneyValuation": 20000000,
"investmentDate": "2025-11-26",
"instrumentType": "SAFE",
"status": "MARKED_UP",
"notes": "Marked up after their seed round."
}' \
https://www.gpagent.ai/api/v1/portfolio/phases
Response 200 on update, 201 on create. Body is the resulting company.
PATCH /portfolio/{slug}
Partial update. Only the fields you send are changed; everything else is left alone. Returns 404 if the slug does not exist.
curl -X PATCH \
-H "Authorization: Bearer $GPAGENT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"status": "MARKED_UP", "notes": "Seed round announced."}' \
https://www.gpagent.ai/api/v1/portfolio/phases
Response 200 — the updated company.
DELETE /portfolio/{slug}
Soft-delete a portfolio company. The record is retained in the database with deletedAt set and excluded from future reads. A subsequent PUT /portfolio/{slug} will restore it.
curl -X DELETE \
-H "Authorization: Bearer $GPAGENT_API_KEY" \
https://www.gpagent.ai/api/v1/portfolio/phases
Response 204 (no body).
Using GPAgent as a backing store for an agent
If you're wiring this API into an agent (OpenClaw, Claude Code, a custom script), the practical rules are:
- Never cache. Query the API at the moment you need the data. GPAgent is cheap and always current.
- Use
PUTfor writes. It's the only verb safe to retry after a timeout. - Pipe the curl output through
jqif you want to let the agent read specific fields without parsing the whole response. - Persist the API key outside the agent's working directory. For OpenClaw, it belongs in
~/.openclaw/openclaw.jsonunderenv.GPAGENT_API_KEY. - Add this file to your agent's context. The doc is served at
/api-docs— point your agent's extra-context list at it (for OpenClaw:agents.defaults.memorySearch.extraPaths).
Deal Pipeline
Deals are org-scoped (not vehicle-scoped) since a pipeline spans all funds. Vehicle auth is still required to identify your organization — the resolved organizationId is used.
GET /deals
List all active deals in your pipeline. Supports filtering, pagination.
Query parameters
| Param | Type | Notes |
|---|---|---|
source | enum | YC | ANGELLIST | REFERRAL | INBOUND | MANUAL |
stage | enum | DISCOVERED | RESEARCHING | PRE_MEETING | MEETING | TERMS | COMMITTED | PASSED |
batch | string | Filter by sourceDetail (case-insensitive contains). E.g. batch=W26 |
limit | number | Max records to return. Default 50, max 200. |
offset | number | Pagination offset. Default 0. |
# All RESEARCHING deals from YC W26
curl -H "Authorization: Bearer $GPAGENT_API_KEY" \
"https://www.gpagent.ai/api/v1/deals?stage=RESEARCHING&batch=W26"
Response 200
{
"total": 12,
"limit": 50,
"offset": 0,
"deals": [
{
"id": "cma...",
"slug": "raindrop",
"companyName": "Raindrop",
"website": "https://raindrop.dev",
"description": "Sentry for AI agents.",
"source": "YC",
"sourceDetail": "YC W26",
"stage": "RESEARCHING",
"thesisFitScore": 5,
"estimatedValuation": 20000000,
"estimatedCheckSize": null,
"sector": "AI/ML",
"companyStage": "seed",
"notes": "...",
"logoUrl": null,
"links": [],
"scoutedByAgentId": "cma...",
"createdAt": "2026-03-22T10:00:00.000Z",
"updatedAt": "2026-03-22T10:00:00.000Z"
}
]
}
POST /deals
Create a new deal. Strict create — returns 409 if a deal with the same slug already exists for this org.
Request body (JSON)
| Field | Type | Required | Notes |
|---|---|---|---|
companyName | string | yes | |
slug | string | no | Derived from companyName if omitted. |
website | string (URL) | no | |
description | string | no | |
source | enum | no | Default MANUAL |
sourceDetail | string | no | e.g. YC W26 |
stage | enum | no | Default DISCOVERED. One of DISCOVERED, RESEARCHING, PRE_MEETING, MEETING, TERMS, COMMITTED, PASSED. |
thesisFitScore | number (0–10) | no | |
estimatedValuation | number | no | USD |
estimatedCheckSize | number | no | USD |
sector | string | no | |
companyStage | string | no | e.g. pre-seed, seed |
notes | string | no | |
logoUrl | string (URL) | no | |
links | array | no | [{ type, label, url }] |
scoutedByAgentId | string | no | Agent that sourced this deal |
Response 201 — the created deal.
GET /deals/{slug}
Fetch a single deal by slug. Returns the deal plus all comments.
curl -H "Authorization: Bearer $GPAGENT_API_KEY" \
https://www.gpagent.ai/api/v1/deals/raindrop
Response 200 — deal object with comments array appended:
{
"id": "cma...",
"slug": "raindrop",
"companyName": "Raindrop",
"...",
"comments": [
{
"id": "cmc...",
"dealId": "cma...",
"authorName": "Scout",
"authorType": "agent",
"content": "High thesis fit. Prioritize at Demo Day.",
"metadata": {},
"createdAt": "2026-03-22T10:05:00.000Z"
}
]
}
PUT /deals/{slug}
Upsert a deal by slug. Full-record replace. Safe to retry.
Body shape matches POST /deals. slug in the body, if present, must match the URL slug.
Response 200 on update, 201 on create.
PATCH /deals/{slug}
Partial update. Send only the fields you want to change. Useful for stage transitions.
# Advance a deal to MEETING
curl -X PATCH \
-H "Authorization: Bearer $GPAGENT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"stage": "MEETING"}' \
https://www.gpagent.ai/api/v1/deals/raindrop
Response 200 — the updated deal. 404 if slug not found.
DELETE /deals/{slug}
Soft-delete a deal. Sets deletedAt; excluded from future reads. A subsequent PUT restores it.
Response 204 (no body).
POST /deals/{slug}/comments
Add a comment to a deal.
Request body (JSON)
| Field | Type | Required | Notes |
|---|---|---|---|
authorName | string | yes | Display name |
authorType | enum | no | human (default) | agent | system |
content | string | yes | |
metadata | object | no | Arbitrary JSON. Default {} |
curl -X POST \
-H "Authorization: Bearer $GPAGENT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"authorName": "Scout", "authorType": "agent", "content": "Confirmed round is open."}' \
https://www.gpagent.ai/api/v1/deals/raindrop/comments
Response 201 — the created comment.
Scoring Criteria
Organization-level scoring rubric used by the Scout agent to evaluate deals.
GET /scoring-criteria
Return the current scoring criteria for your organization.
curl -H "Authorization: Bearer $GPAGENT_API_KEY" \
https://www.gpagent.ai/api/v1/scoring-criteria
Response 200
{
"organizationId": "cma...",
"organizationName": "Element 14 Capital",
"criteria": {
"domainExpertise": {
"4": "Founder/market fit. Complementary team...",
"3": "Some domain knowledge...",
"2": "Limited background...",
"1": "No relevant background..."
},
"product": { "4": "...", "3": "...", "2": "...", "1": "..." },
"fundraisability": { "4": "...", "3": "...", "2": "...", "1": "..." },
"market": { "4": "...", "3": "...", "2": "...", "1": "..." },
"execution": { "4": "...", "3": "...", "2": "...", "1": "..." },
"traction": { "4": "...", "3": "...", "2": "...", "1": "..." }
}
}
If no custom criteria are set, returns the E14 Capital defaults.
PUT /scoring-criteria
Full replace of the scoring criteria. All 6 categories and all 4 score levels are required.
curl -X PUT \
-H "Authorization: Bearer $GPAGENT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"domainExpertise": { "4": "...", "3": "...", "2": "...", "1": "..." },
"product": { "4": "...", "3": "...", "2": "...", "1": "..." },
"fundraisability": { "4": "...", "3": "...", "2": "...", "1": "..." },
"market": { "4": "...", "3": "...", "2": "...", "1": "..." },
"execution": { "4": "...", "3": "...", "2": "...", "1": "..." },
"traction": { "4": "...", "3": "...", "2": "...", "1": "..." }
}' \
https://www.gpagent.ai/api/v1/scoring-criteria
Response 200 — the saved criteria (same shape as GET).
Changelog
- 2026-04-16 — Added
PRE_MEETINGtoDealStageenum (sits betweenRESEARCHINGandMEETING); agents ownDISCOVERED/RESEARCHING, GPs ownPRE_MEETINGonward. - 2026-04-16 —
GET/POST /deals,GET/PUT/PATCH/DELETE /deals/{slug},POST /deals/{slug}/comments,GET/PUT /scoring-criteria. - 2026-04-10 — v1 launch:
GET/POST /portfolio,GET/PUT/PATCH/DELETE /portfolio/{slug}. Deal pipeline and Updates endpoints shipping next.