REST & RealtimePublic KOL Identity

Public KOL Identity

Resolve X and Telegram identities in either direction: from a wallet (Solana or EVM) to its public aliases, or from a public handle to every associated wallet.

⚠️

Every identity record carries a tier. verified means owner-proven. A pump.fun X link the account holder made by signing in to X, or the SNS .twitter registry, is verified while no other source on the wallet disagrees; an operator assertion backed by owner evidence is verified on its own. Every other association is predicted: a tracker, a roster or a single source lists it. The label’s tier (and the compact kol_tier) is verified only when its winning alias is verified and no source on the wallet disagrees. Keep the wallet address visible either way, and preserve tier, basis and source when presenting or exporting an association.

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

Where identities come from

Both endpoints read Conyr’s identity registry on every request. It is built from pump.fun’s public profiles, SNS, kolscan, Axiom’s wallet maps, FOMO’s public profiles, the original KOL registry, and Conyr’s operator file. The original KOL registry’s rows come from Fomo’s trader leaderboards, kolscan, KOLlector (dethective.com) and manual curation; each keeps its own source (fomo, kolscan, dethective, manual). Each alias says which source it comes from: source names where it was read, basis what it rests on, and reason says it in words (for example “x linked on pump.fun” or “x listed by kolscan”).

Choose the right endpoint

Starting pointEndpointUse it for
Solana or EVM walletGET /v1/wallet/{address}/identityPreferred identity, platform-correct profile URL, provenance, timestamps, every active alias, the person, tags, receipts and denials
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 / kol_tier 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
sourcestringWhere the association comes from: the registry source that read it (pumpfun, sns, kolscan, axiom, fomo, operator), or, for a row of the original KOL registry, the row’s own source (fomo, kolscan, dethective, manual)
verification"source_asserted"Deprecated: always source_asserted, kept for existing clients; read tier
created_atISO 8601 datetimeFor a row of the original KOL registry, the row’s own creation time; otherwise the start of the registry run that first wrote the association
updated_atISO 8601 datetimeFor a row of the original KOL registry, the row’s own update time; otherwise the same run start as created_at
tier"verified" | "predicted"verified when the association is owner-proven; predicted when a tracker, a roster or a single source lists it
basisstringWhat the association rests on: chain_proof (the SNS registry), app_binding (an X link made on pump.fun), operator, tracker (kolscan’s leaderboard, Axiom’s wallet maps), kol_app (the original registry’s FOMO rows), kol_tracker (its other rows)
reasonstringThe source in words, such as "x linked on pump.fun"
claim_idintegerThe registry claim behind the association; its key in receipts

aliases lists X before Telegram, each list in the registry’s precedence order. Each alias is one source’s association, so the same handle can appear once per source; for display, dedupe by platform and username. primary is the alias the registry chose for the wallet’s label: verified before predicted, then the operator, the SNS registry, pump.fun’s X link, FOMO’s X link, the original KOL registry’s FOMO rows, trackers (kolscan before Axiom), then the original KOL registry’s other rows (kolscan, KOLlector, manual). It is null when the label names no person, even when tags are present. primary is a copy of one item of aliases; 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, with the person the wallet’s label names, its tags, a receipt per association and the operator’s denials.

Parameters

ParameterInTypeRequiredConstraints
addresspathstringyes32–44 characters from the Solana base58 alphabet, or an EVM address: 0x and 40 hex digits in any case; route-level syntax check

Example

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

Response with aliases

A wallet whose label names a person, with receipts shortened to one (the full response holds one per alias and tag):

