CCXT

CCXT vs the Upbit API and official Upbit SDKs

Upbit ships a modern official SDK in Python, TypeScript and Go. CCXT compared on portability, JWT signing, rate limits and symbol format.

Upbit publishes a first-party SDK family under the upbit-official GitHub organisation: upbit-sdk-python (Apache-2.0, on PyPI as upbit-sdk), upbit-sdk-typescript, upbit-sdk-go and a Go CLI. They are recent, actively pushed, and typed. CCXT speaks the same REST and WebSocket APIs behind method names shared with 103 other exchanges.

This is one of the closer comparisons on this site, because the official SDK is good. The question that decides it: is Upbit the only venue you will ever touch?

TL;DR

  • Pick the official Upbit SDK if Upbit is your only venue and you want typed, Upbit-shaped requests and responses that match global-docs.upbit.com field for field, with sync and async clients from the same package.
  • Pick CCXT if you want Upbit alongside other venues under one API, unified symbols instead of Upbit's reversed KRW-BTC market ids, and the same watch* streaming shape you use everywhere else.
  • CCXT is not a lowest common denominator. All 53 Upbit endpoints are generated as implicit methods, JWT-signed and rate-limited like the unified ones.

At a glance

CCXTOfficial Upbit SDKs
Exchanges covered104 (Upbit is one of them)Upbit only
LanguagesTypeScript, JavaScript, Python, PHP, C#/.NET, Go, Java — one APIPython, TypeScript, Go — separate codebases
Installpip install ccxt / npm i ccxtpip install upbit-sdk and the per-language equivalents
Unified market data + trading APIyes — same method names on every exchangeno — Upbit's own request and response shapes
Symbols'BTC/KRW''KRW-BTC' — quote currency first
Sync and asyncsync ccxt, async ccxt.async_support, streaming ccxt.proUpbit and AsyncUpbit in one package
WebSocketsyes — 9 watch* methods returning the same structures as fetch*yes, built on websockets
Raw endpoint accessyes — 53 Upbit endpoints as implicit methodsthe endpoints the SDK wraps
Built-in rate limiteryes, per-endpoint weights, on by default (rateLimit 50 ms)not a documented feature of the SDK
Unified error typesyes — 41 typed exceptions in one hierarchyUpbit error names such as jwt_verification, validation_error
Testnet / sandboxno — Upbit has no sandbox wired up in CCXTno
Popularity43.8k GitHub stars · 4.8M PyPI + 494k npm installs/month (one package, every venue)upbit-sdk-python 89 stars · 1,557 PyPI installs/month; upbit-sdk-typescript 82 stars
LicenceMITApache-2.0
SupportDiscord, Telegram, GitHub issues — usually same-dayUpbit Developer Center, GitHub

Figures verified September 2026 against CCXT v4.5.77, the upbit-official GitHub repositories, PyPI download counts and Upbit's published API documentation.

CCXT implements 39 unified capabilities for Upbit, 20 of them fetch* methods.

The same job, written both ways

Fetch a ticker

import ccxt

exchange = ccxt.upbit()
ticker = exchange.fetch_ticker('BTC/KRW')
print(ticker['last'], ticker['baseVolume'])

Two things differ, and the second one matters more than it looks. The SDK returns Upbit's payload; CCXT returns a unified ticker structure with the same keys and units it returns everywhere else. And the market id is KRW-BTC, not BTC-KRW — Upbit puts the quote currency first. CCXT normalises that to 'BTC/KRW', base first, the same way it does on every other venue.

Place a limit order

import ccxt

exchange = ccxt.upbit({'apiKey': '...', 'secret': '...'})
order = exchange.create_order('BTC/KRW', 'limit', 'buy', 0.0001, 90000000)
print(order['id'], order['status'])

side="bid" and side="ask" are Upbit's vocabulary. CCXT takes 'buy' and 'sell', the same words it takes on Binance, Kraken and everything else, and translates at the boundary.

Stream an order book

