API
A launch record, as JSON
Every record on this site is also a JSON document, served to API keys. Send the key as Authorization: Bearer <key>. Plans and enterprise agreements. /api/v1/status stays open, and so does every token page on this site.
There is one thing you have to get right, and it is the next section.
Absence of a record is not a clean record
The field to branch on is finding.class, which is one of FINDINGS, NOTE, NO_MARKERS or UNKNOWN. These name classes of entry on a record, not grades of risk and not advice. We record what a launch was; what that is worth is yours to decide. Read them as:
| FINDINGS | a finding: something we recorded about the launch, such as the creator holding most of the supply, or no outside wallet buying its curve |
| NOTE | a note: recorded, weaker, and often about the present rather than the launch |
| UNKNOWN | not established. Not a mild warning: an absence. We could not settle the question |
| NO_MARKERS | we checked this launch against every marker we record and found none. A statement about what we checked, not an endorsement of the token |
cleanAtBirth is a convenience and it has three states, not two:
| true | we watched this launch (or rebuilt its complete history) and checked it against every marker we record; none is present |
| false | we watched it and at least one marker is present, or one criterion is unmet: see finding and flags for which. Not an accusation: a token whose pool we could not read just now is false and UNKNOWN, not FINDINGS. |
| null | we do not know. We did not observe the launch and have not rebuilt it. Do not render this as clean, safe, or "no issues found." |
Once a token's float has been spread across wallets, a launch that was assembled and one that was not look the same to present-tense inspection, and that is the entire reason this archive exists. A null means the evidence is gone, which is the opposite of reassuring. Every refusal and every error we return also carries finding.class = "UNKNOWN", so code that reads only that field is safe even when it ignores the HTTP status.
Endpoints
| GET /api/v1/token/{mint} | one launch record: what the creator took in the first block, how many outside wallets bought its curve, how it graduated, who took it, and the pool right now |
| GET /api/v1/wallet/{address} | a wallet's priors: every bonding curve it has bought outright in this archive, and what it did with the tokens afterwards. A wallet we have never seen returns inArchive: false and nulls, not zeros, because "we hold nothing on it" is not "it has done nothing". In buyouts, sameBatchAsLaunch: true means the buy arrived in the same batch of chain events as the launch itself, hoursAfterLaunch is then null rather than 0, because our timestamps cannot resolve it further. Don't render it as zero. |
| GET /api/v1/status | what the archive holds and what it was awake for |
| GET /data/record.db | with a key, the current record as one SQLite file. For bulk work, take this instead of walking the API. |
| GET /data/documents.ndjson.gz | with a key: every launch document we hold, as captured, with its sha256. Single documents stay public at /d/{mint}. |
curl -H "Authorization: Bearer $COT_KEY" https://chainoftitle.org/api/v1/token/<mint>
Tokens we have never seen
Coverage begins 2026-09-02 12:29 UTC. Ask for an older launch and we reconstruct it from the bonding curve's complete transaction history: thousands of archival RPC reads, which is a background job, not a request. You get 202 with finding.class = "UNKNOWN" and a rebuild object; poll the same URL. A finished record is permanent, so the second call is usually the last one you ever make for that mint.
Reads are metered per key per UTC day, by plan. Rebuilds cost real money, so each caller can start 5 an hour and the service has a daily ceiling. When that is reached you get 503 rebuild_budget_exhausted: the archive is unaffected, only new reconstruction is paused. Bulk historical reconstruction is available under a data agreement: hello@chainoftitle.org.
Statuses
| 200 | a record. It may still be an UNKNOWN one. |
| 202 | accepted; a rebuild is queued or running. Retry-After is set. |
| 401 missing_key / invalid_key / revoked_key | no usable API key on the request |
| 429 quota_exceeded | the key's daily quota is used; it resets at 00:00 UTC |
| 400 not_an_address | not base58, or not 32–44 characters |
| 404 not_a_pump_launch | no pump.fun bonding curve exists for this address. A finding, not a failure. |
| 404 rebuild_failed | we tried to read the chain and could not. Our failure, not a finding. It says nothing about the token. |
| 429 rate_limited | too many rebuilds started from one address this hour |
| 503 rebuild_budget_exhausted / busy | we cannot pay for or keep up with more rebuilds right now |
Getting a key
$299 a month10,000 requests a day, the JSON launch record for any mint we hold, reconstruction of launches we did not watch, and the current record database as one file. Checkout is Stripe's; the key is emailed once and stored only as a hash. Sustained volume past 10,000 requests a day, the creation feed, or bulk historical reconstruction is a data agreement instead.
Terms
| Cost | by plan, on the pricing page. Enterprise data agreements, an SLA, the live feed and bulk history are invoiced. |
| Licence | keyed responses are licensed for use in your own product and may not be redistributed in bulk. |
| CORS | open to every origin. Keep the key on your server: a key in browser code is readable by anyone. |
| Caching | a settled record is immutable and served max-age=3600, stale-while-revalidate=86400. Anything unsettled is no-store. |
| Volume | sustained use past the daily quota, or the creation feed itself, is an enterprise agreement - hello@chainoftitle.org. |
| Stability | fields are added, never repurposed. A breaking change gets a new version prefix and the old one keeps answering. |
| What it is not | not a price feed, not a signal, not advice. A clean record means none of the markers we record was present at launch, and says nothing about what it will do. |
If you ship this in front of users and find a record you think is wrong, tell us: a wrong entry on a record costs more than a missing one. hello@chainoftitle.org
What a response looks like
A real response from the current record - the clean-launch report's subject, refreshed as the record rebuilds. Keyed responses carry a license field beside this.
{
"apiVersion": "v1",
"coverage": {
"from": {
"ms": 1788352172581,
"iso": "2026-09-02T12:29:32.581Z"
},
"downtimeMinutes": 46,
"note": "Launches outside these windows were not observed. We rebuild them from chain history on request; until that succeeds their provenance is unknown, not clean."
},
"asOf": {
"ms": 1789752644449,
"iso": "2026-09-18T17:30:44.449Z"
},
"generatedAt": {
"ms": 1789763071269,
"iso": "2026-09-18T20:24:31.269Z"
},
"source": "https://chainoftitle.org/t/5NP3pTMgbvioJaki2Sdxm7DiaA2tt3BFkqTJyYNF5EwQ.html",
"license": "Chain of Title data licence (https://chainoftitle.org/terms.html)",
"mint": "5NP3pTMgbvioJaki2Sdxm7DiaA2tt3BFkqTJyYNF5EwQ",
"symbol": "PurpDrop",
"name": "PurpDrop",
"venue": "pumpfun",
"finding": {
"class": "NO_MARKERS",
"label": "Checked, no markers found",
"why": "We watched this launch and checked it against every marker we record. None of them is present: the creator kept 1.7%, 428 outside wallets bought on the curve, the curve took longer than a minute to fill, and no single wallet took it. A statement about what we checked, not a judgement about the token."
},
"cleanAtBirth": true,
"observedAtLaunch": true,
"origin": "observed",
"launch": {
"createdAt": {
"ms": 1788938126027,
"iso": "2026-09-09T07:15:26.027Z"
},
"creator": "DM5MVrMMr3vCacySqn5szpqDJMaPQCnSanoLAh7G7gLR",
"creatorSupplyPct": 1.737651816691,
"creatorSold": false,
"curveBuyers": 428,
"buyersFirst30s": 4,
"bundledBuyers": 0,
"graduated": true,
"graduationObserved": true,
"graduationCheck": null,
"graduationConfirmedBy": "pool",
"graduatedAt": {
"ms": 1788940589042,
"iso": "2026-09-09T07:56:29.042Z"
},
"secondsToGraduate": 2463,
"transaction": {
"signature": "4oMx1fn65LhFchEcDTPiY9Y3X3R74VVhkvnXHrGPosiXQmaic7pmVvVMkkw1bFKacK7cSfEg8z4C5ztXNfcxmetF",
"slot": 445551606
}
},
"curveBuyout": null,
"pool": null,
"funder": {
"sharedCohort": null
},
"flags": []
}