API documentation

Three methods: get a rate, create an exchange, read its status. Amounts as strings, errors in one shape, and you issue the key yourself.

Start

A live key is issued immediately, with no forms and no waiting. Create it, put it in the header, go.

Base URL

https://co-re.kz
Get a key

Keys

The key goes in the Authorization header as a Bearer token. It is shown once at creation. It can be revoked and rotated without downtime.

curl https://co-re.kz/api/v1/quotes \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{"send":{"asset":"BTC","network":"BTC"},
       "receive":{"asset":"USDT","network":"TRON"},
       "amount":"0.05"}'
Access

There is also a test key if you want to request rates without creating anything: it works on /quotes only, and answers 401 on create and status.

60 requests per minute, per key.

Conventions

Money is always a string: "0.0213585", never a number. A float loses precision on somebody's balance.

An asset is a code and a network. A bare code is ambiguous: USDT on TRON and USDT on ETH are different coins, and getting it wrong is irreversible.

Every error has the same shape. Branch on code, show message.

{ "error": { "code": "pair_unavailable", "message": "..." } }

Times are UTC, ISO 8601.

Quote

POST/api/v1/quotes

The best live rate for a pair and amount across active providers. Creates nothing and moves nothing. The executor is named in the response.

Access: Live or sandbox key.

Request

{
  "send":    { "asset": "BTC",  "network": "BTC" },
  "receive": { "asset": "USDT", "network": "TRON" },
  "amount":  "0.05"
}

Response 200

{
  "quote": {
    "send":    { "asset": "BTC",  "network": "BTC",  "amount": "0.05" },
    "receive": { "asset": "USDT", "network": "TRON", "amount": "3212.44" },
    "rateType": "float",
    "executor": {
      "name": "ChangeNOW",
      "disclosure": "...",
      "passport": {
        "name": "ChangeNOW",
        "rateModes": ["float"],
        "custody": "executor",
        "refundAddressAtCreate": true,
        "underpaymentResolution": false,
        "terms": { "version": "2026-07-18", "url": "https://..." },
        "memoNetworks": ["XRP"],
        "coreTrackRecord": {
          "legs": 4, "completed": 4, "failedOrRefunded": 0,
          "averageMinutes": 11, "lastUsedAt": "2026-07-20T14:49:13Z"
        }
      }
    },
    "coreMarkup": null,
    "comparedExecutors": [
      { "name": "ChangeNOW",    "amount": "3212.44" },
      { "name": "LetsExchange", "amount": "3209.10" },
      { "name": "StealthEX",    "amount": "3201.77" }
    ]
  }
}

What you can show your own users

The /quotes response carries three fields you can pass straight to your user without asking us: executor.passport (who holds the deposit, rate modes, terms, our own history with that provider), coreMarkup, and comparedExecutors with everyone who answered.

coreMarkup is always null: the receive amount is exactly what the provider promised, with nothing added on top.

Can return

400 invalid_request400 invalid_amount401 unauthorized422 amount_below_minimum422 pair_unavailable429 rate_limited

Create an exchange

POST/api/v1/deals

Picks the executor, re-quotes with it and returns the deposit address your client pays into. A rate from /quotes is never accepted: it is always recomputed.

Access: Live key only. An Idempotency-Key header is required.

Request

{
  "send":          { "asset": "BTC",  "network": "BTC" },
  "receive":       { "asset": "USDT", "network": "TRON" },
  "amount":        "0.05",
  "payoutAddress": "TQ5...",
  "refundAddress": "bc1q...",
  "memo":          null
}

Response 201

{
  "deal": {
    "id": "7f1c...",
    "status": "AWAITING_DEPOSIT",
    "send":    { "asset": "BTC",  "network": "BTC",  "amount": "0.05" },
    "receive": { "asset": "USDT", "network": "TRON", "amount": "3212.44" },
    "deposit": {
      "address":  "bc1q...",
      "tag":      null,
      "amount":   "0.05",
      "deadline": "2026-07-27T10:15:00Z"
    },
    "trackUrl": "https://<site>/track/7f1c..."
  }
}
202: under review, do not send a deposit

