Developers · API v1

The Radar engine,as a read only API.

The same mainnet reader and the same scoring model Radar uses, over plain GET requests. No key, no account, open CORS. A bot gets the answer a person gets on Radar.

Method
GET only
Auth
None
CORS
Open
Format
JSON
01 · Base URL

One base, six endpoints.

Base URL
https://useshader.xyz/api/v1
Version
v1
Endpoints
6
Source now
·

Field names are a contract: fields get added, never renamed. Times are unix seconds, SOL amounts are numbers, and unknown values are null, never guessed. Everything is read from Solana mainnet.

02 · Limits

Per IP, per minute.

Published limits. They refresh from GET /api/v1 when it answers.

All endpoints
30 a minute
Score
20 a minute
Per score
5 addresses
Cache
5 min

Limits count per IP in a fixed one minute window. Score has its own budget because each call reads several wallets. Mainnet reads are cached for 5 minutes, so a repeat request inside that window returns fast and costs no RPC calls. Wallet and score can take 5 to 40 s uncached on the free RPC; set a client timeout of at least 60 s.

03 · Endpoints

Send a real request from here.

Each card calls the live endpoint from your browser and shows the status, the time and the JSON it got back.

GET/api/v1

Name, version, the mainnet source in use, the limits and every endpoint. Cheap, so call it to check the API is up.

curl -s "https://useshader.xyz/api/v1"
Not sent yet
GET/api/v1/wallet/{address}?limit

One wallet: how much history was read, its first funder, its top counterparties, its visibility index and its findings.

Uncached: 5 to 40 s on the free RPC
curl -s "https://useshader.xyz/api/v1/wallet/86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY?limit=300"
Not sent yet
GET/api/v1/score?addresses&limit

Score up to 5 wallets together: the visibility index, clusters, every pair link with its evidence, and the findings. The two default wallets are linked, so the response holds a cluster.

Uncached: 5 to 40 s on the free RPC
curl -s "https://useshader.xyz/api/v1/score?addresses=86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY,FuGFeXc42djG26x8CmtgYqP7cCtbdAKnmFjiGMav3Kmk&limit=300"
Not sent yet
GET/api/v1/address/{address}

Profile any address: its label, whether it is a program, a busy service or a plain wallet, and whether it forwards to an exchange like a deposit address. The default is a labelled Binance hot wallet.

curl -s "https://useshader.xyz/api/v1/address/5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9"
Not sent yet
GET/api/v1/labels

Every exchange hot wallet and shared infrastructure address Radar knows. Cached for an hour.

curl -s "https://useshader.xyz/api/v1/labels"
Not sent yet
GET/api/v1/token

Token facts for $SHADER: venue, status and the contract address, which stays null until launch.

curl -s "https://useshader.xyz/api/v1/token"
Not sent yet
04 · Score response

Every field of /score.

FieldTypeMeaning
addressesstring[]The addresses you sent, without duplicates.
sourcestringWhich mainnet path answered: helius, rpc or publicnode.
history[address]objectsignaturesListed, transactionsRead, transactionsUnavailable, truncated (more history exists than limit), complete (false when the list came from a pruned source).
firstFunders[address]objectstatus (found, none, too-deep, unavailable), address, label, signature, time in unix seconds, sol. Null when unknown.
visibilityIndexnumber0 to 100. The highest wallet index in the request.
verdictstringIN THE SHADE, PARTLY VISIBLE, VISIBLE or EXPOSED.
partialHistorybooleanTrue when part of the history could not be read, so links may be missing.
wallets[]object[]address, visibilityIndex, cluster (position in clusters, or -1), exchange (name or null), reasons.
clustersstring[][]Groups of wallets whose pair links are above 0.50.
pairs[]object[]a, b, link from 0 to 1, and evidence[]: kind, weight, via, detail, signatures.
findings[]object[]strength (STRONG, MEDIUM, WEAK), title, detail, fix, wallets, via, signatures. Strongest first.

Evidence kinds on pairs are direct, gas, deposit, funder, counterparty, amount and sync. Findings can also be about one wallet: an exchange touch or round amounts. /wallet returns the same history, funder and findings for one address, plus firstSeen, lastSeen and its top 10 counterparties. The weights are explained in How scoring works.

05 · Errors

Plain messages, real status codes.

Every error is JSON with an error field written for people. Retry 429 and 502; fix the request on 400 and 405.

StatusWhenBody
400A parameter is missing or invalid.{ "error": "limit must be a number from 10 to 1000." }
405Any method other than GET or OPTIONS. The API is read only.{ "error": "Read only API. Use GET." }
429Over the per minute limit for your IP.{ "error": "Too many requests. Try again in 41 s.", "retryAfter": 41 }
502Mainnet could not be read right now.{ "error": "Could not read mainnet right now. Try again in a minute." }
Try the error paths

Both requests are real. The first sends an address that is not base58, the second sends a POST.

Pick one
06 · Recipes

Two things worth building.

Check a wallet before using it

Before a wallet goes anywhere you want kept apart from your others, read its verdict and its findings. Exits early on a rate limit with the wait in seconds.

JavaScript module · Node 18+ or a browser
const BASE = "https://useshader.xyz/api/v1";

// Check a wallet before you use it for something you want kept apart.
async function checkWallet(address) {
  const res = await fetch(`${BASE}/wallet/${address}?limit=300`);
  const body = await res.json();
  if (res.status === 429) throw new Error(`Rate limited. Retry in ${body.retryAfter} s.`);
  if (!res.ok) throw new Error(body.error);

  const h = body.history;
  const partial = !h.complete || h.transactionsUnavailable / Math.max(1, h.transactionsRead) > 0.1;
  console.log(`${body.verdict}, index ${body.visibilityIndex}${partial ? ", partial history" : ""}`);
  if (body.exchange) console.log(`Tied to ${body.exchange}. Assume it is linked to your ID.`);
  for (const f of body.findings) console.log(`${f.strength} ${f.title}. FIX: ${f.fix}`);
  return body.verdict === "IN THE SHADE";
}

const clean = await checkWallet("86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY");
console.log(clean ? "Looks clean" : "Read the findings first");
Flag a cluster in a bot

Send up to 5 entries together. Any cluster in the response is a group the evidence ties to one person, with the link and the reasons. Waits out a 429 using retryAfter.

JavaScript module · Node 18+ or a browser
const BASE = "https://useshader.xyz/api/v1";
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

// Flag entries that are linked to each other, for example in a giveaway bot.
// Clusters only form inside one request, so send the wallets you suspect together.
async function flagCluster(addresses) {
  const list = addresses.slice(0, 5).join(",");
  for (let tries = 0; tries < 3; tries++) {
    const res = await fetch(`${BASE}/score?addresses=${list}&limit=300`);
    const body = await res.json();
    if (res.status === 429) { await sleep(body.retryAfter * 1000); continue; }
    if (!res.ok) throw new Error(body.error);
    return body.clusters.map((group) => ({
      wallets: group,
      link: Math.max(...body.pairs.filter((p) => group.includes(p.a) && group.includes(p.b)).map((p) => p.link)),
      why: body.findings.filter((f) => f.wallets.length === 2 && group.includes(f.wallets[0])).map((f) => f.title),
    }));
  }
  throw new Error("Still rate limited after 3 tries.");
}

const flagged = await flagCluster([
  "86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY",
  "FuGFeXc42djG26x8CmtgYqP7cCtbdAKnmFjiGMav3Kmk",
]);
for (const c of flagged) console.log(`Linked at ${c.link}: ${c.wallets.join(" and ")} (${c.why.join("; ")})`);