import ccxt.pro
import asyncio

async def main():
    exchange = ccxt.pro.upbit()
    while True:
        orderbook = await exchange.watch_order_book('BTC/KRW')
        print(orderbook['bids'][0], orderbook['asks'][0])

asyncio.run(main())

CCXT returns a merged, depth-limited order book structure, reconnects and resubscribes on a drop, and enforces Upbit's five-connections-per-second and five-messages-per-second limits from the same throttler it uses for REST.

Where the differences actually bite

Symbols are reversed, and only on Upbit

Upbit market ids read quote-first: KRW-BTC, KRW-ETH, BTC-ETH. Every other major venue reads base-first. If your codebase touches more than one exchange, that inversion is a permanent source of subtle bugs — a string that parses "correctly" on both sides but means the opposite pair. CCXT parses the id once in parse_market and gives you 'BTC/KRW', so the rest of your code never sees it.

Portability is the whole point

This is the difference that shows up six months in, not on day one. Adding a second exchange to an official-SDK integration means a second SDK, a second set of payload shapes, a second symbol convention, a second error taxonomy and a second WebSocket dialect — then a translation layer of your own so the rest of your code can stay venue-agnostic. That layer is what CCXT already is.

for exchange_id in ['upbit', 'binance', 'bybit', 'okx', 'kraken']:
    exchange = getattr(ccxt, exchange_id)()
    print(exchange_id, exchange.fetch_ticker('BTC/USDT')['last'])

JWT signing you do not have to build

Upbit does not use the header-plus-HMAC scheme most exchanges use. Every private request carries a JWT in an Authorization: Bearer header, whose payload holds access_key, a fresh nonce UUID and — whenever the request has parameters — a query_hash that is the SHA-512 digest of the encoded query string, plus query_hash_alg. Miss the hash on a request that has parameters and authentication fails; compute it over the wrong encoding and it fails the same way. CCXT builds the token, the nonce and the hash on every call.

Regional hosts, one option

Upbit runs separate hosts per region — api.upbit.com for Korea, <countryCode>-api.upbit.com for Indonesia, Singapore and Thailand, each with its own listings. In CCXT that is a constructor option, and every REST and WebSocket URL follows it:

exchange = ccxt.upbit({'hostname': '<countryCode>-api.upbit.com'})

Rate limits you do not have to model

Upbit meters per endpoint group, and the groups have very different budgets: quotation endpoints (market, candle, trade, ticker, orderbook) allow 10 requests per second per IP; the exchange default group allows 30 per second per account; order allows 8 per second; order-cancel-all allows one request per 2 seconds. WebSocket adds 5 connections per second and 5 messages per second up to 100 per minute. Exceed them and you get HTTP 429, then 418 if you keep going.

CCXT encodes per-endpoint costs in the exchange definition and ships a token-bucket throttler that is on by default (enableRateLimit = True, rateLimit = 50 ms), so a loop over symbols paces itself rather than tripping the order-cancel-all budget on the third call.

WebSockets that look like REST

watch_order_book returns the same structure as fetch_order_book; watch_orders the same as fetch_orders. Swapping polling for streaming is a one-word change. CCXT gives Upbit 9 streaming methods: watchTicker, watchTickers, watchTrades, watchTradesForSymbols, watchOHLCV, watchOrderBook, watchOrders, watchMyTrades and watchBalance.

Precision, rounding and string math

Upbit uses tick-size precision, and a price that is not a multiple of the symbol's tick is rejected. CCXT loads that metadata with the markets and gives you helpers backed by the Precise string-arithmetic class:

amount = exchange.amount_to_precision('BTC/KRW', 0.0012345678)
price = exchange.price_to_precision('BTC/KRW', 90123456.7)

One error hierarchy

CCXT maps Upbit's named errors onto a typed exception treejwt_verification becomes AuthenticationError, validation_error becomes BadRequest, and InsufficientFunds, InvalidOrder, OrderNotFound, RateLimitExceeded and 35 more all descend from BaseError. You catch ccxt.InsufficientFunds once and it keeps working on the next venue.

