Scora

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 is any base58 Solana wallet address (a system account on the ed25519 curve). Program accounts, vaults and token mints are rejected with a 400.

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
curl https://scora.fun/api/v1/wallet/9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM

Fetch 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.

JavaScript
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.

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.

TypeScript
interface ScoraWallet {
  version: "1";
  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;
    solBalance: number; 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;
  }>;
  totalUsd: number;
  notes: string[];
}

const wallet: ScoraWallet = await fetch(
  `https://scora.fun/api/v1/wallet/${address}`
).then((r) => r.json());

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 wallet's history fit our per-request data budget. Anything the budget cut short is listed in notes as plain English.

JSON Schema (generated from zod)
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "version": {
      "type": "string",
      "const": "1"
    },
    "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"
        },
        "solBalance": {
          "type": "number"
        },
        "activeWeekRatio": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ]
        },
        "swapsPerWeek": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ]
        }
      },
      "required": [
        "ageDays",
        "ageIsFloor",
        "txSampled",
        "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"
          }
        },
        "required": [
          "mint",
          "symbol",
          "name",
          "logoUri",
          "amount",
          "price",
          "usdValue",
          "pctOfPortfolio",
          "holdDays",
          "holdDaysIsFloor",
          "tokenAgeDays",
          "alive",
          "isMajor",
          "isStable"
        ],
        "additionalProperties": false
      }
    },
    "totalUsd": {
      "type": "number"
    },
    "notes": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "required": [
    "version",
    "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).

429 response
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.

StatuserrorWhen
400invalid_addressNot base58, off-curve, or a token mint / program account.
404empty_walletNo tokens, no SOL, no history. Nothing to score.
429rate_limitedToo many fresh analyses from one IP. Honors Retry-After.
503upstream_unavailableSolana data sources are down. Retry shortly.

Changelog

v1 · 2026-07 · initial release: wallet analysis endpoint.

Questions or ideas? Ping @scoradotfun or read the methodology.