Skip to content

Errors

Every failure answers in the same shape: a list, because one request can be wrong in several places at once, and each entry says what is wrong, where, and what to do instead.

{
"errors": [
{
"code": "DQL_UNKNOWN_FIELD",
"message": "unknown field 'fundng' at 1:18; did you mean 'funding'?",
"position": {
"line": 1,
"column": 18,
"offset": 17,
"length": 6
},
"hint": "did you mean 'funding'?"
}
]
}

Branch on code. It is stable. message explains and may be reworded between releases, and hint is advice, not a contract. position is present whenever the fault has a place in the query text: line and column are 1-based, offset is 0-based, and length covers the offending token, so an editor can underline it exactly.

Each of these was produced by running the query beside it through the parser. The messages are the server’s own.

The text is not a query. The position is where reading stopped and the message names what was expected there.

funding >
{
"errors": [
{
"code": "DQL_SYNTAX",
"message": "unexpected end of input at 1:11; expected an expression",
"position": {
"line": 1,
"column": 11,
"offset": 10,
"length": 0
},
"hint": "expected an expression"
}
]
}

Underline the position. POST /v2/dql/parse answers this without running anything, which is what an editor should call on every keystroke.

A name that is not a field. The message suggests the nearest one it knows.

any(venues where fundng > 0)
{
"errors": [
{
"code": "DQL_UNKNOWN_FIELD",
"message": "unknown field 'fundng' at 1:18; did you mean 'funding'?",
"position": {
"line": 1,
"column": 18,
"offset": 17,
"length": 6
},
"hint": "did you mean 'funding'?"
}
]
}

Read GET /v2/dql/catalogue for the fields that exist rather than carrying a list in your client.

A name that is not a function.

any(venues where emma(close, 20) > 0)
{
"errors": [
{
"code": "DQL_UNKNOWN_FUNCTION",
"message": "unknown function 'emma' at 1:18; did you mean 'ema'?",
"position": {
"line": 1,
"column": 18,
"offset": 17,
"length": 15
},
"hint": "did you mean 'ema'?"
}
]
}

The catalogue lists every function with its signature.

A venue code the platform does not have. The message lists the ones it does.

any(venues where venue = BINANCE-PERP)
{
"errors": [
{
"code": "DQL_UNKNOWN_VENUE",
"message": "unknown venue 'BINANCE-PERP' at 1:26; known: BYBIT-PERP, HYPERLIQUID, KRAKEN-FUTURES, OKX-PERP",
"position": {
"line": 1,
"column": 26,
"offset": 25,
"length": 12
},
"hint": "known: BYBIT-PERP, HYPERLIQUID, KRAKEN-FUTURES, OKX-PERP"
}
]
}

Read GET /v2/venues. A venue the platform is still onboarding is not queryable until it appears there.

A quote currency that exists on no listing.

any(venues where quote = USDD)
{
"errors": [
{
"code": "DQL_UNKNOWN_CURRENCY",
"message": "unknown currency 'USDD'",
"position": {
"line": 1,
"column": 26,
"offset": 25,
"length": 4
},
"hint": "did you mean 'USD'?"
}
]
}

The catalogue’s currencies is the whole set. DQL never converts between them, so the spelling has to match.

A base asset the platform does not have.

base = BITCOIN
{
"errors": [
{
"code": "DQL_UNKNOWN_ASSET",
"message": "unknown asset 'BITCOIN'",
"position": {
"line": 1,
"column": 8,
"offset": 7,
"length": 7
},
"hint": "known: BTC, ETH, SOL"
}
]
}

The catalogue’s assets is the whole set.

An instrument code that does not exist — often the base asset written where the instrument belongs.

instrument = BTC
{
"errors": [
{
"code": "DQL_UNKNOWN_INSTRUMENT",
"message": "unknown instrument 'BTC'",
"position": {
"line": 1,
"column": 14,
"offset": 13,
"length": 3
},
"hint": "known: BTC-PERP, ETH-PERP, SOL-PERP"
}
]
}

Instruments are BTC-PERP, not BTC. The catalogue’s instruments lists them.

A venue-scoped field used as though it belonged to the instrument. Funding is a fact about one venue’s listing; an instrument has several.

funding > 0
{
"errors": [
{
"code": "DQL_SCOPE",
"message": "'funding' is venue-scoped; wrap it in any(venues where …), all(…), count(…) or an aggregate",
"position": {
"line": 1,
"column": 1,
"offset": 0,
"length": 7
},
"hint": "wrap in any(venues where ...) / all(...) / count(...) or an aggregate"
}
]
}

Say which venues you mean: any(venues where funding > 0), or all, count, or an aggregate.

A quantifier inside a quantifier. There is one level of venues, so the inner one would have nothing to range over.

