KnownAtAPI v1.0.0

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.

Base URLhttps://api.knownat.com

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

  1. 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.
  2. Confirm it works against GET /v1/account/key.
  3. Discover what is available with GET /v1/datasets.
bash
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.

http
Authorization: Bearer kat_live_xxxxxxxx

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

ScopeGrants
account:readAccount metadata, plan and usage counters
datasets:readDataset catalog and schemas
datasets:downloadBulk dataset downloads
polymarket:readPolymarket datasets
hyperliquid:readHyperliquid datasets
live:readLive 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.

json
{
  "error": {
    "code": "INVALID_PARAMETER",
    "message": "Parameter 'start' must be before 'end'.",
    "request_id": "req_01J8ZC4V6QK7M3B0YHP2R9TAXD",
    "details": {}
  }
}
CodeStatusMeaning
INVALID_API_KEY401Missing, malformed, expired or revoked key
INSUFFICIENT_SCOPE403Valid key, but it lacks the scope this endpoint needs
INVALID_PARAMETER400A request parameter failed validation
INVALID_CURSOR400Cursor is malformed, expired, or from different filters
DATASET_NOT_FOUND404No such dataset is available to this key
DATA_NOT_AVAILABLE404No data for this account or time range
RATE_LIMIT_EXCEEDED429Plan rate limit spent; see retry-after
UPSTREAM_DELAYED503The underlying feed is beyond its lag budget
SERVICE_UNAVAILABLE503A dependency is unavailable — retry with backoff
INTERNAL_ERROR500Unexpected 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.

PlanRequests / minuteRequests / monthBandwidth / month
Free6010,0001 GB
Starter100100,00010 GB
Pro1,0001,000,000100 GB
Enterprisecustomcustomcustom

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.

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

FieldMeaning
source_timestampWhen the event happened, according to the source
received_atWhen KnownAt received it
ingested_atWhen 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.

SignalWhat a stale value means
source_lag_msThe venue is quiet, or has stopped publishing
receive_lag_msOur collector lost its connection to the source
write_lag_msEvents 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.

GET/health

Liveness probe

Public — no key required

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.
servicestring
environmentstring
releasestring
Release identifier of the running deployment, for correlating errors with a deploy.
git_shastring
generated_atstring (date-time)
UTC timestamp in ISO 8601 format with millisecond precision.

Errors

StatusCodeWhen
500INTERNAL_ERRORAn unexpected error occurred. The request ID identifies it in our logs.
GET/health/ready

Readiness probe

Public — no key required

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.
servicestring
environmentstring
releasestring
Release identifier of the running deployment, for correlating errors with a deploy.
git_shastring
generated_atstring (date-time)
UTC timestamp in ISO 8601 format with millisecond precision.
dependencies[]object[]
Result of probing each dependency required to serve requests.
dependencies[].namestring
dependencies[].healthyboolean
dependencies[].latency_msinteger
dependencies[].errorstring | null

Errors

StatusCodeWhen
500INTERNAL_ERRORAn unexpected error occurred. The request ID identifies it in our logs.
503At least one dependency is unhealthy.
GET/health/data

Ingestion pipeline health

API key required

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[].datasetstring
feeds[].streamstring

Errors

StatusCodeWhen
401INVALID_API_KEYThe API key is missing, malformed, expired, or revoked.
403INSUFFICIENT_SCOPEThe API key is valid but lacks the scope this endpoint requires.
429RATE_LIMIT_EXCEEDEDThe plan rate limit for this API key has been exceeded.
500INTERNAL_ERRORAn unexpected error occurred. The request ID identifies it in our logs.
503SERVICE_UNAVAILABLEA dependency required to serve this request is unavailable.
GET/v1/status

Data freshness and feed status

Public — no key required

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

