Skip to main content

Migrating from v1

The v2 API is a ground-up rebuild. It runs alongside v1 until September 10, 2026, when v1 is retired.

Key changes

Authentication (breaking change)

The old scheme (X-API-KEY + RSA x509 signature) is gone in v2. In its place, we use the two-tier scheme: skp_live_… bearer keys for reads, and EdDSA-signed JWTs to move money. Onboarding is self-service (no more certificate sent by email).

Errors

v2 always returns application/problem+json (RFC 9457) with a stable code, instead of inconsistent error formats.

Route renames

v1v2
/convert_orders/orders
/converts⚠️ /fillsone row per execution, not per order (see the warning below)
/converts/quotations/quotes
/converts/quotations:confirmPOST /orders
/currencies/balances
/historical_balances/{currencyId}/balances/{assetSymbol}/history
/ledger_movements/{currencyId}/ledger?assetSymbol=
/supported_currencies/assets
/supported_markets/markets
/users/current/account
/convert_orders/id/{id}GET /orders/{id}
/converts/id/{id}⚠️ no 1:1 equivalent — see Lookups by id
/…/clOrdId/{clOrdId}⚠️ no equivalent — see Lookups by id
/converts maps to /fills, not /orders

In v1, /converts returns one row per execution and /convert_orders one row per order. Their v2 equivalents are /fills and /orders respectively. If you point what today reads /converts at /orders, your row counts change: an order with several executions returns a single row from /orders where v1 returned several.

The resource is called an order, not a "conversion": Convert is the product brand, but the model is exchange-shaped (an order carrying a type), so future order types can be added without a breaking restructure.

Lookups by id that change shape

Three v1 routes have no 1:1 equivalent in v2:

v1v2What changes in your code
/convert_orders/id/{id}GET /v2/orders/{id}Nothing: still one row.
/converts/id/{id}GET /v2/orders/{id}/fills or GET /v2/fills?orderId={id}The cardinality changes: you get a list of executions, not a row. There is no GET /v2/fills/{id}.
/convert_orders/clOrdId/{clOrdId}There is no lookup by clientOrderId in v2. That field is returned only on the POST /v2/quotes response, alongside the orderId you can query later. Persist that mapping on your side when you quote.
?sourceId= exists only on the ledger

?orderId= is available on /v2/fills and /v2/ledger. ?sourceId= is on /v2/ledger only — it does not filter orders or fills. See ids and correlation.

Parameter and field renames

Route renames announce themselves with a 404. Query-parameter renames and response-field renames do not behave the same way as each other, so they are split below.

Query parameters — v2 rejects the ones it does not recognize

v2 does not ignore an unknown parameter: it returns 400 validation_error naming the offending parameter (property take should not exist). This is deliberate — on a money API an ignored filter returns the wrong answer with no warning — and in practice it means a rename you miss shows up on the first call, not in the month-end reconciliation.

v1v2
take=limit=
currencyId=assetSymbol=
currencyBase= / currencyQuote=baseAsset= / quoteAsset=

Response fields — these do fail silently

No safety net here: reading a field that no longer exists yields undefined, not an error.

v1v2If you miss it
meta.currentSizepagination.limitAll four change at once, so a pagination loop written for v1 finds no totals and stops after the first page.
meta.totalCountpagination.totalItems
meta.totalPagespagination.totalPages
meta.currentPagepagination.page
quantityamountNull amount. Watch the sign too — see below.
datecreatedAtNull timestamp.
currencyIdassetSymbolNull asset.
transactionIdsource.id (with source.type)⚠️ Not id. id exists in both v1 and v2 and is a different field: the entry's own. See ids and correlation.
qtyCumBase / qtyCumQuotefilledBaseAmount / filledQuoteAmountNull filled quantity.
currencyBase / currencyQuotebaseAsset / quoteAssetUnidentified market.

Changes of meaning, not of name

Two changes keep a reasonable name but change the value. No error announces them — check for them explicitly.

Public amounts are positive magnitudes

In v1, quantity carried a sign (a withdrawal was negative). In v2 amount, fee and total are always positive: direction comes from the entry's type, and from the resource itself (a withdrawal debits, a deposit credits).

If you sum movements to compute a net, the same code produces a different answer: in v2 debits add instead of subtracting. Branch on type before you accumulate.

assetFormat defaults to rebased

/orders, /fills and /withdrawals accept assetFormat, and its default is rebased — a deliberate change from the previous behaviour, which returned base. For tokenised equities that changes the amounts you see relative to v1, with no error signal. For every other asset the multiplier is 1 and nothing changes.

This is an accounting decision before it is a technical one: if your integration depends on settled figures, ask for assetFormat=base explicitly.

New in v2

  • Fills as a first-class resourceGET /v2/fills and GET /v2/orders/{id}/fills.
  • A traceable ledgerGET /v2/ledger, with source: {type, id} and ?sourceId= / ?orderId= filters (guide).
  • Indicative pricingGET /v2/markets/{market}/price, a non-binding rate that reserves nothing and needs no signature. (Market and asset listings already existed in v1 as /supported_markets and /supported_currencies; what is new here is the price.)
  • Signed webhooks (guide).
  • Per-account and per-key rate limits with RateLimit-* headers.

Timeline

v1 is retired on September 10, 2026 (2026-09-10). From that date its endpoints stop serving traffic — complete your migration to v2 before then.

Ahead of the shutdown, v1 will start returning Deprecation / Sunset headers announcing that same date, and we may run brownouts — short, announced windows where v1 returns errors — so you can confirm your migration is complete.