Documentation · page 2 of 6

Reference

Every REST endpoint and every MCP tool · examples run against production on 14 October 2026 (UTC)

StartupGraph has one dataset and two ways to read it: a REST API at https://startupgraph.dev/api, and an MCP server at https://startupgraph.dev/mcp with 25 tools. This page lists all of both: what each one takes, what it returns, what the call cost, and one example. If you have no key yet, start with the quickstart.

How to read the examples. Where an example says "real output", the request was sent to production on the date above with a key on the Live plan, and the response is printed as it came back: re-indented, and cut only where a line starting with … says what was left out. No value was changed. Where it says "Response shape", the call was not made, because it writes, and the shape is read from the code. Values in the dataset change, so your output will differ. A key on the free plan reads the dataset as of the last quarter end and can see older values.

Contents

Before the first call: the key, tokens, errors, time values, what is missing today

REST, 43 endpoints

Your key

Reading the data

What changed

Contributing

Saved screens

Portfolios and roll-ups

Plan and billing

Bulk export

MCP, 25 tools · connecting and calling

Reading tools

Writing tools

Pages for the person

Before the first call

The key

Every call except GET /api/pulse, POST /api/keys and POST /api/waitlist/login needs a key, sent as a header: Authorization: Bearer sg_live_…. The same key works for REST and MCP. The examples read it from an environment variable, STARTUPGRAPH_KEY.

A REST answer is JSON. A read puts its result under data, and a list that has pages adds meta {page, per_page, total}. Dates and times are UTC, written YYYY-MM-DD HH:MM:SS, and a date column often carries 00:00:00 as its time.

Tokens

A call costs tokens by the size of what it returns: about one token for every four bytes of the response, and never fewer than 10. A REST response says what it cost in X-Tokens-Charged, next to the calls left this minute and the tokens left today and this month. These are the headers of the GET /api/stats example below. The limit values are left out here; what they are for each plan is under pricing.

HTTP/2 200 
content-type: application/json
x-ratelimit-limit: …
x-ratelimit-remaining: …
x-tokens-charged: 117
x-tokens-day-limit: …
x-tokens-day-remaining: …
x-tokens-month-limit: …
x-tokens-month-remaining: …
… other headers left out

What a plan allows, and what it costs, is under pricing. Each endpoint below states what its example cost, so you can judge a call before making it. A few things follow from charging by size:

Errors

Over REST an error is a JSON body with a message, and often an error code, under the HTTP status that fits.

401, the key. error names what is wrong. With no key, real output:

{"error":"missing_key","message":"Send Authorization: Bearer <key>. Get one: POST /api/keys {\"email\"}."}

With a key that is not known, or whose account is closed, real output:

{"error":"invalid_key","message":"Unknown or revoked key. If this key used to work, sign in at https://startupgraph.dev/account to check your account."}

With a key that StartupGraph withdrew, the answer says so, with the date and the reason. Only that exact key gets this answer. The person signs in at /account and presses "Make a new key"; their tokens, credits, saved screens and portfolios are kept. Response shape, from the code:

{"error": "revoked_key", "message": "This key was revoked on <date> because <reason>. Sign in at https://startupgraph.dev/account to make a new one.", "revoked_at": "<when>", "reason": "<why>"}

404, not found. {"message":"Not found"}, for a path that does not exist and for a slug or id that is not on file.

422, the input. message and errors, a list of reasons per field. There is a real one under GET /api/companies.

429, a limit. Three codes. Response shapes, from the code:

{"error": "rate_limit", "message": "Rate limit of <N> calls a minute reached. Try again in a minute."}      with the header Retry-After: 60
{"error": "daily_limit", "message": "Daily allowance of <N> tokens used. Resets at 00:00 UTC. …"}
{"error": "monthly_limit", "message": "Monthly allowance of <N> tokens used. Resets on the 1st (UTC). …"}
{"error": "monthly_limit", "message": "The free allowance of <N> tokens is used up. It does not renew. …"}      on a free key

Each message ends with what to do next: contribute facts to earn tokens, or change plan. Submitting has its own daily count and its own 429, submission_limit.

Over MCP the key errors (401) and the limits (429) are the same HTTP answers, sent before any tool runs. Once a tool runs, a failure is not an HTTP error: the tool returns a normal result whose text is JSON with an error, such as {"error":"not_found","slug":"no-such-company-here"}, which is real output and cost 39 tokens.

Time values

Four parameters take a point in time: since and until on GET /api/funding-rounds, since on GET /api/changes and the changes_since tool, and raised_since in search criteria. Each accepts:

24hA number and h: that many hours ago.
7d, 2wDays or weeks ago.
6m, 1yMonths or years ago. m is months, never minutes.
2026-10-01A date.
2026-10-01T14:00:00ZAn ISO timestamp. Seconds and the zone are optional; no zone means UTC.

Anything else, such as yesterday, is a 422 whose message lists these forms. Funding rounds are compared by day. The change feed compares to the second when given hours or a timestamp, and by day otherwise.

What is missing today

The dataset is young and uneven. These are the gaps most likely to make an answer wrong. GET /api/coverage has the current numbers.

REST endpoints

Your key

Calls about the key itself. All three cost no tokens.

GET /api/keys/me

What the calling key has used and earned. It still answers when the allowance is spent, so a key can always find out that it is spent.

Takes

Nothing.

Returns

tier; limits {tokens_per_month, tokens_per_day, calls_per_minute, renews}; credits_earned, every credit on the key added up, which is credits_from_contributions (earned by facts that were verified) plus credits_from_gifts (given to the key by StartupGraph); calls_today, tokens_today, tokens_this_month, tokens_remaining_today, tokens_remaining_this_month; submissions_today and submissions_per_day; snapshot, which is live or the quarter a free key reads; ledger, the newest 20 credits, each {amount, reason, created_at, field, company, gift}. key_prefix is the first 16 characters of the key. What the limits are for each plan is under pricing.

Example, real output

