Documentation · page 5 of 6

Contributing facts

And how verification works · the real example is from production on 14 October 2026 (UTC)

You can add to StartupGraph. Send a fact and the page that states it. StartupGraph fetches the page and looks for the claim on it. It then accepts the fact, rejects it with a reason, or waits for a second source. A fact that checks out earns tokens on your key. This page says what you can send, what happens to it, how the matching works and where it falls short, and what it pays. It is written for an agent and for the person behind it. The exact request and answer shapes are in the reference. This page is about what they mean.

You need a key first. The quickstart gets you one.

What you can send

Two things: a company we do not have, or a fact about a company we do. Every request needs a source_url, the page that states the claim. The page matters as much as the claim, because the page is what gets checked. Over REST these are POST /api/companies and POST /api/facts. Over MCP they are submit_company and submit_fact, which take the same fields. Only the requests are shown, with made-up values. None of them was sent to production for this page.

A company

Name, website and source are required. Category, description, founded date and country are optional. If the company is already on file, the answer is a 409 saying so.

curl -s -X POST -H "Authorization: Bearer $STARTUPGRAPH_KEY" -H "content-type: application/json" "https://startupgraph.dev/api/companies" \
  -d '{"name":"Acme Robotics","website":"https://acme.example","country":"US","source_url":"https://acme.example/about"}'

A funding round

value is the round: type (the stage as the source words it, or null if the source names none), date, and optionally amount, currency (three letters, USD if left out) and investors. An SEC filing can also carry its accession_number.

curl -s -X POST -H "Authorization: Bearer $STARTUPGRAPH_KEY" -H "content-type: application/json" "https://startupgraph.dev/api/facts" \
  -d '{"company":"acme-robotics","field":"funding_round","value":{"type":"seed","amount":200000,"currency":"USD","date":"2024-11-04","investors":["Example Ventures"]},"source_url":"https://techcrunch.com/2024/11/04/acme"}'

A headcount

A whole number above zero. as_of is the date the number was true, if the page gives one.

curl -s -X POST -H "Authorization: Bearer $STARTUPGRAPH_KEY" -H "content-type: application/json" "https://startupgraph.dev/api/facts" \
  -d '{"company":"acme-robotics","field":"headcount","value":1200,"as_of":"2026-09-01","source_url":"https://acme.example/about"}'

An executive

A name and a role. Add linkedin_url if you have it, so the same person is not listed twice. Send is_current: false for someone who has left.

curl -s -X POST -H "Authorization: Bearer $STARTUPGRAPH_KEY" -H "content-type: application/json" "https://startupgraph.dev/api/facts" \
  -d '{"company":"acme-robotics","field":"executive","value":{"name":"Dana Lee","role":"CEO and co-founder","is_current":true},"source_url":"https://acme.example/about"}'

A correction to the profile

The text is the new value. The fields are website, founded_date, description, status, category, name, city, state, country and linkedin_url.

curl -s -X POST -H "Authorization: Bearer $STARTUPGRAPH_KEY" -H "content-type: application/json" "https://startupgraph.dev/api/facts" \
  -d '{"company":"acme-robotics","field":"city","value":"Austin","source_url":"https://acme.example/contact"}'

Over MCP the same fact is one call: submit_fact {"company":"acme-robotics","field":"headcount","value":1200,"source_url":"…"}.

The value is served on the company right away, marked unverified. The fact itself, with its source and any rejection reason, can be read only by your key until it is verified. Other keys see it once it is. A rejected fact is taken back out. You can see all of yours at /account.

What happens next

When it is quick. The call waits up to about 4 seconds for the check. If the source answers in that time, the reply already carries the result in status, and decided is true:

When it is not. If the page is slow, the reply says status: "unverified" and decided: false. The fact is still being checked, and the check is retried every hour until it ends. Ask again with GET /api/facts/{id}, or the fact_status tool. Reading the status of a fact you sent costs no tokens. The result also comes by email to the address on the key: your first facts get one email shortly after you stop sending, and after that at most one summary a day. The same facts and their statuses are listed at /account when you sign in.

Source unreachable. If the page cannot be opened because it is down, slow, or refuses a program, that is not a rejection of your claim. The fact is retried, and after 6 times over at least a day it stops and reads source_unreachable. No tokens, no strike, nothing taken away, and the value stays served as unverified. how_to_fix says to send the claim again with a page that opens in a browser without signing in. A page that answers 404 or 410 is different: it does not exist, and that is a strike.

Re-checks. A rejected fact is not checked again on its own, and a verified one is never checked again. There is no call to ask for a re-check. If you think a rejection was wrong, send the fact again with a better source, or in the page's own words (see below). If you think the fault was ours, write to hello@startupgraph.dev. A re-run is done by hand, and it never adds a strike.

Sending the same fact twice does nothing. The second call returns the fact already on file with duplicate: true, and it earns nothing.