StatusCodeWhen
429RATE_LIMIT_EXCEEDEDThe plan rate limit for this API key has been exceeded.
500INTERNAL_ERRORAn unexpected error occurred. The request ID identifies it in our logs.
503SERVICE_UNAVAILABLEA dependency required to serve this request is unavailable.

Account

API key verification and usage counters.

GET/v1/account/key

Verify an API key

API key required

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.

authenticatedtrue
keyobject
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.planstring
key.expires_atstring (date-time) | null
Expiry, or null for a non-expiring key.

Errors

StatusCodeWhen
401INVALID_API_KEYThe API key is missing, malformed, expired, or revoked.
403INSUFFICIENT_SCOPEThe API key is valid but lacks the scope this endpoint requires.
429RATE_LIMIT_EXCEEDEDThe plan rate limit for this API key has been exceeded.
500INTERNAL_ERRORAn unexpected error occurred. The request ID identifies it in our logs.
GET/v1/account/usage

Current period usage

API key required

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.usedinteger
requests.limitinteger
bandwidth_gbobject
Response bytes served in the period, in gibibytes.
bandwidth_gb.usednumber
bandwidth_gb.limitnumber
rate_limited_requestsinteger
Requests rejected with RATE_LIMIT_EXCEEDED in the period. A persistently non-zero value means the plan is undersized.

Errors

StatusCodeWhen
401INVALID_API_KEYThe API key is missing, malformed, expired, or revoked.
403INSUFFICIENT_SCOPEThe API key is valid but lacks the scope this endpoint requires.
429RATE_LIMIT_EXCEEDEDThe plan rate limit for this API key has been exceeded.
500INTERNAL_ERRORAn unexpected error occurred. The request ID identifies it in our logs.
503SERVICE_UNAVAILABLEA dependency required to serve this request is unavailable.

Datasets

Catalog of the datasets KnownAt publishes.

GET/v1/datasets/polymarket.market_snapshots/latest

Latest Polymarket market snapshots

API key required

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.
metaobject
meta.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

StatusCodeWhen
400INVALID_PARAMETEROne or more request parameters failed validation.
401INVALID_API_KEYThe API key is missing, malformed, expired, or revoked.
403INSUFFICIENT_SCOPEThe API key is valid but lacks the scope this endpoint requires.
429RATE_LIMIT_EXCEEDEDThe plan rate limit for this API key has been exceeded.
500INTERNAL_ERRORAn unexpected error occurred. The request ID identifies it in our logs.
503SERVICE_UNAVAILABLEA dependency required to serve this request is unavailable.
GET/v1/datasets

List datasets

API key required

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_cursor from the previous page. Cursors are bound to the filters they were issued with; changing category requires 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.
metaobject
meta.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

StatusCodeWhen
400INVALID_CURSOROne or more request parameters failed validation. The pagination cursor is malformed, expired, or was issued for different filters.
401INVALID_API_KEYThe API key is missing, malformed, expired, or revoked.
403INSUFFICIENT_SCOPEThe API key is valid but lacks the scope this endpoint requires.
429RATE_LIMIT_EXCEEDEDThe plan rate limit for this API key has been exceeded.
500INTERNAL_ERRORAn unexpected error occurred. The request ID identifies it in our logs.
503SERVICE_UNAVAILABLEA dependency required to serve this request is unavailable.
GET/v1/datasets/{slug}

Get a dataset

API key required

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.

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

StatusCodeWhen
400INVALID_PARAMETEROne or more request parameters failed validation.
401INVALID_API_KEYThe API key is missing, malformed, expired, or revoked.
403INSUFFICIENT_SCOPEThe API key is valid but lacks the scope this endpoint requires.
404DATASET_NOT_FOUNDNo such dataset is available to this API key.
429RATE_LIMIT_EXCEEDEDThe plan rate limit for this API key has been exceeded.
500INTERNAL_ERRORAn unexpected error occurred. The request ID identifies it in our logs.
503SERVICE_UNAVAILABLEA dependency required to serve this request is unavailable.