API reference · beta

Assay API

One live endpoint today: launch risk for a Solana token, read from mainnet on every call. No key, no account, free in beta. The endpoints after it are planned and marked so. Written out from openapi.yaml.

  • OpenAPI 3.1
  • https://www.assayonsol.io/api
  • Free in beta

Overview

Base URL https://www.assayonsol.io/api. Every request is a plain GET and every answer is JSON. Copy this and run it:

curl · works today, no key

curl -s "https://www.assayonsol.io/api/launch-risk?mint=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"

That mint is USDC: you get its holders and authorities, and a note instead of a verdict, because a stablecoin is not a launch token. Paste any other mint to get a verdict.

Endpoints
EndpointReturnsStatus
GET /api/launch-risk?mint=Launch risk for a token: top holders, wallets linked by direct transfers, authorities, flags, a verdictLive · free in beta
POST /api/assaysAssay an eligibility listPlanned
GET /api/assays/{id}Clusters with evidence for an assayPlanned
GET /api/appeals/{id}Status and reason for an appealPlanned

Beta: calls are free; the x402 flow is the planned paid tier and is not enforced.

The live endpoint

GET/api/launch-risk?mint={mint}

Live · free in beta

Launch risk for one token, read from Solana mainnet when you call: who holds it, whether the top holders got their tokens straight from one wallet, whether the issuer can still mint or freeze, and a verdict.

Query parameters

Query parameters
NameTypeDescription
mintrequiredstringToken mint address, base58. A link that contains it works too, for example a pump.fun or Solscan token page.

Example

curl -s "https://www.assayonsol.io/api/launch-risk?mint=$MINT"

Responses

Responses
StatusDescription
200A LaunchRisk object. Cache-Control: public, max-age=300.
400Not a mint address: missing, wrong length, not base58, or a transaction signature.
404Not a token: no account on mainnet, a wallet, a program, a pool address or an NFT. A token account answers with mint_hint, the mint it holds.
429Too many checks from one address. Wait for Retry-After.
502The mint itself couldn’t be read from the Solana data provider. Try again in a moment.

Every error is {"error": "…plain words…"}, with usage on a 400. When a later read runs out of time (the largest holders, the holder count, the transfers), the answer is still a 200: those fields are null and coverage says what wasn’t read.

LaunchRisk

LaunchRisk fields
FieldTypeDescription
mint, name, symbolstringThe token. Name and symbol come from its metadata and can be null.
decimals, supplyinteger, stringSupply in whole tokens, as an exact decimal string.
token_programspl-token · spl-token-2022Which token program the mint uses.
holders, holders_more, holders_displayinteger · null, boolean, string · nullWallets with a balance, counted on one page of up to 1,000 token accounts. Show holders_display: the count, or “1,000+” when the page came back (near) full or the count timed out on a large token. The count is best effort: when it doesn’t come back, holders is null and coverage.holders_note says why.
top1_share, top10_sharenumber, 0 to 1Share of supply held by the largest holder, and by the ten largest together. Pools, the burn address and program accounts are set apart first.
linked_sharenumber, 0 to 1 · nullOf the tokens the top 20 holders hold, the share held by the largest group of them linked by direct transfers. null when the transfers couldn’t be read.
linked_supply_sharenumber · nullThe same group’s holdings as a share of total supply.
linked_sourcestring · nullThe wallet that sent tokens to most of the group.
linked_walletsstring[]The wallets in that group, largest first.
top_holders[]object[]Up to 20 holder wallets: wallet, share, from (the wallet that sent it the most tokens, if any) and linked.
pools[]object[]What was set apart: label (for example “PumpSwap pool”, “Burn address”), kind (pool · burn · mint · program), owner, share.
mint_authority, freeze_authoritybooleanWhether the issuer can still mint more, or freeze holders’ tokens. The addresses are in authorities.
verdictlow · medium · high · nullBy linked_share: low below 0.10, medium from 0.10 to 0.25, high above 0.25. An active freeze authority or a permanent delegate raises it to at least medium. null for tokens that aren’t launch tokens (USDC, USDT, wrapped SOL; see note) and when the transfers couldn’t be read.
summary, flags[]string, string[]The answer in one sentence, and what stands out, in plain words: “Freeze authority is active — the issuer can freeze your tokens”, “Mint authority is active — supply can be inflated”, “Top holder owns 31%”.
coverageobjectWhat was read to link holders: transactions, from, to, complete, holders, traced and a note. When complete is false, the linked share is a lower bound.
checked_at, explorerstringWhen mainnet was read (answers are cached up to 5 minutes), and the token on Solscan.

Response · 200 · shape

From the function’s offline test fixture, shortened: the addresses are made up, the fields are exactly what the API returns.

