Documentation · page 6 of 6

Screens, portfolios and roll-ups

Saving a search, watching a set of companies, and following a fund's deals · written from the code and its tests

Three things let an agent keep a question alive between conversations. A screen is a company search saved under a name. A portfolio is a list of companies that remembers what each looked like when you added it, so it can tell you what moved. A roll-up is the shortcut for "I am invested in these funds": one call makes a screen on those investors and a portfolio that follows it. This page says what each one is, how to make and read them over REST and over MCP, what a call costs, and where they stop short.

You need a key first. The quickstart gets you one. Every example reads the key from STARTUPGRAPH_KEY. Every request and response below is a shape read from the code and its tests, not output from a real call: these calls write to your key, and the values in angle brackets are filled in by the answer. The reference lists the same endpoints with every field.

What each one is

ThingWhat it holdsWhat it is for
ScreenA name, an optional description, and search criteria.Asking the same question again later. It does not hold companies, only the question.
PortfolioA list of companies, each with a note and the headcount, total raised, last round and status it had when it was added.Seeing what changed. A portfolio either is filled by hand, or follows a screen and fills itself.
Roll-upA screen and a portfolio with the same name, made in one call.Following every company that named funds have backed, including deals recorded later.

All three belong to the key that made them. Another key cannot see or run them: it gets a 404. Names are unique on a key, so a name you used for a screen cannot be used for another screen. A screen and a portfolio may share a name. If a key is replaced or revoked and you make a new one from your account, its screens and portfolios are kept.

Screens

criteria takes the same filters as GET /api/companies: for example category, country, min_headcount, stage, raised_since, investors and hiring, plus sort and direction. A value the search would refuse is refused here too, with a 422. {} matches every company. The stored criteria come back with sort and direction filled in.

Create a screen

The search is run once when you save it, to fill result_count. REST POST /api/screens, MCP create_screen.

curl -s -X POST -H "Authorization: Bearer $STARTUPGRAPH_KEY" -H "content-type: application/json" "https://startupgraph.dev/api/screens" \
  -d '{"name":"ai-over-100","description":"AI companies with 100+ people","criteria":{"category":"ai_ml","min_headcount":100}}'
Response shape, status 201
{"data": {"id": <number>, "name": "ai-over-100", "description": "<text>" | null,
          "criteria": {<the criteria, with sort and direction filled in>},
          "result_count": <number>, "last_run_at": "<YYYY-MM-DD HH:MM:SS>", "created_at": "…", "updated_at": "…"}}

Over MCP the answer is the same screen under screen: create_screen {"name":"ai-over-100","criteria":{"category":"ai_ml","min_headcount":100}} returns {"screen": {…}}. A name already used on the key is a 409 over REST and {"error":"duplicate_name"} over MCP.

List your screens

REST GET /api/screens, MCP list_screens. Each screen is as above, ordered by name. result_count and last_run_at are from the last time the screen was run, not live.

curl -s -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/screens"
Response shape
{"data": [<screen>, …]}

Run a screen

REST GET /api/screens/{name or id}, with per_page (1 to 100, default 25) and page. MCP run_screen, with limit in place of per_page. It returns the companies that match now, and stores the new count and time on the screen. That makes a run a write, and MCP marks run_screen as not read-only for that reason.

curl -s -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/screens/ai-over-100?per_page=10"
Response shape
{"screen": {<the screen, with result_count and last_run_at just refreshed>},
 "data": [<search rows, as GET /api/companies returns them>],
 "meta": {"page": 1, "per_page": 10, "total": <number>}}

A name or id that is not on your key is a 404 over REST, and {"error":"not_found","screen":"<ref>"} over MCP.

Change or delete a screen

REST PATCH /api/screens/{ref} takes any of name, description and criteria, and a new criteria replaces the old one whole. It answers with the screen, and result_count stays as it was until the next run. There is no MCP tool for this. Over MCP, delete the screen and create it again. DELETE /api/screens/{ref} (MCP delete_screen) answers 204 with no body, and what that does to a portfolio that followed it is under known limits.

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}}'

Portfolios

Create one

REST POST /api/portfolios, MCP create_portfolio. Without screen it starts empty. With screen (the name or id of one of your screens) it follows that screen: the call fills it with the screen's matches, and an hourly job adds new matches after that.

curl -s -X POST -H "Authorization: Bearer $STARTUPGRAPH_KEY" -H "content-type: application/json" "https://startupgraph.dev/api/portfolios" \
  -d '{"name":"ai-watch","screen":"ai-over-100"}'