When the outcome of the provider order is unknown, the answer is 202 with status REVIEW and no deposit address. There is no auto-retry; an operator resolves it. Treating any 2xx as success tells your client to send coins to a deal with nowhere to receive them.

Can return

400 invalid_request400 invalid_amount400 idempotency_key_required401 unauthorized422 amount_below_minimum422 pair_unavailable422 invalid_payout_address422 invalid_refund_address422 memo_required422 refund_memo_unsupported429 rate_limited

Status

GET/api/v1/deals/:id

The current state of your deal, reconstructed from the event log. While the deposit is awaited the response repeats the deposit details.

Access: Live key only. Only deals your key created are visible.

Response 200

{
  "deal": {
    "id": "7f1c...",
    "status": "ACTIVE",
    "send":    { "asset": "BTC",  "network": "BTC",  "amount": "0.05" },
    "receive": { "asset": "USDT", "network": "TRON", "amount": "3212.44", "final": false },
    "trackUrl": "https://<site>/track/7f1c..."
  }
}

The trackUrl in a response is the public deal page. You can hand it to your client: no account is needed, and it shows the status, the hashes and a support form.

Can return

401 unauthorized404 not_found429 rate_limited

Deal statuses

Create answers AWAITING_DEPOSIT or REVIEW, which describe the execution step. Below are the statuses that reading the deal returns. Poll the GET and branch on that.

DRAFTRecorded; the provider order is not confirmed yet.
ACTIVEIn flight: awaiting the deposit, confirmations, the exchange or the payout.
COMPLETEDThe payout has been sent to the client. · terminal
FAILEDThe exchange did not happen and no refund was made. · terminal
REFUNDEDThe provider returned the coins to the refund address. · terminal
EXPIREDThe deposit did not arrive in time. · terminal

There are no webhooks. Poll GET /api/v1/deals/:id, 15-30 seconds is a sensible interval, and stop on a terminal status.

Idempotency

The Idempotency-Key header is required on create. A retry with the same key returns the original deal instead of creating a second one. This used to fall back to a generated key when the header was absent, so a retry after a timeout created a second provider order, which is a second charge to the client. It is now a 400.

curl https://co-re.kz/api/v1/deals \
  -H "Authorization: Bearer <api_key>" \
  -H "Idempotency-Key: 6b1f2c58-0a1e-4b3f-9d21-8c0f4a7e5b12" \
  -H "Content-Type: application/json" \
  -d '{"send":{"asset":"BTC","network":"BTC"},
       "receive":{"asset":"USDT","network":"TRON"},
       "amount":"0.05",
       "payoutAddress":"TQ5...",
       "refundAddress":"bc1q..."}'

Generate the key per exchange attempt, not per HTTP request. Retry with the same key until you get a final answer.

Errors

HTTPcode
400invalid_requestThe body is not JSON, or a required field is missing: send, receive, amount, payoutAddress, refundAddress.
400invalid_amountamount is not a positive decimal string.
400idempotency_key_requiredPOST /deals came without an Idempotency-Key header. Without it a retry would create a second exchange.
401unauthorizedNo Authorization header, the key is invalid or revoked, or a sandbox key hit a live-only endpoint.
404not_foundNo deal with that id among the ones your key created. Another tenant's deal also returns 404: existence is not leaked.
422amount_below_minimumThe amount is below the provider minimum for this pair. Increase it.
422pair_unavailableNo active provider quotes this pair right now, or the instrument was disabled between the quote and the create.
422invalid_payout_addresspayoutAddress does not look like an address on the receive network. Checked before any provider call.
422invalid_refund_addressrefundAddress does not look like an address on the send network.
422memo_requiredThe receive network requires a memo or tag and the memo field was not sent.
422refund_memo_unsupportedThe send network identifies the beneficiary by memo and there is no refund memo field. refundAddress is mandatory, so this send asset is not supported.
429rate_limitedMore than 60 requests per minute on one key.

What the API does not do

Right now the API cannot do: a fixed rate (every active provider is float-only), cancelling an exchange after creation, a separate network-fee breakdown (providers do not give us one and we will not invent it), or webhooks.