Subscribe to Price Channel
Subscribe request for two-way Crypto and FX pricing
Price Streams
Section titled “Price Streams”Subscribe to real-time two-way prices (bid and offer) for trading. Price streams continuously update with live market prices for your specified quantity and currency pair.
What You’ll Receive
Section titled “What You’ll Receive”- Two-way prices - Both an offer (you buy the base currency from Zodia Markets) and a bid (you sell it to Zodia Markets)
- Tradeable quotes - Each price includes a unique quote ID for order execution
- Continuous updates - New prices automatically replace previous ones
- Quantity-specific - Prices tailored to your requested trade size
Subscribe to Price Stream
Section titled “Subscribe to Price Stream”Request a continuous stream of prices for a specific currency pair and quantity.
Request Fields
Section titled “Request Fields”| Field | Type | Required | Description |
|---|---|---|---|
messageType |
string | yes | Must be subscribe. Every message on this connection is routed by messageType; it is the only thing that tells one message from another. A value outside the protocol’s set is discarded before any handler sees it — no error frame, no reply at all — so a misspelling here is indistinguishable from a lost message. |
instrument |
string | yes | The pair to price, written BASE.QUOTE — USDC.AED, BTC.USD. Matched against the streaming instruments by exact, case-sensitive comparison: a lower-case or otherwise reformatted pair is not rejected as malformed, it simply matches nothing and the subscription fails with RFS100011. currency below decides which of the two halves quantity is denominated in. |
quantity |
string | yes | The size to be priced, as a decimal string, denominated in currency. Prices are quoted for this size specifically — a different size is a different subscription, not a different view of the same one. Send no more than 8 decimal places: beyond that the value is rounded, and the quantity echoed back can sit one unit below the size actually being priced. |
currency |
string | yes | Which half of instrument quantity is expressed in. Must be exactly one of the pair’s two currencies, or the request is refused with RFS100008. The choice changes the shape of every price update: name the base currency and quantity is fixed on both sides while the quote amounts differ; name the quote currency and quoteAmount is fixed while the base quantities differ between bid and offer. |
accountGrpUuid |
string | yes | The account group to price against, from the Account Groups message. It selects the spread, credit limit and trading permissions applied to this subscription, so the same request against two account groups can return two different prices — or be refused on one and accepted on the other. Blank is refused with RFS600004. Mandatory when beneficiaryDetails is present. |
tenor |
string | yes | Settlement tenor. T (closest available day) and T1 (closest available day + 1) are the values to use; both are composed by Zodia Markets from the underlying venue tenors, which are themselves reachable by name — TOD, TOM, SP. The field is not checked against that list: any non-blank string is accepted, and a value with no live price behind it fails the subscription with RFS100011 rather than a format error. Blank is refused with RFS100021. |
tag |
string | A label of your choosing, echoed unchanged on this subscription’s subscribe, pricestream and streamStop messages. Price updates carry no subscriptionId, so where you hold several subscriptions on one connection the tag is what tells their updates apart — set it to something you can route on. Omitted, it comes back as an empty string. |
|
beneficiaryDetails |
object | conditional | Third-party delivery block. Send it only when the trade settles to a beneficiary’s wallet instead of your own — see 3rd Party Beneficiary Delivery Price Channel. Cannot be combined with senderDetails. |
beneficiaryDetails.beneficiaryId |
string | conditional | UUID of pre-configured beneficiary. Get Beneficiaries on how to retrieve list of beneficiary UUID |
beneficiaryDetails.payoutCurrency |
string | conditional | Payout currency. Must be a currency that is contained within the instrument being subscribed to. |
beneficiaryDetails.networkId |
string | Not usually required unless using a specific network. Contact your Relationship Manager for more info. | |
senderDetails |
object | conditional | Third-party collection block. Send it only when Zodia Markets collects the crypto funds from a verified third party instead of from you — see 3rd Party Sender Collection Price Channel. Cannot be combined with beneficiaryDetails. |
senderDetails.senderId |
string | conditional | UUID of pre-configured Sender. Get Senders on how to retrieve list of Sender UUID |
senderDetails.payinCurrency |
string | conditional | Pay in currency. Must be a currency that is contained within the instrument being subscribed to. Only Crypto Currencies are supported for third party collection. |
senderDetails.networkId |
string | Not usually required unless using a specific network. Contact your Relationship Manager for more info. |
Understanding Currency Pairs
Section titled “Understanding Currency Pairs”Currency pairs are formatted as BASE.QUOTE:
- USDC.AED - Base currency: USDC, Quote currency: AED
- When you specify
currency: "AED"withquantity: "100000", you’re requesting a price for 100,000 AED worth of USDC - When you specify
currency: "USDC"withquantity: "1000", you’re requesting a price for 1,000 USDC
Settlement Tenors
Section titled “Settlement Tenors”| Tenor | Description | Use Case |
|---|---|---|
T |
Today settlement | Price for closest available value date to today |
T1 |
Tomorrow settlement | Price for closest available value date to tomorrow |
The actual settlement date is returned in the settleDate field of price updates. Contact your Relationship Manager for custom value date pricing.
Subscribe Response
Section titled “Subscribe Response”Confirms your subscription was successful and provides a subscription ID.
Response Fields
Section titled “Response Fields”| Field | Type | Required | Description |
|---|---|---|---|
timestamp |
integer (int64) | yes | Unix epoch milliseconds, UTC — the moment Zodia Markets built this frame, not the moment your request arrived. |
messageType |
string | yes | Always subscribe. A subscribe request is always answered by exactly one of these, whether or not the subscription was created — read success, not the arrival of the message, to find out which happened. |
success |
boolean | yes | Whether the subscription now exists. Failures are reported here, in band: a rejected subscribe returns success: false with a code, never a separate error frame, and never both. |
message |
string | yes | Subscribed on success. On failure, the reason in plain text, usually naming the offending value. Intended for a human reading a log — branch on code, not on this. |
subscriptionId |
string | yes | UUID naming this subscription; the value unsubscribe takes. Present on failures too, because it is minted before the request is validated — on a success: false response it names nothing and unsubscribing it is silently ignored. Key off success before storing it. |
instrument |
string | yes | The pair now streaming, echoed from the request. |
quantity |
string | yes | Your submitted quantity, echoed in the currency you submitted it in and normalised to 8 decimal places. It is not converted to the base currency: subscribe for 100000 AED on USDC.AED and this reads 100000.00000000 AED, not a USDC amount. On a rejected request the raw string you sent is echoed back unparsed, so it is not guaranteed to be a well-formed decimal when success is false. |
code |
string | Empty on success. On failure, what was wrong: RFS100004/RFS100005 instrument, RFS100008 currency, RFS100009 quantity, RFS100021 tenor, RFS600004 account group, RFS100017 you already hold this subscription, RFS100011 no price at that size, PRE200035 pair disabled for your account, RFS100013 an unexpected failure our side. The full list is in the Response / Error Code Reference. |
|
tenor |
string | The tenor now streaming, echoed from the request. | |
tag |
string | Your tag, echoed. Empty string if you sent none. |
|
networkId |
string | The network this subscription’s trade will settle over. Set only when the request carried beneficiaryDetails or senderDetails; null on an ordinary subscription. It echoes the networkId you supplied if you supplied one; otherwise Zodia Markets resolves it — ZM_TRANSFER for a crypto beneficiary payout, ZM_SENDER for a sender collection, and for a fiat payout the network chosen for that corridor, which is neither of those. |
Store the subscriptionId - you’ll need it to unsubscribe later.
Receiving Price Updates
Section titled “Receiving Price Updates”After successful subscription, you’ll receive continuous price updates.
Price Stream Fields
Section titled “Price Stream Fields”| Field | Type | Required | Description |
|---|---|---|---|
timestamp |
integer (int64) | yes | Unix epoch milliseconds, UTC — when this update was produced. Updates are pushed on a fixed cadence rather than on every market tick, so consecutive prices for one subscription are evenly spaced; compare this field rather than arrival order to tell which of two updates is the later. |
messageType |
string | yes | Always pricestream. |
instrument |
string | yes | The pair being priced, BASE.QUOTE, echoed from the subscription. |
quoteId |
string | yes | The token an order executes against — copy it onto order unchanged. It is opaque and not parseable, and it already carries the price, size, tenor and settlement date of this update, which is why an order need not repeat them (and why any it does send are ignored). It is bound to your user and expires ten minutes after issue: executing one issued to another user is refused with RFS100020, a stale one with RFS100019. It is not single-use — several orders may execute against one quote until their combined size reaches the quoted quantity, after which further orders are refused with RFS600021. |
offer |
object | yes | The side you BUY on. Execute against it with side: BUY to buy the base currency from Zodia Markets. offer.price is always the higher of the two prices — the spread is applied outward from the market, raising the offer and lowering the bid. |
offer.price |
string | yes | The price you buy at, in quote currency per one unit of base currency, as a decimal string. Parse it as an arbitrary-precision decimal: the number of decimal places is not fixed, and very small prices are rendered in scientific notation (1E-8). |
offer.quantity |
string | yes | The base-currency amount on this side. Where the subscription was made in the base currency this is that amount, identical to bid.quantity; where it was made in the quote currency it is the quote amount divided by offer.price, rounded down, and therefore differs from bid.quantity. |
offer.quoteAmount |
string | yes | The quote-currency amount you pay for quantity. Where the subscription was made in the quote currency this is that amount, identical to bid.quoteAmount; where it was made in the base currency it is quantity × price, and therefore differs from bid.quoteAmount. Rounded to the quote currency’s own precision — not to a fixed 8 places — and always in plain notation. |
bid |
object | yes | The side you SELL on. Execute against it with side: SELL to sell the base currency to Zodia Markets. bid.price is always the lower of the two prices. |
bid.price |
string | yes | The price you sell at, in quote currency per one unit of base currency, as a decimal string. Same parsing caveats as offer.price. |
bid.quantity |
string | yes | The base-currency amount on this side. Identical to offer.quantity when the subscription was made in the base currency; the quote amount divided by bid.price when it was made in the quote currency. |
bid.quoteAmount |
string | yes | The quote-currency amount you receive for quantity. Identical to offer.quoteAmount when the subscription was made in the quote currency; quantity × price when it was made in the base currency. |
tenor |
string | yes | The tenor this price settles on, echoed from the subscription. settleDate is the calendar date it currently resolves to. |
settleDate |
string | The date this price settles on, YYYYMMDD. It is a property of the update, not of the subscription: as the day’s cut-offs pass, a T subscription rolls onto a later date and this field moves with it, without the subscription being interrupted. |
|
tag |
string | Your tag, echoed from the subscription. This message carries no subscriptionId, so where a connection holds several subscriptions the tag — with instrument and tenor — is what routes an update to the right one. |
Understanding Bid and Offer
Section titled “Understanding Bid and Offer”For currency pair USDC.AED:
-
Bid (3.672950) - Price at which Zodia will buy USDC from you (you receive AED)
- You’re selling USDC, buying AED
-
Offer (3.673050) - Price at which Zodia will sell USDC to you (you pay AED)
- You’re buying USDC, selling AED
Quote Expiry
Section titled “Quote Expiry”Quote expiry is not published on the stream. The default quote expiry time is 3 seconds.
Handling Price Updates - Example
Section titled “Handling Price Updates - Example”import json
# Store active subscriptionsactive_subscriptions = {}
def handle_websocket_message(message): """Process incoming WebSocket messages""" msg = json.loads(message)
if msg['messageType'] == 'subscribe': # Store subscription ID if msg['success']: subscription_id = msg['subscriptionId'] active_subscriptions[msg['instrument']] = subscription_id print(f"✅ Subscribed to {msg['instrument']}: {subscription_id}") else: print(f"❌ Subscription failed: {msg['message']}")
elif msg['messageType'] == 'pricestream': # Process price update instrument = msg['instrument'] bid_price = float(msg['bid']['price']) offer_price = float(msg['offer']['price']) quote_id = msg['quoteId']
print(f"Price Update - {instrument}") print(f" Bid: {bid_price} (qty: {msg['bid']['quantity']})") print(f" Offer: {offer_price} (qty: {msg['offer']['quantity']})") print(f" Spread: {(offer_price - bid_price):.6f}") print(f" Quote ID: {quote_id}") print(f" Settle: {msg.get('settleDate', 'N/A')}")
# Check if price meets your trading criteria if should_trade(bid_price, offer_price): execute_order(quote_id, 'BUY', msg['offer']['quantity'])// Store active subscriptionsconst activeSubscriptions = {};
function handleWebSocketMessage(message) { const msg = JSON.parse(message);
if (msg.messageType === 'subscribe') { // Store subscription ID if (msg.success) { const subscriptionId = msg.subscriptionId; activeSubscriptions[msg.instrument] = subscriptionId; console.log(`✅ Subscribed to ${msg.instrument}: ${subscriptionId}`); } else { console.error(`❌ Subscription failed: ${msg.message}`); } } else if (msg.messageType === 'pricestream') { // Process price update const instrument = msg.instrument; const bidPrice = parseFloat(msg.bid.price); const offerPrice = parseFloat(msg.offer.price); const quoteId = msg.quoteId;
console.log(`Price Update - ${instrument}`); console.log(` Bid: ${bidPrice} (qty: ${msg.bid.quantity})`); console.log(` Offer: ${offerPrice} (qty: ${msg.offer.quantity})`); console.log(` Spread: ${(offerPrice - bidPrice).toFixed(6)}`); console.log(` Quote ID: ${quoteId}`); console.log(` Settle: ${msg.settleDate || 'N/A'}`);
// Check if price meets your trading criteria if (shouldTrade(bidPrice, offerPrice)) { executeOrder(quoteId, 'BUY', msg.offer.quantity); } }}Unsubscribe from Price Stream
Section titled “Unsubscribe from Price Stream”Stop receiving price updates by unsubscribing using the subscription ID.
Request Fields
Section titled “Request Fields”| Field | Type | Required | Description |
|---|---|---|---|
messageType |
string | yes | Must be unsubscribe. |
subscriptionId |
string | yes | The subscriptionId from the subscribe response that opened this stream. It is matched against the subscriptions held on this connection only — a value from another connection, one already cancelled, or one taken from a failed subscribe matches nothing, and an unmatched request draws no reply of any kind. Time out rather than waiting indefinitely for an acknowledgement. |
Unsubscribe Response
Section titled “Unsubscribe Response”Response Fields
Section titled “Response Fields”| Field | Type | Required | Description |
|---|---|---|---|
timestamp |
integer (int64) | yes | Unix epoch milliseconds, UTC — when the subscription was cancelled. |
messageType |
string | yes | Always unsubscribe. This message is sent only when a subscription was actually cancelled; there is no negative form of it. |
success |
boolean | yes | Always true. The stream is closed and no further pricestream updates will arrive for this subscriptionId. A request that matched nothing produces no message at all rather than success: false, so the arrival of this message is itself the confirmation. |
subscriptionId |
string | yes | The subscriptionId from your request, now cancelled and not reusable. |
message |
string | yes | A diagnostic line naming the cancelled subscription and its size. Its wording is not part of the contract and can change without notice — log it, but do not parse it or match on it. |
instrument |
string | yes | The pair that was streaming, from the cancelled subscription. |
quantity |
string | yes | The size that was being priced, in the currency the subscription was made in. It may carry more trailing zeros than the value you sent; compare it as a decimal, not as a string. |
tenor |
string | yes | The tenor that was streaming, from the cancelled subscription. |
Stream Stop Message (TOD Tenor Only)
Section titled “Stream Stop Message (TOD Tenor Only)”When using the TOD tenor, if the currency pair reaches its daily cut-off time, you’ll receive a stream stop message.
Stream Stop Fields
Section titled “Stream Stop Fields”| Field | Type | Required | Description |
|---|---|---|---|
timestamp |
integer (int64) | yes | Unix epoch milliseconds, UTC — when this message was produced. It is not the moment pricing stopped: the message repeats on each price broadcast for as long as the condition holds, so consecutive frames carry advancing timestamps for one continuous outage. |
messageType |
string | yes | Always streamStop. It replaces the pricestream update for this subscription rather than ending it: the subscription stays open, no unsubscribe is needed, and pricing resumes on its own — as pricestream messages — once the cause clears. |
instrument |
string | yes | The pair that is no longer priced, from the affected subscription. |
tenor |
string | yes | The tenor that is no longer priced, from the affected subscription. Only this tenor is affected; other tenors on the same pair may still be streaming. |
code |
string (enum) | yes | Why pricing stopped, and the only field that distinguishes the two causes. CUT_OFF_TIME_REACHED — the settlement cut-off for this tenor has passed, so it can no longer be dealt today; it reaches same-day tenors only, and re-subscribing to a later tenor is the way forward. STREAM_UNAVAILABLE — this pair is not currently available to your account, which is a permissioning or market-state matter specific to you rather than a market-wide outage. — One of: CUT_OFF_TIME_REACHED, STREAM_UNAVAILABLE |
message |
string | yes | Fixed text, identical for both codes: Prices unavailable — market closed or stream inactive. It carries no information beyond code; branch on code. |
tag |
string | Your tag, echoed from the subscription — the same routing handle as on pricestream. |
When you receive this message, the price stream for that tenor is no longer available. You can subscribe to a different tenor if needed.