Someone pastes a contract address into a chat. You want one line that says whether anyone is actually building it. This is how, end to end, in one HTTP call and no account.
The one call
GET https://heyresearch.xyz/api/token/4663/{contractAddress}
No key. No account. No signup. 120 requests a minute per client, cached 60 seconds at the
edge, and readable from a browser as well as a server — access-control-allow-origin: * is set
and the preflight is answered.
Try it now, on a project that is shipping:
curl -s https://heyresearch.xyz/api/token/4663/0xebb4c5b97e4117e30ec82ce025e6f21dded05436
What comes back
{
"chainId": 4663,
"contractAddress": "0xebb4…5436",
"status": "published",
"project": {
"slug": "darkroute",
"name": "DarkRoute",
"symbol": "dark",
"url": "https://heyresearch.xyz/project/darkroute",
"activityStatus": "SHIPPING",
"activityLabel": "Shipping",
"activityHelp": "Shipped something meaningful in the last 7 days.",
"shipsLast30Days": 4,
"lastShipAt": "2026-09-16T02:38:59.000Z",
"lastShip": {
"title": "Active development: 3 commits in the last 90 days across 1 contributor",
"publishedAt": "2026-09-16T02:38:59.000Z",
"sourceUrl": "https://github.com/darkrouteRH/contracts"
},
"deployedAt": "2026-09-08T09:25:46.000Z",
"badgeUrl": "https://heyresearch.xyz/badge/darkroute.svg"
},
"scanUrl": "https://heyresearch.xyz/scan?address=0xebb4…5436",
"disclaimer": "…"
}
| Field | What it is |
|---|---|
activityStatus |
One of SHIPPING, ACTIVE, QUIET, DORMANT, RESUMED, UNKNOWN. Derived from what a project ships, never from price. |
activityLabel |
The same status in words. Use this rather than mapping the enum yourself — see below. |
shipsLast30Days |
Ships in the window the field names, counted exactly the way the project page counts them. Your number and the page a reader lands on cannot disagree. |
lastShip |
The most recent one, with sourceUrl — the actual commit, release or post it was read from. Link it. |
deployedAt |
When the contract went on chain, read from the block. Absent where HEY has not read one. |
badgeUrl |
A ready-made SVG: name, status, last ship. Drop it in the card if it fits. |
url |
The page behind all of it. Always render this. |
The three answers you must handle
| Case | HTTP | Do this |
|---|---|---|
status: "published" |
200 | Render the line. |
status: "unknown" |
200 | HEY has no published page. There is no project. Offer scanUrl. |
error |
400 | Not an address, or not this chain. Say nothing about the token. |
Why unknown is a 200 and not a 404. Most addresses pasted into a group chat are not
indexed projects. A 404 would make your ordinary case an error branch. So the shape is uniform,
you check one field, and scanUrl gives you somewhere to send the person: HEY reads the chain
live for an address it has never seen.
An address HEY holds but has not reviewed also answers unknown. An unreviewed launch record
is not a project, and this endpoint will not pretend otherwise.
The bot, in about thirty lines
const HEY = 'https://heyresearch.xyz';
async function heyLine(address) {
const r = await fetch(`${HEY}/api/token/4663/${address}`);
// 400 means the address is not one, or not this chain.
if (r.status === 400) return null;
if (!r.ok) return null; // stay quiet on a hiccup
const d = await r.json();
if (d.status !== 'published') {
return `HEY: not indexed yet · read the chain → ${d.scanUrl}`;
}
const p = d.project;
const ships = p.shipsLast30Days === 1 ? '1 ship' : `${p.shipsLast30Days} ships`;
const when = p.lastShipAt ? `, last ${daysAgo(p.lastShipAt)}` : '';
// p.activityLabel is HEY's own wording. Do not invent your own.
return `HEY: ${p.activityLabel} · ${ships} in 30d${when} → ${p.url}`;
}
function daysAgo(iso) {
const days = Math.floor((Date.now() - new Date(iso)) / 86400000);
return days === 0 ? 'today' : days === 1 ? 'yesterday' : `${days}d ago`;
}
Which renders as:
HEY: Shipping · 4 ships in 30d, last today → heyresearch.xyz/project/darkroute
HEY: not indexed yet · read the chain → heyresearch.xyz/scan?address=0x7655…d78a
Four things not to do
Do not translate the status yourself. Use activityLabel. Left to themselves, integrators
turn DORMANT into dead and UNKNOWN into suspicious. Dormant means HEY has observed no
public activity for a long time. It is not the same as abandoned, and unknown is a gap in our
knowledge, not a fault of the project. The labels ship in the payload so nobody has to guess.
Do not present it as a risk reading. HEY gives no score, no grade and no safe/unsafe
verdict, anywhere, by design — the moment there is one number on a card, people read it as a
rating. If you want a risk signal, put one from a tool that actually does that work beside
our line. Honeypot: clean · HEY: Shipping, 4 ships in 30d is a better card than either of us
alone, and neither is pretending to be the other.
Do not drop the link. Whatever you render, carry url. Someone who sees a HEY line should
be one tap from the evidence behind it. This is the one condition of use.
Do not poll POST /api/scan in a hot path. That second, heavier endpoint reads the chain
live for an address nobody has indexed. It allows ten requests an hour, is never cached,
and spends a budget HEY's scheduled indexing needs first. Link people to scanUrl instead —
that is a page, and it costs you nothing.
Limits, caching and failure
- 120 requests a minute per client. Over that,
429with aretry-afterin seconds. - Answers are cached 60 seconds and served stale for five minutes while they refresh. Caching on your side beyond that buys nothing; the catalogue moves on HEY's worker schedule, not per request.
- Dates are ISO 8601, UTC, always.
- Absent means unknown. A field HEY has no answer for is left out — never sent as
nullor0. Do not fill a gap with a zero: a missingdeployedAtmeans HEY has not read one, not that the contract was deployed at the epoch. - If HEY is down, say nothing rather than guessing. A bot that omits a line is fine; one that invents one is not.
If you want more
The same public API carries the rest of what HEY knows, on the same terms — no key, 120 a minute. See the public read API for every endpoint, field and limit, and the MCP server to give an assistant the same index, read-only.
What HEY will never send you
Worth stating plainly, so you do not design around something that is not coming.
- No wallet analytics, no PnL, no smart-money labels, no holder lists read as a claim about people. HEY does not build it. The one thing it does keep is a daily snapshot of a single token's largest balances, which draws the distribution map on that token's market page — no API payload carries it, and nothing in it reaches a status, a score or an ordering.
- No risk score, no safety grade, no verdict.
- No paid placement in any ordering. Nothing anyone pays HEY changes a status, a rank or a figure.
- No claim that a project which keeps building will be worth more. Those are two different things and HEY keeps them apart.
What is left is narrow on purpose: who is building, what they shipped, when, and where HEY read it.