REST & RealtimePublic KOL Identity

Public KOL Identity

Resolve source-asserted X and Telegram identities in either direction: from a Solana wallet to its public aliases, or from a public handle to every associated wallet.

⚠️

Identity records are curated, source-asserted associations. They are useful attribution metadata, but they are not cryptographic proof that a social-account owner controls a wallet. Keep the wallet address visible and preserve source and verification when presenting or exporting an association.

Both endpoints require a Layer 2 API key and the standard Bearer header.

Fomo-sourced KOL wallet data

Conyr indexes public X handle-to-wallet mappings surfaced by Fomo’s trader leaderboards alongside other curated identity sources. Fomo-derived aliases are returned with source: "fomo", so terminals, agents, and exports can preserve that provenance explicitly. Because source is attached to each alias, inspect it per record instead of assuming every registry result came from Fomo.

Choose the right endpoint

Starting pointEndpointUse it for
Solana walletGET /v1/wallet/{address}/identityPreferred identity, platform-correct profile URL, provenance, timestamps, and every active alias
X or Telegram handleGET /v1/kol-wallets/{username}Discovering every wallet associated with an exact handle
Wallet analyticsGET /v1/wallet/{address}/labelsClassification plus compact nullable kol_username / kol_platform enrichment
Ranked tradersGET /v1/leaderboardPerformance rows with compact nullable KOL enrichment

Use /identity as the canonical identity read. The labels endpoint is backed by the behavioral snapshot, so an alias-only wallet can have a valid identity while /labels still returns JSON null.

Identity object

primary and each item in aliases use this contract:

FieldTypeMeaning
platform"x" | "telegram"Platform the username belongs to
usernamestringDisplay handle without a leading @
profile_urlURLPlatform-correct x.com or t.me URL; prefer this over constructing a URL client-side
sourcestringRegistry provenance label, such as "fomo" for mappings sourced from Fomo’s public trader leaderboards; treat it as metadata rather than a verification level
verification"source_asserted"Fixed provenance state: the registry source asserted the association; not proof of wallet ownership
created_atISO 8601 datetimeWhen the association was added
updated_atISO 8601 datetimeWhen the association was last changed

X aliases take precedence over Telegram aliases for primary. Within a platform, the most recently updated active alias wins. When aliases is non-empty, primary is a copy of its first item; it is not an additional association.

Wallet to identity

GET /v1/wallet/{address}/identity

Returns the preferred public identity and every active alias independently of wallet trading history.

Parameters

ParameterInTypeRequiredConstraints
addresspathstringyes32–44 characters from the Solana base58 alphabet; route-level syntax check

Example

curl -sS \
  -H "Authorization: Bearer $API_KEY" \
  "https://api.conyr.ai/v1/wallet/$WALLET_ADDRESS/identity"

Response with aliases

{
  "wallet_address": "A5SEXYJY4jTEi6sjMLfZs5KAP8SVFvLDPDV67GgSSZSk",
  "primary": {
    "platform": "x",
    "username": "frankdegods",
    "profile_url": "https://x.com/frankdegods",
    "source": "fomo",
    "verification": "source_asserted",
    "created_at": "2026-08-20T18:30:00Z",
    "updated_at": "2026-08-25T09:15:00Z"
  },
  "aliases": [
    {
      "platform": "x",
      "username": "frankdegods",
      "profile_url": "https://x.com/frankdegods",
      "source": "fomo",
      "verification": "source_asserted",
      "created_at": "2026-08-20T18:30:00Z",
      "updated_at": "2026-08-25T09:15:00Z"
    }
  ]
}

Unknown wallet identity

A syntactically accepted wallet identifier with no active association is not an error. It returns HTTP 200 with a stable empty object:

{
  "wallet_address": "11111111111111111111111111111111",
  "primary": null,
  "aliases": []
}

Do not turn primary: null into “not a KOL” or “verified anonymous.” It means only that the active registry has no association for that wallet.

Handle to wallets

GET /v1/kol-wallets/{username}

Returns every active wallet associated with an exact, case-insensitive username.

Parameters

ParameterInTypeRequiredConstraints
usernamepathstringyesOptional leading @; 1–32 ASCII letters, digits, or _ after normalization
platformqueryx | telegramnoRestricts the match to one platform

Use platform whenever the user’s starting context identifies the platform. Without it, the response can combine wallets for the same username on X and Telegram, while the compact response does not identify which platform produced each match.

Examples

