# Sources

> The independent liquidity sources behind every quote: how to list them, how to exclude one, and how the best source for a request is chosen.

- Source: https://docs.kotova.io/x/api/sources
- API base URL: https://api.kotova.io/x/v1
- OpenAPI document: https://api.kotova.io/x/v1/openapi.json

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

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 as
  `sideshift`, which is its identifier everywhere in the API.
- **Provider** means Kotova X, and nothing else. The `provider` block 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

`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](https://docs.kotova.io/x/api/currencies#how-the-catalogue-is-served): 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`](https://docs.kotova.io/x/api/errors#invalid_request).

**curl**

```bash
curl -s 'https://api.kotova.io/x/v1/sources?lang=en'
```

**JavaScript**

```js
const res = await fetch('https://api.kotova.io/x/v1/sources?lang=en');
const { provider, attribution, data } = await res.json();
```

**Python**

```python
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:

```json
{
  "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

`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

`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](https://docs.kotova.io/x/api/display-requirements). |

Both flags are `true`.

### 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](https://docs.kotova.io/x/api/orders).

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](https://docs.kotova.io/x/api/orders#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`](https://docs.kotova.io/x/api/errors#source_unavailable)
with `details.requote` set to `true`. Request a new quote in that case.

### 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`](https://docs.kotova.io/x/api/errors#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](https://docs.kotova.io/x/api/orders#refund-addresses).

### 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](https://docs.kotova.io/x/api/display-requirements).

**Endpoint:** `GET https://api.kotova.io/x/v1/sources` (List sources)

- Authentication: none (public endpoint, send no key)
- Weight: none
- Parameters:
  - `lang` (query, optional): Language of the source notice (en default). One of: `en`, `de`, `it`, `es`, `fr`, `ru`.

Example request:

```sh
curl 'https://api.kotova.io/x/v1/sources'
```

Example response (`200`):

```json
{
  "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": []
}
```

## How the best source is chosen

A [quote](https://docs.kotova.io/x/api/quotes) 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.

1. **Find the candidates.** These are the active sources that serve the pair: each one
   is in the `send_sources` of the `from` leg and in the `receive_sources` of the `to`
   leg. If there are none, the answer is a 422
   [`pair_unavailable`](https://docs.kotova.io/x/api/errors#pair_unavailable).

2. **Apply the request's filters.** A candidate is left out when it is in
   `exclude_sources`, when `freeze_protection` is `true` and it can freeze funds, when it
   does not offer the requested `rate_type`, when it needs `end_user_ip` and none was
   sent, when it needs a refund address at creation and `can_provide_refund_address` is
   `false`, or when it cannot take more orders right now. If nothing is left, the answer
   is a 422 [`no_source_available`](https://docs.kotova.io/x/api/errors#no_source_available), and
   `details.reasons` lists which filter removed which sources.

3. **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_skipped` warning, because a later quote may be better.

4. **Rank the answers.** Only quotes within the source's own minimum and maximum count.
   With `amount_type: "from"`, the largest amount received wins. With
   `amount_type: "to"`, the smallest amount sent wins. Amounts are compared as exact
   decimals. The winner is the quote's `source`; the next ones, up to two, best first,
   are its `fallback_sources`.

5. **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](https://docs.kotova.io/x/how-it-works/quoting-and-routing#ranking-parameters-and-weighting).

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](https://docs.kotova.io/x/api/quotes) and
[Errors](https://docs.kotova.io/x/api/errors) pages.

## 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](https://docs.kotova.io/x/api/currencies#send-receive-and-source-lists).

**curl**

```bash
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"
  }'
```

**JavaScript**

```js
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();
```

**Python**

```python
import os
import 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](https://docs.kotova.io/x/api/quotes) page.

> [!NOTE]
> **Freeze protection is a per-request filter**
>
> `freeze_protection` applies to the one request that sends it. It is not the same thing as
> [DEX-only mode](https://docs.kotova.io/x/security/dex-only-mode) in the Kotova X app, which is a setting stored
> on your user's device. Fewer sources qualify, so expect fewer pairs, a narrower amount
> range, and a rate that is at best the same as without the filter. Which sources can freeze funds, and why, is explained in
> [Source and freeze risk](https://docs.kotova.io/x/security/counterparty-risk).

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](https://docs.kotova.io/x/api/display-requirements#fallback-sources).
