Search & Resolve
Risk-aware token search with tunable policy gates, and a resolve endpoint that answers with confidence or refuses.
All endpoints below use https://api.tokens.xyz/v2 with an x-api-key header; see
Authentication. How the pipeline judges, the policy presets, and every
reasons, warnings and suppressedBy code are on
Risk-aware search.
Both routes share the per-key search provider budget (120 calls per 10 minutes by default) with
GET /v2/lists/search-tokens; see
Provider lookup budget.
GET /v2/search
Ranked results under a policy, with the suppressed candidates and the gate that removed each.
Auth: x-api-key, scope assets:read.
Query params
q(required): symbol, name or mint address, max 100 chars (invalid_queryabove that).policy(optional):strict,default(default) ordegen. See Policies and overrides.- Gate overrides (optional, each layered on the preset):
minLiquidityUsd(number ≥ 0 ornone),minAgeDays(integer ≥ 0 ornone),minMarketScore(integer 0–100 ornone; only gates tokens whose score is known),requireMarketData,suppressImpersonation,verifiedOnly(booleans).none,nullandoffdisable a numeric gate; booleans also accept1/0andyes/no. limit(1–50, default 10): ranked results only;suppressedis never truncated. Values above the max are clamped.includeSuppressed(true|false, defaulttrue):falseomits thesuppressedarray.
Out-of-range numbers and unknown policy values are 400; an invalid policy echoes the valid
ids in details.policies.
Response
interface SearchResponse {
query: string;
interpretation: { intent: 'mint' | 'ticker' | 'name'; normalizedQuery: string; hadSuspiciousCharacters: boolean };
policy: {
id: 'strict' | 'default' | 'degen';
gates: {
minLiquidityUsd: number | null;
requireMarketData: boolean;
suppressImpersonation: boolean;
minMarketScore: number | null;
minAgeDays: number | null;
requireRegistry: boolean;
};
overrides: string[]; // gate keys your request changed from the preset
};
policyVersion: string;
scoringVersion: string;
sources: { provider: 'ok' | 'degraded' | 'disabled'; db: 'ok' | 'degraded'; registry: 'ok' };
latencyMs: number;
results: Array<{
mint: string;
claims: { symbol: string | null; name: string | null; attestations: Array<{ code: string; detail: string }> };
market: {
price: number | null;
liquidityUsd: number | null;
volume24hUsd: number | null;
marketCapUsd: number | null;
priceChange24hPercent: number | null;
holderCount: number | null;
decimals: number | null;
logoURI: string | null;
dataAsOf: number | null; // ms epoch of the freshest market data
};
score: { total: number; components: Record<string, number> };
reasons: string[]; // why it ranks: exact_symbol_match, curated_list_member, deep_liquidity, …
warnings: string[]; // caution signals: possible_impersonation, new_token, unverified, weak_market_score, …
badges: string[]; // curated:<list>, tier1|tier2|tier3, grade:A|B|C, advisory:<status>
verified: boolean; // a curated-registry variant exists for this mint
}>;
suppressed?: Array<{
mint: string;
symbol: string | null;
name: string | null;
liquidityUsd: number | null;
suppressedBy: string[]; // gates that fired, e.g. gate_min_liquidity, gate_unverified
warnings: string[];
}>;
}Example
# A swap UI: registry-attested tokens with real depth, no suppressed rows
curl -sS "$API_BASE_URL/v2/search?q=bonk&verifiedOnly=true&minLiquidityUsd=250000&includeSuppressed=false" \
-H "x-api-key: $API_KEY"
# A research tool: everything, with the warnings doing the talking
curl -sS "$API_BASE_URL/v2/search?q=dog&policy=degen&limit=50" -H "x-api-key: $API_KEY"Notes
policy.gatesis the effective gate document andpolicy.overrideslists what you changed, so any result is reproducible from its own envelope.- Advisory gates (
gate_advisory_blocked,gate_advisory_compromised) and tombstones fire under every policy and cannot be overridden. - The vocabularies for
reasons,warnings,badges,claims.attestationsandsuppressedByare listed under Explanation codes. - Cached for 30 s; identical queries inside the window do not count against the budget.
GET /v2/resolve
One answer with confidence, or an explicit refusal.
Auth: x-api-key, scope assets:read.
Query params
q(required),policy, and the same gate overrides as/v2/search.limitis validated (limit=0is a400) but otherwise ignored: resolve always judges a fixed candidate window and returns at most 5candidates.includeSuppressedis not accepted here.
Response
Always HTTP 200 when the request is valid; read status.
interface ResolveResponse {
query: string;
interpretation: SearchResponse['interpretation'];
policy: SearchResponse['policy'];
policyVersion: string;
scoringVersion: string;
sources: SearchResponse['sources'];
latencyMs: number;
status: 'resolved' | 'ambiguous' | 'no_confident_match';
// `resolved`: the single answer. `confidence` is 0–1 (exact mint paste → 1).
best: (SearchResponse['results'][number] & { confidence: number }) | null;
// `ambiguous`: credible contenders, best first (≤ 5). Empty otherwise.
candidates: SearchResponse['results'];
}Example
curl -sS "$API_BASE_URL/v2/resolve?q=USDC" -H "x-api-key: $API_KEY"Notes
- A mint-address query is a lookup, never fuzzy: the exact mint or
no_confident_match. resolvedrequires the best candidate to clear the policy's refusal bar and beat the runner-up by a margin;ambiguousmeans several credible claimers exist, so showcandidatesand let the user pick. See Resolve: confidence or refusal.- Each policy has its own refusal bar:
strictrefuses most readily,degenleast. - Cached for 30 s.