GPAgent

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 apex gpagent.ai issues a 307 redirect to www.gpagent.ai, and most HTTP clients (curl, fetch, reqwest) drop the Authorization header on cross-host redirects as a security measure. Pointing at www.gpagent.ai directly avoids an unauthenticated retry and the resulting 401.


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

VerbBehavior
POST /portfolioStrict 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.

StatusMeaning
400Invalid JSON, failed validation, or ambiguous vehicle scope
401Missing, malformed, or revoked API key
403Key is valid but does not have access to the requested resource
404Resource not found (or soft-deleted)
409Slug 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)

FieldTypeRequiredNotes
namestringyesDisplay name
slugstringnoLowercase alphanumeric + hyphens. Derived from name if omitted.
websitestring (URL)no
sectorstringno
stagestringnoe.g. pre-seed, seed, series-a
checkSizenumberyesUSD, non-negative
postMoneyValuationnumbernoUSD, non-negative
investmentDatestring (ISO 8601)yes
instrumentTypeenumyesSAFE | EQUITY | CONVERTIBLE
statusenumnoACTIVE (default) | MARKED_UP | WRITTEN_DOWN | EXITED
notesstringno
logoUrlstring (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:

  1. Never cache. Query the API at the moment you need the data. GPAgent is cheap and always current.
  2. Use PUT for writes. It's the only verb safe to retry after a timeout.
  3. Pipe the curl output through jq if you want to let the agent read specific fields without parsing the whole response.
  4. Persist the API key outside the agent's working directory. For OpenClaw, it belongs in ~/.openclaw/openclaw.json under env.GPAGENT_API_KEY.
  5. 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

ParamTypeNotes
sourceenumYC | ANGELLIST | REFERRAL | INBOUND | MANUAL
stageenumDISCOVERED | RESEARCHING | PRE_MEETING | MEETING | TERMS | COMMITTED | PASSED
batchstringFilter by sourceDetail (case-insensitive contains). E.g. batch=W26
limitnumberMax records to return. Default 50, max 200.
offsetnumberPagination 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)

FieldTypeRequiredNotes
companyNamestringyes
slugstringnoDerived from companyName if omitted.
websitestring (URL)no
descriptionstringno
sourceenumnoDefault MANUAL
sourceDetailstringnoe.g. YC W26
stageenumnoDefault DISCOVERED. One of DISCOVERED, RESEARCHING, PRE_MEETING, MEETING, TERMS, COMMITTED, PASSED.
thesisFitScorenumber (0–10)no
estimatedValuationnumbernoUSD
estimatedCheckSizenumbernoUSD
sectorstringno
companyStagestringnoe.g. pre-seed, seed
notesstringno
logoUrlstring (URL)no
linksarrayno[{ type, label, url }]
scoutedByAgentIdstringnoAgent 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)

FieldTypeRequiredNotes
authorNamestringyesDisplay name
authorTypeenumnohuman (default) | agent | system
contentstringyes
metadataobjectnoArbitrary 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_MEETING to DealStage enum (sits between RESEARCHING and MEETING); agents own DISCOVERED/RESEARCHING, GPs own PRE_MEETING onward.
  • 2026-04-16GET/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.