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 point | Endpoint | Use it for |
|---|---|---|
| Solana or EVM wallet | GET /v1/wallet/{address}/identity | Preferred identity, platform-correct profile URL, provenance, timestamps, every active alias, the person, tags, receipts and denials |
| X or Telegram handle | GET /v1/kol-wallets/{username} | Discovering every wallet associated with an exact handle |
| Wallet analytics | GET /v1/wallet/{address}/labels | Classification plus compact nullable kol_username / kol_platform / kol_tier enrichment |
| Ranked traders | GET /v1/leaderboard | Performance 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:
| Field | Type | Meaning |
|---|---|---|
platform | "x" | "telegram" | Platform the username belongs to |
username | string | Display handle without a leading @ |
profile_url | URL | Platform-correct x.com or t.me URL; prefer this over constructing a URL client-side |
source | string | Where 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_at | ISO 8601 datetime | For 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_at | ISO 8601 datetime | For 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 |
basis | string | What 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) |
reason | string | The source in words, such as "x linked on pump.fun" |
claim_id | integer | The 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
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
address | path | string | yes | 32–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:
| Source | Fields |
|---|---|
pumpfun, sns, kolscan, axiom, fomo | run_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 |
operator | the 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
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
username | path | string | yes | Optional leading @; 1–32 ASCII letters, digits, or _ after normalization |
platform | query | x | telegram | no | Restricts the match to one platform |
chain | query | solana | evm | all | no | Which 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_urlfrom/identity. - For compact
kol_username/kol_platformfields, usehttps://x.com/{username}only forxandhttps://t.me/{username}only fortelegram. - 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
| Status | Meaning |
|---|---|
200 | Found association, stable empty result, or unknown handle depending on the response body |
400 | Wallet 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 |
401 | Missing or invalid API key |
403 | API key does not include Layer 2 |
429 | Per-key request limit exceeded; back off before retrying |
500 | Identity registry or service failure |
503 | Authentication 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.