API documentation

Bring exchange to your product: get a quote, create an order and track execution. API methods, examples and integration steps in one place.

Start

Create a key in your account and include it in the header of each request.

Base URL

https://co-re.kz

Integration sequence

  1. Keep the key on your server. Use the embed widget if you do not need a custom exchange interface. Configure widget
  2. Load the currency catalogue, check networks and limits, then request a quote.
  3. Collect payout and refund addresses and any required memo. Create the exchange with a unique Idempotency-Key.
  4. Show deposit instructions only for AWAITING_DEPOSIT. Wait for review on REVIEW. Give the client the trackUrl and poll the status.

Open partner dashboard

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. Up to five active keys per account. There is no sandbox: a key is live from the first minute.

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

60 requests per minute per key, and 120 per minute per IP before the key is checked. Polling every open deal every 15-30 seconds counts against this.

Conventions

Amounts are decimal strings, for example "0.0213585". Do not convert them to floating-point numbers: this loses precision.

Specify both the asset code and network. USDT on TRON and USDT on Ethereum require different addresses and are not interchangeable.

Use code to handle the error and message to inform the user.

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

Times are UTC, ISO 8601.

Currency catalogue

GET/api/v1/currencies

What CORE can exchange right now: code, network, which direction works, whether a memo is required, and the precision. It asks no provider, so it is cheap and cacheable. Integration starts here: without it a coin list has to be hardcoded.

Access: Live key.

Response 200

{
  "currencies": [
    {
      "asset": "USDT",
      "network": "TRON",
      "name": "Tether USD",
      "sendEnabled": true,
      "receiveEnabled": true,
      "memoRequired": false,
      "decimals": 6
    },
    {
      "asset": "XRP",
      "network": "XRP",
      "name": "XRP",
      "sendEnabled": false,
      "receiveEnabled": true,
      "memoRequired": true,
      "decimals": 6
    }
  ],
  "count": 2
}

Can return

401 unauthorized429 rate_limited

Amount band

POST/api/v1/limits

The minimum and maximum for a pair, both denominated in the send asset. Derived by asking providers with two deliberately out-of-range amounts, so it costs two provider round-trips: cache it per pair, do not call it per keystroke. null means no provider stated one - not zero, and not unlimited.

Access: Live key.

Request

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

Response 200

{
  "limits": {
    "send":    { "asset": "BTC",  "network": "BTC" },
    "receive": { "asset": "USDT", "network": "TRON" },
    "min": "0.0007",
    "max": null
  }
}

Can return

400 invalid_request401 unauthorized422 pair_unavailable429 rate_limited

Validate an address

POST/api/v1/validate-address

The same question create will ask, from the same module - validate first and create will not then refuse the address. It also answers the memo question, which is the one that loses coins: an XRP payout without its tag is unrecoverable, and knowing that before the form is submitted is worth more than knowing it after. Structural only: shape, charset, length. Not a checksum, and not whether anybody controls it.

Access: Live key.

Request

{
  "asset":   "XRP",
  "network": "XRP",
  "address": "rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH"
}

Response 200

{
  "valid": true,
  "networkKnown": true,
  "memoRequired": true,
  "checked": "structure"
}

Can return

400 invalid_request401 unauthorized429 rate_limited

Commission balance

GET/api/v1/balance

What has been credited and what you can withdraw, in USD. Every figure is zero today: the partner rate is zero on every key until the commission has a way of being withheld at the executor. Computed from the same view the payout path uses, so this figure cannot disagree with what a payout would pay. `held` is not "not yet" but "something is wrong with that deal".

Access: Live key.

Response 200

{
  "balance": {
    "currency": "USD",
    "available": "0",
    "pending": "0",
    "held": "0",
    "reserved": "0",
    "paid": "0",
    "minPayout": "10"
  }
}

Can return

401 unauthorized503 balance_unavailable429 rate_limited

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 where its own terms require it, and marked undisclosed otherwise.

Access: Live 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",
    "expiresAt": "2026-08-24T09:14:44Z",
    "executor": { "disclosed": false, "executorTermsAccepted": true },
    "coreMarkup": "0",
    "partnerMarkup": "0",
    "comparedExecutors": 3
  }
}

What you can show your own users