# X handle; leading @ is optional
curl -sS \
  -H "Authorization: Bearer $API_KEY" \
  "https://api.conyr.ai/v1/kol-wallets/frankdegods?platform=x"
 
# Telegram handle
curl -sS \
  -H "Authorization: Bearer $API_KEY" \
  "https://api.conyr.ai/v1/kol-wallets/DecusCalls?platform=telegram"

Response

{
  "username": "frankdegods",
  "wallets": [
    "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
    "A5SEXYJY4jTEi6sjMLfZs5KAP8SVFvLDPDV67GgSSZSk"
  ]
}

An unknown handle returns HTTP 200 with an empty wallets array:

{
  "username": "unknown_handle",
  "wallets": []
}

Wallets are returned in deterministic address order, not by recency, confidence, performance, or “main wallet” status. Do not treat the first address as canonical.

The response username is normalized to lowercase and never includes @. Preserve the user’s original display text separately if casing matters to your product.

Practical workflow: handle to attributed wallets

Handle lookup intentionally returns only addresses. Fetch /identity for each result when the UI or agent needs a platform-correct link, provenance, or timestamps:

const base = "https://api.conyr.ai/v1";
const headers = { Authorization: `Bearer ${apiKey}` };
const handle = input.trim().replace(/^@+/, "");
 
const lookupResponse = await fetch(
  `${base}/kol-wallets/${encodeURIComponent(handle)}?platform=x`,
  { headers },
);
if (!lookupResponse.ok) throw new Error(`lookup: ${lookupResponse.status}`);
 
const lookup = await lookupResponse.json();
const hydrated: Array<
  { wallet: string; identity?: unknown; error?: string }
> = [];
 
// Bound concurrency and preserve per-wallet failures.
for (let offset = 0; offset < lookup.wallets.length; offset += 5) {
  const wallets = lookup.wallets.slice(offset, offset + 5);
  const batch = await Promise.allSettled(
    wallets.map(async (wallet: string) => {
      const response = await fetch(
        `${base}/wallet/${encodeURIComponent(wallet)}/identity`,
        { headers },
      );
      if (!response.ok) throw new Error(`Conyr ${response.status}`);
      return response.json();
    }),
  );
 
  batch.forEach((result, index) => {
    const wallet = wallets[index];
    hydrated.push(
      result.status === "fulfilled"
        ? { wallet, identity: result.value }
        : { wallet, error: String(result.reason) },
    );
  });
}

Limit hydration concurrency if a handle maps to many wallets; every /identity read counts against the Layer 2 request limit.

The alias used for reverse lookup is not guaranteed to be the hydrated wallet’s primary. Confirm that the normalized (platform, username) still appears in aliases: another active X alias may have higher priority, and the registry can change between the lookup and hydration requests.

For a performance view, join these identity results to /wallet/{address}/performance, /quality, or /trades by the exact wallet address. Do not aggregate multiple wallets into one PnL total unless the product explicitly communicates that grouping and its time window.

Platform-safe rendering

  • Display usernames with @, but store and compare the returned username without it.
  • Prefer the returned profile_url from /identity.
  • For compact kol_username / kol_platform fields, use https://x.com/{username} only for x and https://t.me/{username} only for telegram.
  • If either compact field is null, keep the wallet address as the fallback and do not guess the platform.
  • Keep personal/user-defined wallet names separate from the public identity so one does not overwrite the other.

Status and freshness semantics

StatusMeaning
200Found association, stable empty result, or unknown handle depending on the response body
400Wallet identifier outside accepted base58-form syntax, invalid username, or invalid platform
401Missing or invalid API key
403API key does not include Layer 2
429Per-key request limit exceeded; back off before retrying
500Identity registry or service failure
503Authentication or another required backend is temporarily unavailable

Both direct identity routes query active Postgres registry rows at request time and are not response-cached by the API. Compact KOL fields on /labels and /leaderboard are projections through a ClickHouse dictionary with a 30–60 second refresh lifetime; those endpoint responses can then remain cached for another 60 seconds.

The wallet-label WebSocket channel is activity-driven. An identity-registry update alone does not guarantee a label event for a dormant wallet. In its payload, empty-string kol_username / kol_platform values explicitly clear cached identity; REST compact projections use JSON null. Read /identity when current identity state matters; use the stream only as opportunistic enrichment.

See Wallet Intelligence for behavioral labels and performance endpoints, and Leaderboard for compact identity fields on ranked wallets.