Guides
Getting started
The Quova API is a REST API over HTTPS that returns JSON. This page covers what every endpoint has in common, and the API reference has the parameters, fields and credit cost of each one.
Your first request
Create a key in the dashboard, then ask for a week of prices for Apple and BHP. Every request goes to https://api.quova.io.
curl "https://api.quova.io/v1/prices/daily?symbols=NASDAQ:AAPL,ASX:BHP&from=2026-06-01&to=2026-06-05¤cy=USD" \
-H "Authorization: Bearer $QUOVA_API_KEY"You get back the bars for each symbol along with what it resolved to and what the call cost. A symbol we can't
answer for gets an entry in errors instead of failing the whole call.
{
"data": [
{
"symbol": "NASDAQ:AAPL",
"instrument": {
"id": "ins_34VInjBb4l9Qz9gdXnDar",
"symbol": "NASDAQ:AAPL",
"name": "Apple Inc.",
"type": "equity",
"mic": "XNAS",
"currency": "USD",
"country": "US"
},
"asOf": "2026-06-05T00:00:00Z",
"currency": "USD",
"adjust": "splits",
"bars": [
{
"date": "2026-06-01",
"open": 309.625,
"high": 310.94,
"low": 305.02,
"close": 306.31,
"volume": 48849933
},
{
"date": "2026-06-02",
"open": 307.46,
"high": 315.45,
"low": 306.685,
"close": 315.2,
"volume": 44534716
},
{
"date": "2026-06-03",
"open": 314.175,
"high": 316.94,
"low": 308.85,
"close": 310.26,
"volume": 50836705
},
{
"date": "2026-06-04",
"open": 313.23,
"high": 313.54,
"low": 309.65,
"close": 311.23,
"volume": 44869134
},
{
"date": "2026-06-05",
"open": 312.86,
"high": 315.17,
"low": 307.15,
"close": 307.34,
"volume": 65310502
}
]
},
{
"symbol": "ASX:BHP",
"instrument": {
"id": "ins_34VMvft6kZEXThtzFGcAL",
"symbol": "ASX:BHP",
"name": "BHP Group Ltd",
"type": "equity",
"mic": "XASX",
"currency": "AUD",
"country": "AU"
},
"asOf": "2026-06-05T00:00:00Z",
"currency": "USD",
"adjust": "splits",
"bars": [
{
"date": "2026-06-01",
"open": 44.582805,
"high": 45.040725,
"low": 44.4575925,
"close": 44.70444,
"volume": 6338357
},
{
"date": "2026-06-02",
"open": 45.50852,
"high": 45.637724,
"low": 44.89839,
"close": 45.486986,
"volume": 7621289
},
{
"date": "2026-06-03",
"open": 46.01124,
"high": 46.360512,
"low": 45.897192,
"close": 46.267848,
"volume": 8405596
},
{
"date": "2026-06-04",
"open": 45.57987,
"high": 45.6512,
"low": 44.488521,
"close": 44.79524,
"volume": 7871447
},
{
"date": "2026-06-05",
"open": 43.8199,
"high": 43.967845,
"low": 42.953365,
"close": 43.14358,
"volume": 7385673
}
]
}
],
"errors": [],
"meta": {
"credits": 2,
"requestId": "req_34VPGnu6lzdOIqJwSfMDT"
}
}Authentication
Send your key in the Authorization header as a bearer token. Keys start with qv_ and are
shown once, when you create them, because we only store a hash.
Authorization: Bearer qv_...Keys never go in the query string, since URLs end up in logs. A key can call every endpoint or a fixed list of operation IDs, and it can have its own credit budget, rate limit, IP allowlist and expiry date.
Responses
Data endpoints share one envelope. data has an entry for each symbol you asked for, in the order you
sent them. errors lists the symbols that failed, and meta says what the call cost along with
its request ID.
Every field is always present, and a value we don't know is null rather than zero. Each entry has an asOf timestamp for the point in time its data describes. That's the newest record's date for history,
when a live value was observed, and when we last checked a source for reference data.
Timestamps are ISO 8601 in UTC, and daily data uses the exchange's local trading date. Fields and parameters are camelCase while enum values and error codes are snake_case. An unknown query parameter is rejected with a 400, so a typo can't quietly change what you get back.
Identifiers
Every instrument is referenced as namespace:value, and any of these work wherever a symbol is
expected.
| Kind | Example |
|---|---|
| Listed security | NASDAQ:AAPL, LSE:VOD, XETRA:SAP or ASX:VAS. The MIC works too, as in XASX:VAS |
| Open identifiers | isin:US0378331005 or figi:BBG000B9XRY4 |
| Licensed identifiers | cusip:037833100 and sedol:... are accepted as input but never returned |
| FX pair | FX:EURUSD |
| Crypto | CRYPTO:BTC for the most liquid match, or SOLANA:<contract> for an exact token |
| Index, rate, future | INDEX:AXJO, RATE:AU10Y, FUT:CL1 |
| Quova ID | ins_7Hq2kP3xY9vLmN4bR8sT1u. Store this one, it survives renames and ticker changes |
A bare ticker like BHP resolves to its home listing. Each entry in the response includes the instrument your symbol resolved to, so you can always check.
Errors
When a whole request fails you get an error status and an RFC 9457 application/problem+json body.
Branch on code, which never changes, rather than on the title or detail, which we may reword.
{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"detail": "The request has invalid parameters",
"code": "invalid_request",
"requestId": "req_34VPGo1WDsmVr53mia3HV",
"errors": [
{
"location": "query.from",
"message": "must be a date like 2026-09-01"
}
]
}| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | A parameter is missing or malformed. errors says which one and why |
| 400 | invalid_cursor | The cursor isn't valid. Start again without one |
| 401 | missing_api_key | No Authorization header was sent |
| 401 | invalid_api_key | The key doesn't exist, was revoked or isn't sent as a bearer token |
| 401 | key_expired | The key is past its expiry date |
| 402 | trial_expired | The trial has ended. Choose a plan to keep going |
| 402 | trial_credits_exhausted | The trial has used all of its credits |
| 402 | spend_cap_reached | Your organisation hit its spend cap for this billing period |
| 403 | plan_upgrade_required | Your plan doesn't include this endpoint |
| 403 | endpoint_not_allowed | The key isn't allowed to call this endpoint |
| 403 | ip_not_allowed | The request came from an IP outside the key's allowlist |
| 403 | org_suspended | The organisation is suspended |
| 404 | not_found | The path or the item you asked for doesn't exist |
| 429 | key_rate_limited | The key went over its own rate limit |
| 429 | org_rate_limited | Your organisation went over its plan's rate limit |
| 429 | credit_budget_exhausted | The key used up its credit budget for the day, week or month |
| 500 | internal_error | Something went wrong on our side |
| 503 | data_pending | We're still fetching this data. Retry after the Retry-After header |
Inside a batch, a symbol that fails gets its own entry in errors with one of these codes, and you aren't
billed for it.
| Code | Meaning |
|---|---|
invalid_symbol | The identifier isn't in a format we recognise |
instrument_not_found | Nothing matches the identifier |
not_supported | The dataset doesn't apply to this instrument, like dividends on a currency pair |
data_unavailable | None of our sources have this data |
data_pending | We're fetching it now. Ask again shortly |
source_failed | Every source failed for this item. Try again later |
Credits
Every endpoint has a weight in credits, shown in the API reference and in the x-credits field of the OpenAPI
spec. Most endpoints charge the weight for each instrument that resolves, and the rest charge per call, page or series.
History length doesn't change the price.
meta.credits is what the call cost. The Credits-Remaining header is what's left under the
tightest limit on your key. That's the key's own budget, the trial cap, your plan's monthly pool or your spend cap,
whichever runs out first, and it reads 0 once you're into overage.
HTTP/2 200
Content-Type: application/json
Credits-Remaining: 998The pricing page lists the cost of every endpoint.
Rate limits
Each plan has a requests per minute limit that every key in the organisation shares. You can give a key a lower limit of its own.
| Plan | Requests a minute |
|---|---|
| Trial | 60 |
| Starter | 300 |
| Growth | 1,000 |
| Scale | 3,000 |
Going over returns a 429 with key_rate_limited or org_rate_limited and a Retry-After header in seconds. Wait that long before you retry.
Batch requests
Endpoints that take symbols accept up to 100 comma separated identifiers, and one symbol
or fifty use the same call. Results come back in the order you sent them.
If a symbol fails it goes into errors and the rest of the call still succeeds. Only items that return
data are billed, and a symbol sent twice in one call, like ASX:VAS,XASX:VAS, is billed once.
Fresh data
We keep everything we fetch and refresh it when someone reads it after it has gone stale, so most reads come from our cache. The first request for an instrument we've never seen waits while we fetch it, which makes it slower than the ones after it.
Add fresh=true to skip the cache and fetch from sources during your call. Items fetched fresh are
billed at 2x. If every source fails, the item falls back to our cache and costs the normal price.
CSV
Add format=csv to any data endpoint to get text/csv instead of JSON. The body only has
data rows, so the Batch-Errors header tells you how many inputs failed.
Pagination
List endpoints return a page at a time. Set the page size with limit, then send meta.nextCursor back as cursor to get the next page. On the last page nextCursor is null. There's no offset pagination.