API v1
One GET. Every wallet.
The same engine behind scora.fun, as a free public JSON API. No key, no auth, CORS open. Build leaderboards, bots, Discord flexes, whatever.
Endpoint
GET/api/v1/wallet/{address}
address accepts either a 32–44 character Solana base58 wallet address (an ed25519 system account) or a 42-character Robinhood Chain address beginning with 0x. Solana program accounts, vaults and token mints are rejected with a 400, as are Robinhood Chain contract addresses. Malformed addresses on either network receive the same status.
Responses are cached ~5 minutes at the edge and ~10 minutes in the engine; X-Scora-Cache: HIT|MISS tells you which you got.
curl https://scora.fun/api/v1/wallet/9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWMFetch it
Plain fetch works from browsers and servers alike. The API sends Access-Control-Allow-Origin: * on every GET.
scora is null only for empty wallets (which also return 404), so a 200 always carries a real score.
const res = await fetch(
"https://scora.fun/api/v1/wallet/9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM"
);
if (!res.ok) throw new Error((await res.json()).message);
const wallet = await res.json();
console.log(wallet.scora, wallet.tier); // 78 "Titanium"TypeScript
The response shape is stable and versioned (version: "1"). Fields are only ever added within v1, never removed or renamed.
chain identifies which validator matched the address. Use wallet.nativeBalance and wallet.nativeSymbol for cross-chain clients. solBalance remains in v1 for compatibility with existing Solana integrations, but new clients should not use it as a cross-chain balance.
Hold times can be floors: when holdDaysIsFloor is true, the wallet has held the token at least holdDays days. Render it as "90+d", like we do.
On Solana, recognized PreStocks mints may carry a prestock object, and the report may include stocks. These additive fields are absent or null on Robinhood Chain. Match by mint; a ticker alone does not establish issuer identity.
interface ScoraWallet {
version: "1";
chain: "solana" | "robinhood";
address: string;
analyzedAt: string; // ISO 8601
coverage: number; // 0–1 data confidence
scora: number | null; // 0–100
tier: string | null; // Paper | Steel | Titanium | Diamond
kind: "scored" | "empty" | "fresh";
isEstimate: boolean;
subScores: {
conviction: number; vintage: number; portfolioHealth: number;
discipline: number; degenIndex: number; // each 0–10
} | null;
degen: { memePct: number; label: string };
wallet: {
ageDays: number | null; ageIsFloor: boolean; txSampled: number;
nativeBalance: number; nativeSymbol: string; // "SOL" or "ETH"
solBalance: number; // legacy Solana-compatible field
activeWeekRatio: number | null;
swapsPerWeek: number | null;
};
tokens: Array<{
mint: string; symbol: string; name: string | null;
logoUri: string | null; amount: number; price: number | null;
usdValue: number; pctOfPortfolio: number;
holdDays: number | null; holdDaysIsFloor: boolean;
tokenAgeDays: number | null; alive: boolean;
isMajor: boolean; isStable: boolean;
prestock?: {
issuer: "prestocks"; company: string; productUrl: string;
logoUri: string; status: "active" | "transition";
transitionDeadline: string | null; // ISO 8601
markPrice: number | null; premiumPct: number | null;
} | null;
}>;
totalUsd: number;
stocks?: {
status: "complete" | "partial" | "unavailable";
totalUsd: number | null; tokenCount: number;
pricedTokenCount: number; portfolioPct: number | null;
largestPositionPct: number | null;
catalogFetchedAt: string | null; notes: string[];
} | null;
notes: string[];
}
const wallet: ScoraWallet = await fetch(
`https://scora.fun/api/v1/wallet/${address}`
).then((r) => r.json());PreStocks fields
token.price is the market quote in USD per displayed token unit. prestock.markPrice is the issuer's reference mark in the same unit. The premiumPct is percentage points relative to the mark: 20 means the market quote is 20% above the mark, not a 20% return. A null price, mark or premium means that value was unavailable; do not replace it with zero. These tokens do not imply direct company ownership or a guaranteed cash-out price.
stocks.totalUsd sums priced recognized positions of at least $1. portfolioPct divides that value by the priced portfolio, while largestPositionPct divides the largest priced stock position by the priced PreStocks total. Both ratios run from 0 to 1. tokenCount includes recognized unpriced balances, but excludes priced dust below $1; pricedTokenCount only counts priced positions. If no stock quote is available, the total and ratios may be null. A zero count is conclusive only when status is complete.
status and notes report incomplete issuer data. catalogFetchedAt records the issuer reference snapshot; market quotes and marks can have different update times. A transition deadline is issuer guidance, not an estimate of future value.
Response schema
Generated at build time from the exact zod schema the API validates against, so the docs cannot drift from the implementation.
coverage (0–1) says how much of the relevant wallet history fit the network's per-request data budget. Solana and Robinhood Chain use different history sources and sample shapes, so equal values do not promise identical historical depth. Anything the budget cut short is listed in notes as plain English.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"version": {
"type": "string",
"const": "1"
},
"chain": {
"type": "string",
"enum": [
"solana",
"robinhood"
]
},
"address": {
"type": "string"
},
"analyzedAt": {
"type": "string"
},
"coverage": {
"type": "number"
},
"scora": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"tier": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"kind": {
"type": "string",
"enum": [
"scored",
"empty",
"fresh"
]
},
"isEstimate": {
"type": "boolean"
},
"subScores": {
"anyOf": [
{
"type": "object",
"properties": {
"conviction": {
"type": "number"
},
"vintage": {
"type": "number"
},
"portfolioHealth": {
"type": "number"
},
"discipline": {
"type": "number"
},
"degenIndex": {
"type": "number"
}
},
"required": [
"conviction",
"vintage",
"portfolioHealth",
"discipline",
"degenIndex"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"degen": {
"type": "object",
"properties": {
"memePct": {
"type": "number"
},
"label": {
"type": "string"
}
},
"required": [
"memePct",
"label"
],
"additionalProperties": false
},
"wallet": {
"type": "object",
"properties": {
"ageDays": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"ageIsFloor": {
"type": "boolean"
},
"txSampled": {
"type": "number"
},
"nativeBalance": {
"type": "number"
},
"nativeSymbol": {
"type": "string"
},
"solBalance": {
"type": "number"
},
"activeWeekRatio": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"swapsPerWeek": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
}
},
"required": [
"ageDays",
"ageIsFloor",
"txSampled",
"nativeBalance",
"nativeSymbol",
"solBalance",
"activeWeekRatio",
"swapsPerWeek"
],
"additionalProperties": false
},
"tokens": {
"type": "array",
"items": {
"type": "object",
"properties": {
"mint": {
"type": "string"
},
"symbol": {
"type": "string"
},
"name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"logoUri": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"amount": {
"type": "number"
},
"price": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"usdValue": {
"type": "number"
},
"pctOfPortfolio": {
"type": "number"
},
"holdDays": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"holdDaysIsFloor": {
"type": "boolean"
},
"tokenAgeDays": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"alive": {
"type": "boolean"
},
"isMajor": {
"type": "boolean"
},
"isStable": {
"type": "boolean"
},
"prestock": {
"anyOf": [
{
"type": "object",
"properties": {
"issuer": {
"type": "string",
"const": "prestocks"
},
"company": {
"type": "string"
},
"productUrl": {
"type": "string"
},
"logoUri": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"active",
"transition"
]
},
"transitionDeadline": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"markPrice": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"premiumPct": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
}
},
"required": [
"issuer",
"company",
"productUrl",
"logoUri",
"status",
"transitionDeadline",
"markPrice",
"premiumPct"
],
"additionalProperties": false
},
{
"type": "null"
}
]
}
},
"required": [
"mint",
"symbol",
"name",
"logoUri",
"amount",
"price",
"usdValue",
"pctOfPortfolio",
"holdDays",
"holdDaysIsFloor",
"tokenAgeDays",
"alive",
"isMajor",
"isStable"
],
"additionalProperties": false
}
},
"totalUsd": {
"type": "number"
},
"stocks": {
"anyOf": [
{
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"complete",
"partial",
"unavailable"
]
},
"totalUsd": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"tokenCount": {
"type": "number"
},
"pricedTokenCount": {
"type": "number"
},
"portfolioPct": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"largestPositionPct": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"catalogFetchedAt": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"notes": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"status",
"totalUsd",
"tokenCount",
"pricedTokenCount",
"portfolioPct",
"largestPositionPct",
"catalogFetchedAt",
"notes"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"notes": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"version",
"chain",
"address",
"analyzedAt",
"coverage",
"scora",
"tier",
"kind",
"isEstimate",
"subScores",
"degen",
"wallet",
"tokens",
"totalUsd",
"notes"
],
"additionalProperties": false
}Rate limits
10 fresh analyses per 5 minutes per IP, shared with the web UI. Cache hits don't count, so hammering a hot wallet is free.
On 429, honor Retry-After (seconds).
HTTP/1.1 429 Too Many Requests
Retry-After: 117
{
"error": "rate_limited",
"message": "Too many fresh analyses. Cached wallets still return instantly."
}Errors
Every error body is JSON with error (machine-readable) and message (human-readable).
Partial-data policy: when an upstream source hiccups mid-analysis we return a 200 with lower coverage and honest notes. A 503 only means we got nothing at all.
| Status | error | When |
|---|---|---|
| 400 | invalid_address | Not a valid Solana base58 or Robinhood Chain 0x wallet address. |
| 404 | empty_wallet | No tokens, native balance, or history. Nothing to score. |
| 429 | rate_limited | Too many fresh analyses from one IP. Honors Retry-After. |
| 503 | upstream_unavailable | The selected network's data sources are unavailable. Retry shortly. |
Changelog
v1 · 2026-09 · added optional PreStocks token identity and stock summary for Solana wallets. Existing v1 fields are unchanged.
v1 · 2026-09 · added Robinhood Chain addresses plus the additive chain, nativeBalance, and nativeSymbol fields. No v1 fields were removed.
v1 · 2026-07 · initial release: wallet analysis endpoint.
Questions or ideas? Ping @scorawallet or read the methodology.