SDKs and CLI

Python SDK

myntkaup: a client for Python 3.10 and later, synchronous or async, built on httpx and cryptography. It signs requests, retries safely and returns the API's own JSON.

Install

Shell
pip install myntkaup
  • Python 3.10 or later, typed (py.typed).
  • Two dependencies: httpx and cryptography.
  • Server-side only. A private key never belongs in a client-side app.

Configure

Python
import os

from myntkaup import Myntkaup

# Reads MYNTKAUP_API_KEY_ID and MYNTKAUP_API_PRIVATE_KEY from the environment…
client = Myntkaup()

# …or give everything explicitly.
explicit = Myntkaup(
    key_id=os.environ["MYNTKAUP_API_KEY_ID"],
    private_key=os.environ["MYNTKAUP_API_PRIVATE_KEY"],
    timeout=30.0,
    max_retries=2,
)
OptionDefaultMeaning
key_idMYNTKAUP_API_KEY_IDThe key id, mk_live_… or mk_test_….
private_keyMYNTKAUP_API_PRIVATE_KEYThe private key, mk_sk_….
base_urlhttps://public-api.myntkaup.is; staging for a mk_test_ keyWhere requests go.
timeout30.0Per attempt, in seconds.
max_retries2Retries after the first attempt.
transportthe defaultAn httpx transport, for tests or proxies.

Methods

MethodEndpoint
client.status()GET /v1/status (unsigned)
client.me()GET /v1/me
client.revoke_key()POST /v1/me/key/revoke
client.claim_key_pairing(code=, public_key=, client=)POST /v1/key-pairings/claim (unsigned)
client.assets.list()GET /v1/assets
client.prices.list()GET /v1/prices
client.prices.get("BTC")GET /v1/prices/{asset}
client.balances.list()GET /v1/balances
client.limits.get()GET /v1/limits
client.quotes.create(asset=, side=, quote_amount=)POST /v1/quotes
client.quotes.get(id)GET /v1/quotes/{id}
client.trades.create(quote_id=, idempotency_key=None)POST /v1/trades
client.trades.get(id)GET /v1/trades/{id}
client.trades.list(**filters)GET /v1/trades, one page
client.trades.list_all(**filters)every page, as an iterator
client.transactions.list / get / list_allGET /v1/transactions[/{id}]
client.giftcards.create(asset=, amount=, recipient_name=None, idempotency_key=None)POST /v1/giftcards
client.giftcards.list / get / list_allGET /v1/giftcards[/{id}]
client.giftcards.pdf(id)GET /v1/giftcards/{id}/pdf, as bytes

quotes.create takes quote_amount or base_amount, exactly one. Each endpoint's reference page has a sample. List filters are keyword arguments named like the query parameters, except from, a Python keyword, which is from_: client.trades.list(asset="BTC", from_="2026-01-01T00:00:00Z").

Async

AsyncMyntkaup has the same methods, as coroutines, and list_all as an async iterator:

Python
import asyncio

from myntkaup import AsyncMyntkaup


async def main():
    client = AsyncMyntkaup()
    balances = await client.balances.list()
    async for trade in client.trades.list_all(asset="BTC"):
        print(trade["id"], trade["status"])
    print(balances["total_value_usdc"])


asyncio.run(main())

Fields and amounts

  • Arguments are snake_case, like the API's fields.
  • Responses are returned as the API sends them, so the JSON examples in these docs apply as they are: quote["base_amount"], trade["filled_at"].
  • Amounts are decimal strings, in both directions. Use decimal.Decimal for arithmetic and str() to send.

Helpers

client.buy() and client.sell() create a quote and accept it. If the trade comes back pending, they poll it for up to 30 seconds with backoff, and return the trade.

Python
trade = client.buy(asset="BTC", quote_amount="100.00")
if trade["status"] != "filled":
    print("Not filled yet:", trade["id"], trade["status"])

Retries and idempotency

  • Requests are retried at most max_retries times, with exponential backoff and jitter, honouring Retry-After.
  • A GET is retried on 429, 502, 503, 504 and network errors. A POST is retried only when it has an Idempotency-Key.
  • 401, 403, 409, 422 and 429 key_suspended are never retried.
  • trades.create and giftcards.create send a random Idempotency-Key unless you pass idempotency_key; quotes.create sends one only if you pass it. See Idempotency.

Errors

Every non-2xx answer raises myntkaup.ApiError with status, code, message, request_id, and retry_after (seconds) plus the rate-limit headers when present. Network failures raise myntkaup.ConnectionError.

ClassStatus
myntkaup.AuthenticationError401
myntkaup.PermissionError403
myntkaup.NotFoundError404
myntkaup.ConflictError409
myntkaup.ValidationError400, 422
myntkaup.RateLimitError429
myntkaup.ServiceError5xx

PermissionError and ConnectionError share their names with Python built-ins, so refer to them through the module.

Python
import myntkaup

try:
    client.trades.get("tr_01JB8X2Q4M7Z3V9K5T1N6R0P2W")
except myntkaup.NotFoundError:
    print("No such trade")
except myntkaup.ApiError as error:
    print(error.code, error.request_id)

Signing only

To use another HTTP client, the package exports its signing function. It returns the four MK-* headers:

Python
import json
import os

import myntkaup

body = json.dumps({"asset": "BTC", "side": "buy", "quote_amount": "100.00"}, separators=(",", ":"))
headers = myntkaup.sign_request(
    key_id=os.environ["MYNTKAUP_API_KEY_ID"],
    private_key=os.environ["MYNTKAUP_API_PRIVATE_KEY"],
    method="POST",
    path_and_query="/v1/quotes",
    body=body,
)

Testing

Pass an httpx.MockTransport to test code that uses the client without network access:

Python
import httpx

from myntkaup import Myntkaup


def handler(request: httpx.Request) -> httpx.Response:
    return httpx.Response(200, json={"status": "ok", "read_only": False, "trading_enabled": True,
                                     "giftcards_enabled": True, "server_time": "2026-11-03T10:15:10Z"})


client = Myntkaup(
    key_id="mk_test_0123456789abcdefghij",
    private_key="mk_sk_nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A",  # RFC 8032 test key, public
    transport=httpx.MockTransport(handler),
)