{
  "wallet_address": "CyaE1VxvBrahnPWkqm5VsdCvyS2QmNht2UFrKJHga54o",
  "chain": "solana",
  "primary": {
    "platform": "x",
    "username": "Cented7",
    "profile_url": "https://x.com/Cented7",
    "source": "pumpfun",
    "verification": "source_asserted",
    "created_at": "2026-09-29T00:00:00Z",
    "updated_at": "2026-09-29T00:00:00Z",
    "tier": "verified",
    "basis": "app_binding",
    "reason": "x linked on pump.fun",
    "claim_id": 26
  },
  "aliases": [
    {
      "platform": "x",
      "username": "Cented7",
      "profile_url": "https://x.com/Cented7",
      "source": "pumpfun",
      "verification": "source_asserted",
      "created_at": "2026-09-29T00:00:00Z",
      "updated_at": "2026-09-29T00:00:00Z",
      "tier": "verified",
      "basis": "app_binding",
      "reason": "x linked on pump.fun",
      "claim_id": 26
    },
    {
      "platform": "x",
      "username": "Cented7",
      "profile_url": "https://x.com/Cented7",
      "source": "kolscan",
      "verification": "source_asserted",
      "created_at": "2026-09-29T00:00:00Z",
      "updated_at": "2026-09-29T00:00:00Z",
      "tier": "predicted",
      "basis": "tracker",
      "reason": "x listed by kolscan",
      "claim_id": 39
    },
    {
      "platform": "x",
      "username": "Cented7",
      "profile_url": "https://x.com/Cented7",
      "source": "axiom",
      "verification": "source_asserted",
      "created_at": "2026-09-29T00:00:00Z",
      "updated_at": "2026-09-29T00:00:00Z",
      "tier": "predicted",
      "basis": "tracker",
      "reason": "x listed by Axiom",
      "claim_id": 51
    },
    {
      "platform": "x",
      "username": "Cented7",
      "profile_url": "https://x.com/Cented7",
      "source": "kolscan",
      "verification": "source_asserted",
      "created_at": "2026-08-16T00:00:00Z",
      "updated_at": "2026-08-16T00:00:00Z",
      "tier": "predicted",
      "basis": "kol_tracker",
      "reason": "x alias (kolscan)",
      "claim_id": 6
    }
  ],
  "person": {
    "id": "person:x:cented7",
    "name": "Cented7",
    "handles": [
      {
        "platform": "x",
        "username": "Cented7",
        "profile_url": "https://x.com/Cented7"
      }
    ],
    "formerly": []
  },
  "tier": "verified",
  "reason": "verified: x linked on pump.fun, plain wallet at slot 7; x listed by kolscan and x listed by Axiom and x alias (kolscan) agree",
  "conflict": false,
  "tags": [
    {
      "platform": "pumpfun",
      "username": "cented69420",
      "source": "pumpfun",
      "basis": "self_attested",
      "tier": "predicted",
      "reason": "pump.fun name",
      "flags": [
        "default_name"
      ],
      "hidden": true,
      "claim_id": 25
    },
    {
      "platform": "pumpfun",
      "username": "cented69420",
      "source": "axiom",
      "basis": "tracker",
      "tier": "predicted",
      "reason": "pump.fun name listed by Axiom",
      "flags": [],
      "hidden": false,
      "claim_id": 50
    },
    {
      "platform": "sns",
      "username": "popscandle.sol",
      "source": "sns",
      "basis": "self_attested",
      "tier": "predicted",
      "reason": ".sol primary domain",
      "flags": [],
      "hidden": false,
      "claim_id": 32
    }
  ],
  "receipts": {
    "26": {
      "source": "pumpfun",
      "first_check": {
        "state": "plain_wallet",
        "slot": 7,
        "checked_at": "2026-09-29T00:00:00Z"
      },
      "run_id": "20260927T182156Z-soak",
      "lane": "batch",
      "linked": null,
      "lines": [
        {
          "url": "https://frontend-api-v3.pump.fun/users/batch",
          "fetched_at": "2026-09-27T21:16:31.049Z",
          "copy_sha256": "a257a64a55b7fa6f42e0e8c4c92b3b6bd99446e8b4a23f2e35c5146d8001bd43"
        }
      ]
    }
  },
  "denials": []
}

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",
  "chain": "solana",
  "primary": null,
  "aliases": [],
  "person": null,
  "tier": null,
  "reason": null,
  "conflict": false,
  "tags": [],
  "receipts": {},
  "denials": []
}

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.

Tags

tags lists the associations that name no one: pump.fun names, .sol primary domains, SNS records, FOMO handles, and handles a tracker lists for a wallet. Each tag has platform, username (as the source displays it), source, basis, tier, reason, flags, hidden and claim_id, in platform order (x, telegram, pumpfun, fomo, sns, discord, github, backpack). A FOMO handle that Axiom listed also carries via (claim_id, listed_as): the FOMO claim that gave the handle and the name Axiom listed it under.

A tag is never a person’s name, even on X: an X handle a wallet declares in its SNS records is a tag, not an alias. hidden is true for default names (default_name, auto_created or low15 among flags); hide those unless the user asks for them. Tags are returned whether or not the label names a person, so a wallet with primary: null can still have tags.

Person