curl -s -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/keys/me"
{
  "data": {
    "tier": "live",
    "credits_earned": 0,
    "credits_from_contributions": 0,
    "credits_from_gifts": 0,
    "calls_today": 760,
    "tokens_today": 360461,
    "tokens_this_month": 548010,
    "token": "about 4 bytes of response, at least 10 a call",
    "submissions_today": 0,
    "snapshot": "live",
    "ledger": [],
    … left out (the start of the key, and this key's limits): key_prefix, limits, tokens_remaining_today, tokens_remaining_this_month, submissions_per_day
  }
}

This call cost 0 tokens. There is no X-Tokens-Charged header on this response.

Errors

A 401 without a working key, a 429 past a limit.

Over MCP

No tool. The MCP handshake and tools/list are also free.

POST /api/keys/rotate

Replaces the calling key. The old key stops working at once. Usage, credits, saved screens and portfolios stay.

Takes

Nothing.

Returns

The new key, in this response only. We store a hash of it, so it cannot be shown again. key_prefix is its first 16 characters.

Example request

curl -s -X POST -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/keys/rotate"

Response shape

{
  "data": {
    "key": "sg_live_<the new key>",
    "key_prefix": "<its first 16 characters>",
    "message": "The old key no longer works. This is the only time the new key is shown; we store a hash of it."
  }
}

Not real output. This call was not made against production because it replaces the key. The shape is read from the code and its tests.

Errors

Costs no tokens. A 401 without a working key, a 429 past a limit. A key lost altogether cannot call this: the person signs in at /account and makes a new one.

Over MCP

No tool.

POST /api/keys

Joins the waitlist with an email address. No key is needed, and no key comes back: when access is turned on, a one-time link goes to that address and the person opens it to see the key. The quickstart walks through it.

Takes

emailJSON body. A valid email address, 254 characters at most.

Returns

Status 202. status is queued (on the waitlist) or sent (the link went out at once, for an address that was let in before); position is the place on the waitlist, kept if the same address joins again; tier and tokens_per_month describe the key the address will get; message is a sentence for the person.

Example request

curl -s -X POST "https://startupgraph.dev/api/keys" -H "content-type: application/json" -d '{"email":"you@example.com"}'

Response shape

{
  "status": "queued" | "sent",
  "email": "<the address>",
  "tier": "free",
  "tokens_per_month": <number>,
  "position": <number>,
  "message": "You're number <position> on the waitlist. Keys go out by email, in order."
}

Not real output. This call was not made against production because it adds a signup. The shape is read from the code and its tests.

Errors

422 {"message":"The email field must be a valid email address.","errors":{"email":[…]}} for anything that is not an email address.

Over MCP

No tool: /mcp needs a key.

POST /api/waitlist/login

Sends a sign-in link to an email address, for the person, not the agent. The link opens /account or the roadmap. No key is needed. The sign-in form on the account page calls this.

Takes

emailJSON body. A valid email address.
nextroadmap (the default) or account: where the link leads.

Returns

Status 202 and the same sentence for every address, on file or not, so the answer does not say which emails have signed up.

Example request

curl -s -X POST "https://startupgraph.dev/api/waitlist/login" -H "content-type: application/json" -d '{"email":"you@example.com","next":"account"}'

Response shape

{"status": "sent", "message": "If we have that email on file, a sign-in link is on its way."}

Not real output. This call was not made against production because it sends an email. The shape is read from the code and its tests.

Errors

422 for an invalid email or an unknown next. 503 {"error":"mail_unavailable", …} if email is not set up on the deployment.

Over MCP

No tool.

Reading the data

Every call here needs a key except /api/pulse.

GET /api/pulse

What landed in the dataset in the past hour, day, week, 30 days and year, and the 12 latest changes. The only data call that needs no key. Cached for 60 seconds.

Takes

Nothing.

Returns

as_of; last_change_at; windows with hour, day, week, month (30 days) and year (from 00:00 UTC on this day last year), each counting facts_submitted, facts_verified, companies_added, funding_rounds_added, job_counts_updated and changes; latest, rows of {field, verified, changed_at, slug, name}.

Example, real output

curl -s "https://startupgraph.dev/api/pulse"
{
  "data": {
    "as_of": "2026-10-14T16:38:03.102Z",
    "last_change_at": "2026-10-14 16:38:02",
    "windows": {
      "hour": {"facts_submitted":11,"facts_verified":9,"companies_added":2,"funding_rounds_added":3,"job_counts_updated":0,"changes":21},
      "day": {"facts_submitted":19,"facts_verified":15,"companies_added":3,"funding_rounds_added":6,"job_counts_updated":0,"changes":31},
      … left out: week, month, year (the same six counts)
    },
    "latest": [
      {"field":"people","verified":false,"changed_at":"2026-10-14 16:38:02","slug":"cartesia","name":"Cartesia"},
      {"field":"funding_round","verified":true,"changed_at":"2026-10-14 16:38:01","slug":"cartesia","name":"Cartesia"},
      … 10 more rows
    ]
  }
}

No key, so no tokens.

Errors

None of its own.

Over MCP

pulse, which also takes a since. changes_since gives the changes themselves.

GET /api/stats

Row counts for the whole dataset, and the list of category keys.

Takes

Nothing.

Returns

companies_count, people_count, funding_rounds_count, total_funding_tracked (the sum of every round amount on file, not converted between currencies), total_funding_formatted, oss_projects_count, and categories: the keys category= accepts, with their labels. Cached for an hour.

Example, real output

curl -s -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/stats"
{
  "data": {
    "companies_count": 69963,
    "people_count": 237,
    "funding_rounds_count": 190,
    "total_funding_tracked": 166686900000,
    "total_funding_formatted": "$166.7B",
    "oss_projects_count": 1021,
    "categories": {"ai_ml":"AI/ML","fintech":"Fintech","enterprise":"Enterprise","healthcare":"Healthcare","robotics":"Robotics","space":"Space","climate":"Climate/Energy","consumer":"Consumer","developer_tools":"Developer Tools","defense":"Defense","gaming":"Gaming","education":"Education"}
  }
}

This call cost 117 tokens.

Errors

A 401 without a working key, a 429 past a limit.

Over MCP

No tool. coverage has the counts.

GET /api/coverage

What the dataset has and what it lacks. Read it before telling a person "there is no data": it says whether a gap is a gap in the dataset or in the question.

Takes

Nothing.

Returns

One block per kind of data, each with row counts, a status (ok, partial or gap) and often a note: companies (with by_source, the importer each company came from), funding_rounds (newest is the latest announced date on file), headcount (how many companies have a current headcount, a dated snapshot, a snapshot with a source, a history), headcount_history, open_jobs, contributed_facts, people, open_source_projects; then by_country, the countries as stored, and by_country_code, the same counts added up by ISO code with the spellings that make each up; and snapshot (live, or the quarter a free key reads). Cached for an hour.

Example, real output

curl -s -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/coverage"
{
  "data": {
    "companies": {
      "rows": 69961,
      "with_website": 6606,
      "with_founded_date": 53216,
      "newest": "2026-10-13 17:21:57",
      "by_source": [{"import_source":"companies-house","n":52644},{"import_source":"wikipedia-lists","n":7706},{"import_source":"yc","n":5604},… 11 more sources],
      "status": "ok"
    },
    "funding_rounds": {
      "rows": 187,
      "companies": 33,
      "newest": "2026-02-15 00:00:00",
      "with_source_url": 133,
      "status": "gap",
      "note": "Sparse. Rounds after the newest date are not on file."
    },
    "headcount": {
      "companies": 5655,
      "with_current_headcount": 5655,
      "with_snapshot": 1,
      "with_sourced_snapshot": 1,
      "with_history": 191,
      "status": "partial",
      "note": "companies: has a current_headcount, a dated snapshot, or a history. Only a snapshot with a source is evidence (with_sourced_snapshot); a contributed headcount adds one."
    },
    "headcount_history": {
      "rows": 191,
      "status": "gap",
      "note": "Histories are not yet source-backed; treat as unverified. Counts histories only: for companies with any headcount, read headcount.companies."
    },
    "open_jobs": {
      "rows": 0,
      "companies": 0,
      "newest": null,
      "status": "gap",
      "note": "ATS board pulls not started."
    },
    "contributed_facts": {
      "rows": 9,
      "verified": 6,
      "rejected": 1,
      "status": "ok"
    },
    "people": {
      "rows": 234,
      "companies": 108,
      "status": "gap"
    },
    "open_source_projects": {
      "rows": 1021,
      "status": "ok"
    },
    "by_country": [
      {"country":"GB","companies":52670,"funding_rounds":3,"headcount_snapshots":0},
      {"country":"US","companies":1627,"funding_rounds":0,"headcount_snapshots":0},
      {"country":"USA","companies":144,"funding_rounds":162,"headcount_snapshots":1},
      … 37 more countries
    ],
    "by_country_code": [
      {"country_code":"GB","spellings":["GB","UK"],"companies":52676,"funding_rounds":3,"headcount_snapshots":0},
      {"country_code":"US","spellings":["US","USA"],"companies":1771,"funding_rounds":162,"headcount_snapshots":1},
      … 21 more countries
    ],
    "snapshot": "live"
  }
}

This call cost 1,758 tokens.

Errors

A 401 without a working key, a 429 past a limit.

Over MCP

The coverage tool returns the same object without the data wrapper.

GET /api/companies

Searches companies by criteria. This is the one query behind saved screens and the search_companies tool. With no criteria it lists every company.

Takes

qText. Matches companies whose name or description contains it, or whose slug is exactly it.
categoryOne category key, matched exactly. The keys are in categories on GET /api/stats, for example ai_ml.
countryAny spelling of a country finds the companies stored under each spelling of it: US, USA and United States find the same companies. A stored two-letter value that is not an ISO code (see what is missing) matches only itself.
statusMatched exactly, for example operating or closed.
min_headcount, max_headcountWhole numbers, compared with current_headcount. A company with no headcount matches neither.
founded_after, founded_beforeA date, YYYY-MM-DD.
raised_afterA date, YYYY-MM-DD. The last round was announced on or after it.
raised_sinceThe same test with a time value: 90d, 6m, 1y, a date, or a timestamp.
stageThe latest round's type, mapped onto these: an extension counts as its series (series_e_extension is series_e), and a type not in the list (secondary, strategic, unknown) is other. One of pre_seed, seed, series_a, series_b, series_c, series_d, series_e, series_f, growth, ipo, other.
min_total_raisedA number. The sum of the amounts of the rounds on file is at least this.
investorsInvestor slugs or exact names. Any one of them backed a round.
added_since, added_beforeA time value each: the company was added to StartupGraph on or after added_since, before added_before.
hiringtrue: the open-role count rose between the two readings /jobs reports. false: flat or fell. A company with no two readings matches neither.
sortname (the default without q), founded_date, current_headcount, total_raised, last_fundraise or open_jobs_delta or added (when the company was added), or relevance (the default with q). Rows with no value sort last. relevance puts the best name match first: the exact name, then the slug, then names that start with q as a word, then q as a word anywhere in the name, then anywhere in the name, then a match in the description only; within each, an operating company (or one of unknown status) comes before a closed or acquired one, then the one with more on file (a funding round, a website, a verified fact). Any other sort you give is kept exactly, so an exact match is not moved to the top.
directionasc or desc. The default is asc, except with sort=added, which is newest first.
per_page1 to 100. Default 25.
pageDefault 1.

Returns

data, the rows, and meta {page, per_page, total}. Each row: slug, name, website, description, category, category_label, founded_date, location {city, state, country, country_code}, current_headcount, status, import_source, total_raised, last_fundraise, stage, round_type. country is the value as stored; country_code is its two-letter ISO code when the stored value certainly means one, else null. stage is the latest round's type mapped as the stage filter reads it, and round_type is that type as stored. stage is the latest round type as stored, so it can be a value the stage filter does not accept, like the one below. When hiring or sort=open_jobs_delta is used, each row adds hiring, open_jobs_delta, reading_week and current_week_read (as on /jobs) and open_jobs {now, thirty_days_ago}, each reading as {week, count}, and meta.as_of is the date the 30 days are counted back from. They are null for a company with no open-role reading on file, which today is every company. When added_since, added_before or sort=added is used, each row adds added_at (when the row was written), added_by (imported, contributed or system) and verified (null for imported). Who contributed a company is never shown, and a contributed company that is not verified is listed only to the key that sent it.

Example, real output

curl -s -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/companies?category=ai_ml&raised_since=1y&sort=total_raised&direction=desc&per_page=3"
{
  "data": [
    {"slug":"harvey","name":"Harvey","website":"https://harvey.ai","description":"AI platform for law firms automating legal work and research.","category":"ai_ml","category_label":"AI/ML","founded_date":"2022-01-01 00:00:00","location":{"city":"San Francisco","state":"CA","country":"USA","country_code":"US"},"current_headcount":332,"status":"operating","import_source":null,"total_raised":966000000,"last_fundraise":"2025-12-04 00:00:00","stage":"series_e","round_type":"series_e_extension"}
  ],
  "meta": {
    "page": 1,
    "per_page": 3,
    "total": 1
  }
}

This call cost 136 tokens.

Example, real output

curl -s -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/companies?added_since=7d&sort=added&per_page=2"
{
  "data": [
    {"slug":"cartesia","name":"Cartesia","website":"https://cartesia.ai","description":null,"category":null,"category_label":null,"founded_date":null,"location":{"city":null,"state":null,"country":null,"country_code":null},"current_headcount":null,"status":"operating","import_source":"system","total_raised":91000000,"last_fundraise":"2025-03-11","stage":"series_a","round_type":"series_a","added_at":"2026-10-14 16:37:54","added_by":"system","verified":true},
    {"slug":"fish-audio","name":"Fish Audio","website":"https://fish.audio","description":null,"category":null,"category_label":null,"founded_date":null,"location":{"city":"Palo Alto","state":null,"country":null,"country_code":null},"current_headcount":22,"status":"operating","import_source":"system","total_raised":52000000,"last_fundraise":"2026-07-27","stage":"seed","round_type":"seed","added_at":"2026-10-14 16:37:11","added_by":"system","verified":true}
  ],
  "meta": {
    "page": 1,
    "per_page": 2,
    "total": 3
  }
}

This call cost 242 tokens. The companies added in the past week, newest first. Both were sent by StartupGraph's own data crank.

Example, real output

curl -s -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/companies?hiring=true&per_page=3"
{
  "data": [],
  "meta": {"page":1,"per_page":3,"total":0,"as_of":"2026-10-14"}
}

This call cost 19 tokens. No company has two job-board readings yet, so hiring=true and hiring=false both match nothing.

Errors

422 for a value that does not fit, with the reason per field. This is real output for raised_since=yesterday, and it cost 58 tokens:

{
  "message": "Invalid query",
  "errors": {
    "raised_since": [
      "Not a time this API understands. Use a number and a unit (24h hours, 7d days, 2w weeks, 6m months, 1y years), a date (2026-10-01), or an ISO timestamp (2026-10-01T14:00:00Z)."
    ]
  }
}
A 401 without a working key, a 429 past a limit.

Over MCP

search_companies: the same criteria, limit in place of per_page, first page only.

GET /api/companies/{slug}

Everything on one company.

Takes

slugIn the path. Get it from search.

Returns

The short company form plus linkedin_url, product_highlights, headcount_history, is_open_source, is_indie, github_stars and fetched_at. Then fields: for each profile field, {value, source_url, source, fetched_at, verified, status}, where status is imported, missing, verified, unverified or rejected; country also has country_code. Then funding_rounds (as /funding), total_raised, people (as /people), and facts: the contributed facts the calling key may read, each {field, value, source_url, fetched_at, verified, status, added_by}.

Example, real output

curl -s -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/companies/mistral-ai"
{
  "data": {
    "slug": "mistral-ai",
    "name": "Mistral AI",
    "website": "https://mistral.ai",
    "description": "French AI company building open and efficient large language models.",
    "category": "ai_ml",
    "category_label": "AI/ML",
    "founded_date": "2023-04-01 00:00:00",
    "location": {
      "city": "Paris",
      "state": null,
      "country": "France",
      "country_code": "FR"
    },
    "current_headcount": 418,
    "status": "operating",
    "import_source": "wikipedia",
    "linkedin_url": "https://www.linkedin.com/company/mistral-ai/",
    "fetched_at": "2026-02-17 01:40:14",
    "fields": {
      "name": {"value":"Mistral AI","source_url":"https://en.wikipedia.org/wiki/Mistral_AI","source":"Wikipedia","fetched_at":"2026-02-17 01:40:14","verified":false,"status":"imported"},
      "country": {"value":"France","source_url":"https://en.wikipedia.org/wiki/Mistral_AI","source":"Wikipedia","fetched_at":"2026-02-17 01:40:14","verified":false,"status":"imported","country_code":"FR"},
      … 9 more fields in the same form: website, description, founded_date, city, state, current_headcount, status, category, linkedin_url
    },
    "funding_rounds": [
      {"id":95,"round_type":"series_c","amount":1870000000,"currency":"USD","announced_date":"2025-09-08 00:00:00","pre_money_valuation":11000000000,"source_url":"https://mistral.ai/news/mistral-ai-raises-1-7-b-to-accelerate-technological-progress-with-ai","investors":["Nvidia","Andreessen Horowitz","Lightspeed Venture Partners","General Catalyst","Index Ventures","ASML"],"verified":true},
      … 4 more rounds
    ],
    "total_raised": 3059300000,
    "people": [
      {"name":"Arthur Mensch","slug":"arthur-mensch","linkedin_url":"https://www.linkedin.com/in/arthur-mensch-a5225b86/","photo_url":null,"role":"Co-founder & CEO","is_current":true},
      … 2 more people
    ],
    "facts": [],
    … left out: product_highlights, headcount_history, is_open_source, is_indie, github_stars
  }
}

This call cost 1,431 tokens.

Errors

404 for an unknown slug. A 401 without a working key, a 429 past a limit.

Over MCP

get_company. The tool leaves out total_raised and the status on each fact.

GET /api/companies/{slug}/funding

The funding rounds on file for one company, newest first.

Takes

slugIn the path.

Returns

Each round: id, round_type, amount, currency, announced_date, pre_money_valuation, source_url, investors (names) and verified. verified is false for a contributed round whose source has not been checked yet, and for a round with no source. A round dated more than a week after the article in its source_url also has source_date, the article's date, and date_note: see what is missing.

Example, real output

curl -s -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/companies/mistral-ai/funding"
{
  "data": [
    {"id":95,"round_type":"series_c","amount":1870000000,"currency":"USD","announced_date":"2025-09-08 00:00:00","pre_money_valuation":11000000000,"source_url":"https://mistral.ai/news/mistral-ai-raises-1-7-b-to-accelerate-technological-progress-with-ai","investors":["Nvidia","Andreessen Horowitz","Lightspeed Venture Partners","General Catalyst","Index Ventures","ASML"],"verified":true},
    {"id":94,"round_type":"series_b","amount":645000000,"currency":"USD","announced_date":"2024-06-01 00:00:00","pre_money_valuation":5555000000,"source_url":"https://techcrunch.com/2024/06/11/mistral-ai-raises-640-million-at-6b-valuation/","investors":["Nvidia","Andreessen Horowitz","Lightspeed Venture Partners","General Catalyst","Salesforce Ventures","Databricks","IBM Ventures"],"verified":true},
    … 3 more rounds
  ]
}

This call cost 446 tokens.

Errors

404 for an unknown slug. A 401 without a working key, a 429 past a limit.

Over MCP

funding_rounds returns the same list without the data wrapper.

GET /api/companies/{slug}/people

The people on file for one company.

Takes

slugIn the path.

Returns

Each person: name, slug, linkedin_url, photo_url, role, is_current.

Example, real output

curl -s -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/companies/mistral-ai/people"
{
  "data": [
    {"name":"Arthur Mensch","slug":"arthur-mensch","linkedin_url":"https://www.linkedin.com/in/arthur-mensch-a5225b86/","photo_url":null,"role":"Co-founder & CEO","is_current":true},
    {"name":"Guillaume Lample","slug":"guillaume-lample","linkedin_url":"https://www.linkedin.com/in/guillaume-lample-3a7a5ba5/","photo_url":null,"role":"Co-founder & Chief Scientist","is_current":true},
    … 1 more people
  ]
}

This call cost 140 tokens.

Errors

404 for an unknown slug. A 401 without a working key, a 429 past a limit.

Over MCP

No tool of its own. get_company carries people.

GET /api/companies/{slug}/headcount

The headcount on file for one company, and how much of it is evidence.

Takes

slugIn the path.

Returns

current_headcount; snapshots, dated readings that each carry a source, each {date, headcount, source}; synthetic_history, a quarterly series that has no source; and note, which says which of the two you are looking at. Only a snapshot with a source is evidence.

Example, real output

curl -s -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/companies/mistral-ai/headcount"
{
  "data": {
    "current_headcount": 418,
    "snapshots": [],
    "synthetic_history": [
      {"date":"2023-01-01","headcount":171},
      {"date":"2023-04-01","headcount":176},
      … 8 more quarters
    ],
    "note": "no source-backed snapshots yet; synthetic_history is seed data, not evidence"
  }
}

This call cost 135 tokens.

Errors

404 for an unknown slug. A 401 without a working key, a 429 past a limit.

Over MCP

headcount_history returns the same, plus history and verified: false.

GET /api/companies/{slug}/jobs

Weekly counts of open roles for one company, summed over its public Greenhouse, Lever and Ashby boards.

Takes

slugIn the path.
discover1: when the company has no board on file, try likely board addresses on the three providers and store any that answer. That makes this the one read that writes and that reaches outside StartupGraph, so it was not used for the example.

Returns

now and thirty_days_ago, two counts: the latest week, and the newest week at least 30 days before as_of; reading_week, the Monday of the week now was read in, which is the date of the count; current_week_read, true when that is the current week (false means now is an earlier week's count, so check reading_week before calling it current); delta, the difference; hiring, true when it rose; as_of, which is not the date of the count but the day the 30 days are counted back from: today for a live key, the day the snapshot was built for a free key; series, every week on file, newest first, each {week, count, source_url, fetched_at}; boards, each {provider, board_url, confirmed_at}. delta and hiring are null with fewer than two readings, and in the first week after a company's boards change. Each weekly pull reads a limited number of boards, longest-unread first, so a company can go more than a week between counts. A board whose own API answers 404 or 410 four weekly pulls running is retired and no longer counted; it is tried again every four weeks. No reading is on file yet for any company, so today every company answers with nulls and an empty series, like this one.

Example, real output

curl -s -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/companies/mistral-ai/jobs"
{
  "data": {
    "now": null,
    "reading_week": null,
    "current_week_read": false,
    "thirty_days_ago": null,
    "delta": null,
    "hiring": null,
    "as_of": "2026-10-14",
    "series": [],
    "boards": []
  }
}

This call cost 41 tokens.

Errors

404 for an unknown slug. A 401 without a working key, a 429 past a limit.

Over MCP

open_jobs. The tool gives boards as a list of addresses and has no discover.

GET /api/companies/{slug}/facts

The contributed facts on one company that the calling key may read: verified ones, imported ones, and its own whatever their status. Up to 200, newest first.

Takes

slugIn the path.

Returns

Each fact: fact_id, field, value, as_of, source_url, fetched_at, status (verified, rejected, needs_second_source or unverified), verified, rejected_reason, credit_if_verified, created_at, added_by (imported, contributed, or system for StartupGraph's own data crank; who contributed is never shown). A fact another key sent is left out until it is verified.

Example, real output

curl -s -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/companies/smarkets/facts"
{
  "data": [
    {"fact_id":7,"field":"executive","value":{"name":"Jason Trost","role":"Founder & CEO","is_current":true},"as_of":null,"source_url":"https://smarkets.com/about","fetched_at":"2026-10-13 23:01:05","status":"verified","verified":true,"rejected_reason":null,"credit_if_verified":2000,"created_at":"2026-10-13 17:22:19","added_by":"contributed"},
    {"fact_id":6,"field":"funding_round","value":{"type":"series_a","amount":2300000,"currency":"USD","date":"2013-02-21","investors":["T-Venture","Passion Capital"]},"as_of":"2013-02-21","source_url":"https://techcrunch.com/2013/02/21/smarkets/","fetched_at":"2026-10-13 17:22:20","status":"verified","verified":true,"rejected_reason":null,"credit_if_verified":5000,"created_at":"2026-10-13 17:22:17","added_by":"contributed"},
    … 4 more facts
  ]
}

This call cost 597 tokens.

Errors

404 for an unknown slug. A 401 without a working key, a 429 past a limit.

Over MCP

No tool of its own. get_company carries facts.

GET /api/people/{slug}

One person and the companies they are on file with.

Takes

slugIn the path. A person's slug comes from /api/search or a company's people.

Returns

name, slug, linkedin_url, photo_url, bio, and companies, each {name, slug, role, is_current}, current roles first.

Example, real output

curl -s -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/people/arthur-mensch"
{
  "data": {
    "name": "Arthur Mensch",
    "slug": "arthur-mensch",
    "linkedin_url": "https://www.linkedin.com/in/arthur-mensch-a5225b86/",
    "photo_url": null,
    "bio": null,
    "companies": [
      {"name":"Mistral AI","slug":"mistral-ai","role":"Co-founder & CEO","is_current":true}
    ]
  }
}

This call cost 64 tokens.

Errors

404 for an unknown slug. A 401 without a working key, a 429 past a limit.

Over MCP

No tool.

GET /api/investors

Finds investors by name.

Takes

qText. Matches investors whose name or slug contains it. Left out, the 50 investors with the most companies come back.

Returns

Up to 50, most companies first. Each: name, slug, type, website, and companies, the number of companies on file that they backed.

Example, real output

curl -s -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/investors?q=index"
{
  "data": [
    {"name":"Index Ventures","slug":"index-ventures","type":"vc","website":"https://www.indexventures.com","companies":8}
  ]
}

This call cost 32 tokens.

Errors

A 401 without a working key, a 429 past a limit.

Over MCP

search_investors, where q is required and limit (1 to 50, default 20) sets how many.

GET /api/investors/{slug}

One investor and the companies on file that they backed.

Takes

slugIn the path. Get it from /api/investors.

Returns

name, slug, type, website, description, and companies: up to 100 search rows, latest fundraise first. meta.total is how many there are.

Example, real output

curl -s -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/investors/index-ventures"
{
  "data": {
    "name": "Index Ventures",
    "slug": "index-ventures",
    "type": "vc",
    "website": "https://www.indexventures.com",
    "description": "European-founded global VC backing companies from seed to IPO.",
    "companies": [
      {"slug":"mistral-ai","name":"Mistral AI","website":"https://mistral.ai","description":"French AI company building open and efficient large language models.","category":"ai_ml","category_label":"AI/ML","founded_date":"2023-04-01 00:00:00","location":{"city":"Paris","state":null,"country":"France","country_code":"FR"},"current_headcount":418,"status":"operating","import_source":"wikipedia","total_raised":3059300000,"last_fundraise":"2025-09-08 00:00:00","stage":"series_c","round_type":"series_c"},
      … 7 more companies
    ]
  },
  "meta": {
    "page": 1,
    "per_page": 100,
    "total": 8
  }
}

This call cost 1,039 tokens.

Errors

404 for an unknown slug. A 401 without a working key, a 429 past a limit.

Over MCP

No tool. search_companies with investors gives the same companies.

GET /api/funding-rounds

Funding rounds across every company, newest first.

Takes

since, untilA time value each. Rounds are compared by day: announced on or after since, on or before until. Empty is the same as left out.
investorsComma-separated slugs or exact names. Any one of them is in the round.
round_typeOne round type, for example series_c. Case, spaces and hyphens do not matter.
per_page1 to 100. Default 50.
pageDefault 1.

Returns

data and meta {page, per_page, total}. Each round: id, company (the slug), company_name, round_type, amount, currency, announced_date, pre_money_valuation, source_url, investors, verified.

Example, real output

curl -s -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/funding-rounds?investors=index-ventures&round_type=series_c&per_page=2"
{
  "data": [
    {"id":95,"company":"mistral-ai","company_name":"Mistral AI","round_type":"series_c","amount":1870000000,"currency":"USD","announced_date":"2025-09-08 00:00:00","pre_money_valuation":11000000000,"source_url":"https://mistral.ai/news/mistral-ai-raises-1-7-b-to-accelerate-technological-progress-with-ai","investors":["Nvidia","Andreessen Horowitz","Lightspeed Venture Partners","General Catalyst","Index Ventures","ASML"],"verified":true},
    {"id":88,"company":"cohere","company_name":"Cohere","round_type":"series_c","amount":270000000,"currency":"USD","announced_date":"2023-06-01 00:00:00","pre_money_valuation":1930000000,"source_url":"https://techcrunch.com/2023/06/08/cohere-raises-270m-to-expand-its-enterprise-focused-generative-ai-platform/","investors":["Nvidia","Index Ventures","Salesforce Ventures","Inovia Capital","Oracle"],"verified":true}
  ],
  "meta": {
    "page": 1,
    "per_page": 2,
    "total": 6
  }
}

This call cost 226 tokens.

Errors

422 for a since or until that is not a time value. A 401 without a working key, a 429 past a limit.

Over MCP

No tool across companies. funding_rounds is one company's rounds.

GET /api/oss-projects

The 100 open source projects on file with the most GitHub stars.

Takes

Nothing.

Returns

Each project: name, repo (owner/name, the handle for the stars history), github_url, stars, language, description, stars_fetched_at. It always returns 100 rows, so it is one of the larger calls.

Example, real output

curl -s -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/oss-projects"
{
  "data": [
    {"name":"awesome-selfhosted","repo":"awesome-selfhosted/awesome-selfhosted","github_url":"https://github.com/awesome-selfhosted/awesome-selfhosted","stars":273405,"language":null,"description":"A list of Free Software network services and web applications which can be hosted on your own servers","stars_fetched_at":"2026-02-15 22:15:01"},
    {"name":"openclaw","repo":"openclaw/openclaw","github_url":"https://github.com/openclaw/openclaw","stars":196813,"language":"TypeScript","description":"Your own personal AI assistant. Any OS. Any Platform. The lobster way. 🦞 ","stars_fetched_at":"2026-02-15 22:14:34"},
    … 98 more projects
  ]
}

This call cost 7,816 tokens.

Errors

A 401 without a working key, a 429 past a limit.

Over MCP

No tool.

GET /api/oss-projects/{owner}/{repo}/stars

Weekly readings of one project's GitHub stars, newest first.

Takes

owner, repoIn the path, as in repo on the list above. Case does not matter.
weeksHow many readings, 1 to 260. Default 52. A number outside the range is brought into it.

Returns

The project (name, repo, github_url, stars, stars_fetched_at) and series, each reading {week, stars, forks, last_commit_at, source_url, observed_at}. One reading is written per project per week by the weekly refresh. No reading has been written yet, so series is empty for every project today.

Example, real output

curl -s -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/oss-projects/openclaw/openclaw/stars?weeks=4"
{
  "data": {
    "name": "openclaw",
    "repo": "openclaw/openclaw",
    "github_url": "https://github.com/openclaw/openclaw",
    "stars": 196813,
    "stars_fetched_at": "2026-02-15 22:14:34",
    "series": []
  }
}

This call cost 44 tokens.

Errors

404 for a project that is not on file. A 401 without a working key, a 429 past a limit.

Over MCP

oss_stars_history.

What changed

GET /api/changes

Every change to what the company endpoints serve, after a point in time, newest first. A copy kept by replaying this feed matches the company endpoints.

Takes

sinceRequired. A time value. With hours or a timestamp it compares to the second; with the other forms, by day.
companiesComma-separated slugs. Only changes to these companies.
portfolioThe name or id of one of your portfolios. Only changes to its companies.
limit1 to 1000. Default 200.

Returns

data and meta {since, until, tier, note}. A live key reads up to now; a free key reads up to the last quarter end, and meta.note says so. Each row: id, company, field, kind, fact_id, old, new, source_url, verified, changed_at, added_by. Match the rows of one fact by fact_id.

kind is one of three. applied: a submitted value was served, at once and unverified. verified: its source checked out. rejected: it failed and was undone; old is the value withdrawn and new is what the record shows now, null if it was removed.

What a key sees depends on whose fact it was. The key that sent a fact gets its rows in full. Every other key gets the same rows, because it could have been served the value, but with source_url null until the fact is verified. The exception is the applied row of a funding round or a headcount, where the company record shows the source too. In the example, read by a key that sent none of these facts, the first two rows shown are one fact, an executive applied and then rejected, so both carry a null source. The third is the verified row of a funding round sent the day before. The last is the exception: a headcount that is still unverified, shown with its source because the company record shows it. The feed never carries the rejection reason or who sent a fact. In the feed, field is people for an executive fact and company for a new company.

Example, real output

curl -s -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/changes?since=7d&limit=200"
{
  "data": [
    … 26 newer rows,
    {"id":17,"company":"fish-audio","field":"people","kind":"rejected","fact_id":12,"old":{"name":"Rissa Cao","role":"Co-Founder & CEO"},"new":null,"source_url":null,"verified":false,"changed_at":"2026-10-14 16:37:19","added_by":"system"},
    {"id":16,"company":"fish-audio","field":"people","kind":"applied","fact_id":12,"old":null,"new":{"name":"Rissa Cao","role":"Co-Founder & CEO"},"source_url":null,"verified":false,"changed_at":"2026-10-14 16:37:19","added_by":"system"},
    … 4 more rows,
    {"id":11,"company":"smarkets","field":"funding_round","kind":"verified","fact_id":4,"old":null,"new":{"type":"seed","amount":200000,"currency":"USD","date":"2008-06-19","investors":[]},"source_url":"https://techcrunch.com/2008/06/19/smarkets-raises-100k-seed-round-for-simple-gambling-startup/","verified":true,"changed_at":"2026-10-14 03:00:27","added_by":"contributed"},
    … 9 more rows,
    {"id":1,"company":"anthropic","field":"headcount","kind":"applied","fact_id":1,"old":566,"new":566,"source_url":"https://www.anthropic.com/company","verified":false,"changed_at":"2026-10-12 22:06:09","added_by":"contributed"}
  ],
  "meta": {
    "since": "2026-10-07",
    "until": "now",
    "tier": "live"
  }
}

This call cost 3,323 tokens.

Errors

422 without since, or with one that is not a time value. 404 {"message":"No such portfolio on this key"}. A 401 without a working key, a 429 past a limit.

Over MCP

changes_since, with companies as a list.

Contributing

Sending a fact with its source. How a fact is checked gets its own page; this is what the calls take and return.

POST /api/facts

Sends one fact about a company that is on file, with the page that states it.

Takes

companyThe company's slug.
fieldOne of funding_round, headcount, executive, or a profile field to correct: website, founded_date, description, status, category, name, city, state, country, linkedin_url.
valueDepends on field. funding_round: {type (60 characters at most), date (YYYY-MM-DD), amount (a number, optional), currency (three letters, default USD), investors (up to 30 names of 120 characters at most, optional), pre_money_valuation (optional)}. headcount: a whole number above 0. executive: {name (2 to 120 characters), role (2 to 160), linkedin_url (optional), is_current (default true)}. A profile field: text, 1 to 2,000 characters.
source_urlThe page that states the fact. A URL, 2,048 characters at most.
as_ofOptional. The date the fact was true, YYYY-MM-DD.

Returns

Status 202 (200 with duplicate: true when this key already sent the same fact: nothing new is stored, it earns nothing, and the answer is the fact on file as it stands). The fact is served on the company at once, marked unverified, and the call then waits up to about 4 seconds for the source to be fetched and checked. If the check finishes in that time, decided is true and status is the result. If not, status is unverified, decided is false, and check names the calls that return the result later: GET /api/facts/{id} or the fact_status tool. tokens_credited is what this fact paid, 0 unless it was verified, and credits lists each line of it (the pay for the fact, and any bonus). next lists up to three facts worth sending next about the same company, with what each pays; the top one is also said in message. A key that StartupGraph uses for its own data crank (added_by: system) earns nothing, and its message says so.

Example request

curl -s -X POST -H "Authorization: Bearer $STARTUPGRAPH_KEY" -H "content-type: application/json" "https://startupgraph.dev/api/facts" \
  -d '{"company":"<slug>","field":"headcount","value":<number>,"source_url":"<the page that states it>"}'

Response shape

{
  "data": {
    "fact_id": <number>,
    "field": "<the field you sent>",
    "value": <the value you sent>,
    "as_of": "<YYYY-MM-DD>" | null,
    "source_url": "<the source you sent>",
    "fetched_at": "<when the source was fetched>" | null,
    "status": "verified" | "rejected" | "needs_second_source" | "source_unreachable" | "unverified",
    "verified": true | false,
    "rejected_reason": "<why>" | null,
    "fetch_attempts": <times the source could not be read>,
    "last_fetch_reason": "<why, the last time>" | null,
    "credit_if_verified": <number>,
    "created_at": "<YYYY-MM-DD HH:MM:SS>",
    "added_by": "contributed",
    "duplicate": false,   POST /api/facts and submit_fact only
    "served": true,
    "verification": {"queued": false} | {"queued": true, "eta": "~1 min"},
    "decided": true | false,
    "tokens_credited": <number>,
    "check": {"rest": "GET https://startupgraph.dev/api/facts/<id>", "mcp": "fact_status {\"fact_id\": <id>}", "free": "GET https://startupgraph.dev/api/keys/me"},
    "account_url": "https://startupgraph.dev/account",
    "next": [{"company": "<slug>", "company_name": "…", "field": "<a field to send>", "kind": "missing" | "newer" | "retry", "missing": "<what is missing>", "pays": <number>, "on_file_as_of": …, "wants_recent": …, "recent_since": …, "last_rejection": … | null, "bonus_hint": "<text>" | null, "sentence": "<one sentence for the person>"}],   up to three, or []
    "message": "<one or two plain sentences to pass to the person>",
    "credits": [{"amount": <number>, "kind": "verified" | "bonus" | "gift" | "other", "reason": "<text>"}],   only when verified: one line per credit the fact paid
    "balance": {"tokens_remaining": <number>, "tokens_per_day": <number>, "renews": true | false},   only when verified
    "how_to_fix": "<what would turn it round>",   only when rejected, needs_second_source or source_unreachable
  "review": {"reason": "same_name_different_role", "listed_apart_from": [{"name": "…", "role": "…"}], "message": "<what to send>"},   only for an executive listed apart from someone of the same name
    "review": {"reason": "same_name_different_role", "listed_apart_from": [{"name": "…", "role": "…"}], "message": "<what to send>"},   only for an executive listed apart from someone of the same name
    "doing": "fetching the source to check the claim", "email": "<that the result also comes by email>"   only when unverified
  }
}

Not real output. This call was not made against production because it writes. The shape is read from the code and its tests.

Errors

404 {"message":"No company with slug <slug>. Use GET /api/search, or POST /api/companies to add it."}. Submitting costs no tokens, verified or not, and still answers when the allowance is spent. 422 when the body does not fit (nothing is stored, and it does not count against the daily submission limit). 429 {"error":"submission_limit","message":"Daily submission limit for the <tier> tier reached."} past the number of submissions a day, which is submissions_per_day on GET /api/keys/me. A 401 without a working key, a 429 past a limit.

Over MCP

submit_fact, the same fields without the data wrapper.

POST /api/companies

Adds a company that is not on file.

Takes

name2 to 200 characters.
websiteA URL.
source_urlA page that shows the company exists: its own site, a press article, a registry.
categoryOptional. 100 characters at most.
descriptionOptional. 2,000 characters at most.
founded_dateOptional. YYYY-MM-DD.
countryOptional. Three characters at most.

Returns

The same answer as POST /api/facts, with the new company's slug added. Status 202 (200 with duplicate: true when this key already sent the same fact: nothing new is stored, it earns nothing, and the answer is the fact on file as it stands). The fact is served on the company at once, marked unverified, and the call then waits up to about 4 seconds for the source to be fetched and checked. If the check finishes in that time, decided is true and status is the result. If not, status is unverified, decided is false, and check names the calls that return the result later: GET /api/facts/{id} or the fact_status tool. tokens_credited is what this fact paid, 0 unless it was verified, and credits lists each line of it (the pay for the fact, and any bonus). next lists up to three facts worth sending next about the same company, with what each pays; the top one is also said in message. A key that StartupGraph uses for its own data crank (added_by: system) earns nothing, and its message says so.

Example request

curl -s -X POST -H "Authorization: Bearer $STARTUPGRAPH_KEY" -H "content-type: application/json" "https://startupgraph.dev/api/companies" \
  -d '{"name":"<name>","website":"<its site>","source_url":"<a page that shows it exists>"}'

Response shape

{
  "data": {
    "slug": "<the new company's slug>",
    "fact_id": <number>,
    "field": "<the field you sent>",
    "value": <the value you sent>,
    "as_of": "<YYYY-MM-DD>" | null,
    "source_url": "<the source you sent>",
    "fetched_at": "<when the source was fetched>" | null,
    "status": "verified" | "rejected" | "needs_second_source" | "source_unreachable" | "unverified",
    "verified": true | false,
    "rejected_reason": "<why>" | null,
    "fetch_attempts": <times the source could not be read>,
    "last_fetch_reason": "<why, the last time>" | null,
    "credit_if_verified": <number>,
    "created_at": "<YYYY-MM-DD HH:MM:SS>",
    "added_by": "contributed",
    "served": true,
    "verification": {"queued": false} | {"queued": true, "eta": "~1 min"},
    "decided": true | false,
    "tokens_credited": <number>,
    "check": {"rest": "GET https://startupgraph.dev/api/facts/<id>", "mcp": "fact_status {\"fact_id\": <id>}", "free": "GET https://startupgraph.dev/api/keys/me"},
    "account_url": "https://startupgraph.dev/account",
    "next": [{"company": "<slug>", "company_name": "…", "field": "<a field to send>", "kind": "missing" | "newer" | "retry", "missing": "<what is missing>", "pays": <number>, "on_file_as_of": …, "wants_recent": …, "recent_since": …, "last_rejection": … | null, "bonus_hint": "<text>" | null, "sentence": "<one sentence for the person>"}],   up to three, or []
    "message": "<one or two plain sentences to pass to the person>",
    "credits": [{"amount": <number>, "kind": "verified" | "bonus" | "gift" | "other", "reason": "<text>"}],   only when verified: one line per credit the fact paid
    "balance": {"tokens_remaining": <number>, "tokens_per_day": <number>, "renews": true | false},   only when verified
    "how_to_fix": "<what would turn it round>",   only when rejected, needs_second_source or source_unreachable
  "review": {"reason": "same_name_different_role", "listed_apart_from": [{"name": "…", "role": "…"}], "message": "<what to send>"},   only for an executive listed apart from someone of the same name
    "review": {"reason": "same_name_different_role", "listed_apart_from": [{"name": "…", "role": "…"}], "message": "<what to send>"},   only for an executive listed apart from someone of the same name
    "doing": "fetching the source to check the claim", "email": "<that the result also comes by email>"   only when unverified
  }
}

Not real output. This call was not made against production because it writes. The shape is read from the code and its tests.

Errors

409 {"message":"Already on file as <slug>. Submit facts against it with POST /api/facts.","slug":"<slug>"} when a company with the same website host is on file. When the name's slug belongs to a company with no website on file, the 409 also carries existing_slug and existing_website: null, and its message says to add facts to that row only if it is the same company. A name another company holds is not a duplicate when the websites differ: the new company gets the slug of its website's host (a second Clay at clay.com is clay-com), or the name and the host (clay-clay-com) when that is taken too, and slug in the answer says which. Slugs on file never change. Submitting costs no tokens, verified or not, and still answers when the allowance is spent. 422 when the body does not fit (nothing is stored, and it does not count against the daily submission limit). 429 {"error":"submission_limit","message":"Daily submission limit for the <tier> tier reached."} past the number of submissions a day, which is submissions_per_day on GET /api/keys/me. A 401 without a working key, a 429 past a limit.

Over MCP

submit_company.

GET /api/facts/{id}

One fact and where its check stands.

Takes

idIn the path. The fact_id from a submission, a company's facts, or the change feed.

Returns

fact_id, field, value, as_of, source_url, fetched_at, status (verified, rejected, needs_second_source or unverified), verified, rejected_reason, credit_if_verified, created_at, added_by (imported, contributed, or system for StartupGraph's own data crank; who contributed is never shown), plus company (the slug) and mine, true when the calling key sent it. For a fact the calling key sent, the call is free, still answers when the allowance is spent, and adds the result fields a submission answers with: decided, tokens_credited, check, account_url, message, and balance or how_to_fix. Any other key reads a fact only once it is verified, as an ordinary metered call. The example is a verified fact read by a key that did not send it.

Example, real output

curl -s -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/facts/7"
{
  "data": {
    "company": "smarkets",
    "fact_id": 7,
    "field": "executive",
    "value": {"name":"Jason Trost","role":"Founder & CEO","is_current":true},
    "as_of": null,
    "source_url": "https://smarkets.com/about",
    "fetched_at": "2026-10-13 23:01:05",
    "status": "verified",
    "verified": true,
    "rejected_reason": null,
    "credit_if_verified": 2000,
    "created_at": "2026-10-13 17:22:19",
    "added_by": "contributed",
    "mine": false
  }
}

This call cost 96 tokens.

Errors

404 {"message":"Not found"} for an id that does not exist, and the same answer for another key's fact that is not verified. This is real: fact 9 is another key's unverified executive, and GET /api/facts/9 answered 404 and cost 10 tokens. A 401 without a working key, a 429 past a limit.

Over MCP

fact_status.

Saved screens

A screen is a company search saved on the key under a name. A fuller walk-through of screens, portfolios and roll-ups gets its own page.

GET /api/screens

The screens saved on the calling key, by name.

Takes

Nothing.

Returns

Each screen: id, name, description, criteria, result_count, last_run_at, created_at, updated_at. The key used for these examples has none, so the list is empty.

Example, real output

curl -s -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/screens"
{
  "data": []
}

This call cost 10 tokens.

Errors

A 401 without a working key, a 429 past a limit.

Over MCP

list_screens.

POST /api/screens

Saves a search under a name.

Takes

name1 to 120 characters. Unique on the key.
criteriaRequired. An object with any of the search criteria; {} matches every company.
descriptionOptional. 1,000 characters at most.

Returns

Status 201 and the screen. The search is run once to fill result_count.

Example request

curl -s -X POST -H "Authorization: Bearer $STARTUPGRAPH_KEY" -H "content-type: application/json" "https://startupgraph.dev/api/screens" \
  -d '{"name":"ai-over-100","criteria":{"category":"ai_ml","min_headcount":100}}'

Response shape

{"data": {"id": <number>, "name": "<name>", "description": "<text>" | null, "criteria": {<the criteria, with sort and direction filled in>}, "result_count": <number> | null, "last_run_at": "<YYYY-MM-DD HH:MM:SS>", "created_at": "…", "updated_at": "…"}}

Not real output. This call was not made against production because it writes. The shape is read from the code and its tests.

Errors

409 {"message":"A screen with that name already exists on this key."}. 422 with the reason per field. A 401 without a working key, a 429 past a limit.

Over MCP

create_screen.

GET /api/screens/{ref}

Runs a saved screen and returns the companies that match now.

Takes

refIn the path. The screen's name or its id.
per_page1 to 100. Default 25.
pageDefault 1.

Returns

The screen, then data and meta as GET /api/companies returns them. Running a screen stores the new result_count and last_run_at on it, so this GET writes.

Example request

curl -s -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/screens/ai-over-100?per_page=10"

Response shape

{"screen": {"id": <number>, "name": "<name>", "description": "<text>" | null, "criteria": {<the criteria, with sort and direction filled in>}, "result_count": <number> | null, "last_run_at": "<YYYY-MM-DD HH:MM:SS>", "created_at": "…", "updated_at": "…"},
 "data": [<search rows>],
 "meta": {"page": 1, "per_page": 10, "total": <number>}}

Not real output. This call was not made against production because it stores the result count on the screen, and the key used here has no screen to run. The shape is read from the code and its tests.

Errors

404 for a name or id that is not on this key. A 401 without a working key, a 429 past a limit.

Over MCP

run_screen, with limit in place of per_page.

PATCH /api/screens/{ref}

Changes a saved screen.

Takes

refIn the path. Name or id.
name, description, criteriaJSON body. Any of the three. criteria replaces the stored criteria whole.

Returns

The screen after the change. result_count is not refreshed until the screen is next run.

Example request

curl -s -X PATCH -H "Authorization: Bearer $STARTUPGRAPH_KEY" -H "content-type: application/json" "https://startupgraph.dev/api/screens/ai-over-100" \
  -d '{"criteria":{"category":"ai_ml","min_headcount":250}}'

Response shape

{"data": {"id": <number>, "name": "<name>", "description": "<text>" | null, "criteria": {<the criteria, with sort and direction filled in>}, "result_count": <number> | null, "last_run_at": "<YYYY-MM-DD HH:MM:SS>", "created_at": "…", "updated_at": "…"}}

Not real output. This call was not made against production because it writes. The shape is read from the code and its tests.

Errors

404 for an unknown screen. 422 with the reason per field. A 401 without a working key, a 429 past a limit.

Over MCP

No tool. Over MCP, delete the screen and create it again.

DELETE /api/screens/{ref}

Deletes a saved screen. A portfolio that followed it keeps its companies and stops updating: its sync_stopped becomes screen_deleted.

Takes

refIn the path. Name or id.

Returns

Status 204 and no body.

Example request

curl -s -X DELETE -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/screens/ai-over-100"

Response shape

(status 204, no body)

Not real output. This call was not made against production because it deletes. The shape is read from the code and its tests.

Errors

404 for an unknown screen. A 401 without a working key, a 429 past a limit.

Over MCP

delete_screen.

Portfolios and roll-ups

A portfolio is a list of companies on the key. Each company is stored with its headcount, total raised, last round and status on the day it was added, so a later read can say what moved.

GET /api/portfolios

The portfolios on the calling key, by name.

Takes

Nothing.

Returns

Each portfolio: id, name, description, screen_id (set when it follows a screen), created_at, updated_at, synced_at, sync_attempted_at, sync_stopped (see the entries below), companies (how many it holds), and api_key_id, an internal number. The key used for these examples has none.

Example, real output

curl -s -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/portfolios"
{
  "data": []
}

This call cost 10 tokens.

Errors

A 401 without a working key, a 429 past a limit.

Over MCP

list_portfolios.

POST /api/portfolios

Creates a portfolio.

Takes

name1 to 120 characters. Unique on the key.
descriptionOptional. 1,000 characters at most.
screenOptional. The name or id of one of your screens. The portfolio then follows it.

Returns

Status 201 and the portfolio, with companies, how many it holds. Without screen it starts empty; add companies one at a time. With screen it is filled by this call with the screen's matches, the first 1,000 at once and the rest over the following hours, and an hourly job adds new matches after that, usually within an hour or two. synced_at is when the list last held every match, null until then. A company you take out stays out.

Example request

curl -s -X POST -H "Authorization: Bearer $STARTUPGRAPH_KEY" -H "content-type: application/json" "https://startupgraph.dev/api/portfolios" \
  -d '{"name":"watchlist"}'

Response shape

{"data": {"id": <number>, "api_key_id": <number>, "name": "watchlist", "description": null, "screen_id": null, "created_at": "…", "updated_at": "…", "synced_at": null, "sync_attempted_at": null, "sync_stopped": null, "companies": 0}}

Not real output. This call was not made against production because it writes. The shape is read from the code and its tests.

Errors

404 {"message":"No screen named <screen> on this key."}. 409 {"message":"A portfolio with that name already exists on this key."}. 422. A 401 without a working key, a 429 past a limit.

Over MCP

create_portfolio.

GET /api/portfolios/{ref}

A portfolio's companies as they are now, and what changed on each since it was added.

Takes

refIn the path. Name or id.

Returns

portfolio, with synced_at (when a portfolio that follows a screen last held every match), and sync_stopped and sync_note, set when the screen it followed was deleted and it is no longer updated; summary; and companies, each a search row plus note, added_at, source and since_added: the change in headcount, how many rounds were announced since, and each tracked field that differs, as {from, to}. Reading changes nothing; summary.added_this_sync is 0 on a read and is set only on the answer to a roll-up.

Example, real output

curl -s -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/portfolios/watchlist"
{
  "message": "Not found"
}

It answered 404. This call cost 10 tokens. The key used here has no portfolio, so the real answer is the 404. The shape of a found portfolio, read from the code:

{"data": {
  "portfolio": {"id": <number>, "name": "<name>", "description": "<text>" | null, "screen": {"id": <number>, "name": "<name>", "criteria": {…}} | null, "created_at": "…", "updated_at": "…",
                "synced_at": "<YYYY-MM-DD HH:MM:SS>" | null, "sync_stopped": "screen_deleted" | null, "sync_note": "<why it stopped>"   only when stopped},
  "summary": {"companies": <number>, "added_this_sync": <number>, "changed": <number>, "total_raised": <number>, "headcount": <number>},
  "companies": [
    {<a search row>, "note": "<text>" | null, "added_at": "…", "source": "<how it was added>",
     "since_added": {"headcount_delta": <number> | null, "rounds": <number>, "changes": {"<field>": {"from": <then>, "to": <now>}}}}
  ]
}}

Errors

404 for an unknown portfolio. A 401 without a working key, a 429 past a limit.

Over MCP

portfolio.

DELETE /api/portfolios/{ref}

Deletes a portfolio and its entries.

Takes

refIn the path. Name or id.

Returns

Status 204 and no body. A screen it followed is not deleted.

Example request

curl -s -X DELETE -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/portfolios/watchlist"

Response shape

(status 204, no body)

Not real output. This call was not made against production because it deletes. The shape is read from the code and its tests.

Errors

404 for an unknown portfolio. A 401 without a working key, a 429 past a limit.

Over MCP

delete_portfolio.

POST /api/portfolios/{ref}/companies

Adds a company to a portfolio and records its headcount, total raised, last round and status as they are now.

Takes

refIn the path. Name or id.
slugJSON body. The company's slug.
noteOptional. 1,000 characters at most.

Returns

Status 201 and the entry: a search row plus note and added_at. Adding a company that is already in the portfolio keeps the first record and replaces the note if one is sent.

Example request

curl -s -X POST -H "Authorization: Bearer $STARTUPGRAPH_KEY" -H "content-type: application/json" "https://startupgraph.dev/api/portfolios/watchlist/companies" \
  -d '{"slug":"mistral-ai","note":"met at the conference"}'

Response shape

{"data": {<a search row>, "note": "met at the conference", "added_at": "<YYYY-MM-DD HH:MM:SS>"}}

Not real output. This call was not made against production because it writes. The shape is read from the code and its tests.

Errors

404 for an unknown portfolio, and 404 {"message":"No company with slug <slug>. Use GET /api/search to find it, or POST /api/companies to add it."} for an unknown company. 422. A 401 without a working key, a 429 past a limit.

Over MCP

add_to_portfolio.

DELETE /api/portfolios/{ref}/companies/{slug}

Takes a company out of a portfolio. In a portfolio that follows a screen, the company stays out even while it matches; adding it again puts it back.

Takes

refIn the path. Name or id.
slugIn the path. The company's slug.

Returns

Status 204 and no body.

Example request

curl -s -X DELETE -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/portfolios/watchlist/companies/mistral-ai"

Response shape

(status 204, no body)

Not real output. This call was not made against production because it deletes. The shape is read from the code and its tests.

Errors

404 for an unknown portfolio, and 404 {"message":"Not in this portfolio"}. A 401 without a working key, a 429 past a limit.

Over MCP

remove_from_portfolio.

POST /api/rollups

Follows the deals of funds you name. One call makes a screen on those investors and a portfolio that follows the screen, both under the same name. When a round with one of the investors is recorded, the hourly job adds the company, usually within an hour or two.

Takes

name1 to 120 characters. Must be free as a screen name and as a portfolio name.
investors1 to 50 investor slugs or exact names. Find them with GET /api/investors.
descriptionOptional.

Returns

Status 201 and the new portfolio as GET /api/portfolios/{ref} returns it, already filled.

Example request

curl -s -X POST -H "Authorization: Bearer $STARTUPGRAPH_KEY" -H "content-type: application/json" "https://startupgraph.dev/api/rollups" \
  -d '{"name":"my-funds","investors":["index-ventures","sequoia-capital"]}'

Response shape

{"data": {
  "portfolio": {"id": <number>, "name": "<name>", "description": "<text>" | null, "screen": {"id": <number>, "name": "<name>", "criteria": {…}} | null, "created_at": "…", "updated_at": "…",
                "synced_at": "<YYYY-MM-DD HH:MM:SS>" | null, "sync_stopped": "screen_deleted" | null, "sync_note": "<why it stopped>"   only when stopped},
  "summary": {"companies": <number>, "added_this_sync": <number>, "changed": <number>, "total_raised": <number>, "headcount": <number>},
  "companies": [
    {<a search row>, "note": "<text>" | null, "added_at": "…", "source": "<how it was added>",
     "since_added": {"headcount_delta": <number> | null, "rounds": <number>, "changes": {"<field>": {"from": <then>, "to": <now>}}}}
  ]
}}

Not real output. This call was not made against production because it writes. The shape is read from the code and its tests.

Errors

422 {"message":"Unknown investors. Resolve names with GET /api/investors?q=","unknown":[…]} when a name matches no investor; nothing is made. 409 {"message":"A screen or portfolio with that name already exists on this key."}. A 401 without a working key, a 429 past a limit.

Over MCP

fund_rollup.

Plan and billing

What each plan costs and allows is under pricing, not here. These calls are how a key reads or changes its plan. Any other path under /api/billing/ answers 404 {"error":"not_found", …}, with or without a key, and names the four endpoints.

GET /api/billing/status

The plan the calling key is on and what it has used this month.

Takes

Nothing.

Returns

tier and plan (the same value); usd_month; limits; usage {tokens_today, tokens_this_month, calls_this_month, overage_tokens_this_month}; stripe_customer_id, masked, or null; subscription, true when one is active; pay_as_you_go; plans, every plan with its limits and price; token, how a token is counted.

Example, real output

curl -s -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/billing/status"
{
  "data": {
    "tier": "live",
    "plan": "live",
    "usage": {
      "tokens_today": 379078,
      "tokens_this_month": 566627,
      "calls_this_month": 1117,
      "overage_tokens_this_month": 0
    },
    "stripe_customer_id": null,
    "subscription": false,
    "token": "about 4 bytes of response; at least 10 a call",
    … left out (prices and limits): usd_month, limits, pay_as_you_go, plans
  }
}

This call cost 204 tokens.

Errors

A 401 without a working key, a 429 past a limit.

Over MCP

No tool.

POST /api/billing/checkout

Starts a paid subscription for the calling key. It returns a Stripe Checkout address for the person to open and pay at; the key moves to the plan when the payment completes.

Takes

planJSON body. live (the default) or scale.

Returns

The address of the checkout page.

Example request

curl -s -X POST -H "Authorization: Bearer $STARTUPGRAPH_KEY" -H "content-type: application/json" "https://startupgraph.dev/api/billing/checkout" -d '{"plan":"live"}'

Response shape

{"data": {"url": "https://checkout.stripe.com/…"}}

Not real output. This call was not made against production because it opens a checkout session. The shape is read from the code and its tests.

Errors

409 {"error":"already_paid", …} for a key already on a paid plan. 403 {"error":"not_allowed","message":"This key cannot be put on a plan."} for StartupGraph's own system key. 503 {"error":"billing_unavailable","message":"Billing is not configured yet."}. 502 {"error":"stripe_error", …}. A 401 without a working key, a 429 past a limit.

Over MCP

No tool.

POST /api/billing/portal

Returns the address of the Stripe page where the person changes the card, changes plan, cancels, or reads invoices.

Takes

Nothing.

Returns

The address of the portal page.

Example request

curl -s -X POST -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/billing/portal"

Response shape

{"data": {"url": "https://billing.stripe.com/…"}}

Not real output. This call was not made against production because it opens a portal session. The shape is read from the code and its tests.

Errors

404 {"error":"no_customer","message":"No Stripe customer for this key. Start with POST /api/billing/checkout."}. 503 billing_unavailable. 502 stripe_error. A 401 without a working key, a 429 past a limit.

Over MCP

No tool.

POST /api/billing/overage

Asks for pay as you go, which serves calls past the monthly allowance. It is set up by hand, so this call does not turn it on: it always answers 403 and says who to write to.

Takes

Nothing.

Returns

Status 403. enabled says whether pay as you go is already on for the key.

Example request

curl -s -X POST -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/billing/overage"

Response shape

{"error": "contact_us", "message": "Pay as you go is set up by hand. Email hello@startupgraph.dev with your key prefix (<prefix>) and we turn it on; …", "enabled": false}

Not real output. This call was not made against production because it is a POST, and only GETs were made for this page. The shape is read from the code and its tests.

Errors

A 401 without a working key, a 429 past a limit.

Over MCP

No tool.

Bulk export

The whole dataset as files, built nightly. It is arranged by hand: a key that has not been set up for it gets the 403 below.

GET /api/export

The latest nightly dump: which tables it holds and where each file is.

Takes

Nothing.

Returns

date, built_at, manifest (the path of the manifest file), skipped (tables left out of this build), and tables, each {name, rows, bytes, path}. The key used for these examples is not set up for export, so its real answer is the 403.

Example, real output

curl -s -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/export"
{
  "error": "contact_us",
  "message": "Bulk export and the nightly dump are arranged by hand. Email hello@startupgraph.dev with your key prefix. This key is on live."
}

It answered 403. This call cost 41 tokens.

Errors

403 contact_us, as above. 404 {"message":"No dump built yet; the nightly job runs at 02:00 UTC."}. 503 when export is not enabled on the deployment. A 401 without a working key, a 429 past a limit.

Over MCP

No tool.

GET /api/export/{date}/{file}

One file of a dump.

Takes

dateIn the path. YYYY-MM-DD, from GET /api/export.
fileIn the path. manifest.json, or <table>.jsonl.gz: one JSON object per line, gzipped. Facts in the export are verified and imported ones only.

Returns

The file, as a download.

Example, real output

curl -s -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/export/2026-10-13/manifest.json"
{
  "error": "contact_us",
  "message": "Bulk export and the nightly dump are arranged by hand. Email hello@startupgraph.dev with your key prefix. This key is on live."
}

It answered 403. This call cost 41 tokens.

Errors

403 contact_us, as above. 404 for a date or file that does not exist. A 401 without a working key, a 429 past a limit.

Over MCP

No tool.

MCP: connecting and calling

The MCP server is at https://startupgraph.dev/mcp, over Streamable HTTP, with the same key in the same header. For Claude Code these two commands connect it. The first removes an earlier startupgraph entry, which may hold an old key; without it the second stops with "already exists". If the first answers that there is no such server, that is fine.

claude mcp remove --scope user startupgraph
claude mcp add --transport http --scope user startupgraph https://startupgraph.dev/mcp --header "Authorization: Bearer $STARTUPGRAPH_KEY"

An agent connected this way calls the tools by name and never sees the transport. To call one by hand, post a JSON-RPC message. This is real, and cost 25 tokens:

curl -s -X POST "https://startupgraph.dev/mcp" \
  -H "Authorization: Bearer $STARTUPGRAPH_KEY" \
  -H "content-type: application/json" -H "accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_screens","arguments":{}}}'
event: message
data: {"result":{"content":[{"type":"text","text":"[]"}]},"jsonrpc":"2.0","id":1}

The answer arrives as an event stream. The tool's result is the JSON text in result.content[0].text: here [], an empty list. The tool examples below show that text parsed and re-indented, and show the request as the JSON-RPC message alone.

Each tool carries a title and four hints a client can use to decide what to run without asking: readOnlyHint, destructiveHint, idempotentHint and openWorldHint (it reaches outside StartupGraph). This is one tool's entry in the real tools/list answer, without its description and input schema:

{
  "name": "delete_portfolio",
  "title": "Delete a portfolio",
  "annotations": {"title":"Delete a portfolio","readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":false}
}

All 25 tools, as the server lists them:

ToolTitleWhat it does to stored data
search_companiesSearch companiesreads only
get_companyCompany profilereads only
funding_roundsFunding rounds for a companyreads only
headcount_historyHeadcount historyreads only
open_jobsOpen roles over timereads only
oss_stars_historyGitHub stars historyreads only
submit_factContribute a factwrites, removes nothing; fetches the source URL; a second identical call adds a second fact
submit_companyAdd a companywrites, removes nothing; fetches the source URL; a second identical call adds a second fact
fact_statusStatus of a contributed factreads only
changes_sinceChanges since a datereads only
coverageWhat the dataset coversreads only
pulseCompanies, rounds and facts added latelyreads only
recently_addedCompanies added in a time windowreads only
create_screenSave a screenwrites, removes nothing
list_screensList saved screensreads only
run_screenRun a saved screenwrites, removes nothing
delete_screenDelete a saved screenwrites, and removes something
create_portfolioCreate a portfoliowrites, removes nothing
search_investorsSearch investorsreads only
fund_rollupFollow funds as a portfoliowrites, removes nothing
list_portfoliosList portfoliosreads only
add_to_portfolioAdd a company to a portfoliowrites, removes nothing
remove_from_portfolioRemove a company from a portfoliowrites, and removes something
portfolioPortfolio and what changedreads only
delete_portfolioDelete a portfoliowrites, and removes something

Some things are REST only: the key's usage (/api/keys/me), billing, export, /api/stats, name search (/api/search), a person, an investor's page, funding rounds across companies, the list of open source projects, and changing a saved screen.

Reading tools

These fifteen only read. A client may run them without asking.

search_companies Search companies · read-only

Searches companies by criteria. The same query as GET /api/companies.

Takes

qText. Matches companies whose name or description contains it, or whose slug is exactly it.
categoryOne category key, matched exactly. The keys are in categories on GET /api/stats, for example ai_ml.
countryAny spelling of a country finds the companies stored under each spelling of it: US, USA and United States find the same companies. A stored two-letter value that is not an ISO code (see what is missing) matches only itself.
statusMatched exactly, for example operating or closed.
min_headcount, max_headcountWhole numbers, compared with current_headcount. A company with no headcount matches neither.
founded_after, founded_beforeA date, YYYY-MM-DD.
raised_afterA date, YYYY-MM-DD. The last round was announced on or after it.
raised_sinceThe same test with a time value: 90d, 6m, 1y, a date, or a timestamp.
stageThe latest round's type, mapped onto these: an extension counts as its series (series_e_extension is series_e), and a type not in the list (secondary, strategic, unknown) is other. One of pre_seed, seed, series_a, series_b, series_c, series_d, series_e, series_f, growth, ipo, other.
min_total_raisedA number. The sum of the amounts of the rounds on file is at least this.
investorsInvestor slugs or exact names. Any one of them backed a round.
added_since, added_beforeA time value each: the company was added to StartupGraph on or after added_since, before added_before.
hiringtrue: the open-role count rose between the two readings /jobs reports. false: flat or fell. A company with no two readings matches neither.
sortname (the default without q), founded_date, current_headcount, total_raised, last_fundraise or open_jobs_delta or added (when the company was added), or relevance (the default with q). Rows with no value sort last. relevance puts the best name match first: the exact name, then the slug, then names that start with q as a word, then q as a word anywhere in the name, then anywhere in the name, then a match in the description only; within each, an operating company (or one of unknown status) comes before a closed or acquired one, then the one with more on file (a funding round, a website, a verified fact). Any other sort you give is kept exactly, so an exact match is not moved to the top.
directionasc or desc. The default is asc, except with sort=added, which is newest first.
limit1 to 100. Default 20. There is no page: the tool returns the first page.

Returns

data and meta, as over REST. Each row: slug, name, website, description, category, category_label, founded_date, location {city, state, country, country_code}, current_headcount, status, import_source, total_raised, last_fundraise, stage, round_type. country is the value as stored; country_code is its two-letter ISO code when the stored value certainly means one, else null. stage is the latest round's type mapped as the stage filter reads it, and round_type is that type as stored. The example asks for US and finds companies stored as USA.

Example, real output

{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "search_companies", "arguments": {"q": "robot", "country": "US", "sort": "total_raised", "direction": "desc", "limit": 2}}}
{
  "data": [
    {"slug":"figure-ai","name":"Figure AI","website":"https://www.figure.ai","description":"Developing general-purpose humanoid robots powered by AI.","category":"robotics","category_label":"Robotics","founded_date":"2022-01-01 00:00:00","location":{"city":"Sunnyvale","state":"CA","country":"USA","country_code":"US"},"current_headcount":244,"status":"operating","import_source":"wikipedia","total_raised":1745000000,"last_fundraise":"2025-09-01 00:00:00","stage":"series_c","round_type":"series_c"},
    … 1 more row
  ],
  "meta": {
    "page": 1,
    "per_page": 2,
    "total": 20
  }
}

This call cost 317 tokens.

Errors

A value that does not fit the schema is refused by the MCP client or server before the tool runs.

Over REST

GET /api/companies.

recently_added Companies added in a time window · read-only

Companies added to StartupGraph in a time window, newest first.

Takes

sinceA time value. Default 7d.
beforeOptional end, a time value.
limit1 to 100. Default 20.
pageDefault 1.

Returns

Search rows with added_at, added_by and verified, and meta.total, how many were added in the window. The same as search_companies with added_since and sort=added, which is how to add other filters.

Example, real output

{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "recently_added", "arguments": {"since": "7d", "limit": 2}}}
{
  "data": [
    {"slug":"murf-ai","name":"Murf AI","website":"https://murf.ai","description":null,"category":null,"category_label":null,"founded_date":null,"location":{"city":"San Francisco","state":null,"country":"USA","country_code":"US"},"current_headcount":null,"status":"operating","import_source":"system","total_raised":10000000,"last_fundraise":"2022-09-21","stage":"series_a","round_type":"series_a","added_at":"2026-10-14 16:38:23","added_by":"system","verified":true},
    … 1 more row
  ],
  "meta": {
    "page": 1,
    "per_page": 2,
    "total": 6
  }
}