The /quotes response carries executor: by default only the fact that there is one and that its terms are accepted. The name, disclosure and passport (who holds the deposit, rate modes, terms, and our own history with that provider) come back where the executor's terms require naming it. comparedExecutors is a count by default: how many executors answered. Whether you show any of it to your own user is your decision, not our requirement. The executor names are switched on per key, for a partner that really does show the comparison.

Partner rewards are not live yet. partnerMarkup is always the string "0"; do not use it to calculate earnings or promise payouts. The API and exchange tracking are available separately from the rewards programme. Agree accrual and payout terms with CORE before a commercial launch.

Can return

400 invalid_request400 invalid_amount401 unauthorized422 amount_below_minimum422 amount_above_maximum422 pair_unavailable429 rate_limited

Deal list

GET/api/v1/deals

Your deals, newest first, paged by cursor. Query parameters: limit (default 50, maximum 100) and cursor from the previous page's nextCursor. Cursor rather than offset: new deals arrive at the top, so offset=50 means something different every time, and a partner walking the pages would see some rows twice and miss others. partnerMarkup and commissionUsd are zero today: the rate on the key is zero.

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

Response 200

{
  "deals": [
    {
      "id": "7f1c...",
      "status": "COMPLETED",
      "createdAt": "2026-08-24T09:12:44Z",
      "send":    { "asset": "BTC",  "network": "BTC",  "amount": "0.05" },
      "receive": { "asset": "USDT", "network": "TRON", "amount": "3180.31", "final": true },
      "partnerMarkup": "0",
      "commissionUsd": "0"
    }
  ],
  "nextCursor": "MjAyNi0wOC0yNFQwOToxMjo0NFp8N2YxYw"
}

Can return

400 invalid_request401 unauthorized429 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

If creation is unconfirmed, the API returns 202 with status REVIEW and no deposit address. This is not confirmation that the exchange was created. Do not request a deposit from the customer; an operator will review the order.

Can return

400 invalid_request400 invalid_amount400 idempotency_key_required409 idempotency_key_conflict503 replay_executor_unavailable503 order_reservation_failed422 executor_refused_request401 unauthorized422 amount_below_minimum422 amount_above_maximum422 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 },
    "deposit": {
      "address": "bc1q...",
      "tag": null,
      "amount": "0.05",
      "deadline": "2026-08-24T09:42:44Z"
    },
    "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

Idempotency-Key is required when creating an exchange. Without it, the API returns 400. A retry with the same key returns the original deal without creating another.

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. Format: 8 to 128 characters, ASCII letters, digits and . _ : - , starting with a letter or digit; a UUID works.

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, or the key is invalid or revoked.
404not_foundNo deal with that id among the ones your key created. Another tenant's deal also returns 404: existence is not leaked.
409idempotency_key_conflictThe same Idempotency-Key against a different exchange: another pair, amount or address. Generate one key per exchange attempt and reuse it only for retries of that attempt. Since migration 085 this no longer fires merely because the comparison routed elsewhere on the retry.
503replay_executor_unavailableA retry with the same Idempotency-Key: the exchange already exists, but the executor holding it cannot be reached right now. Retry with the SAME key shortly. A new key would create a second real order.
503order_reservation_failedThe order could not be reserved; the provider was never called and no order exists. Retry with the SAME key. This used to surface as a bare 500.
422executor_refused_requestThe executor validated the request and refused it: a bad address, an amount out of range, an unsupported asset, or an expired quote. No order exists and no deposit is needed. Correct it and submit with a NEW Idempotency-Key. This used to return 202 REVIEW telling you to hold a deposit for an order that was never coming.
422amount_below_minimumThe amount is below the provider minimum for this pair. Increase it.
422amount_above_maximumThe amount is above the provider maximum for this pair. Reduce it. This case used to arrive as pair_unavailable, which was false.
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.
503balance_unavailableThe balance cannot be read right now. An error, not a zero: a false zero on a money endpoint is worse than a 5xx.
429rate_limitedMore than 60 requests per minute on one key.

What the API does not do

Not supported: fixed rates, cancelling a created exchange, specifying an exact receive amount, separate network-fee breakdowns, webhooks or a sandbox. Active providers use floating rates.