Documentation · page 2 of 6
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.
Before the first call: the key, tokens, errors, time values, what is missing today
REST, 43 endpoints
GET /api/pulseGET /api/statsGET /api/coverageGET /api/searchGET /api/companiesGET /api/companies/{slug}GET /api/companies/{slug}/fundingGET /api/companies/{slug}/peopleGET /api/companies/{slug}/headcountGET /api/companies/{slug}/jobsGET /api/companies/{slug}/factsGET /api/people/{slug}GET /api/investorsGET /api/investors/{slug}GET /api/funding-roundsGET /api/oss-projectsGET /api/oss-projects/{owner}/{repo}/starsGET /api/screensPOST /api/screensGET /api/screens/{ref}PATCH /api/screens/{ref}DELETE /api/screens/{ref}GET /api/portfoliosPOST /api/portfoliosGET /api/portfolios/{ref}DELETE /api/portfolios/{ref}POST /api/portfolios/{ref}/companiesDELETE /api/portfolios/{ref}/companies/{slug}POST /api/rollupsMCP, 25 tools · connecting and calling
search_companiesrecently_addedpulseget_companyfunding_roundsheadcount_historyopen_jobsoss_stars_historyfact_statuschanges_sincecoveragelist_screenssearch_investorslist_portfoliosportfoliosubmit_factsubmit_companycreate_screenrun_screendelete_screencreate_portfolioadd_to_portfolioremove_from_portfoliodelete_portfoliofund_rollupEvery 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.
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:
GET /api/keys/me, POST /api/keys/rotate, POST /api/facts, POST /api/companies, GET /api/facts/{id} for a fact your key sent, and over MCP the handshake, tools/list, submit_fact and submit_company.per_page and limit set how many rows come back.X-Tokens-Charged header and none of the limit headers. A tool call is charged like a REST call, by the bytes of the response stream: list_screens below returned 98 bytes and cost 25 tokens. The cost of each tool example was measured by reading tokens_today on GET /api/keys/me before and after the call.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.
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:
24h | A number and h: that many hours ago. |
7d, 2w | Days or weeks ago. |
6m, 1y | Months or years ago. m is months, never minutes. |
2026-10-01 | A date. |
2026-10-01T14:00:00Z | An 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.
The dataset is young and uneven. These are the gaps most likely to make an answer wrong. GET /api/coverage has the current numbers.
/jobs and open_jobs return nulls and an empty series, hiring is null on every company, and hiring=true and hiring=false both match nothing.funding_rounds.companies in the coverage call is how many. The imported rounds stop in February 2026: a round announced after that is on file only because someone contributed it, StartupGraph's own data crank included. A company with no rounds has total_raised, last_fundraise and stage null, which means "not on file", not "raised nothing".round_type unknown came from a news import. Where the article in source_url is dated more than a week earlier, the round also has source_date, the article's date, and a date_note. since and until still read announced_date.current_headcount and a quarterly history, and fewer than ten have a reading with a source. The headcount call names which is which; treat a number without a source as unverified.series is empty for every open source project, and the star counts on file were last read in February 2026 (stars_fetched_at).IS for Israel, GE for Germany), so they have no country_code and match only themselves. by_country_code in the coverage call adds up the ones that are certain.Calls about the key itself. All three cost no tokens.
GET /api/keys/meWhat 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/rotateReplaces 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/keysJoins 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
email | JSON 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/loginSends 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
email | JSON body. A valid email address. |
next | roadmap (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.
Every call here needs a key except /api/pulse.
GET /api/pulseWhat 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/statsRow 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/coverageWhat 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/searchFinds companies and people by name. Use it to turn a name into a slug.
Takes
q | Two characters at least. Shorter, or missing, returns empty lists, not an error. |
Returns
companies: up to 20 whose name or slug contains q, best name match first (the same order as sort=relevance on GET /api/companies, then the largest headcount), in the short company form. people: up to 10 whose name contains it or whose slug is it, best name match first, each {name, slug, linkedin_url}.
Example, real output
curl -s -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/search?q=mistral"
{
"data": {
"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"},
… 5 more companies
],
"people": []
}
}This call cost 539 tokens.
Errors
A 401 without a working key, a 429 past a limit.
Over MCP
No tool with this exact behaviour. search_companies with q searches names and descriptions.
GET /api/companiesSearches companies by criteria. This is the one query behind saved screens and the search_companies tool. With no criteria it lists every company.
Takes
q | Text. Matches companies whose name or description contains it, or whose slug is exactly it. |
category | One category key, matched exactly. The keys are in categories on GET /api/stats, for example ai_ml. |
country | Any 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. |
status | Matched exactly, for example operating or closed. |
min_headcount, max_headcount | Whole numbers, compared with current_headcount. A company with no headcount matches neither. |
founded_after, founded_before | A date, YYYY-MM-DD. |
raised_after | A date, YYYY-MM-DD. The last round was announced on or after it. |
raised_since | The same test with a time value: 90d, 6m, 1y, a date, or a timestamp. |
stage | The 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_raised | A number. The sum of the amounts of the rounds on file is at least this. |
investors | Investor slugs or exact names. Any one of them backed a round. |
added_since, added_before | A time value each: the company was added to StartupGraph on or after added_since, before added_before. |
hiring | true: the open-role count rose between the two readings /jobs reports. false: flat or fell. A company with no two readings matches neither. |
sort | name (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. |
direction | asc or desc. The default is asc, except with sort=added, which is newest first. |
per_page | 1 to 100. Default 25. |
page | Default 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
slug | In 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}/fundingThe funding rounds on file for one company, newest first.
Takes
slug | In 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}/peopleThe people on file for one company.
Takes
slug | In 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}/headcountThe headcount on file for one company, and how much of it is evidence.
Takes
slug | In 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}/jobsWeekly counts of open roles for one company, summed over its public Greenhouse, Lever and Ashby boards.
Takes
slug | In the path. |
discover | 1: 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}/factsThe 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
slug | In 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
slug | In 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/investorsFinds investors by name.
Takes
q | Text. 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
slug | In 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-roundsFunding rounds across every company, newest first.
Takes
since, until | A time value each. Rounds are compared by day: announced on or after since, on or before until. Empty is the same as left out. |
investors | Comma-separated slugs or exact names. Any one of them is in the round. |
round_type | One round type, for example series_c. Case, spaces and hyphens do not matter. |
per_page | 1 to 100. Default 50. |
page | Default 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-projectsThe 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}/starsWeekly readings of one project's GitHub stars, newest first.
Takes
owner, repo | In the path, as in repo on the list above. Case does not matter. |
weeks | How 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
GET /api/changesEvery 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
since | Required. A time value. With hours or a timestamp it compares to the second; with the other forms, by day. |
companies | Comma-separated slugs. Only changes to these companies. |
portfolio | The name or id of one of your portfolios. Only changes to its companies. |
limit | 1 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.
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/factsSends one fact about a company that is on file, with the page that states it.
Takes
company | The company's slug. |
field | One of funding_round, headcount, executive, or a profile field to correct: website, founded_date, description, status, category, name, city, state, country, linkedin_url. |
value | Depends 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_url | The page that states the fact. A URL, 2,048 characters at most. |
as_of | Optional. 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/companiesAdds a company that is not on file.
Takes
name | 2 to 200 characters. |
website | A URL. |
source_url | A page that shows the company exists: its own site, a press article, a registry. |
category | Optional. 100 characters at most. |
description | Optional. 2,000 characters at most. |
founded_date | Optional. YYYY-MM-DD. |
country | Optional. 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
GET /api/facts/{id}One fact and where its check stands.
Takes
id | In 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
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/screensThe 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
POST /api/screensSaves a search under a name.
Takes
name | 1 to 120 characters. Unique on the key. |
criteria | Required. An object with any of the search criteria; {} matches every company. |
description | Optional. 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
GET /api/screens/{ref}Runs a saved screen and returns the companies that match now.
Takes
ref | In the path. The screen's name or its id. |
per_page | 1 to 100. Default 25. |
page | Default 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
ref | In the path. Name or id. |
name, description, criteria | JSON 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
ref | In 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
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/portfoliosThe 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
POST /api/portfoliosCreates a portfolio.
Takes
name | 1 to 120 characters. Unique on the key. |
description | Optional. 1,000 characters at most. |
screen | Optional. 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
GET /api/portfolios/{ref}A portfolio's companies as they are now, and what changed on each since it was added.
Takes
ref | In 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
DELETE /api/portfolios/{ref}Deletes a portfolio and its entries.
Takes
ref | In 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
POST /api/portfolios/{ref}/companiesAdds a company to a portfolio and records its headcount, total raised, last round and status as they are now.
Takes
ref | In the path. Name or id. |
slug | JSON body. The company's slug. |
note | Optional. 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
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
ref | In the path. Name or id. |
slug | In 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
POST /api/rollupsFollows 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
name | 1 to 120 characters. Must be free as a screen name and as a portfolio name. |
investors | 1 to 50 investor slugs or exact names. Find them with GET /api/investors. |
description | Optional. |
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
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/statusThe 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/checkoutStarts 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
plan | JSON 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/portalReturns 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/overageAsks 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.
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/exportThe 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
date | In the path. YYYY-MM-DD, from GET /api/export. |
file | In 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.
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:
| Tool | Title | What it does to stored data |
|---|---|---|
search_companies | Search companies | reads only |
get_company | Company profile | reads only |
funding_rounds | Funding rounds for a company | reads only |
headcount_history | Headcount history | reads only |
open_jobs | Open roles over time | reads only |
oss_stars_history | GitHub stars history | reads only |
submit_fact | Contribute a fact | writes, removes nothing; fetches the source URL; a second identical call adds a second fact |
submit_company | Add a company | writes, removes nothing; fetches the source URL; a second identical call adds a second fact |
fact_status | Status of a contributed fact | reads only |
changes_since | Changes since a date | reads only |
coverage | What the dataset covers | reads only |
pulse | Companies, rounds and facts added lately | reads only |
recently_added | Companies added in a time window | reads only |
create_screen | Save a screen | writes, removes nothing |
list_screens | List saved screens | reads only |
run_screen | Run a saved screen | writes, removes nothing |
delete_screen | Delete a saved screen | writes, and removes something |
create_portfolio | Create a portfolio | writes, removes nothing |
search_investors | Search investors | reads only |
fund_rollup | Follow funds as a portfolio | writes, removes nothing |
list_portfolios | List portfolios | reads only |
add_to_portfolio | Add a company to a portfolio | writes, removes nothing |
remove_from_portfolio | Remove a company from a portfolio | writes, and removes something |
portfolio | Portfolio and what changed | reads only |
delete_portfolio | Delete a portfolio | writes, 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.
These fifteen only read. A client may run them without asking.
search_companies Search companies · read-onlySearches companies by criteria. The same query as GET /api/companies.
Takes
q | Text. Matches companies whose name or description contains it, or whose slug is exactly it. |
category | One category key, matched exactly. The keys are in categories on GET /api/stats, for example ai_ml. |
country | Any 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. |
status | Matched exactly, for example operating or closed. |
min_headcount, max_headcount | Whole numbers, compared with current_headcount. A company with no headcount matches neither. |
founded_after, founded_before | A date, YYYY-MM-DD. |
raised_after | A date, YYYY-MM-DD. The last round was announced on or after it. |
raised_since | The same test with a time value: 90d, 6m, 1y, a date, or a timestamp. |
stage | The 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_raised | A number. The sum of the amounts of the rounds on file is at least this. |
investors | Investor slugs or exact names. Any one of them backed a round. |
added_since, added_before | A time value each: the company was added to StartupGraph on or after added_since, before added_before. |
hiring | true: the open-role count rose between the two readings /jobs reports. false: flat or fell. A company with no two readings matches neither. |
sort | name (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. |
direction | asc or desc. The default is asc, except with sort=added, which is newest first. |
limit | 1 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
recently_added Companies added in a time window · read-onlyCompanies added to StartupGraph in a time window, newest first.
Takes
since | A time value. Default 7d. |
before | Optional end, a time value. |
limit | 1 to 100. Default 20. |
page | Default 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
pulse Companies, rounds and facts added lately · read-onlyHow much landed in the dataset lately, as counts.
Takes
since | Optional. 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-onlyEverything on one company.
Takes
slug | The 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
funding_rounds Funding rounds for a company · read-onlyThe funding rounds on file for one company, newest first.
Takes
slug | The 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
headcount_history Headcount history · read-onlyThe headcount on file for one company, and how much of it is evidence.
Takes
slug | The 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
open_jobs Open roles over time · read-onlyWeekly counts of open roles for one company.
Takes
slug | The 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
oss_stars_history GitHub stars history · read-onlyWeekly readings of one open source project's GitHub stars.
Takes
repo | owner/name, for example openclaw/openclaw. Only projects on file answer; the list is GET /api/oss-projects. |
weeks | 1 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
fact_status Status of a contributed fact · read-onlyOne fact and where its check stands.
Takes
fact_id | A 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-onlyEvery change to what the company tools serve, after a point in time, newest first.
Takes
since | Required. A time value. |
companies | Optional. A list of slugs. |
portfolio | Optional. The name or id of one of your portfolios. |
limit | 1 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
coverage What the dataset covers · read-onlyWhat 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
list_screens List saved screens · read-onlyThe 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
search_investors Search investors · read-onlyFinds investors by name. Use it before fund_rollup.
Takes
q | Required. Text the name or slug contains. |
limit | 1 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
list_portfolios List portfolios · read-onlyThe 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
portfolio Portfolio and what changed · read-onlyA 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
portfolio | Name 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
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 itSends 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_of | As 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
submit_company Add a company · writes, removes nothing, fetches the source URL you give itAdds a company that is not on file. Costs no tokens.
Takes
name, website, source_url, category, description, founded_date, country | As 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
create_screen Save a screen · writes, removes nothingSaves a search under a name.
Takes
name | 1 to 120 characters. |
criteria | Required. The search criteria as an object. |
description | Optional. |
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
run_screen Run a saved screen · writes, removes nothingRuns 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
screen | Name or id. |
limit | 1 to 100. Default 25. |
page | Default 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
delete_screen Delete a saved screen · writes, removes somethingDeletes a saved screen. A portfolio that followed it keeps its companies and stops updating (sync_stopped: "screen_deleted").
Takes
screen | Name 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
create_portfolio Create a portfolio · writes, removes nothingCreates 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
name | 1 to 120 characters. |
description | Optional. |
screen | Optional. 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
add_to_portfolio Add a company to a portfolio · writes, removes nothingAdds a company to a portfolio and records its headcount, total raised, last round and status as they are now.
Takes
portfolio | Name or id. |
slug | The company's slug. |
note | Optional. 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
remove_from_portfolio Remove a company from a portfolio · writes, removes somethingTakes a company out of a portfolio. In a portfolio that follows a screen it stays out; add_to_portfolio puts it back.
Takes
portfolio | Name or id. |
slug | The 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_portfolio Delete a portfolio · writes, removes somethingDeletes a portfolio and its entries.
Takes
portfolio | Name 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
fund_rollup Follow funds as a portfolio · writes, removes nothingFollows 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
name | 1 to 120 characters. |
investors | 1 to 50 slugs or exact names. Find them with search_investors. |
description | Optional. |
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
These are web pages, not API calls. An agent sends its person to them.
| /account | Sign 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/claim | Where 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. |
| /quickstart | From joining to the first call. |
| /roadmap | What is planned. The full page is for people on the waitlist. |