When the wallet’s label names a person, person holds id, name, handles (the person’s current X and Telegram profiles, X first, each with platform, username and profile_url) and formerly (X and Telegram handles the person no longer holds, newest first, each with platform, username and until). The label’s own tier and reason sit beside it, and conflict is true when the wallet’s live associations name more than one entity; such a label is always predicted. When the label names no person, person, tier and reason are null.

Receipts and denials

receipts holds one receipt per alias and tag, and one per claim a tag’s via names, keyed by claim_id as a string. Each receipt has the registry source and the account check made when the claim was first read (first_check: state, slot, checked_at). By source it adds:

SourceFields
pumpfun, sns, kolscan, axiom, fomorun_id, lane, linked, and lines (each url, fetched_at, copy_sha256)
kol_aliases (the original KOL registry)read_at, the row’s created_at and updated_at, row_sha256
operatorthe line’s id, added_at, and evidence (kind, url, copy_sha256, captured_at, note)

The saved copies of each source page stay with Conyr; a copy_sha256 lets anyone holding a copy verify it. A receipt never includes a path on Conyr’s machines, a database host, the source’s raw extra data, a registry row’s notes, or the operator’s name.

denials lists the associations on the wallet that the operator denied, each with claim_id, platform, username, source, basis, operator_line (the operator line’s id) and note. A denied association never appears in aliases or tags.

EVM wallets

The route accepts an EVM address (0x and 40 hex digits, any case). It is looked up in lower case, and wallet_address echoes it as sent; chain is "evm". There is no on-chain account check for an EVM address: its receipts’ first_check.state is not_checkable.

Handle to wallets

GET /v1/kol-wallets/{username}

Returns every active wallet associated with an exact, case-insensitive username, with the person who holds the handle and one match summary per wallet.

Parameters

ParameterInTypeRequiredConstraints
usernamepathstringyesOptional leading @; 1–32 ASCII letters, digits, or _ after normalization
platformqueryx | telegramnoRestricts the match to one platform
chainquerysolana | evm | allnoWhich wallets to return; default solana

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, and no response field says which platform produced each match (only each match’s reason, in words).

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"
 
# EVM wallets only; chain=all returns Solana and EVM wallets together
curl -sS \
  -H "Authorization: Bearer $API_KEY" \
  "https://api.conyr.ai/v1/kol-wallets/orangie?chain=evm"

Response

For orangie?chain=evm:

{
  "username": "orangie",
  "wallets": [
    "0x0fd94b02867a7311c87c5b995793e9a3dca5c565",
    "0xb3fd97526020fbf5560857b7dca5dd2f1652422f"
  ],
  "person": {
    "id": "person:x:orangie",
    "name": "orangie"
  },
  "matches": [
    {
      "address": "0x0fd94b02867a7311c87c5b995793e9a3dca5c565",
      "chain": "evm",
      "tier": "predicted",
      "basis": "tracker",
      "source": "axiom",
      "reason": "x listed by Axiom",
      "conflict": false
    },
    {
      "address": "0xb3fd97526020fbf5560857b7dca5dd2f1652422f",
      "chain": "evm",
      "tier": "predicted",
      "basis": "tracker",
      "source": "axiom",
      "reason": "x listed by Axiom",
      "conflict": false
    }
  ]
}

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

{
  "username": "nobody_here",
  "wallets": [],
  "person": null,
  "matches": []
}

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

person is the entity that currently holds the handle’s profile (X before Telegram when no platform is given), or null. matches has one entry per wallet, in the same order: the first association on that wallet that names the handle, with address, chain, tier, basis, source, reason, and the wallet’s conflict flag. A handle the operator denied on a wallet does not match that wallet.

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 returns addresses with one match summary each. Fetch /identity for each result when the UI or agent needs a platform-correct link, the full provenance and receipts, 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 that is neither a base58-form Solana key nor an EVM address (0x and 40 hex digits), invalid username, invalid platform, or invalid chain
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 the identity registry’s Postgres tables at request time and are not response-cached by the API. The registry changes only when Conyr reloads it from its sources, so these routes show it as of its last load. Compact kol_username, kol_platform and kol_tier fields on /labels and /leaderboard are projections of the registry through its ClickHouse mirror, a dictionary with a 5–10 minute refresh lifetime after a registry load; those endpoint responses can then remain cached for another 60 seconds. The compact fields name every person the registry names for a wallet, not only people on KOL rosters; kol_tier says whether that name is verified or predicted.

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 / kol_tier 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.