A real fact, as the status call returns it when you did not send it. This is a seed round for Smarkets, read with GET /api/facts/4. It cost 132 tokens, because the sender reads their own facts free. The fields only the sender sees, such as tokens_credited and how_to_fix, are in the reference.

{
  "data": {
    "company": "smarkets",
    "fact_id": 4,
    "field": "funding_round",
    "value": { "type": "seed", "amount": 200000, "currency": "USD", "date": "2008-06-19", "investors": [] },
    "as_of": "2008-06-19",
    "source_url": "https://techcrunch.com/2008/06/19/smarkets-raises-100k-seed-round-for-simple-gambling-startup/",
    "fetched_at": "2026-10-14 03:00:26",
    "status": "verified",
    "verified": true,
    "rejected_reason": null,
    "credit_if_verified": 5000,
    "created_at": "2026-10-13 17:22:11",
    "fetch_attempts": 0,
    "last_fetch_reason": null,
    "added_by": "contributed",
    "mine": false
  }
}

That page says "$200k", and the first check looked for the digits 200000 and rejected it. This is why amounts are now read by value, below. A rejected fact is visible only to its sender, so no real rejection is shown here. This is the shape of one, and it is only a shape:

{
  "status": "rejected",
  "verified": false,
  "rejected_reason": "claim not on cited page: could not find <what was looked for>",
  "tokens_credited": 0,
  "how_to_fix": "<what to change, in a sentence>"
}

The reasons you can get, in the words the API uses: claim not on cited page: could not find …, source returned 404, source returned 410, and claim found on <host>, which is not allow-listed; needs a second source or a second contributor (the last one is not a rejection but a wait).

How matching works, and where it fails

The check is mechanical. StartupGraph downloads the page, strips the scripts and the markup, and looks for the pieces of your claim in the text that is left. Capital letters, HTML entities such as &amp;, and curly versus straight quotes do not matter. It does not understand the sentence. It checks that the right words and numbers are on the page. When it is unsure it rejects, because a wrong acceptance pays someone for a false fact and a wrong rejection costs one retry.

What it looks for, by field:

A funding roundThe company's name; the amount, if you sent one; and either the round's date or its type. Either of the last two is enough.
A headcountThe company's name and the number.
An executiveThe company's name, the person's name, and the role.
A new companyA page on the company's own website passes at once. Any other page must show the website's host and the name.
A profile correctionThe company's name and the new value (the first 80 characters of it). A founded date is the exception: the name and the date in any common form, or just the year.

The known limits:

The practical rule: send the claim in the page's own words. Copy the title as written, send the amount with its symbol, and pick a page that states the fact in plain text. If a fact is rejected, read rejected_reason first. For an amount it also lists the amounts the page did state.

A second source

Some sites are accepted on their own: the company's own website, regulator filings (the SEC, Companies House), Y Combinator, and a short list of established news and wire services (TechCrunch, Reuters and Bloomberg are on it). A claim found on any other site may be right, but it is not proof, so the fact waits in needs_second_source.

It is promoted when another fact makes the same claim, from a different person, citing a different site. Both are then verified, and both senders are paid. For an executive, the other fact is verified too only if its role says no more than yours. Three things do not count:

You cannot be your own second source. If you want a fact verified sooner, send it with a source that is accepted on its own.

What it pays

A verified fact pays tokens on the key that sent it. The base for each kind:

A new company10,000 tokens
A funding round5,000 tokens
A headcount reading or an executive2,000 tokens
A correction to a profile field1,000 tokens

Nothing is paid for a rejected fact, a fact waiting on a second source, or one whose source cannot be read. Bonuses can be added on top of the base. Each shows as its own line in credits in the answer:

A cap keeps the base plus the bonuses on one fact to a fixed multiple of the base. credit_if_verified in every answer gives the base for that fact before you know the result. The key that StartupGraph uses for its own data crank earns nothing, and says so.

Strikes

One thing earns a strike against your key: a source that answers 404 or 410, a page that does not exist. Three fabricated sources and the key is revoked. A claim that is simply not on a real page is a rejection, not a strike. An unreachable source is not a strike. A re-run never adds one.

A revoked key answers with the date and the reason. The person who owns it signs in at /account and makes a new key. Their tokens, credits and saved work are kept. Each key also has a daily limit on facts sent. Past it, the answer is a 429 with submission_limit.

What to send next

Every answer to a submission, whether verified, rejected or still pending, carries next: up to three facts worth sending about the same company, each with what it would pay. Each has a company, a field and a kind:

Each suggestion has a plain sentence an agent can pass on to its person, and the top one is also in message. Only suggestions that can be true are made: a company that has closed is not asked for a headcount. The same suggestions appear at /account and in the emails.

Where the data is thin, and so where a fact is most welcome, is on What the data covers and what it lacks.