{
  "mint": "2rrVfjh9pDocit4YxxAkPhV5UYv86mW8rYrgzCk3cQRY",
  "name": "Kettle Test",
  "symbol": "KTT",
  "decimals": 6,
  "supply": "1000000000",
  "token_program": "spl-token",
  "holders": 37,
  "holders_more": false,
  "holders_display": "37",
  "top1_share": 0.06,
  "top10_share": 0.257,
  "linked_share": 0.7198,
  "linked_supply_share": 0.185,
  "linked_source": "HsSCaYFSZ4RoRr3JJ7myV9gAWcRMVejjfcfEwEx8QeUk",
  "linked_wallets": ["HsSCaYFSZ4RoRr3JJ7myV9gAWcRMVejjfcfEwEx8QeUk", "FBJTFuRUeCbW2G4jTZ94mMxpf4xwaUHQxmQ9LgLriMGG", "…"],
  "top_holders": [{ "wallet": "HsSC…QeUk", "share": 0.06, "from": null, "linked": true }, "…"],
  "pools": [{ "owner": "5zXH…LMUu", "label": "Pump.fun bonding curve", "kind": "pool", "share": 0.4 }, "…"],
  "mint_authority": false,
  "freeze_authority": false,
  "authorities": { "mint": null, "freeze": null },
  "verdict": "high",
  "summary": "5 of the top 10 holders are linked by direct transfers (HsSC…QeUk sent tokens to 3 of them). Together they hold 72% of the top holders’ tokens, 19% of supply.",
  "flags": ["5 top holders are linked by direct transfers — HsSC…QeUk sent tokens to 3 of them"],
  "coverage": { "read": true, "order": "oldest first", "transactions": 8, "complete": true, "holders": 10, "traced": 9, "note": "…" },
  "checked_at": "2026-10-14T20:39:19.280Z",
  "explorer": "https://solscan.io/token/2rrVfjh9pDocit4YxxAkPhV5UYv86mW8rYrgzCk3cQRY"
}

How the check works

Every call reads Solana mainnet through Helius, inside an 8-second budget:

  • The mint account: supply, decimals, mint and freeze authority, and Token-2022 extensions such as a permanent delegate, a transfer fee, a transfer hook or a pause switch.
  • The 20 largest token accounts and their owners. Accounts owned by a known pool or curve (Pump.fun, PumpSwap, Raydium, Orca, Meteora and others), by the burn address, by the mint itself or by any other program are set apart from holders.
  • The holder count, from the first 1,000 token accounts.
  • The token’s first 100 transactions, oldest first: the launch window. A holder is linked to a wallet when that wallet signed a transfer of the token straight to it, or minted it to them. Buys from a pool link no one, and dust under 1% of a holder’s balance is ignored. Holders that share a sender, or got tokens from another top holder, form one group.

The largest group gives linked_share, and the share gives the verdict. What the check didn’t read is in coverage: holders who got their tokens after the first 100 transactions aren’t traced, so the linked share can be higher than shown.

Linked wallets show transfers between wallets, not who owns them. Treat the answer as evidence to look at, with the wallets on Solscan, not as proof against a person.

Errors and limits

Limits
WhatIn beta
PriceFree. No key, no account.
Rate30 checks a minute per IP address, then 429 with Retry-After.
FreshnessAnswers are cached for 5 minutes per mint; checked_at says when mainnet was read.
TimeA few seconds; the check gives up on slow reads after 8 seconds and says what it couldn’t read.
BrowsersCORS is open (Access-Control-Allow-Origin: *), so a web page can call it with fetch.

Planned endpoints

Not served yet: calling them today gets a 404. Their intended shape, so you can plan for them:

Planned endpoints
EndpointIntended shapePlanned price
POST /api/assaysBody {"wallets": [...], "allocations": [...], "snapshot_slot": 0}. Answers 202 with assay_id, status_url and estimated_minutes.0.30 USDC per wallet, first 5,000 free per signed-in wallet
GET /api/assays/{id}wallets, kept, cut, allocation_saved and clusters[]: id, wallets, funder, window_minutes, confidence, signals (funding, timing, deposits, behaviour, fees), evidence_url.Free once the assay is paid
GET /api/appeals/{id}appeal_id, wallet (short form), cluster, decision (restored · cut_upheld · pending), reason.Free

Planned: paying per call with x402

Beta: calls are free; the x402 flow is the planned paid tier and is not enforced.

  1. Request

    Call the endpoint without a payment. A paid call will answer 402 Payment Required with the terms in a PAYMENT-REQUIRED header.

  2. Pay

    Sign a USDC transfer on Solana for exactly amount to payTo: base units with 6 decimals, so 50000 is 0.05 USDC.

  3. Resend

    Repeat the request with the signed transfer, base64-encoded, in X-PAYMENT.

  4. Settle

    A facilitator settles the transfer; the answer comes back with X-PAYMENT-RESPONSE carrying the transaction signature as a receipt.

PAYMENT-REQUIRED, as planned

{
  "scheme": "exact",
  "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
  "asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
  "amount": "50000",
  "payTo": "<address>",
  "resource": "https://www.assayonsol.io/api/launch-risk?mint=<mint>"
}

Planned: MCP tools

Use the HTTP API today (curl / fetch); the MCP server and skill package ship when the API leaves beta.

Planned MCP tools
ToolInputCallsStatus
assay.launch_riskmintGET /api/launch-risk?mint=Endpoint live; tool planned
assay.listwallets, allocations?, snapshot_slot?POST /api/assaysPlanned
assay.appeal_statusappeal_idGET /api/appeals/{id}Planned

Files