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&currency=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.

Response
{
  "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.

Header
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.

KindExample
Listed securityNASDAQ:AAPL, LSE:VOD, XETRA:SAP or ASX:VAS. The MIC works too, as in XASX:VAS
Open identifiersisin:US0378331005 or figi:BBG000B9XRY4
Licensed identifierscusip:037833100 and sedol:... are accepted as input but never returned
FX pairFX:EURUSD
CryptoCRYPTO:BTC for the most liquid match, or SOLANA:<contract> for an exact token
Index, rate, futureINDEX:AXJO, RATE:AU10Y, FUT:CL1
Quova IDins_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.

400 Bad Request
{
  "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"
    }
  ]
}
StatusCodeMeaning
400invalid_requestA parameter is missing or malformed. errors says which one and why
400invalid_cursorThe cursor isn't valid. Start again without one
401missing_api_keyNo Authorization header was sent
401invalid_api_keyThe key doesn't exist, was revoked or isn't sent as a bearer token
401key_expiredThe key is past its expiry date
402trial_expiredThe trial has ended. Choose a plan to keep going
402trial_credits_exhaustedThe trial has used all of its credits
402spend_cap_reachedYour organisation hit its spend cap for this billing period
403plan_upgrade_requiredYour plan doesn't include this endpoint
403endpoint_not_allowedThe key isn't allowed to call this endpoint
403ip_not_allowedThe request came from an IP outside the key's allowlist
403org_suspendedThe organisation is suspended
404not_foundThe path or the item you asked for doesn't exist
429key_rate_limitedThe key went over its own rate limit
429org_rate_limitedYour organisation went over its plan's rate limit
429credit_budget_exhaustedThe key used up its credit budget for the day, week or month
500internal_errorSomething went wrong on our side
503data_pendingWe'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.

CodeMeaning
invalid_symbolThe identifier isn't in a format we recognise
instrument_not_foundNothing matches the identifier
not_supportedThe dataset doesn't apply to this instrument, like dividends on a currency pair
data_unavailableNone of our sources have this data
data_pendingWe're fetching it now. Ask again shortly
source_failedEvery 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.

Response headers
HTTP/2 200
Content-Type: application/json
Credits-Remaining: 998

The 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.

PlanRequests a minute
Trial60
Starter300
Growth1,000
Scale3,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.