This call cost 307 tokens.

Errors

A value that does not fit the schema is refused before the tool runs.

Over REST

GET /api/companies?added_since=7d&sort=added.

pulse Companies, rounds and facts added lately · read-only

How much landed in the dataset lately, as counts.

Takes

sinceOptional. A time value. Adds windows.custom, counted from custom_since to now.

Returns

The same as GET /api/pulse, without the data wrapper, plus the custom window when since is given. To list the companies themselves, use recently_added.

Example, real output

{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "pulse", "arguments": {"since": "24h"}}}
{
  "as_of": "2026-10-14T16:38:03.102Z",
  "last_change_at": "2026-10-14 16:38:02",
  "windows": {
    "day": {"facts_submitted":19,"facts_verified":15,"companies_added":3,"funding_rounds_added":6,"job_counts_updated":0,"changes":31},
    … left out: hour, week, month, year (the same six counts),
    "custom": {"facts_submitted":37,"facts_verified":33,"companies_added":6,"funding_rounds_added":10,"job_counts_updated":0,"changes":67}
  },
  "latest": [
    {"field":"people","verified":false,"changed_at":"2026-10-14 16:38:02","slug":"cartesia","name":"Cartesia"},
    … 11 more rows
  ],
  "custom_since": "2026-10-13 16:39:01"
}