Nothing is hidden — the implicit API

Alongside the 39 unified capabilities, all 53 Upbit endpoints are generated as callable implicit methods, with JWT signing, query hashing, rate-limit accounting and error mapping applied. Browse them on the Upbit implicit API page.

What the official Upbit SDKs do better

An honest list, and these are strong:

  • They are typed, modern and current. upbit-sdk-python requires Python 3.9+, ships sync (Upbit) and async (AsyncUpbit) clients built on httpx and websockets, includes type definitions for request params and response fields, and handles pagination. It was last pushed in September 2026. This is not a stale vendor sample.
  • Field names match the docs exactly. client.candles.list_minutes(), client.orderbooks.list(), client.orders.cancel_and_new() map one-to-one onto global-docs.upbit.com. When you are debugging against the vendor reference, that is one less hop than a unified structure.
  • Upbit-only features are modelled first. Travel-rule endpoints, deposit and withdrawal flows, API-key management and "pockets" are exposed with Upbit's own vocabulary. CCXT unifies what is common across venues; venue-specific product surfaces reach it through the implicit API rather than a unified wrapper.
  • A first-party ecosystem. The same organisation ships a Go CLI, a TypeScript SDK, and agent skills. If your tooling lives in that ecosystem, staying inside it is coherent.
  • A smaller install. One SDK covering one exchange is a smaller dependency than a library covering 104.

If Upbit is your only venue, forever, the official SDK is a genuinely good choice and you will not regret it.

Migrating from the Upbit SDK to CCXT

What you are doingUpbit SDKCCXT
Symbols'KRW-BTC' (quote first)'BTC/KRW' (base first)
Sidesside="bid" / side="ask"'buy' / 'sell'
Marketsclient.orderbooks.list_instruments()load_markets()
Tickerclient.tickers.list_by_trading_pairs()fetch_ticker() / fetch_tickers()
Order bookclient.orderbooks.list()fetch_order_book()
Candlesclient.candles.list_minutes() / list_days()fetch_ohlcv()
Tradesclient.trades.list()fetch_trades()
New orderclient.orders.create()create_order()
Cancel orderclient.orders.cancel()cancel_order()
Open ordersclient.orders.list_open()fetch_open_orders()
Closed ordersclient.orders.list_closed()fetch_closed_orders()
Balanceclient.accounts.list()fetch_balance()
Streamsthe SDK's WebSocket clientwatch_* on ccxt.pro.upbit
Anything not listedthe SDK methodthe same endpoint as an implicit method

FAQ

Does Upbit have an official SDK? Yes. The upbit-official GitHub organisation publishes upbit-sdk-python (Apache-2.0, on PyPI as upbit-sdk, Python 3.9+, sync and async), upbit-sdk-typescript, upbit-sdk-go and a Go CLI. They are actively maintained.

Why does CCXT call the market BTC/KRW when Upbit calls it KRW-BTC? Upbit writes market ids quote-currency-first. CCXT's unified symbol format is always BASE/QUOTE, so it parses KRW-BTC into 'BTC/KRW' at the boundary. That keeps one convention across every exchange in the library, instead of one exception you have to remember.

How does Upbit authenticate API requests? With a JWT sent as an Authorization: Bearer header. The payload carries access_key and a fresh nonce UUID, plus query_hash (a SHA-512 digest of the encoded query string) and query_hash_alg whenever the request has parameters. CCXT builds all of it on every private call.

Does CCXT support Upbit WebSockets? Yes — 9 watch* methods on ccxt.pro.upbit, including order book, trades, tickers, candles, orders, my trades and balance. They return the same structures as the matching fetch* methods.

Does CCXT support an Upbit sandbox? No. Upbit has no urls.test in CCXT, so set_sandbox_mode(True) will not work for it.

Is CCXT free? Yes. MIT-licensed, including the WebSocket support.

Next steps

On this page