Sources
A source is an independent service or protocol that executes a swap. Kotova is not one of them. It compares what the sources offer, and every quote and every order names the source that executes it.
Sources and the provider
Section titled “Sources and the provider”The API uses two words with fixed meanings:
- Source is the party that executes a swap. Your user’s deposit goes to an address
that belongs to the source, and the source pays out. Each source has a
slug, such assideshift, which is its identifier everywhere in the API. - Provider means Kotova X, and nothing else. The
providerblock names Kotova X, which compares the sources and routes the order to the one that made the best offer.
Slugs are stable. Sources are added, paused and removed over time, so do not hard-code a
list: read it from GET /sources, and make your code accept a slug it has not seen
before. The sources the API routes to can differ from the ones in the Kotova X app.
List sources
Section titled “List sources”GET /sources returns every source the API can route to, with its rules and the notice
your user must see before an order is created, plus the Kotova X provider block and
the display requirements. It needs no key, and it follows the same caching rules as the
catalogue endpoints: CDN-cached for 60
seconds, with a weak ETag and 304 answers to If-None-Match.
Its only parameter is lang, the language of the notices: en (the default), de,
it, es, fr or ru. A region suffix is accepted, so de-AT and de_DE both give
German. Any other value is not an error: the notices come in English, with an
unsupported_lang warning in warnings. Any other parameter is a 400
invalid_request.
curl -s 'https://api.kotova.io/x/v1/sources?lang=en'const res = await fetch('https://api.kotova.io/x/v1/sources?lang=en');const { provider, attribution, data } = await res.json();import requests
res = requests.get( "https://api.kotova.io/x/v1/sources", params={"lang": "en"}, timeout=10)res.raise_for_status()body = res.json()An answer, shortened to two sources:
{ "provider": { "name": "Kotova X", "icon": { "url": "https://cdn.kotova.io/images/brand/kotova-x/icon.svg", "fallback_url": "https://assets.kotova.io/images/brand/kotova-x/icon.svg" }, "icon_png": { "url": "https://cdn.kotova.io/images/brand/kotova-x/icon-256.png", "fallback_url": "https://assets.kotova.io/images/brand/kotova-x/icon-256.png" }, "website_url": "https://app.kotova.io" }, "attribution": { "provider_display_required": true, "source_notice_required_before_order": true, "docs_url": "https://docs.kotova.io/x/api/display-requirements" }, "data": [ { "slug": "sideshift", "name": "SideShift", "kind": "centralized", "status": "active", "icon": { "url": "https://cdn.kotova.io/images/counterparties/cp_icon_sideshift.svg", "fallback_url": "https://assets.kotova.io/images/counterparties/cp_icon_sideshift.svg" }, "terms_url": "https://sideshift.ai/legal", "rate_types": ["fixed", "variable"], "refund_address": "optional", "refund_address_forwarding": "at_creation", "requires_end_user_ip": true, "freeze_protection": false, "display_order": 1, "notice": { "text": "The exchange service is provided by SideShift. Creating an order confirms your agreement with the SideShift rules.", "link_text": "SideShift rules", "link_url": "https://sideshift.ai/legal", "lang": "en" } }, { "slug": "chainflip", "name": "Chainflip", "kind": "decentralized", "status": "active", "icon": { "url": "https://cdn.kotova.io/images/counterparties/cp_icon_chainflip.svg", "fallback_url": "https://assets.kotova.io/images/counterparties/cp_icon_chainflip.svg" }, "terms_url": null, "rate_types": ["variable"], "refund_address": "required", "refund_address_forwarding": "at_creation", "requires_end_user_ip": false, "freeze_protection": true, "display_order": 2, "notice": { "text": "The exchange service is provided by Chainflip. Creating an order confirms your agreement with the Kotova Terms of Service.", "link_text": "Kotova Terms of Service", "link_url": "https://kotova.io/terms/service?lang=en", "lang": "en" } } ], "has_more": false, "next_cursor": null, "catalog_version": "394da0f044c4", "updated_at": "2026-11-10T07:00:52Z", "warnings": []}The provider block
Section titled “The provider block”provider describes Kotova X. The same block comes with every quote and every order.
| Field | Type | Meaning |
|---|---|---|
name |
string | Always Kotova X. |
icon |
icon | The Kotova X icon as SVG. |
icon_png |
icon | The Kotova X icon as a 256-pixel PNG. |
website_url |
string | https://app.kotova.io, the Kotova X app. |
The attribution flags
Section titled “The attribution flags”attribution restates what every integration must show:
| Field | Type | Meaning |
|---|---|---|
provider_display_required |
boolean | Kotova X must appear by name and icon wherever you present its quotes or orders. |
source_notice_required_before_order |
boolean | The source’s notice must appear before your user confirms an order. |
docs_url |
string | The page that explains both: Display requirements. |
Both flags are true.
Source fields
Section titled “Source fields”Each entry in data is a source. The list is in display order.
| Field | Type | Meaning |
|---|---|---|
slug |
string | The identifier, such as sideshift. Lower case. |
name |
string | The display name. |
kind |
string | Extensible: centralized (a service run by a company), decentralized (a protocol) or internal. Accept values you do not know. |
status |
string | active, or unavailable when the API does not quote or create orders there right now. |
icon |
icon or null |
The source’s logo, or null when it may not be shown. |
terms_url |
string or null |
The source’s own rules page; null when its notice links to Kotova’s Terms of Service instead. |
rate_types |
array | The rate types the source offers: fixed, variable or both. |
refund_address |
string | When the source needs a refund address at creation: required (always), fixed_rate_only (for fixed-rate orders) or optional. |
refund_address_forwarding |
string | What an order’s refund address is used for at this source. Extensible: at_creation (it goes to the source with the order), when_needed (kept with the order, and given to the source only if the order needs a refund) or none (kept with the order for support; the source handles refunds itself). |
requires_end_user_ip |
boolean | Quotes and orders at this source need end_user_ip. Without it, the source is skipped. |
freeze_protection |
boolean | The source cannot freeze funds. A quote with freeze_protection: true keeps only these sources. |
display_order |
integer | The tie-break rank when two sources quote the same amount; lower comes first. |
notice |
object | The notice your user must see before confirming an order at this source. |
refund_address is the source’s general rule. Each quote states what applies to that
request in requirements.refund_address, and that is the value to follow when you
create the order.
A source with status: unavailable stays in the list but is not used: a quote never
comes from it. An order from a quote that named it goes to the quote’s next best source
(see Next best source); with
retry_next_best_source: false, or when none of the quote’s other sources is available
either, the answer is a 503 source_unavailable
with details.requote set to true. Request a new quote in that case.
Refund addresses per source
Section titled “Refund addresses per source”We recommend sending a refund address with every order, whatever the source: it is
never required unless the quote says so, but where the source takes it with the order,
a refund needs no extra step, and the quote’s fallback sources that need one stay
eligible. refund_address_forwarding says what happens to it at each source, and every
quote repeats it in its source blocks:
| Value | At this source | On the order |
|---|---|---|
at_creation |
The refund address, with its memo, goes to the source with the order, and the source checks it then. A refusal is a 422 invalid_address with details.field refund_address or refund_memo, and no other source is tried. |
refund.forwarded: true |
when_needed |
The source takes no refund address when the order is created. It is kept with the order and given to the source only if the order needs a refund. Today: FixedFloat (ffio). |
refund.forwarded: false, and the 201 carries the warning refund_address_on_file |
none |
The source never takes one and handles refunds itself. It is kept with the order for support only. Today: CCE Cash (ccecash). |
refund.forwarded: false, and the 201 carries the warning refund_address_on_file |
Every other source takes it at_creation today. Read the field rather than keeping a
list, and accept values you do not know. See
Refund addresses.
The notice
Section titled “The notice”notice holds the line your user must see before confirming an order at this source,
word for word as the Kotova X app shows it:
| Field | Type | Meaning |
|---|---|---|
text |
string | The full line. |
link_text |
string | The part of text to render as a link. |
link_url |
string | Where that link goes: the source’s rules, or Kotova’s Terms of Service for a source without its own. |
lang |
string | The language the notice is in. |
Quotes and orders carry the notices of their sources in the same shape: a quote for
its source and each of its fallback_sources, an order for the source that executes
it. How to show it,
with the text in all six languages, is in
Display requirements.
Try itGET/sourcesNo key
How the best source is chosen
Section titled “How the best source is chosen”A quote returns the source with the best offer for your user, under the published ranking rule, and the next best ones as its fallback sources. The Kotova X app ranks sources by the same rule.
-
Find the candidates. These are the active sources that serve the pair: each one is in the
send_sourcesof thefromleg and in thereceive_sourcesof thetoleg. If there are none, the answer is a 422pair_unavailable. -
Apply the request’s filters. A candidate is left out when it is in
exclude_sources, whenfreeze_protectionistrueand it can freeze funds, when it does not offer the requestedrate_type, when it needsend_user_ipand none was sent, when it needs a refund address at creation andcan_provide_refund_addressisfalse, or when it cannot take more orders right now. If nothing is left, the answer is a 422no_source_available, anddetails.reasonslists which filter removed which sources. -
Ask the remaining sources. They are asked at the same time, or the answers come from a short-lived shared cache of a recent request for the same pair, amount and rate type. A source that times out, fails or is busy is skipped; a request never fails while another source answers. Skipped sources are named in a
sources_temporarily_skippedwarning, because a later quote may be better. -
Rank the answers. Only quotes within the source’s own minimum and maximum count. With
amount_type: "from", the largest amount received wins. Withamount_type: "to", the smallest amount sent wins. Amounts are compared as exact decimals. The winner is the quote’ssource; the next ones, up to two, best first, are itsfallback_sources. -
Break ties. An exact tie goes to the lower
display_order, then to the name in alphabetical order.
Nothing else weighs. What Kotova earns on an order never enters the ranking, and neither does how fast a source usually is. The rule is the one described in Quoting and routing.
When no source returns a usable quote, the answer explains why: the amount is below every minimum or above every maximum, every source was skipped for the moment, or the filters removed every source. The codes are listed on the Quotes and Errors pages.
Excluding sources
Section titled “Excluding sources”Exclusions are part of each quote request, not a stored setting. Four fields of
POST /quote remove sources:
| Field | Effect |
|---|---|
exclude_sources |
Leaves out the listed slugs. At most 20, matched in any case. A slug the API does not know is ignored, with an unknown_source warning. |
freeze_protection |
When true, keeps only sources whose freeze_protection is true, the ones that cannot freeze funds. Defaults to false. |
can_provide_refund_address |
When false, leaves out sources that need a refund address at creation. Defaults to true. |
end_user_ip |
Sources whose requires_end_user_ip is true are skipped when it is missing, with an end_user_ip_missing warning. |
The order is then created at the source the quote named or, when that source cannot take
it, at one of the quote’s fallback_sources, which passed the same filters.
POST /orders takes no filters.
To show your user only what your exclusions leave, use the per-leg source lists described
in Currencies, networks and icons.
curl -s https://api.kotova.io/x/v1/quote \ -H "Authorization: Bearer $KOTOVA_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "from": {"currency": "BTC", "network": "btc"}, "to": {"currency": "USDT", "network": "trx"}, "amount": "0.05", "rate_type": "variable", "exclude_sources": ["changenow"], "freeze_protection": false, "end_user_ip": "93.184.216.34" }'const res = await fetch('https://api.kotova.io/x/v1/quote', { method: 'POST', headers: { Authorization: `Bearer ${process.env.KOTOVA_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ from: { currency: 'BTC', network: 'btc' }, to: { currency: 'USDT', network: 'trx' }, amount: '0.05', rate_type: 'variable', exclude_sources: ['changenow'], freeze_protection: false, end_user_ip: '93.184.216.34', }),});const quote = await res.json();import osimport requests
key = os.environ["KOTOVA_API_KEY"]res = requests.post( "https://api.kotova.io/x/v1/quote", headers={"Authorization": f"Bearer {key}"}, json={ "from": {"currency": "BTC", "network": "btc"}, "to": {"currency": "USDT", "network": "trx"}, "amount": "0.05", "rate_type": "variable", "exclude_sources": ["changenow"], "freeze_protection": False, "end_user_ip": "93.184.216.34", }, timeout=30,)quote = res.json()end_user_ip is your user’s public IP address. Leave the field out if you do not have
it; the sources that need it are then skipped. The quote echoes what was applied in
filters, and the full request is described on the Quotes page.
Every quote and every order names its source in a source block, and a quote names its
fallback sources in fallback_sources. Your application must show the notices of the
quote’s source and of its fallback sources before your user confirms (the fallback
notices may follow the quote’s notice, also in an expandable section directly next to
it), or send retry_next_best_source: false with the order. See
Display requirements.