API REFERENCE · V1.0.0
KnownAt API
Point-in-time market and alternative data for quantitative research, backtesting and live trading systems. Read-only HTTP, JSON responses, cursor pagination and UTC timestamps throughout.
https://api.knownat.comGetting started
From a fresh API key to your first response.
The KnownAt API serves point-in-time market and alternative data for quantitative research, backtesting and live trading systems. It is a read-only HTTP API: every endpoint is a GET, every response is JSON, and every field is documented.
- Create an API key in the KnownAt dashboard. The key is shown once and stored only as a hash — we cannot recover it for you.
- Confirm it works against
GET /v1/account/key. - Discover what is available with
GET /v1/datasets.
curl "https://api.knownat.com/v1/datasets?limit=10" \
-H "Authorization: Bearer kat_live_..."The base URL is https://api.knownat.com. Every path is versioned; there is no unversioned alias.
Authentication
Bearer API keys, test and live environments, and scopes.
Every endpoint except /health, /health/ready and /v1/status requires an API key, sent as a bearer token.
Authorization: Bearer kat_live_xxxxxxxxKeys come in two environments. kat_test_ keys are for development and integration tests; kat_live_ keys carry production quotas. KnownAt stores only a SHA-256 hash of a key, so a lost key is replaced, never recovered.
Scopes. Each key carries an explicit scope set. A request outside it fails with INSUFFICIENT_SCOPE rather than returning partial data.
| Scope | Grants |
|---|---|
account:read | Account metadata, plan and usage counters |
datasets:read | Dataset catalog and schemas |
datasets:download | Bulk dataset downloads |
polymarket:read | Polymarket datasets |
hyperliquid:read | Hyperliquid datasets |
live:read | Live streams |
Errors
One envelope, stable codes, and a request ID for support.
Every error shares one envelope. Branch on error.code; never parse error.message, which is written for humans and changes without notice.
{
"error": {
"code": "INVALID_PARAMETER",
"message": "Parameter 'start' must be before 'end'.",
"request_id": "req_01J8ZC4V6QK7M3B0YHP2R9TAXD",
"details": {}
}
}| Code | Status | Meaning |
|---|---|---|
INVALID_API_KEY | 401 | Missing, malformed, expired or revoked key |
INSUFFICIENT_SCOPE | 403 | Valid key, but it lacks the scope this endpoint needs |
INVALID_PARAMETER | 400 | A request parameter failed validation |
INVALID_CURSOR | 400 | Cursor is malformed, expired, or from different filters |
DATASET_NOT_FOUND | 404 | No such dataset is available to this key |
DATA_NOT_AVAILABLE | 404 | No data for this account or time range |
RATE_LIMIT_EXCEEDED | 429 | Plan rate limit spent; see retry-after |
UPSTREAM_DELAYED | 503 | The underlying feed is beyond its lag budget |
SERVICE_UNAVAILABLE | 503 | A dependency is unavailable — retry with backoff |
INTERNAL_ERROR | 500 | Unexpected failure; quote the request ID |
Every response carries an x-request-id header, echoed in error.request_id. Quote it to support and the full path of that request can be retrieved from the logs.
Rate limits
Measured per API key, not per IP address.
Limits apply per API key, so a client running across a serverless fleet is measured as one customer rather than punished for its address count. Successful responses carry ratelimit-limit and ratelimit-policy; a rejection carries retry-after and the code RATE_LIMIT_EXCEEDED.
| Plan | Requests / minute | Requests / month | Bandwidth / month |
|---|---|---|---|
| Free | 60 | 10,000 | 1 GB |
| Starter | 100 | 100,000 | 10 GB |
| Pro | 1,000 | 1,000,000 | 100 GB |
| Enterprise | custom | custom | custom |
Bulk and other expensive requests draw on an additional, smaller budget so that one heavy scan cannot crowd out your own interactive queries. Track consumption against your plan with GET /v1/account/usage.
Pagination
Cursor-based, because offsets skip and duplicate rows.
List endpoints are cursor-paginated. Pass meta.next_cursor back as cursor and iterate until meta.has_more is false.
cursor, rows = None, []
while True:
page = requests.get(
"https://api.knownat.com/v1/datasets",
headers={"Authorization": f"Bearer {key}"},
params={"limit": 1000, **({"cursor": cursor} if cursor else {})},
).json()
rows += page["data"]
if not page["meta"]["has_more"]:
break
cursor = page["meta"]["next_cursor"]Offset pagination is deliberately not offered. On an append-only dataset, ?page=18272 silently skips and duplicates records between pages as rows arrive — the failure is invisible until a backtest is already wrong.
Cursors are opaque, expire after 24 hours, and are bound to the filters they were issued with. Changing a filter mid-scan returns INVALID_CURSOR instead of a subtly incomplete result set. Page size may change between pages.
Timestamps
UTC ISO 8601, with millisecond precision, everywhere.
Every timestamp the API emits is UTC ISO 8601 with millisecond precision, for example 2026-08-28T10:00:00.000Z. The API never returns local time, epoch seconds, or a datetime without an offset.
Timestamp parameters accept the same format. A range is half-open: start is inclusive, end is exclusive, so consecutive ranges tile without overlapping.
Point-in-time semantics
The difference between when something happened and when you could have known.
Point-in-time datasets carry three timestamps rather than one. Which you filter on decides whether a backtest is honest.
| Field | Meaning |
|---|---|
source_timestamp | When the event happened, according to the source |
received_at | When KnownAt received it |
ingested_at | When KnownAt persisted it to queryable storage |
Where the source guarantees it, source_timestamp <= received_at <= ingested_at holds for every record.
Check is_point_in_time on a dataset before relying on this: it tells you whether the dataset records both source and ingestion time.
Data freshness
Three lags, because a pipeline fails in three different places.
GET /v1/status reports three separate lags per feed. One number would hide which part of the chain broke.
| Signal | What a stale value means |
|---|---|
source_lag_ms | The venue is quiet, or has stopped publishing |
receive_lag_ms | Our collector lost its connection to the source |
write_lag_ms | Events are arriving but are not being persisted |
A fresh receive_lag_ms with a stale write_lag_ms is the signature of a persistence failure: data is flowing in but is not queryable yet. Feed status is derived per feed against its own lag budget, because a tick stream and a daily dataset cannot share one threshold.
Live and bulk data
What REST is for, and what it is not for.
REST endpoints are for queries: historical windows, snapshots, metadata and catalog reads.
Live data will be delivered over WebSocket rather than REST polling. Polling a tick endpoint is neither cheap for you nor kind to your rate limit; the streaming interface is documented separately as feeds become available.
Bulk history is served as Parquet through signed download URLs, not as one enormous JSON response. Large ranges belong in a columnar format your pipeline can read directly.
System
Health, availability and data freshness.
/healthLiveness probe
Reports whether the API Worker is running. This endpoint performs no dependency checks and no authentication, so it stays answerable during a database outage and is safe for a high-frequency external uptime monitor. Use /health/ready to check dependencies and /v1/status to check data freshness.
Response · 200
The Worker is running.
status"ok" | "degraded"- Whether the Worker itself is serving requests.
servicestringenvironmentstringreleasestring- Release identifier of the running deployment, for correlating errors with a deploy.
git_shastringgenerated_atstring (date-time)- UTC timestamp in ISO 8601 format with millisecond precision.
Errors
| Status | Code | When |
|---|---|---|
| 500 | INTERNAL_ERROR | An unexpected error occurred. The request ID identifies it in our logs. |
/health/readyReadiness probe
Probes every dependency required to serve API traffic and reports each one individually with its latency. Returns 503 when any dependency is unhealthy, so a load balancer or deployment gate can act on it. Slower than /health: do not poll it at high frequency.
Response · 200
Every dependency is healthy.
status"ok" | "degraded"- Whether the Worker itself is serving requests.
servicestringenvironmentstringreleasestring- Release identifier of the running deployment, for correlating errors with a deploy.
git_shastringgenerated_atstring (date-time)- UTC timestamp in ISO 8601 format with millisecond precision.
dependencies[]object[]- Result of probing each dependency required to serve requests.
dependencies[].namestringdependencies[].healthybooleandependencies[].latency_msintegerdependencies[].errorstring | null
Errors
| Status | Code | When |
|---|---|---|
| 500 | INTERNAL_ERROR | An unexpected error occurred. The request ID identifies it in our logs. |
| 503 | — | At least one dependency is unhealthy. |
/health/dataIngestion pipeline health
Flat view of every collector heartbeat, including source, receive and write lag per feed. Intended for the KnownAt watchdog and operational dashboards rather than for client applications, which should use /v1/status. Requires the account:read scope.
Response · 200
Current state of every ingestion feed.
status"operational" | "delayed" | "degraded" | "offline" | "maintenance"- `operational` — the feed is within its documented lag budget. `delayed` — data is still arriving but later than the budget allows. `degraded` — lag is severe enough to affect most use cases. `offline` — no recent data has been persisted. `maintenance` — a planned interruption, announced in advance.
generated_atstring (date-time)- UTC timestamp in ISO 8601 format with millisecond precision.
feeds[]object[]- Flat list of every collector heartbeat, for internal dashboards and the watchdog.
feeds[].status"operational" | "delayed" | "degraded" | "offline" | "maintenance"- `operational` — the feed is within its documented lag budget. `delayed` — data is still arriving but later than the budget allows. `degraded` — lag is severe enough to affect most use cases. `offline` — no recent data has been persisted. `maintenance` — a planned interruption, announced in advance.
feeds[].source_lag_msinteger | null- Time since the newest event timestamp reported by the source. Grows when the source itself stops producing data.
feeds[].receive_lag_msinteger | null- Time since KnownAt last received an event. Grows when the collector loses its connection to the source.
feeds[].write_lag_msinteger | null- Time since KnownAt last persisted an event. Fresh receive lag with stale write lag means the pipeline, not the source, is failing.
feeds[].last_source_timestampstring (date-time) | null- Event time of the newest record, as reported by the source.
feeds[].last_received_atstring (date-time) | null- When KnownAt received that record.
feeds[].last_written_atstring (date-time) | null- When KnownAt persisted that record to queryable storage.
feeds[].events_per_secondnumber- Recent throughput of the feed.
feeds[].feedstring- Feed identifier, `dataset.stream`.
feeds[].datasetstringfeeds[].streamstring
Errors
| Status | Code | When |
|---|---|---|
| 401 | INVALID_API_KEY | The API key is missing, malformed, expired, or revoked. |
| 403 | INSUFFICIENT_SCOPE | The API key is valid but lacks the scope this endpoint requires. |
| 429 | RATE_LIMIT_EXCEEDED | The plan rate limit for this API key has been exceeded. |
| 500 | INTERNAL_ERROR | An unexpected error occurred. The request ID identifies it in our logs. |
| 503 | SERVICE_UNAVAILABLE | A dependency required to serve this request is unavailable. |
/v1/statusData freshness and feed status
Current status and lag of every live KnownAt feed, grouped by dataset and stream. Public and unauthenticated so it can be polled by a client before it decides to trust live data, or wired into a customer-side monitor. status at the top level is the worst status across all feeds. Poll at most once every 10 seconds.
Response · 200
Status of every feed.
status"operational" | "delayed" | "degraded" | "offline" | "maintenance"- Worst status across every feed. Use it as a single health signal.
generated_atstring (date-time)- UTC timestamp in ISO 8601 format with millisecond precision.
datasetsobject- Per-dataset, per-stream freshness, keyed by dataset slug and then by stream name.
datasets.{key}.{key}.status"operational" | "delayed" | "degraded" | "offline" | "maintenance"- `operational` — the feed is within its documented lag budget. `delayed` — data is still arriving but later than the budget allows. `degraded` — lag is severe enough to affect most use cases. `offline` — no recent data has been persisted. `maintenance` — a planned interruption, announced in advance.
datasets.{key}.{key}.source_lag_msinteger | null- Time since the newest event timestamp reported by the source. Grows when the source itself stops producing data.
datasets.{key}.{key}.receive_lag_msinteger | null- Time since KnownAt last received an event. Grows when the collector loses its connection to the source.
datasets.{key}.{key}.write_lag_msinteger | null- Time since KnownAt last persisted an event. Fresh receive lag with stale write lag means the pipeline, not the source, is failing.
datasets.{key}.{key}.last_source_timestampstring (date-time) | null- Event time of the newest record, as reported by the source.
datasets.{key}.{key}.last_received_atstring (date-time) | null- When KnownAt received that record.
datasets.{key}.{key}.last_written_atstring (date-time) | null- When KnownAt persisted that record to queryable storage.
datasets.{key}.{key}.events_per_secondnumber- Recent throughput of the feed.
Errors
| Status | Code | When |
|---|---|---|
| 429 | RATE_LIMIT_EXCEEDED | The plan rate limit for this API key has been exceeded. |
| 500 | INTERNAL_ERROR | An unexpected error occurred. The request ID identifies it in our logs. |
| 503 | SERVICE_UNAVAILABLE | A dependency required to serve this request is unavailable. |
Account
API key verification and usage counters.
/v1/account/keyVerify an API key
Returns the identity, scopes and plan associated with the presented API key. Use it as a connectivity and credentials check when wiring up a client: a 200 means the key is valid, unexpired and unrevoked, and the scopes array tells you exactly what it may call. The key itself is never echoed back.
Response · 200
The key is valid.
authenticatedtruekeyobject- Everything the API knows about the presented key. The key itself is never echoed.
key.idstring (uuid)- Identifier of the presented key, safe to log.
key.namestring- Label given to the key in the dashboard.
key.environment"test" | "live"- Whether this is a test or live key, derived from its prefix.
key.scopes[]string[]- Permissions granted to this key.
key.planstringkey.expires_atstring (date-time) | null- Expiry, or null for a non-expiring key.
Errors
| Status | Code | When |
|---|---|---|
| 401 | INVALID_API_KEY | The API key is missing, malformed, expired, or revoked. |
| 403 | INSUFFICIENT_SCOPE | The API key is valid but lacks the scope this endpoint requires. |
| 429 | RATE_LIMIT_EXCEEDED | The plan rate limit for this API key has been exceeded. |
| 500 | INTERNAL_ERROR | An unexpected error occurred. The request ID identifies it in our logs. |
/v1/account/usageCurrent period usage
Requests and bandwidth consumed by the account in the current calendar month (UTC), against the limits of its plan. Counters are derived from KnownAt’s own per-request accounting, not from edge rate-limiter state, and are the same numbers billing uses. They may lag live traffic by up to a minute.
Response · 200
Usage for the current period.
planstring- Plan the limits below are drawn from.
periodobject- Calendar month in UTC that these counters cover.
period.startstring (date-time)- Start of the current usage period, inclusive.
period.endstring (date-time)- End of the current usage period, exclusive.
requestsobject- Billable API requests consumed in the period.
requests.usedintegerrequests.limitintegerbandwidth_gbobject- Response bytes served in the period, in gibibytes.
bandwidth_gb.usednumberbandwidth_gb.limitnumberrate_limited_requestsinteger- Requests rejected with RATE_LIMIT_EXCEEDED in the period. A persistently non-zero value means the plan is undersized.
Errors
| Status | Code | When |
|---|---|---|
| 401 | INVALID_API_KEY | The API key is missing, malformed, expired, or revoked. |
| 403 | INSUFFICIENT_SCOPE | The API key is valid but lacks the scope this endpoint requires. |
| 429 | RATE_LIMIT_EXCEEDED | The plan rate limit for this API key has been exceeded. |
| 500 | INTERNAL_ERROR | An unexpected error occurred. The request ID identifies it in our logs. |
| 503 | SERVICE_UNAVAILABLE | A dependency required to serve this request is unavailable. |
Datasets
Catalog of the datasets KnownAt publishes.
/v1/datasets/polymarket.market_snapshots/latestLatest Polymarket market snapshots
Returns the newest stored price snapshot for each selected Polymarket outcome token, ordered by observation time descending. This small endpoint is intended for live dashboards and v0 integration checks; historical range scans will use a separate cursor-based endpoint.
Data source. Polymarket CLOB simplified markets, collected by the KnownAt ingestion service. observed_at marks the snapshot cycle, available_at records when the source response reached the collector, and ingested_at records persistence in ClickHouse.
Query budget. Results come from a compact latest-state table rather than the full history and are capped at 100 rows, protecting the shared ClickHouse instance from dashboard refreshes.
Parameters
limitinteger · default 20optional- Maximum number of current token snapshots to return. Between 1 and 100.
Response · 200
Newest stored Polymarket token snapshots, or an empty page before the first successful ingestion.
data[]object[]- Page of records, in the endpoint’s documented order.
data[].observed_atstring (date-time)- UTC time at which the collector started this Polymarket snapshot.
data[].available_atstring (date-time)- UTC time at which this response was available to the KnownAt collector.
data[].ingested_atstring (date-time)- UTC time at which ClickHouse persisted this row.
data[].market_idstring- Polymarket condition identifier shared by all outcome tokens in the market.
data[].token_idstring- Polymarket CLOB token identifier for this outcome.
data[].outcomestring- Human-readable market outcome represented by the token.
data[].pricenumber- Snapshot price quoted by Polymarket, from 0 through 1.
metaobjectmeta.recordsinteger- Number of records in this page.
meta.next_cursorstring | null- Cursor for the next page, or null when the scan is complete.
meta.has_moreboolean- Whether more records exist after this page.
meta.generated_atstring (date-time)- When this response was produced by the API.
Errors
| Status | Code | When |
|---|---|---|
| 400 | INVALID_PARAMETER | One or more request parameters failed validation. |
| 401 | INVALID_API_KEY | The API key is missing, malformed, expired, or revoked. |
| 403 | INSUFFICIENT_SCOPE | The API key is valid but lacks the scope this endpoint requires. |
| 429 | RATE_LIMIT_EXCEEDED | The plan rate limit for this API key has been exceeded. |
| 500 | INTERNAL_ERROR | An unexpected error occurred. The request ID identifies it in our logs. |
| 503 | SERVICE_UNAVAILABLE | A dependency required to serve this request is unavailable. |
/v1/datasetsList datasets
Every dataset published in the KnownAt catalog, with its provenance, update cadence, historical coverage window and available access formats.
Use this endpoint to discover what is queryable and over what period, before hitting a dataset’s data endpoints. is_point_in_time tells you whether a dataset records both source and ingestion time and is therefore safe to backtest against without lookahead bias.
Data source. The KnownAt catalog, updated whenever a dataset is published or its coverage window advances. Coverage timestamps reflect the state at the moment of the response; use /v1/status for live feed lag.
Ordering. Datasets are returned in ascending slug order. The slug is unique, so the ordering is total and deterministic: two calls with the same filters return rows in the same sequence.
Pagination. Cursor-based. Pass meta.next_cursor as cursor to fetch the next page; iterate until meta.has_more is false. Because paging resumes strictly after the last slug returned rather than at a numeric offset, a full scan neither skips nor duplicates a record while datasets are being published concurrently. Cursors are opaque, expire after 24 hours, and are bound to the filters they were issued with — change category and you must restart the scan.
Parameters
limitinteger · default 100optional- Maximum number of datasets to return. Between 1 and 1000.
cursorstringoptional- Pass
meta.next_cursorfrom the previous page. Cursors are bound to the filters they were issued with; changingcategoryrequires restarting the scan. categorystringoptional- Restrict results to a single catalog category.
Response · 200
A page of datasets in ascending slug order.
data[]object[]- Page of records, in the endpoint’s documented order.
data[].slugstring- Stable dataset identifier used in every other endpoint path.
data[].titlestring- Human-readable dataset name.
data[].descriptionstring- What the dataset contains and how it is collected.
data[].categorystring- Catalog grouping, e.g. `prediction-markets`.
data[].sourceobject- Provenance of the dataset.
data[].source.namestring- Origin of the data.
data[].source.urlstring | null- Public reference for the source, when one exists.
data[].source.licensestring | null- Licensing terms KnownAt redistributes under.
data[].update_frequencystring- How often new records land, e.g. `realtime`, `hourly`, `daily`.
data[].access_formats[]string[]- Formats this dataset can be retrieved in. Large ranges should use `parquet` bulk downloads.
data[].coverageobject- Historical availability window.
data[].coverage.earliest_available_atstring (date-time) | null- Oldest record available. Null while the dataset is still backfilling.
data[].coverage.latest_available_atstring (date-time) | null- Newest record available at the time of this response.
data[].row_countinteger- Approximate number of records currently queryable.
data[].is_point_in_timeboolean- Whether the dataset records both source and ingestion time, making it safe for point-in-time backtests.
data[].updated_atstring (date-time)- When this catalog entry last changed.
metaobjectmeta.recordsinteger- Number of records in this page.
meta.next_cursorstring | null- Cursor for the next page, or null when the scan is complete.
meta.has_moreboolean- Whether more records exist after this page.
meta.generated_atstring (date-time)- When this response was produced by the API.
Errors
| Status | Code | When |
|---|---|---|
| 400 | INVALID_CURSOR | One or more request parameters failed validation. The pagination cursor is malformed, expired, or was issued for different filters. |
| 401 | INVALID_API_KEY | The API key is missing, malformed, expired, or revoked. |
| 403 | INSUFFICIENT_SCOPE | The API key is valid but lacks the scope this endpoint requires. |
| 429 | RATE_LIMIT_EXCEEDED | The plan rate limit for this API key has been exceeded. |
| 500 | INTERNAL_ERROR | An unexpected error occurred. The request ID identifies it in our logs. |
| 503 | SERVICE_UNAVAILABLE | A dependency required to serve this request is unavailable. |
/v1/datasets/{slug}Get a dataset
Full catalog entry for one dataset, including its column schema in the order the data endpoints return them.
Each column carries a semantic_role identifying the point-in-time timestamps: source_timestamp is the event time reported by the source, received_at is when KnownAt received it, and ingested_at is when it was persisted. Where the source guarantees it, source_timestamp <= received_at <= ingested_at holds for every record.
Data source. The KnownAt catalog. Unpublished datasets are indistinguishable from non-existent ones and return DATASET_NOT_FOUND.
Parameters
slugstringrequired- Stable dataset identifier. Slugs never change once published.
Response · 200
The dataset and its column schema.
dataobjectdata.slugstring- Stable dataset identifier used in every other endpoint path.
data.titlestring- Human-readable dataset name.
data.descriptionstring- What the dataset contains and how it is collected.
data.categorystring- Catalog grouping, e.g. `prediction-markets`.
data.sourceobject- Provenance of the dataset.
data.source.namestring- Origin of the data.
data.source.urlstring | null- Public reference for the source, when one exists.
data.source.licensestring | null- Licensing terms KnownAt redistributes under.
data.update_frequencystring- How often new records land, e.g. `realtime`, `hourly`, `daily`.
data.access_formats[]string[]- Formats this dataset can be retrieved in. Large ranges should use `parquet` bulk downloads.
data.coverageobject- Historical availability window.
data.coverage.earliest_available_atstring (date-time) | null- Oldest record available. Null while the dataset is still backfilling.
data.coverage.latest_available_atstring (date-time) | null- Newest record available at the time of this response.
data.row_countinteger- Approximate number of records currently queryable.
data.is_point_in_timeboolean- Whether the dataset records both source and ingestion time, making it safe for point-in-time backtests.
data.updated_atstring (date-time)- When this catalog entry last changed.
data.columns[]object[]- Column schema in the order the data endpoints return them.
data.columns[].namestring- Column name as returned by the data endpoints.
data.columns[].data_typestring- Storage type, e.g. `timestamptz`, `numeric`, `text`.
data.columns[].descriptionstring- Meaning of the column.
data.columns[].nullableboolean- Whether the column may be null.
data.columns[].semantic_rolestring | null- Point-in-time role of the column: `source_timestamp`, `received_at`, `ingested_at`, or null for ordinary fields.
Errors
| Status | Code | When |
|---|---|---|
| 400 | INVALID_PARAMETER | One or more request parameters failed validation. |
| 401 | INVALID_API_KEY | The API key is missing, malformed, expired, or revoked. |
| 403 | INSUFFICIENT_SCOPE | The API key is valid but lacks the scope this endpoint requires. |
| 404 | DATASET_NOT_FOUND | No such dataset is available to this API key. |
| 429 | RATE_LIMIT_EXCEEDED | The plan rate limit for this API key has been exceeded. |
| 500 | INTERNAL_ERROR | An unexpected error occurred. The request ID identifies it in our logs. |
| 503 | SERVICE_UNAVAILABLE | A dependency required to serve this request is unavailable. |