Candles summarize a Pair's trades per time bucket: open, high, low, close, base volume, and quote
volume. Read history with GET /v1/spot/markets/{symbol}/candles or MarketDataService.GetCandles,
and follow the current candle on the public candles channel. Both are public and need no
authentication.
Timeframes
All buckets are in UTC; daylight saving time never shifts a boundary.
| REST | ConnectRPC | Bucket |
|---|---|---|
1s | SEC_1 | 1 second |
1m, 3m, 5m | MIN_1, MIN_3, MIN_5 | 1, 3, 5 minutes |
15m, 30m | MIN_15, MIN_30 | 15, 30 minutes |
1h, 2h, 4h | HOUR_1, HOUR_2, HOUR_4 | 1, 2, 4 hours |
6h, 8h, 12h | HOUR_6, HOUR_8, HOUR_12 | 6, 8, 12 hours |
1d | DAY_1 | 1 day, starting at 00:00 UTC |
3d | DAY_3 | 3 days, counted from 1970-01-01 |
1w | WEEK_1 | 1 week, starting Monday 00:00 UTC |
1mo | MONTH_1 | Calendar month, starting on the 1st at 00:00 UTC |
Fixed-length buckets up to 1d start at whole multiples of their length since
1970-01-01T00:00:00Z, so they always align to UTC midnight. The REST timeframe parameter is
required and takes only the strings above. A missing value is rejected with HTTP 400
(INVALID_ARGUMENT), and any other value with HTTP 400 (BAD_REQUEST).
Read candles
GET https://api.testnet.polyester.com/v1/spot/markets/BTC-USDT/candles?timeframe=1m&limit=3&includeIncomplete=true{
"nextPageToken": "cGcBEm1hcmtldGRhdGEuY2FuZGxlcw...",
"symbol": "BTC-USDT",
"timeframe": "1m",
"candles": [
[
"1793914620",
"76135.97",
"76135.97",
"76044.66",
"76059.87",
"0.78155926",
"59468.9262044147"
],
[
"1793914560",
"76105.51",
"76166.43",
"76090.29",
"76135.97",
"0.7577467",
"57690.8632925045"
],
[
"1793914500",
"76075.08",
"76151.2",
"76075.08",
"76105.51",
"0.46245503",
"35196.8818432678"
],
[
"1793914440",
"76090.29",
"76120.74",
"76029.45",
"76075.08",
"0.31737989",
"24144.8216305437"
]
],
"referenceCandles": []
}Each candle is a tuple of strings: [tsSec, open, high, low, close, volume, quoteVolume]. tsSec is the bucket start in Unix seconds.
Prices and volumes are decimals.
POST https://api.testnet.polyester.com/marketdata.v1.MarketDataService/GetCandles
Content-Type: application/json
{"symbolId": 1, "timeframe": "MIN_1", "limit": 2, "includeIncomplete": true}{
"symbolId": 1,
"timeframe": "MIN_1",
"candles": [
{
"tsSec": "1793914620",
"open": "76135970000000",
"high": "76135970000000",
"low": "76044660000000",
"close": "76059870000000",
"volume": "78155926",
"quoteVolume": "59468.9262044147"
},
{
"tsSec": "1793914560",
"open": "76105510000000",
"high": "76166430000000",
"low": "76090290000000",
"close": "76135970000000",
"volume": "75774670",
"isClosed": true,
"quoteVolume": "57690.8632925045"
}
],
"nextPageToken": "cGcBEm1hcmtldGRhdGEuY2FuZGxlcw..."
}Prices use a fixed scale of 9 and volume the base Asset's marketDataVolumeScale; quoteVolume is a decimal string. The response above is shortened to two candles.
These responses were captured on 5 November 2026; page tokens are shortened.
Every parameter is listed in the API reference. The rules that matter when you read history:
limitcounts closed candles only: default500, maximum10000. WithincludeIncomplete, the open candle is added on top, which is why the REST request above forlimit=3returned four candles.startTimeandendTimeare inclusive bounds on the bucket start time. Neither may be more than 5 minutes in the future, andendTimemay not be beforestartTime.includeIncompleteadds the current open candle only when it is inside the requested range.includeReferenceadds the reference candles for the same timeframe and range.- Send
pageTokenwith the same Pair, timeframe, and bounds as the request that returned it.
Candles are ordered newest first. An empty nextPageToken means there are no more pages. A limit
above the maximum is rejected with HTTP 400 (INVALID_ARGUMENT). An unknown symbol answers HTTP 404
on REST; an unknown symbolId answers HTTP 400 (invalid_argument) on ConnectRPC.
Empty series
A series without closed candles answers with empty arrays, not missing fields. Right after a Pair opens, for example, every daily and weekly series is empty until its first bucket closes:
{
"nextPageToken": "",
"symbol": "BTC-USDT",
"timeframe": "1d",
"candles": [],
"referenceCandles": []
}REST always sends candles, referenceCandles, and nextPageToken. Test whether they are empty,
not whether they are present. REST symbol is the Pair symbol from the request path. ConnectRPC
JSON omits empty fields, as usual.
Open and closed candles
History returns closed candles only. With includeIncomplete, the current open candle is added in
front of them, so it is always the first candle on the first page.
- ConnectRPC sets
isClosed: trueon every closed candle. The open candle has noisClosed, which meansfalse. - REST tuples carry no closed flag. With
includeIncomplete=true, the first tuple is the open candle when itstsSecis the start of the current bucket; every other tuple is closed. - An open candle changes until its bucket ends. Never store it as final.
How a candle is built
- A candle opens at the previous candle's close, so consecutive candles join at every timeframe. A Pair's first candle opens at its first trade.
highandlowinclude the open.- A bucket without trades is flat at the previous close, with zero volume.
volumeis the traded base quantity.quoteVolumeis the exact traded quote amount, the sum of price times quantity of each trade, notclosetimesvolume.
Reference candles
With includeReference, the response adds referenceCandles: a composite candle series of the same
market on other venues. Use it to draw price history from before the Pair was listed on Polyester.
- The reference series covers the time before the Pair's
listingAt. In the example below, the lastBTC-USDTreference minute is1793880720(2026-11-05T12:12:00Z), right before that Pair'slistingAtof 12:13:33Z. - REST reference tuples have six slots,
[tsSec, open, high, low, close, volume], withoutquoteVolume. - Reference prices use the Pair's
referencePriceScaleon ConnectRPC, not the fixed scale of 9. Volume usesmarketDataVolumeScale. - Reference
volumewas0in every reference candle captured for this page. ConnectRPC JSON omits it. - The
1stimeframe has no reference series;referenceCandlesis empty.
{
"referenceCandles": [
["1793880720", "80219.15", "80219.16", "80199.99", "80218", "0"],
["1793880660", "80153.6", "80219.16", "80153.59", "80219.16", "0"]
]
}When a reference series is requested but temporarily unavailable, the request fails as unavailable instead of returning an empty list. Retry with backoff.
Backfill
Request the newest page
Call GetCandles with the timeframe, a limit of up to 10000, and optionally startTime.
Follow the page token
Request older pages with pageToken until nextPageToken is empty. Keep the same timeframe and
bounds; changing them invalidates the token.
Key candles by bucket
Store candles by tsSec. A later read of the same bucket replaces the earlier one.
Live candles
Subscribe to one timeframe of one Pair, using the REST timeframe string:
public:spot:market:candles:<timeframe>:<symbolId>
public:spot:market:candles:<timeframe>:<symbolId>:protoThe JSON channel publishes one candle object with decimal strings; the Protobuf channel publishes marketdata.v1.CandlePoint. These updates were sent on public:spot:market:candles:1m:1, in this
order:
{"tsSec":"1793914200","open":"75983.86","high":"75983.86","low":"75983.86","close":"75983.86","volume":"0","quoteVolume":"0","isClosed":false}
{"tsSec":"1793914140","open":"75983.86","high":"76014.26","low":"75923.1","close":"75983.86","volume":"0.16636898","quoteVolume":"12640.141624887","isClosed":true}
{"tsSec":"1793914200","open":"75983.86","high":"76029.46","low":"75983.86","close":"76029.46","volume":"0.00249794","quoteVolume":"189.9170000951","isClosed":false}- Each update for the open bucket has
isClosed: falseand replaces the previous update of that bucket. - After a bucket ends, one terminal update with
isClosed: truecarries its final values. - The next bucket's first update can arrive before the previous bucket's terminal update, as above.
Key every update by
tsSec; never assume the latest update is the newest bucket.
GetCandles and replace your candles by tsSec. The live channel only carries the current bucket
and the terminal update of the one before.See Realtime channels for connecting, Rate limits for quotas, and the API reference for the full schema.