Currencies, networks and icons
Dieser Inhalt ist noch nicht in deiner Sprache verfügbar.
The catalogue endpoints list every currency Kotova X can swap, the networks each one can be sent and received on, and the icons to show next to them. They need no key.
Identifiers
Section titled “Identifiers”A currency on one network is called a leg, and every request names a leg with two identifiers. Send both, always: the same currency on two networks is two different legs.
currencyis the ticker, such asBTCorUSDT. Requests match it in any case; answers always use upper case.networkis the network code, such asbtc,trxoreth. Requests match it in any case; answers always use lower case.
So USDT on TRON is {"currency": "USDT", "network": "trx"}, and USDT on Ethereum is
{"currency": "USDT", "network": "eth"}. Numeric ids never appear in the API. A leg also
has a ticker field, which is only for display (on some networks a currency trades under
another symbol); identifiers always use currency.
A currency must match ^[A-Za-z0-9.]{1,12}$ and a network must match
^[A-Za-z0-9_-]{1,24}$. Anything else is a 400
invalid_request with details.reason set to
invalid_format. A well-formed pair of identifiers that is not in the catalogue is a 422
unknown_currency.
Aliases
Section titled “Aliases”When the catalogue renames or merges a currency, the old spelling keeps working as an
alias. GET /currencies lists a currency’s old spellings in aliases; USDT, for
example, lists USDT0. A request that uses an alias is answered with the canonical
identifiers, plus a deprecated_alias warning that names both:
{ "code": "deprecated_alias", "message": "from.currency \"USDT0\" is a deprecated alias of \"USDT\"; use \"USDT\".", "field": "from.currency", "given": "USDT0", "canonical": "USDT"}The same mechanism applies to network codes. When you see this warning, switch to the
canonical value. Warnings never make a request fail, so log them where you will
notice them.
How the catalogue is served
Section titled “How the catalogue is served”GET /currencies, GET /networks and GET /pairs (and GET /sources, see
Sources) share the same rules.
- No key and no weight. Send no
Authorizationheader: it is ignored, and leaving it out lets the CDN answer. The calls do not count against your account’s rate limit. - Browsers welcome. Answers carry
Access-Control-Allow-Origin: *, so a web page can read the catalogue directly.HEADworks on every one of these endpoints. - One page. A list answer has
data,has_more,next_cursor,catalog_versionandupdated_at.has_moreis alwaysfalseandnext_cursoralwaysnull: the whole list comes in one answer.catalog_versionchanges whenever the catalogue is rebuilt, andupdated_atis when that happened. - Strict query strings. An unknown or repeated query parameter, or an empty segment
such as
?&, is a 400invalid_request(unknown_parameterorduplicate_parameter).GET /currenciesandGET /networkstake no parameters at all, so a cache-busting?t=…is refused rather than ignored. - Per-IP limit. Calls that reach the server, rather than the CDN, are limited to 120
per minute per IP address. Over that, the answer is a 429
rate_limitedwithdetails.reasonset toipand aRetry-Afterheader. - Unavailable catalogue. If the catalogue cannot be read, the answer is a 503
service_unavailablewithdetails.reasonset tocatalog_missingand aRetry-Afterheader.
Caching and ETags
Section titled “Caching and ETags”Successful answers are cached by the CDN with
Cache-Control: public, s-maxage=60, stale-while-revalidate=86400 and carry a weak
ETag such as W/"394da0f044c4-5be2a7d1c09e". Send the ETag back in If-None-Match and
an unchanged answer comes back as a 304 Not Modified with no body. Error answers are
never cached (Cache-Control: no-store).
The catalogue changes rarely, but it does change: currencies are added, and a source can
be switched on or off at any time, which changes send, receive and the source lists
without a new catalog_version. The ETag covers both, so compare ETags rather than
versions. A good pattern is to keep a copy on your server and revalidate it every few
minutes with If-None-Match.
# First call: note the ETag header.curl -si https://api.kotova.io/x/v1/currencies | grep -i '^etag'
# Later: revalidate. 304 means your copy is current.curl -si https://api.kotova.io/x/v1/currencies \ -H 'If-None-Match: W/"394da0f044c4-5be2a7d1c09e"' | head -n 1const BASE = 'https://api.kotova.io/x/v1';let cached = null; // { etag, body }
async function getCurrencies() { const headers = cached ? { 'If-None-Match': cached.etag } : {}; const res = await fetch(`${BASE}/currencies`, { headers }); if (res.status === 304) return cached.body; if (!res.ok) throw new Error(`currencies: HTTP ${res.status}`); cached = { etag: res.headers.get('ETag'), body: await res.json() }; return cached.body;}import requests
BASE = "https://api.kotova.io/x/v1"cached = None # {"etag": ..., "body": ...}
def get_currencies(): global cached headers = {"If-None-Match": cached["etag"]} if cached else {} res = requests.get(f"{BASE}/currencies", headers=headers, timeout=10) if res.status_code == 304: return cached["body"] res.raise_for_status() cached = {"etag": res.headers["ETag"], "body": res.json()} return cached["body"]List currencies
Section titled “List currencies”GET /currencies returns every currency that can be sent or received through the API
right now, each with the networks it can be used on. It takes no parameters.
curl -s https://api.kotova.io/x/v1/currenciesconst res = await fetch('https://api.kotova.io/x/v1/currencies');const { data, catalog_version } = await res.json();import requests
res = requests.get("https://api.kotova.io/x/v1/currencies", timeout=10)res.raise_for_status()data = res.json()["data"]An answer, shortened to one currency:
{ "data": [ { "currency": "BTC", "name": "Bitcoin", "type": "cryptocurrency", "icon": { "url": "https://cdn.kotova.io/images/currencies/BTC.svg", "fallback_url": "https://assets.kotova.io/images/currencies/BTC.svg" }, "aliases": [], "networks": [ { "network": "btc", "network_name": "Bitcoin", "name": "Bitcoin", "ticker": "BTC", "contract_address": null, "token_standard": null, "decimals": 8, "memo": { "supported": false, "label": null }, "address_format": "standard", "icon": { "url": "https://cdn.kotova.io/images/currencies/BTC.svg", "fallback_url": "https://assets.kotova.io/images/currencies/BTC.svg" }, "send": true, "receive": true, "send_sources": ["sideshift", "chainflip", "changenow", "ffio"], "receive_sources": ["sideshift", "chainflip", "changenow", "ffio"] } ] } ], "has_more": false, "next_cursor": null, "catalog_version": "394da0f044c4", "updated_at": "2026-11-10T07:00:52Z"}Each entry in data is a currency:
| Field | Type | Meaning |
|---|---|---|
currency |
string | The identifier: the ticker, in upper case. |
name |
string | Display name, such as Tether. |
type |
string | Extensible: cryptocurrency, stablecoin, utility_token, meme and others. Accept values you do not know. |
icon |
icon or null |
The currency’s icon (see Icons). |
aliases |
array of strings | Old spellings still accepted for this currency. |
networks |
array | One entry per network the currency can be used on through the API. |
Each entry in networks is a leg:
| Field | Type | Meaning |
|---|---|---|
network |
string | The identifier: the network code, in lower case. |
network_name |
string | The network’s display name, such as TRON. |
name |
string | The currency’s display name on this network, such as Tether (TRC20). |
ticker |
string | Display ticker on this network. Never use it as an identifier. |
contract_address |
string or null |
The token contract; null for a network’s native coin. |
token_standard |
string or null |
TRC20, ERC20 and so on; null for a native coin. |
decimals |
integer | On-chain decimals of this leg. Amounts carry at most min(decimals, 12) fractional digits. |
memo |
object | Whether the network takes a memo, and what it calls it (see Memos). |
address_format |
string | Extensible: standard, or lightning_invoice on the Lightning network. |
icon |
icon or null |
The icon for this currency on this network. |
send |
boolean | Usable as from through the API right now. |
receive |
boolean | Usable as to through the API right now. |
send_sources |
array of slugs | Sources that accept this leg as from, in display order. Empty when send is false. |
receive_sources |
array of slugs | Sources that pay out this leg, in display order. Empty when receive is false. |
A currency is listed when at least one of its legs can be used in at least one direction. A leg that cannot be used in either direction is left out.
Send, receive and source lists
Section titled “Send, receive and source lists”send and receive say whether a leg can be used as from or as to right now. A
quote with a leg in a direction it does not support fails with a 422
currency_unavailable. Lightning legs always have
receive: false in v1: receiving on Lightning is not available yet.
The per-leg source lists let you apply your own source exclusions without an extra
request. A pair is served by exactly the sources that appear in the from leg’s
send_sources and in the to leg’s receive_sources. Remove the sources you exclude;
if nothing is left, do not offer the pair, because a quote would fail. You can narrow it
further with the fields from GET /sources, such as rate_types or
freeze_protection.
// Sources that could serve from → to, minus the slugs you exclude.function servingSources(currencies, from, to, excluded = []) { const leg = (id) => currencies .find((c) => c.currency === id.currency) ?.networks.find((n) => n.network === id.network); const a = leg(from); const b = leg(to); if (!a?.send || !b?.receive) return []; return a.send_sources.filter( (slug) => b.receive_sources.includes(slug) && !excluded.includes(slug) );}
servingSources(data, { currency: 'BTC', network: 'btc' }, { currency: 'USDT', network: 'trx' }, ['changenow']);These lists say which sources serve a leg. Whether a source returns a quote for a given amount, at a given moment, is only known when you request a quote.
Some networks need a memo (also called a destination tag) next to the address. memo
says whether a network takes one:
supportedistruewhen the network takes a memo;labelis what the network calls it, such asMemoorDestination Tag, andnullwhensupportedisfalse.
When supported is true, show a memo field labelled with label wherever your user
enters an address, and send it as to_memo or refund_memo when you
create the order. A memo on a network that does not take one is
refused with a 422 memo_not_supported.
Try itGET/currenciesNo key
List networks
Section titled “List networks”GET /networks returns every network that has at least one currency usable through the
API. It takes no parameters.
curl -s https://api.kotova.io/x/v1/networksconst res = await fetch('https://api.kotova.io/x/v1/networks');const { data } = await res.json();import requests
res = requests.get("https://api.kotova.io/x/v1/networks", timeout=10)res.raise_for_status()data = res.json()["data"]One entry from the answer:
{ "network": "xrp", "name": "XRP Ledger", "symbol": "XRP", "family": "other", "icon": { "url": "https://cdn.kotova.io/images/chains/xrp.svg", "fallback_url": "https://assets.kotova.io/images/chains/xrp.svg" }, "memo": { "supported": true, "label": "Destination Tag" }, "address_format": "standard", "evm_chain_id": null, "explorer_tx_url_template": "https://xrpscan.com/tx/{hash}"}| Field | Type | Meaning |
|---|---|---|
network |
string | The identifier: the network code, in lower case. |
name |
string | Display name, such as XRP Ledger. |
symbol |
string | Symbol of the network’s native coin. |
family |
string | Extensible, lower case: utxo, evm, solana, tron, cosmos, near or other. Accept values you do not know. |
icon |
icon or null |
The network’s icon. |
memo |
object | supported and label, as on a leg. |
address_format |
string | Extensible: standard, or lightning_invoice on the Lightning network. |
evm_chain_id |
integer or null |
The EVM chain id; null outside the evm family. |
explorer_tx_url_template |
string or null |
A block explorer link for a transaction. Replace {hash} with the transaction hash. |
The list has the same envelope as GET /currencies: has_more, next_cursor,
catalog_version and updated_at.
Try itGET/networksNo key
Find pairs
Section titled “Find pairs”GET /pairs tells you what a leg can be swapped into, what can be swapped into it, or
whether one particular pair is served. There is no list of every pair: give at least one
side.
| Parameter | Meaning |
|---|---|
from |
Currency sent. Needs from_network. |
from_network |
Network of from. Needs from. |
to |
Currency received. Needs to_network. |
to_network |
Network of to. Needs to. |
What you get depends on the sides you give:
- Both sides: that pair, when at least one source serves it, or an empty
datalist when none does. fromandfrom_networkonly: every leg thefromleg can be swapped into.toandto_networkonly: every leg that can be swapped into thetoleg.
curl -s 'https://api.kotova.io/x/v1/pairs?from=BTC&from_network=btc&to=USDT&to_network=trx'const params = new URLSearchParams({ from: 'BTC', from_network: 'btc', to: 'USDT', to_network: 'trx',});const res = await fetch(`https://api.kotova.io/x/v1/pairs?${params}`);const { data, warnings } = await res.json();import requests
res = requests.get( "https://api.kotova.io/x/v1/pairs", params={"from": "BTC", "from_network": "btc", "to": "USDT", "to_network": "trx"}, timeout=10,)res.raise_for_status()pairs = res.json()["data"]{ "data": [ { "from": { "currency": "BTC", "network": "btc" }, "to": { "currency": "USDT", "network": "trx" }, "rate_types": ["fixed", "variable"], "sources": ["sideshift", "changenow", "ffio"] } ], "has_more": false, "next_cursor": null, "catalog_version": "394da0f044c4", "updated_at": "2026-11-10T07:00:52Z", "warnings": []}Each pair has:
| Field | Type | Meaning |
|---|---|---|
from |
object | currency and network of the leg sent. |
to |
object | currency and network of the leg received. |
rate_types |
array | The rate types (fixed, variable) that at least one serving source offers. |
sources |
array of slugs | The sources that serve this pair, in display order. |
A pair carries no amounts and no limits: those come only from a
quote. warnings holds a deprecated_alias warning when you used an
old spelling.
Errors from pairs
Section titled “Errors from pairs”| Status | Code | When |
|---|---|---|
| 400 | invalid_request |
No side at all (details.reason is from_or_to_required), half a side (required, with details.field naming the missing parameter), an identifier in the wrong format, or an unknown parameter. |
| 422 | unknown_currency |
A leg is not in the catalogue. details.side says which. |
| 422 | currency_unavailable |
A leg exists but cannot be used in that direction right now. |
| 422 | same_currency |
Both sides name the same leg. |
Try itGET/pairsNo key
Currencies, legs, networks, sources and Kotova X itself come with an icon. Every icon is an object with two URLs:
{ "url": "https://cdn.kotova.io/images/currencies/BTC.svg", "fallback_url": "https://assets.kotova.io/images/currencies/BTC.svg"}urlis the file on Kotova’s CDN,https://cdn.kotova.io.fallback_urlis the same path on Kotova’s mirror,https://assets.kotova.io. Some networks block the CDN; ifurlfails to load, retry once withfallback_url. It isnullwhen an icon is hosted elsewhere, in which caseurlis that address as stored.- The icon itself can be
null. Show a placeholder, such as the ticker’s first letters, rather than a broken image.
A leg’s icon is the currency’s icon on that network: the leg’s own artwork where there
is one, otherwise the currency’s icon, otherwise the network’s.
Use the URLs exactly as returned. They are absolute and already percent-encoded, paths are case-sensitive, and they do not follow a naming pattern, so never build one from a ticker or a network code.
<img src="https://cdn.kotova.io/images/currencies/BTC.svg" data-fallback="https://assets.kotova.io/images/currencies/BTC.svg" onerror="this.onerror = null; if (this.dataset.fallback) this.src = this.dataset.fallback;" alt="BTC" width="24" height="24">// Builds an <img> for an icon object from the API (or null).function iconImg(icon, alt, size = 24) { const img = document.createElement('img'); img.alt = alt; img.width = img.height = size; if (!icon) return img; // or render your own placeholder img.src = icon.url; img.addEventListener('error', function retry() { img.removeEventListener('error', retry); if (icon.fallback_url) img.src = icon.fallback_url; }); return img;}Caching icons
Section titled “Caching icons”Icon files are served with Access-Control-Allow-Origin: *, and browsers and proxies
may cache them. You may also cache or self-host unmodified copies of
Kotova-hosted icon files to display Kotova data. A file can change under the same URL, so
refresh any copy you keep, for example once a week.
The Kotova X icon, which you must show wherever you present a Kotova X quote or order, is covered in Display requirements.