Guides
Buy or sell crypto
Ask for a quote, then accept it within a few seconds. This guide covers amounts, pending trades, the errors to expect, when a quote can be refused, and how to retry without trading twice.
Before you start
- A key with the
tradescope. Addaccount:readto check your balance first. We recommend an IP allowlist for the key. - USDC in the account to buy with, or the asset to sell. Trades settle in USDC.
- Trading switched on for the API: GET
/v1/statusshowstrading_enabled.
The flow
Create a quote
POST /v1/quoteswith the asset, the side and an amount returns a firm price, valid for a few seconds (5 today).Accept it
POST /v1/tradeswith the quote's id and anIdempotency-Key, beforeexpires_at. The answer is the trade.Follow a pending trade
Usually the trade is
filledat once (201). If it's stillpending(202), checkGET /v1/trades/{id}until it'sfilledorfailed.
Amounts
Give exactly one of quote_amount (in USDC) and base_amount (in the asset):
| You want to | Send |
|---|---|
| Spend 100 USDC on BTC | side: "buy", quote_amount: "100.00" |
| Get 0.001 BTC | side: "buy", base_amount: "0.001" |
| Sell 0.001 BTC | side: "sell", base_amount: "0.001" |
| Sell enough BTC for 100 USDC | side: "sell", quote_amount: "100.00" |
The quote fills in the other amount at your account tier's price; the fee is included in the price. Amounts are decimal strings, rounded to the asset's base_increment. Each asset's smallest and largest trade, in USDC, are in GET/v1/assets, and your daily API volume is in GET/v1/limits. GET/v1/assets has every coin you can trade, and a new coin appears there as soon as Myntkaup lists it.
Create a quote
# Signed by sign-headers.sh: see Authentication
body='{"asset":"BTC","side":"buy","quote_amount":"100.00"}'
curl https://public-api.myntkaup.is/v1/quotes \
-H "Content-Type: application/json" \
$(./sign-headers.sh POST /v1/quotes "$body" | sed 's/^/-H /') \
--data "$body"import { Myntkaup } from "@myntkaup/api";
const client = new Myntkaup(); // reads the key from the environment
const quote = await client.quotes.create({ asset: "BTC", side: "buy", quoteAmount: "100.00" });
console.log(quote.id, quote.base_amount, quote.expires_at);from myntkaup import Myntkaup
client = Myntkaup() # reads the key from the environment
quote = client.quotes.create(asset="BTC", side="buy", quote_amount="100.00")
print(quote["id"], quote["base_amount"], quote["expires_at"]){
"id": "qt_01JB8X2Q4M7Z3V9K5T1N6R0P2W",
"asset": "BTC",
"side": "buy",
"quote_currency": "USDC",
"base_amount": "0.00104210",
"quote_amount": "100.00",
"price": "95960.12",
"isk_estimate": "13421",
"status": "open",
"created_at": "2026-11-03T10:15:10Z",
"expires_at": "2026-11-03T10:15:20Z",
"trade_id": null
}- A quote is valid until
expires_at, a few seconds aftercreated_at(5 today), and can be accepted once. - A key can hold at most 5 open quotes; a sixth gets
422 limit_exceeded. - The quote checks the size, whether the asset can be traded and your account up front, so most problems show here rather than at the trade:
amount_below_minimum,amount_above_maximum,asset_not_tradable,limit_exceeded,trading_halted,service_unavailable. GET /v1/quotes/{id}shows itsstatus:open,accepted,expired,filledorfailed, and thetrade_idonce accepted.
Accept the quote
# Signed by sign-headers.sh: see Authentication
body='{"quote_id":"qt_01JB8X2Q4M7Z3V9K5T1N6R0P2W"}'
idempotency_key=$(uuidgen) # reuse it to retry safely
curl https://public-api.myntkaup.is/v1/trades \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $idempotency_key" \
$(./sign-headers.sh POST /v1/trades "$body" | sed 's/^/-H /') \
--data "$body"import { Myntkaup } from "@myntkaup/api";
const client = new Myntkaup(); // reads the key from the environment
// Sends a random Idempotency-Key unless you pass idempotencyKey.
const trade = await client.trades.create({ quoteId: "qt_01JB8X2Q4M7Z3V9K5T1N6R0P2W" });
console.log(trade.status, trade.base_amount);from myntkaup import Myntkaup
client = Myntkaup() # reads the key from the environment
# Sends a random Idempotency-Key unless you pass idempotency_key.
trade = client.trades.create(quote_id="qt_01JB8X2Q4M7Z3V9K5T1N6R0P2W")
print(trade["status"], trade["base_amount"]){
"id": "tr_01JB8X2Q4M7Z3V9K5T1N6R0P2W",
"quote_id": "qt_01JB8X2Q4M7Z3V9K5T1N6R0P2W",
"asset": "BTC",
"side": "buy",
"quote_currency": "USDC",
"base_amount": "0.00104210",
"quote_amount": "100.00",
"price": "95960.12",
"status": "filled",
"source": "public_api",
"created_at": "2026-11-03T10:15:12Z",
"filled_at": "2026-11-03T10:15:13Z",
"failure_reason": null
}201 Created means the trade finished: status is filled, or failed with a failure_reason. 202 Accepted means it's still pending.
What can go wrong:
| Code | What happened | What to do |
|---|---|---|
quote_expired | The quote's time was up. | Create a new quote and accept it sooner. |
price_moved | The market moved too far from the quoted price. | Create a new quote. |
quote_already_used | The quote was already accepted, perhaps by an earlier try. | GET /v1/quotes/{id} gives its trade_id. |
insufficient_funds | The available balance doesn't cover it any more. | Check GET /v1/balances. |
limit_exceeded | Today's API volume or trade count would be exceeded. | Wait for the next UTC day. |
trading_halted (503) | Trading is paused, or this asset is for a few seconds. | Retry after Retry-After seconds. |
service_unavailable (503) | We had no current price for the asset. | Retry after Retry-After seconds. |
too_many_concurrent_requests (429) | Another trade for the account is still running. | Wait for it, then retry. |
When a quote is refused
A quote holds its price only for its few seconds, and only while the market stays close to it. We check the price again when you accept, and refuse the trade if the market has since moved against the quote by more than your tolerance. The rules are the same for every customer:
-
Your tolerance depends on your fee tier: half of what your fee leaves after our own trading cost, and never more than 0.5 %. The lowest fees therefore tolerate the smallest moves.
Your fee Tolerance today 1.2 % or more 0.50 % 1.00 % 0.41 % 0.50 % 0.16 % 0.25 % 0.035 % A move in your favour never refuses a trade; you get the quoted price either way.
-
Only current prices. We quote and fill only on a price that's at most a couple of seconds old. If we don't have one, you get
503 service_unavailablewithRetry-After. -
Fast markets. After a fast move in an asset (1 % within a minute), quotes and trades in it pause for 30 seconds:
503 trading_haltedwithRetry-After. The app keeps trading. -
How fills turn out. We follow how each key's trades compare with the market afterwards. A key that accepts mostly after the market has moved against its quotes may be reviewed, and its limits lowered.
A refused trade charges nothing. Ask for a new quote.
Pending trades
A trade still running after 20 seconds, or one whose outcome needs a manual check, is answered 202 with status: "pending". The funds stay held meanwhile. Follow it with GET/v1/trades/{id}:
- check after 1 second, then back off (2, 4, 8 seconds…);
- stop when
statusisfilledorfailed; - don't start another trade with the same funds while it's pending.
{
"id": "tr_01JB8X2Q4M7Z3V9K5T1N6R0P2W",
"quote_id": "qt_01JB8X2Q4M7Z3V9K5T1N6R0P2W",
"asset": "BTC",
"side": "buy",
"quote_currency": "USDC",
"base_amount": "0.00104210",
"quote_amount": "100.00",
"price": "95960.12",
"status": "pending",
"source": "public_api",
"created_at": "2026-11-03T10:15:12Z",
"filled_at": null,
"failure_reason": null
}Timeouts and retries
If POST /v1/trades times out, or answers 502, 503 or 504, retry with the same Idempotency-Key and the same body. If the first attempt went through, you get its trade back; if not, it runs now. Never retry with a new key unless you've confirmed with GET /v1/quotes/{id} that the quote wasn't accepted. See Idempotency.
The buy() and sell() helpers
The SDKs wrap the whole flow: they create the quote, accept it, and poll a pending trade for up to 30 seconds with backoff. They return the trade.
import { Myntkaup } from "@myntkaup/api";
const client = new Myntkaup();
const bought = await client.buy({ asset: "BTC", quoteAmount: "100.00" }); // spend 100 USDC
console.log(bought.status, bought.base_amount, bought.price);
const sold = await client.sell({ asset: "BTC", baseAmount: "0.0005" }); // sell 0.0005 BTC
console.log(sold.status, sold.quote_amount);from myntkaup import Myntkaup
client = Myntkaup()
bought = client.buy(asset="BTC", quote_amount="100.00") # spend 100 USDC
print(bought["status"], bought["base_amount"], bought["price"])
sold = client.sell(asset="BTC", base_amount="0.0005") # sell 0.0005 BTC
print(sold["status"], sold["quote_amount"])A trade can still be pending after the helper's 30 seconds. Check status before you treat it as done.
Example: a recurring buy
Buys 25 USDC of BTC, trying once more with a fresh quote if the price moved. Run it from a scheduler, such as cron, once a day.
import { ApiError, Myntkaup } from "@myntkaup/api";
const client = new Myntkaup();
async function buyOnce() {
for (let attempt = 1; attempt <= 2; attempt++) {
try {
return await client.buy({ asset: "BTC", quoteAmount: "25.00" });
} catch (error) {
const retryable = error instanceof ApiError && ["price_moved", "quote_expired"].includes(error.code);
if (!retryable || attempt === 2) throw error;
}
}
}
const trade = await buyOnce();
console.log(new Date().toISOString(), trade?.id, trade?.status, trade?.base_amount);from datetime import datetime, timezone
import myntkaup
from myntkaup import Myntkaup
client = Myntkaup()
def buy_once():
for attempt in (1, 2):
try:
return client.buy(asset="BTC", quote_amount="25.00")
except myntkaup.ApiError as error:
if error.code not in ("price_moved", "quote_expired") or attempt == 2:
raise
trade = buy_once()
print(datetime.now(timezone.utc).isoformat(), trade["id"], trade["status"], trade["base_amount"])Checklist
- The key has
trade, an IP allowlist, and nothing it doesn't need. - Amounts are decimal strings, never floats.
- Each trade has one
Idempotency-Key, reused on every retry. -
price_movedandquote_expiredlead to a new quote, not a loop. -
trading_haltedandservice_unavailablewait forRetry-After. - Pending trades are polled with backoff.
-
request_idis logged for every failure.