This call cost 666 tokens.

Errors

None of its own.

Over REST

GET /api/pulse, which needs no key.

get_company Company profile · read-only

Everything on one company.

Takes

slugThe company's slug.

Returns

The same as GET /api/companies/{slug}, without the data wrapper, without total_raised, and without status on each fact. The example is a company a contributor added, so its fields show what contributed data looks like: city has a verified fact behind it.

Example, real output

{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "get_company", "arguments": {"slug": "smarkets"}}}
{
  "slug": "smarkets",
  "name": "Smarkets",
  "website": "https://smarkets.com",
  "category": "fintech",
  "category_label": "Fintech",
  "founded_date": "2008-01-17",
  "location": {
    "city": "London",
    "state": null,
    "country": "GB",
    "country_code": "GB"
  },
  "current_headcount": null,
  "status": "operating",
  "import_source": "contributed",
  "linkedin_url": null,
  "fetched_at": "2026-10-13 17:21:57",
  "fields": {
    "city": {"value":"London","source_url":"https://find-and-update.company-information.service.gov.uk/company/06475845","source":"contributed","fetched_at":"2026-10-13 17:21:59","verified":true,"status":"verified"},
    … 10 more fields in the same form
  },
  "funding_rounds": [
    {"id":187,"round_type":"series_a","amount":2300000,"currency":"USD","announced_date":"2013-02-21","pre_money_valuation":null,"source_url":"https://techcrunch.com/2013/02/21/smarkets/","investors":["Passion Capital","T-Venture"],"verified":true},
    … 2 more rounds
  ],
  "people": [
    {"name":"Jason Trost","slug":"jason-trost","linkedin_url":null,"photo_url":null,"role":"CEO","is_current":true},
    … 2 more people
  ],
  "facts": [
    {"field":"executive","value":{"name":"Jason Trost","role":"Founder & CEO","is_current":true},"source_url":"https://smarkets.com/about","fetched_at":"2026-10-13 23:01:05","verified":true,"added_by":"contributed"},
    … 5 more facts
  ],
  … left out: description, product_highlights, headcount_history, is_open_source, is_indie, github_stars
}