any(venues where any(venues where funding > 0))
{
"errors": [
{
"code": "DQL_NESTING",
"message": "quantifiers cannot be nested",
"position": {
"line": 1,
"column": 18,
"offset": 17,
"length": 29
}
}
]
}

Flatten the condition.

An operation on a value of the wrong kind. Summing prices across venues is a category error, not a small inaccuracy — the result would be a number with no meaning.

sum(venues, mark) > 0
{
"errors": [
{
"code": "DQL_TYPE",
"message": "sum requires an additive field; mark is a price",
"position": {
"line": 1,
"column": 1,
"offset": 0,
"length": 17
},
"hint": "additive: oi, volume_24h, turnover_24h, bid_size, ask_size, volume, the depth functions, or arithmetic over those"
}
]
}

Sum only additive fields; the catalogue’s additive says which. For a price across venues, use max, min or an average of your own.

A series function with no period to read at. A moving average is a different number on every timeframe, so the platform will not choose one for you.

any(venues where ema(close, 20) > close)
{
"errors": [
{
"code": "DQL_TIMEFRAME_REQUIRED",
"message": "series function 'ema' needs a timeframe: add 'timeframe 15m' or a trailing argument",
"position": {
"line": 1,
"column": 18,
"offset": 17,
"length": 14
}
}
]
}

Add timeframe 1h to the query, or give the function its own: ema(close, 20, 1h).

A period the platform does not store. The message lists the ones it does.

any(venues where sma(close, 3) > 0) timeframe 7m
{
"errors": [
{
"code": "DQL_TIMEFRAME_UNSUPPORTED",
"message": "timeframe '7m' is not supported; supported: 1m, 2m, 3m, 5m, 15m, 30m, 1h, 4h, 12h, 1d",
"position": {
"line": 1,
"column": 37,
"offset": 36,
"length": 9
}
}
]
}

Use a period from the catalogue’s timeframes.

Two series of different periods compared as though they lined up. They do not: one bar of the first is not one bar of the second.

any(venues where crossover(ema(close, 20, 1h), ema(close, 50, 4h)))
{
"errors": [
{
"code": "DQL_TIMEFRAME_MISMATCH",
"message": "series timeframes differ (1h vs 4h)",
"position": {
"line": 1,
"column": 18,
"offset": 17,
"length": 49
}
}
]
}

Read both at the same period.

An argument outside what the function accepts. The message states the range.

any(venues where ema(close, 0) > 0) timeframe 1h
{
"errors": [
{
"code": "DQL_ARGUMENT",
"message": "argument 'n' of ema must be an integer in 1…500",
"position": {
"line": 1,
"column": 29,
"offset": 28,
"length": 1
}
}
]
}

Use a window inside the stated range.

A clause given twice. The platform will not guess which one you meant.

venues_listed > 0 timeframe 1h timeframe 4h
{
"errors": [
{
"code": "DQL_DUPLICATE_CLAUSE",
"message": "clause 'timeframe' given twice",
"position": {
"line": 1,
"column": 32,
"offset": 31,
"length": 9
},
"hint": "each clause may appear at most once"
}
]
}

Give it once.

An instant outside what can be answered — in the future, or before the earliest data retained.

any(venues where funding > 0) as of 2030-01-01T00:00:00Z
{
"errors": [
{
"code": "DQL_AS_OF_RANGE",
"message": "as_of is in the future",
"position": {
"line": 1,
"column": 31,
"offset": 30,
"length": 2
},
"hint": "the server clock is 2026-09-23T12:00:05Z"
}
]
}

The catalogue’s earliest_retained is the floor; the present is the ceiling.

These are about the request rather than about the query, so no query produces them and none is shown.

code status what it means
DQL_JSON_SCHEMA 400 The request body is not the shape the endpoint takes. path points at the offending member, as a JSON pointer.
DQL_VERSION 400 A language version other than 1 was asked for. There is only one.
DQL_LIMIT 400 The request is within the grammar but past a batch limit — too many listings, too many rows, or a response too large. The message states both the figure and the limit.
DQL_COMPLEXITY 400 The query is within the grammar but costs more than the limits allow. POST /v2/dql/parse returns a query’s cost, so this can be seen before it is hit.
AUTH_REQUIRED 401 The endpoint needs a key and none was sent.
AUTH_INVALID 401 The key is unknown, revoked, expired, or the header is not Bearer <key>.
KEY_IN_URL 400 A key was sent as a query parameter. Query strings are written to logs and browser history, so the key should be treated as compromised and replaced.
PLAN_LIMIT 403 The request is valid but reaches past what the plan includes — usually further back in history.
RATE_LIMITED 429 A per-second, per-day or stream limit of the plan. Retry-After says when to come back.
UNAVAILABLE 503 The platform cannot answer yet; retry in a minute.

A 400 from POST /v2/dql/parse is not a failure of the API: it is the answer you asked for.