Response shape, status 201
{"data": {"id": <number>, "api_key_id": <number>, "name": "ai-watch", "description": null, "screen_id": <number> | null,
          "created_at": "…", "updated_at": "…", "synced_at": "<YYYY-MM-DD HH:MM:SS>" | null,
          "sync_attempted_at": "…" | null, "sync_stopped": null,
          "companies": <how many the call added>}}

synced_at is when the job last finished a pass with nothing left to add, among the matches it looks at (at most 10,000 of a screen). It is null for a portfolio filled by hand, and for one that is still filling. Over MCP the same portfolio is under portfolio, and a screen that is not on your key is {"error":"screen_not_found"} (404 over REST).

Add and remove companies

REST POST /api/portfolios/{ref}/companies with a slug and an optional note, MCP add_to_portfolio. This stores the company's headcount, total raised, last round date and status as they are now. Adding a company already in the list keeps its first snapshot and updates the note. REST DELETE /api/portfolios/{ref}/companies/{slug}, MCP remove_from_portfolio.

curl -s -X POST -H "Authorization: Bearer $STARTUPGRAPH_KEY" -H "content-type: application/json" "https://startupgraph.dev/api/portfolios/ai-watch/companies" \
  -d '{"slug":"cursor","note":"met at the offsite"}'
Response shape, status 201
{"data": {<a search row>, "note": "met at the offsite", "added_at": "<YYYY-MM-DD HH:MM:SS>"}}

A slug we do not have is a 404 saying so. Search for it first with GET /api/search.

Read one, and see what moved

REST GET /api/portfolios/{ref}, MCP portfolio. It returns every company with its state now, and for each, what changed since it was added. A read changes nothing: it never adds a company, even to a portfolio that follows a screen.

curl -s -H "Authorization: Bearer $STARTUPGRAPH_KEY" "https://startupgraph.dev/api/portfolios/ai-watch"
Response shape
{"data": {
  "portfolio": {"id": <number>, "name": "ai-watch", "description": "<text>" | null,
                "screen": {"id": <number>, "name": "<name>", "criteria": {…}} | null,
                "created_at": "…", "updated_at": "…", "synced_at": "…" | null,
                "sync_stopped": "screen_deleted" | null, "sync_note": "<why>"   only when stopped},
  "summary": {"companies": <n>, "added_this_sync": 0, "changed": <n>, "total_raised": <n>, "headcount": <n>},
  "companies": [{<a search row>, "note": "<text>" | null, "added_at": "…", "source": "<how it was added>",
                 "since_added": {"headcount_delta": <n> | null, "rounds": <n>,
                                 "changes": {"<field>": {"from": <then>, "to": <now>}}}}]}}

changes lists only the four tracked fields that differ from the snapshot: current_headcount, total_raised, last_fundraise and status. rounds counts rounds announced after the day the company was added. summary.changed is how many companies have at least one change. Over MCP the answer is the same object without the data wrapper. To read facts that changed on a portfolio's companies, GET /api/changes and MCP changes_since take a portfolio.

List portfolios with GET /api/portfolios (MCP list_portfolios), each with a companies count. Delete one with DELETE /api/portfolios/{ref} (MCP delete_portfolio); that removes its list and leaves its screen alone.

Roll-ups

You name funds, and get a portfolio of every company they backed. First find the investors: GET /api/investors?q=thrive or MCP search_investors returns each name, slug, type, website and how many companies it has backed. Then POST /api/rollups (MCP fund_rollup) with name, investors (slugs or exact names, 1 to 49) and an optional description.

curl -s -X POST -H "Authorization: Bearer $STARTUPGRAPH_KEY" -H "content-type: application/json" "https://startupgraph.dev/api/rollups" \
  -d '{"name":"my-lp-book","investors":["thrive-capital","Andreessen Horowitz"]}'
Response shape, status 201
{"data": {<the portfolio read shape above>}}

The call makes a screen named my-lp-book with investors set and the newest round first, makes a portfolio of the same name that follows it, and fills it. summary.added_this_sync is the number of companies the call added. Without a description, the portfolio's is "Companies backed by" and the investors' names. Over MCP the answer is under rollup.

An investor we do not know is a 422 with the names we could not find in unknown, and nothing is made. Over MCP that is {"error":"unknown_investors","unknown":[…]}. A name already used is a 409 over REST and {"error":"duplicate_name"} over MCP.

What they cost

Every call costs tokens by the size of what it returns: about one token for every 4 bytes of the response, never fewer than 10. The same rule holds over REST and MCP. So the price of each call here is the size of its answer:

A REST response says what it cost in X-Tokens-Charged. What your plan allows, and what happens when the allowance is spent, is under pricing and in the reference.

Known limits

What to read next

The reference for every field of the search criteria and the exact errors, the coverage page for what the data holds, and the contributing page if a portfolio shows a gap you can fill.