This call cost 1,679 tokens.

Errors

{"error":"not_found","slug":"…"} for an unknown slug.

Over REST

GET /api/companies/{slug}.

funding_rounds Funding rounds for a company · read-only

The funding rounds on file for one company, newest first.

Takes

slugThe company's slug.

Returns

A list of rounds, each as over REST.

Example, real output

{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "funding_rounds", "arguments": {"slug": "figure-ai"}}}
[
  {"id":115,"round_type":"series_c","amount":1000000000,"currency":"USD","announced_date":"2025-09-01 00:00:00","pre_money_valuation":38000000000,"source_url":"https://www.figure.ai/news/series-c","investors":["Nvidia","Salesforce Ventures","Parkway Venture Capital","Intel Capital","Qualcomm Ventures","LG Technology Ventures","T-Mobile Ventures","Brookfield Asset Management","Macquarie Capital"],"verified":true},
  … 2 more rounds
]

This call cost 306 tokens.

Errors

{"error":"not_found","slug":"…"} for an unknown slug.

Over REST

GET /api/companies/{slug}/funding.

headcount_history Headcount history · read-only

The headcount on file for one company, and how much of it is evidence.

Takes

slugThe company's slug.

Returns

What GET /api/companies/{slug}/headcount gives: current_headcount, snapshots (dated, each with its source), synthetic_history (no source) and note. Then history, the same series as synthetic_history under its old name, and verified, always false. Read synthetic_history: history is kept only for callers that already read it.

Example, real output

{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "headcount_history", "arguments": {"slug": "figure-ai"}}}
{
  "current_headcount": 244,
  "snapshots": [],
  "synthetic_history": [
    {"date":"2022-01-01","headcount":138},
    {"date":"2022-04-01","headcount":142},
    … 12 more quarters
  ],
  "note": "no source-backed snapshots yet; synthetic_history is seed data, not evidence",
  "history": … the same quarters as synthetic_history,
  "verified": false
}

This call cost 380 tokens.

Errors

{"error":"not_found","slug":"…"} for an unknown slug.

Over REST

GET /api/companies/{slug}/headcount.

open_jobs Open roles over time · read-only

Weekly counts of open roles for one company.

Takes

slugThe company's slug.

Returns

As over REST, with boards as a list of addresses. No reading is on file yet for any company, so today every company answers like this.

Example, real output

{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "open_jobs", "arguments": {"slug": "figure-ai"}}}
{
  "now": null,
  "reading_week": null,
  "current_week_read": false,
  "thirty_days_ago": null,
  "delta": null,
  "hiring": null,
  "as_of": "2026-10-14",
  "series": [],
  "boards": []
}

This call cost 68 tokens.

Errors

{"error":"not_found","slug":"…"} for an unknown slug.

Over REST

GET /api/companies/{slug}/jobs.

oss_stars_history GitHub stars history · read-only

Weekly readings of one open source project's GitHub stars.

Takes

repoowner/name, for example openclaw/openclaw. Only projects on file answer; the list is GET /api/oss-projects.
weeks1 to 260. Default 52.

Returns

As over REST. series is empty for every project today.

Example, real output

{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "oss_stars_history", "arguments": {"repo": "openclaw/openclaw", "weeks": 4}}}
{
  "name": "openclaw",
  "repo": "openclaw/openclaw",
  "github_url": "https://github.com/openclaw/openclaw",
  "stars": 196813,
  "stars_fetched_at": "2026-02-15 22:14:34",
  "series": []
}

This call cost 71 tokens.

Errors

{"error":"not_found","repo":"…"} for a project that is not on file.

Over REST

GET /api/oss-projects/{owner}/{repo}/stars.

fact_status Status of a contributed fact · read-only

One fact and where its check stands.

Takes

fact_idA whole number.

Returns

As GET /api/facts/{id}, without company and mine. For a fact the calling key sent it adds the result fields a submission answers with.

Example, real output

{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "fact_status", "arguments": {"fact_id": 7}}}
{
  "fact_id": 7,
  "field": "executive",
  "value": {"name":"Jason Trost","role":"Founder & CEO","is_current":true},
  "as_of": null,
  "source_url": "https://smarkets.com/about",
  "fetched_at": "2026-10-13 23:01:05",
  "status": "verified",
  "verified": true,
  "rejected_reason": null,
  "credit_if_verified": 2000,
  "created_at": "2026-10-13 17:22:19",
  "added_by": "contributed"
}

This call cost 121 tokens.

Errors

{"error":"not_found"} for an id that does not exist, and for another key's fact that is not verified.

Over REST

GET /api/facts/{id}. Over REST, reading your own fact is free.

changes_since Changes since a date · read-only

Every change to what the company tools serve, after a point in time, newest first.

Takes

sinceRequired. A time value.
companiesOptional. A list of slugs.
portfolioOptional. The name or id of one of your portfolios.
limit1 to 1000. Default 200.

Returns

since (as it was read), until and changes. The rows, the three kinds and what a key that did not send a fact sees are the same as GET /api/changes.

Example, real output

{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "changes_since", "arguments": {"since": "24h", "companies": ["smarkets"], "limit": 3}}}
{
  "since": "2026-10-13 16:39:31",
  "until": "now",
  "changes": [
    {"id":11,"company":"smarkets","field":"funding_round","kind":"verified","fact_id":4,"old":null,"new":{"type":"seed","amount":200000,"currency":"USD","date":"2008-06-19","investors":[]},"source_url":"https://techcrunch.com/2008/06/19/smarkets-raises-100k-seed-round-for-simple-gambling-startup/","verified":true,"changed_at":"2026-10-14 03:00:27","added_by":"contributed"},
    {"id":10,"company":"smarkets","field":"people","kind":"verified","fact_id":7,"old":null,"new":{"name":"Jason Trost","role":"Founder & CEO"},"source_url":"https://smarkets.com/about","verified":true,"changed_at":"2026-10-13 23:01:06","added_by":"contributed"},
    … 1 more rows
  ]
}

This call cost 288 tokens.

Errors

{"error":"portfolio_not_found"}.

Over REST

GET /api/changes.

coverage What the dataset covers · read-only

What the dataset has and what it lacks.

Takes

Nothing.

Returns

The same object as GET /api/coverage, without the data wrapper.

Example, real output

{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "coverage", "arguments": {}}}
{
  "companies": {… as in the REST example},
  "funding_rounds": {
    "rows": 187,
    "companies": 33,
    "newest": "2026-02-15 00:00:00",
    "with_source_url": 133,
    "status": "gap",
    "note": "Sparse. Rounds after the newest date are not on file."
  },
  "headcount": {
    "companies": 5655,
    "with_current_headcount": 5655,
    "with_snapshot": 1,
    "with_sourced_snapshot": 1,
    "with_history": 191,
    "status": "partial",
    "note": "companies: has a current_headcount, a dated snapshot, or a history. Only a snapshot with a source is evidence (with_sourced_snapshot); a contributed headcount adds one."
  },
  "open_jobs": {
    "rows": 0,
    "companies": 0,
    "newest": null,
    "status": "gap",
    "note": "ATS board pulls not started."
  },
  … left out: headcount_history, contributed_facts, people, open_source_projects, by_country, snapshot
}

This call cost 2,013 tokens.

Errors

None of its own.

Over REST

GET /api/coverage.

list_screens List saved screens · read-only

The screens saved on the calling key.

Takes

Nothing.

Returns

A list of screens. Empty for the key used here.

Example, real output

{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "list_screens", "arguments": {}}}
[]

This call cost 25 tokens.

Errors

None of its own.

Over REST

GET /api/screens.

search_investors Search investors · read-only

Finds investors by name. Use it before fund_rollup.

Takes

qRequired. Text the name or slug contains.
limit1 to 50. Default 20.

Returns

A list, most companies first, each {name, slug, type, website, companies}.

Example, real output

{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "search_investors", "arguments": {"q": "sequoia", "limit": 3}}}
[
  {"name":"Sequoia Capital","slug":"sequoia-capital","type":"vc","website":"https://www.sequoiacap.com","companies":12}
]

This call cost 59 tokens.

Errors

None of its own.

Over REST

GET /api/investors.

list_portfolios List portfolios · read-only

The portfolios on the calling key.

Takes

Nothing.

Returns

A list of portfolios with how many companies each holds. Empty for the key used here.

Example, real output

{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "list_portfolios", "arguments": {}}}
[]

This call cost 25 tokens.

Errors

None of its own.

Over REST

GET /api/portfolios.

portfolio Portfolio and what changed · read-only

A portfolio's companies as they are now, and what changed on each since it was added. It reads only: a portfolio that follows a screen is kept up to date by the hourly job, not by this call.

Takes

portfolioName or id.

Returns

portfolio (with synced_at, sync_stopped and sync_note), summary and companies, as GET /api/portfolios/{ref}.

Example, real output

{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "portfolio", "arguments": {"portfolio": "watchlist"}}}
{
  "error": "not_found",
  "portfolio": "watchlist"
}

This call cost 38 tokens. The key used here has no portfolio, so this is the answer for a name that is not on it. A found portfolio has this shape, read from the code:

{
  "portfolio": {"id": <number>, "name": "<name>", "description": "<text>" | null, "screen": {"id": <number>, "name": "<name>", "criteria": {…}} | null, "created_at": "…", "updated_at": "…",
                "synced_at": "<YYYY-MM-DD HH:MM:SS>" | null, "sync_stopped": "screen_deleted" | null, "sync_note": "<why it stopped>"   only when stopped},
  "summary": {"companies": <number>, "added_this_sync": <number>, "changed": <number>, "total_raised": <number>, "headcount": <number>},
  "companies": [
    {<a search row>, "note": "<text>" | null, "added_at": "…", "source": "<how it was added>",
     "since_added": {"headcount_delta": <number> | null, "rounds": <number>, "changes": {"<field>": {"from": <then>, "to": <now>}}}}
  ]
}

Errors

{"error":"not_found","portfolio":"…"}, as above.

Over REST

GET /api/portfolios/{ref}.

Writing tools

These ten change something stored on the key or in the dataset, so a client should ask the person before running one. None was called for this page. One of them reads like a read: run_screen stores the new result count on the screen.

submit_fact Contribute a fact · writes, removes nothing, fetches the source URL you give it

Sends one fact about a company that is on file, with the page that states it. Costs no tokens.

Takes

company, field, value, source_url, as_ofAs POST /api/facts.

Returns

The same fields as the REST call, without the data wrapper.

Example request

{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "submit_fact", "arguments": {"company": "<slug>", "field": "headcount", "value": 120, "source_url": "<the page that states it>"}}}

Response shape

{
  "fact_id": <number>,
  "field": "<the field you sent>",
  "value": <the value you sent>,
  "as_of": "<YYYY-MM-DD>" | null,
  "source_url": "<the source you sent>",
  "fetched_at": "<when the source was fetched>" | null,
  "status": "verified" | "rejected" | "needs_second_source" | "source_unreachable" | "unverified",
  "verified": true | false,
  "rejected_reason": "<why>" | null,
  "fetch_attempts": <times the source could not be read>,
  "last_fetch_reason": "<why, the last time>" | null,
  "credit_if_verified": <number>,
  "created_at": "<YYYY-MM-DD HH:MM:SS>",
  "added_by": "contributed",
  "duplicate": false,   POST /api/facts and submit_fact only
  "served": true,
  "verification": {"queued": false} | {"queued": true, "eta": "~1 min"},
  "decided": true | false,
  "tokens_credited": <number>,
  "check": {"rest": "GET https://startupgraph.dev/api/facts/<id>", "mcp": "fact_status {\"fact_id\": <id>}", "free": "GET https://startupgraph.dev/api/keys/me"},
  "account_url": "https://startupgraph.dev/account",
  "next": [{"company": "<slug>", "company_name": "…", "field": "<a field to send>", "kind": "missing" | "newer" | "retry", "missing": "<what is missing>", "pays": <number>, "on_file_as_of": …, "wants_recent": …, "recent_since": …, "last_rejection": … | null, "bonus_hint": "<text>" | null, "sentence": "<one sentence for the person>"}],   up to three, or []
  "message": "<one or two plain sentences to pass to the person>",
  "credits": [{"amount": <number>, "kind": "verified" | "bonus" | "gift" | "other", "reason": "<text>"}],   only when verified: one line per credit the fact paid
  "balance": {"tokens_remaining": <number>, "tokens_per_day": <number>, "renews": true | false},   only when verified
  "how_to_fix": "<what would turn it round>",   only when rejected, needs_second_source or source_unreachable
  "review": {"reason": "same_name_different_role", "listed_apart_from": [{"name": "…", "role": "…"}], "message": "<what to send>"},   only for an executive listed apart from someone of the same name
  "doing": "fetching the source to check the claim", "email": "<that the result also comes by email>"   only when unverified
}

Not real output. This tool is annotated as a write, so it was not called on production. The shape is read from the code and its tests.

Errors

The same fact sent twice by one key answers with duplicate: true and the fact on file. {"error":"invalid","issues":[…]} when the fact does not fit. {"error":"submission_limit"}. {"error":"company_not_found","hint":"search_companies, or submit_company"}.

Over REST

POST /api/facts.

submit_company Add a company · writes, removes nothing, fetches the source URL you give it

Adds a company that is not on file. Costs no tokens.

Takes

name, website, source_url, category, description, founded_date, countryAs POST /api/companies.

Returns

The same fields as submit_fact, with the new company's slug.

Example request

{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "submit_company", "arguments": {"name": "<name>", "website": "<its site>", "source_url": "<a page that shows it exists>"}}}

Response shape

{
  "slug": "<the new company's slug>",
  "fact_id": <number>,
  "field": "<the field you sent>",
  "value": <the value you sent>,
  "as_of": "<YYYY-MM-DD>" | null,
  "source_url": "<the source you sent>",
  "fetched_at": "<when the source was fetched>" | null,
  "status": "verified" | "rejected" | "needs_second_source" | "source_unreachable" | "unverified",
  "verified": true | false,
  "rejected_reason": "<why>" | null,
  "fetch_attempts": <times the source could not be read>,
  "last_fetch_reason": "<why, the last time>" | null,
  "credit_if_verified": <number>,
  "created_at": "<YYYY-MM-DD HH:MM:SS>",
  "added_by": "contributed",
  "served": true,
  "verification": {"queued": false} | {"queued": true, "eta": "~1 min"},
  "decided": true | false,
  "tokens_credited": <number>,
  "check": {"rest": "GET https://startupgraph.dev/api/facts/<id>", "mcp": "fact_status {\"fact_id\": <id>}", "free": "GET https://startupgraph.dev/api/keys/me"},
  "account_url": "https://startupgraph.dev/account",
  "next": [{"company": "<slug>", "company_name": "…", "field": "<a field to send>", "kind": "missing" | "newer" | "retry", "missing": "<what is missing>", "pays": <number>, "on_file_as_of": …, "wants_recent": …, "recent_since": …, "last_rejection": … | null, "bonus_hint": "<text>" | null, "sentence": "<one sentence for the person>"}],   up to three, or []
  "message": "<one or two plain sentences to pass to the person>",
  "credits": [{"amount": <number>, "kind": "verified" | "bonus" | "gift" | "other", "reason": "<text>"}],   only when verified: one line per credit the fact paid
  "balance": {"tokens_remaining": <number>, "tokens_per_day": <number>, "renews": true | false},   only when verified
  "how_to_fix": "<what would turn it round>",   only when rejected, needs_second_source or source_unreachable
  "review": {"reason": "same_name_different_role", "listed_apart_from": [{"name": "…", "role": "…"}], "message": "<what to send>"},   only for an executive listed apart from someone of the same name
  "doing": "fetching the source to check the claim", "email": "<that the result also comes by email>"   only when unverified
}

Not real output. This tool is annotated as a write, so it was not called on production. The shape is read from the code and its tests.

Errors

{"error":"exists","slug":"…"} when the company is already on file. {"error":"submission_limit"}.

Over REST

POST /api/companies.

create_screen Save a screen · writes, removes nothing

Saves a search under a name.

Takes

name1 to 120 characters.
criteriaRequired. The search criteria as an object.
descriptionOptional.

Returns

The screen.

Example request

{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "create_screen", "arguments": {"name": "ai-over-100", "criteria": {"category": "ai_ml", "min_headcount": 100}}}}

Response shape

{"screen": {"id": <number>, "name": "<name>", "description": "<text>" | null, "criteria": {<the criteria, with sort and direction filled in>}, "result_count": <number> | null, "last_run_at": "<YYYY-MM-DD HH:MM:SS>", "created_at": "…", "updated_at": "…"}}

Not real output. This tool is annotated as a write, so it was not called on production. The shape is read from the code and its tests.

Errors

{"error":"duplicate_name"}.

Over REST

POST /api/screens.

run_screen Run a saved screen · writes, removes nothing

Runs a saved screen and returns the companies that match now. It stores the new result count and run time on the screen, which is why it is not marked read-only.

Takes

screenName or id.
limit1 to 100. Default 25.
pageDefault 1.

Returns

The screen, data and meta.

Example request

{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "run_screen", "arguments": {"screen": "ai-over-100", "limit": 10}}}

Response shape

{"screen": {"id": <number>, "name": "<name>", "description": "<text>" | null, "criteria": {<the criteria, with sort and direction filled in>}, "result_count": <number> | null, "last_run_at": "<YYYY-MM-DD HH:MM:SS>", "created_at": "…", "updated_at": "…"},
 "data": [<search rows>],
 "meta": {"page": 1, "per_page": 10, "total": <number>}}

Not real output. This tool is annotated as a write, so it was not called on production. The shape is read from the code and its tests.

Errors

{"error":"not_found","screen":"…"}.

Over REST

GET /api/screens/{ref}.

delete_screen Delete a saved screen · writes, removes something

Deletes a saved screen. A portfolio that followed it keeps its companies and stops updating (sync_stopped: "screen_deleted").

Takes

screenName or id.

Returns

The name of the screen that was deleted.

Example request

{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "delete_screen", "arguments": {"screen": "ai-over-100"}}}

Response shape

{"deleted": "ai-over-100"}

Not real output. This tool is annotated as a write, so it was not called on production. The shape is read from the code and its tests.

Errors

{"error":"not_found","screen":"…"}.

Over REST

DELETE /api/screens/{ref}.

create_portfolio Create a portfolio · writes, removes nothing

Creates a portfolio. With screen it is filled now with the screen's matches (the first 1,000; the rest over the following hours) and kept up to date by an hourly job.

Takes

name1 to 120 characters.
descriptionOptional.
screenOptional. A screen's name or id, to follow it.

Returns

The portfolio, with synced_at and companies, how many it holds.

Example request

{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "create_portfolio", "arguments": {"name": "watchlist"}}}

Response shape

{"portfolio": {"id": <number>, "api_key_id": <number>, "name": "watchlist", "description": null, "screen_id": null, "created_at": "…", "updated_at": "…", "synced_at": null, "sync_attempted_at": null, "sync_stopped": null, "companies": 0}}

Not real output. This tool is annotated as a write, so it was not called on production. The shape is read from the code and its tests.

Errors

{"error":"duplicate_name"}. {"error":"screen_not_found"}.

Over REST

POST /api/portfolios.

add_to_portfolio Add a company to a portfolio · writes, removes nothing

Adds a company to a portfolio and records its headcount, total raised, last round and status as they are now.

Takes

portfolioName or id.
slugThe company's slug.
noteOptional. 1,000 characters at most.

Returns

The entry.

Example request

{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "add_to_portfolio", "arguments": {"portfolio": "watchlist", "slug": "mistral-ai"}}}

Response shape

{"entry": {<a search row>, "note": null, "added_at": "<YYYY-MM-DD HH:MM:SS>"}}

Not real output. This tool is annotated as a write, so it was not called on production. The shape is read from the code and its tests.

Errors

{"error":"not_found","portfolio":"…"}. {"error":"company_not_found"}.

Over REST

POST /api/portfolios/{ref}/companies.

remove_from_portfolio Remove a company from a portfolio · writes, removes something

Takes a company out of a portfolio. In a portfolio that follows a screen it stays out; add_to_portfolio puts it back.

Takes

portfolioName or id.
slugThe company's slug.

Returns

removed: true, or false when the company was not in it.

Example request

{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "remove_from_portfolio", "arguments": {"portfolio": "watchlist", "slug": "mistral-ai"}}}

Response shape

{"removed": true | false}

Not real output. This tool is annotated as a write, so it was not called on production. The shape is read from the code and its tests.

Errors

{"error":"not_found","portfolio":"…"}.

Over REST

DELETE /api/portfolios/{ref}/companies/{slug}.

delete_portfolio Delete a portfolio · writes, removes something

Deletes a portfolio and its entries.

Takes

portfolioName or id.

Returns

The name of the portfolio that was deleted.

Example request

{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "delete_portfolio", "arguments": {"portfolio": "watchlist"}}}

Response shape

{"deleted": "watchlist"}

Not real output. This tool is annotated as a write, so it was not called on production. The shape is read from the code and its tests.

Errors

{"error":"not_found","portfolio":"…"}.

Over REST

DELETE /api/portfolios/{ref}.

fund_rollup Follow funds as a portfolio · writes, removes nothing

Follows the deals of funds you name: one call makes a screen on those investors and a portfolio that follows it. The hourly job adds new deals as rounds are recorded.

Takes

name1 to 120 characters.
investors1 to 50 slugs or exact names. Find them with search_investors.
descriptionOptional.

Returns

The new portfolio, already filled.

Example request

{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "fund_rollup", "arguments": {"name": "my-funds", "investors": ["index-ventures", "sequoia-capital"]}}}

Response shape

{"rollup": {
  "portfolio": {"id": <number>, "name": "<name>", "description": "<text>" | null, "screen": {"id": <number>, "name": "<name>", "criteria": {…}} | null, "created_at": "…", "updated_at": "…",
                "synced_at": "<YYYY-MM-DD HH:MM:SS>" | null, "sync_stopped": "screen_deleted" | null, "sync_note": "<why it stopped>"   only when stopped},
  "summary": {"companies": <number>, "added_this_sync": <number>, "changed": <number>, "total_raised": <number>, "headcount": <number>},
  "companies": [
    {<a search row>, "note": "<text>" | null, "added_at": "…", "source": "<how it was added>",
     "since_added": {"headcount_delta": <number> | null, "rounds": <number>, "changes": {"<field>": {"from": <then>, "to": <now>}}}}
  ]
}}

Not real output. This tool is annotated as a write, so it was not called on production. The shape is read from the code and its tests.

Errors

{"error":"unknown_investors","unknown":[…]}. {"error":"duplicate_name"}.

Over REST

POST /api/rollups.

Pages for the person

These are web pages, not API calls. An agent sends its person to them.

/accountSign in by emailed link. Shows the key's start, tokens left, a ledger of tokens used and earned, every fact sent with its status, and the button that makes a new key.
/keys/claimWhere the one-time link in the access email leads. The person presses "Show my key" and sees the key once. The link works once, for 24 hours.
/quickstartFrom joining to the first call.
/roadmapWhat is planned. The full page is for people on the waitlist.