Make your first request
Search for a dataset and inspect its schema before choosing filters, so you never guess slugs, columns, date fields or point-in-time support.
- 1Create a key
Approved accounts create keys in the authenticated Data API console. Store the show-once secret in a secrets manager.
- 2Search, then inspect
Resolve a dataset with
GET /datasets?q=…, then read/schemabefore querying rows. - 3Query or export
Use bounded JSON for applications, complete-or-rejected CSV for filtered extracts, or checksummed Parquet for a full immutable version.
export TP_API_KEY="tp_live_..."
curl --fail-with-body \
--header "Authorization: Bearer $TP_API_KEY" \
"https://api.tradepolaris.com/platform/v1/datasets?q=apple&limit=10"import hashlib
import os
import requests
BASE_URL = "https://api.tradepolaris.com/platform/v1"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['TP_API_KEY']}"
# Search first; do not page the full catalog.
catalog = session.get(
f"{BASE_URL}/datasets",
params={"q": "apple", "limit": 10},
)
catalog.raise_for_status()
dataset = catalog.json()[0]["name"]
# Inspect capabilities before choosing filters.
schema_response = session.get(f"{BASE_URL}/datasets/{dataset}/schema")
schema_response.raise_for_status()
schema = schema_response.json()
# JSON rows are paginated and DataFrame-ready.
rows_response = session.get(
f"{BASE_URL}/datasets/{dataset}/query",
params={"start": "2026-01-01", "limit": 5000},
)
rows_response.raise_for_status()
page = rows_response.json()
print(page["columns"], page["rows"], page["truncated"])
# For the immutable full version, verify the exported Parquet bytes.
export_response = session.get(f"{BASE_URL}/datasets/{dataset}/export")
export_response.raise_for_status()
artifact = export_response.json()
download = requests.get(artifact["url"]) # no API Authorization header
download.raise_for_status()
expected_sha256 = artifact.get("content_sha256")
if not expected_sha256:
raise RuntimeError("Export did not include a checksum")
assert hashlib.sha256(download.content).hexdigest() == expected_sha256import { createHash } from "node:crypto";
const BASE_URL = "https://api.tradepolaris.com/platform/v1";
const API_KEY = process.env.TP_API_KEY;
if (!API_KEY) throw new Error("Set TP_API_KEY in the server environment");
const sleep = (milliseconds: number) =>
new Promise((resolve) => setTimeout(resolve, milliseconds));
async function api(path: string): Promise<Response> {
const url = new URL(path, BASE_URL + "/");
if (url.origin !== new URL(BASE_URL).origin) {
throw new Error("Refusing to forward API auth to another origin");
}
for (let attempt = 0; attempt < 5; attempt += 1) {
const response = await fetch(url, {
headers: { Authorization: "Bearer " + API_KEY },
});
if (![429, 503].includes(response.status) || attempt === 4) return response;
const seconds = Number(response.headers.get("Retry-After"));
const delay = Number.isFinite(seconds) && seconds > 0
? Math.min(seconds * 1000, 5000)
: Math.min(250 * 2 ** attempt, 5000);
await sleep(delay);
}
throw new Error("unreachable");
}
async function json(path: string) {
const response = await api(path);
if (!response.ok) throw new Error("Data API returned " + response.status);
return response.json();
}
const catalog = await json("datasets?q=example&limit=10");
if (!catalog[0]) throw new Error("No matching dataset");
const name = catalog[0].name;
const version = catalog[0].latest_version; // pin every following request
const route = "datasets/" + encodeURIComponent(name);
const schema = await json(route + "/schema?version=" + version);
let offset = 0;
const rows: unknown[][] = [];
for (let pageNumber = 0; pageNumber < 20; pageNumber += 1) {
const page = await json(
route + "/query?version=" + version + "&limit=5000&offset=" + offset,
);
rows.push(...page.rows);
if (!page.truncated) break;
if (page.returned < 1 || pageNumber === 19) {
throw new Error("Pagination did not terminate within the client bound");
}
offset += page.returned;
}
const artifact = await json(route + "/export?version=" + version);
const download = await fetch(artifact.url); // deliberately no Authorization header
if (!download.ok) throw new Error("Export download returned " + download.status);
const bytes = Buffer.from(await download.arrayBuffer());
const actual = createHash("sha256").update(bytes).digest("hex");
if (!artifact.content_sha256 || actual !== artifact.content_sha256) {
throw new Error("Export checksum mismatch");
}
console.log({ columns: schema.columns.length, rows: rows.length, version });Authentication and scopes
Send the key on every request. The secret starts with tp_live_ and is shown only once.
Authorization: Bearer tp_live_...Never put a Data API key in browser bundles, local storage, mobile binaries or client-side environment variables. Browser CORS is not supported, so call Kappa from a server you control.
Curated scope
Reads the shared catalog and curated datasets. The account's own uploads stay hidden and return 404.
All-datasets scope
Adds the account's own datasets, for trusted internal services that need those rows.
- Keys expire after 1–365 days (90 by default).
- Up to five keys can be active on one account.
- Optional IPv4 and IPv6 CIDRs restrict where a key works (an empty list means unrestricted).
- Rotation keeps the service-account identity, with an optional bounded overlap.
- Revocation rejects the key immediately on every API endpoint.
A versioned contract with an explicit change trail
The current public wire contract is 1.0.0-beta.1 (beta), and CI fingerprints its endpoints, parameters, response fields, headers and error shapes against the latest changelog entry.
A new optional field, parameter, operation, media type, or other backwards-compatible capability.
Documentation or examples become more precise without changing request or response behavior.
An existing operation or field remains available but gains explicit replacement and announcement metadata.
A removal, rename, new requirement, narrowed input, or incompatible response change that requires a new contract version.
A future deprecation will identify the affected operation, announcement date, and optional replacement. A sunset value and runtime Deprecation / Sunsetheaders will be published only after that behavior exists on the server.
Read the HTML changelog, JSON feed, or Markdown feed. The /health and /ready routes are operational diagnostics outside this five-operation contract and do not define supported integration behavior.
Dataset endpoints
Paths are relative to https://api.tradepolaris.com/platform/v1, and the read-only surface accepts no SQL or arbitrary ordering.
/datasetsFind a dataset
Search the datasets visible to the key. The catalog is large and alphabetically paged, so search by name or label instead of walking every page.
| Parameter | Type | Meaning |
|---|---|---|
q | string | Search dataset names and human labels. For example, q=apple matches us-eq-aapl-1d. |
asset_class | enum | Optional family filter: equity, intl, futures, fx, crypto, macro, fundamentals, options, positioning, settlement, news. An unrecognised value is ignored and the unfiltered catalog is returned. |
limit | integer · 1–500default 200 | Maximum catalog rows in this page. |
offset | integer · ≥0default 0 | Zero-based catalog offset. |
Response
body- An array of dataset summaries: name, kind, latest_version, source, license, description, owned, updated_at, and asset.
X-Has-More- The reliable pagination signal. Do not infer completion from the returned row count.
/datasets/{name}/previewPreview its shape
Return a render-ready sample as line points, candles, or table rows. Use schema or query when a dataset is too large to preview.
| Parameter | Type | Meaning |
|---|---|---|
namerequired | string | Dataset slug returned by GET /datasets. |
version | integer · ≥1 | Pinned dataset version. Omit to use the latest version. |
max_points | integerdefault 500 | Requested preview density. Values are safely clamped to 10–2,000 points. |
Response
chart- line, candles, or table; identifies the populated payload field.
points / candles / rows- The render-ready preview payload.
total_rows / downsampled- Full version size and whether the preview was thinned.
/datasets/{name}/schemaInspect the schema
Read columns, dtypes, coverage, detected time and symbol fields, and the producer-declared capability contract before querying rows.
| Parameter | Type | Meaning |
|---|---|---|
namerequired | string | Dataset slug returned by GET /datasets. |
version | integer · ≥1 | Pinned dataset version. Omit to use the latest version. |
Response
columns[] / row_count- Column names and dtypes plus the version's total row count.
date_column / symbol_column- Detected effective-time and symbol fields when present.
date_min / date_max- Dataset- and version-specific coverage bounds.
capabilities- The source contract, including point_in_time_supported, time fields, revision_model, valid query operators, and provenance.
/datasets/{name}/queryQuery rows
Apply structured filters and column projection. This is not a SQL endpoint. JSON is paginated; CSV is complete-or-rejected up to 1,000,000 matching rows.
| Parameter | Type | Meaning |
|---|---|---|
namerequired | string | Dataset slug returned by GET /datasets. |
version | integer · ≥1 | Pinned dataset version. Omit to use the latest version. |
symbol | string · ≤32 chars | Exact symbol filter when the schema declares a symbol column. |
start | ISO date | Inclusive lower bound on the dataset's effective date field. |
end | ISO date | Inclusive upper bound on the dataset's effective date field. |
as_of | YYYY-MM-DD | Point-in-time knowledge bound. Use only when schema.capabilities.point_in_time_supported is true; unsupported datasets return 400. |
columns | comma-separated string | Projected column names. |
limit | integer · 1–5,000default 500 | Maximum rows in a JSON page. |
offset | integer · ≥0default 0 | JSON page offset. |
tail | booleandefault false | For JSON, keep the newest bounded window instead of the oldest one. CSV is already the complete filtered result. |
format | json | csvdefault json | JSON returns a bounded page. CSV returns the complete filtered result or rejects it before sending partial bytes. |
Response
columns / rows- Column names and row arrays, ready to construct a DataFrame.
total_matched / returned / truncated- Exact match count and page status for JSON responses.
as_of evidence- When applied: as_of, knowledge_time_column, knowledge_time_min, and knowledge_time_max.
CSV headers- X-Total-Rows is the emitted row count and X-Export-Complete: true confirms the body was not clipped.
/datasets/{name}/exportExport a pinned version
Mint a short-lived URL for the immutable Parquet artifact. Download the URL without an Authorization header and verify its SHA-256 checksum.
| Parameter | Type | Meaning |
|---|---|---|
namerequired | string | Dataset slug returned by GET /datasets. |
version | integer · ≥1 | Pinned dataset version. Omit to use the latest version. |
as_of | unsupported | Do not send this parameter. The endpoint returns 400 rather than silently exporting unfiltered history; use /query with as_of. |
Response
url / expires_in- A bearer download URL and its lifetime in seconds: 900 for curated datasets and 300 for account-owned uploads.
name / version / bytes / rows- Immutable artifact identity and size.
content_sha256- Checksum to verify after download.
Pagination without silent truncation
Increase offset while the X-Has-More header is true, even after a short page.
Increase offset while truncated is true, and read total_matched for the exact count.
Set tail=true for the newest bounded window, without arbitrary sorting.
A successful CSV is complete with X-Export-Complete: true, and oversized results fail before any body is sent.
Point-in-time queries are capability-based
Use as_of=YYYY-MM-DD only when the resolved schema says capabilities.point_in_time_supported: true. The bound means “known by the end of that UTC date” on the declared availability field. Unsupported datasets return 400 instead of silently ignoring the bound.
Read time_field, availability_time_field, revision_model, and the response's knowledge_time_min / knowledge_time_max. An empty result before the retained floor is not evidence that no filing or disclosure existed.
| Catalog selector | Availability | Effective time | Revision model |
|---|---|---|---|
us-13f-hist-* | filing_date | period | filing_history_with_amendments |
us-form4-* | filing_date | transaction_date | filing_history_with_amendments |
house-ptr-* | filed_date | transaction_date | rolling_disclosure_history |
Coverage is dataset- and version-specific, and Kappa makes no catalog-wide history promise, so inspect date_min and date_max. See the complete machine-readable capability matrix.
SEC 13F integration notes
History datasets keep every accession, including amendments, and are not amendment-resolved canonical portfolios. ADD-type amendments and firms that file through multiple entities require consumer-side interpretation. Do not infer a complete manager position unless an amendment-resolved portfolio is supplied.
Put, call, and unflagged positions remain distinct history rows. Use value_usd for normalized values, because value keeps the raw SEC number, whose unit changes across filing eras.
pct_of_filing_value is a weight within one accession. The legacy pct_of_portfolio field is a deprecated alias, not a canonical firm-level portfolio weight.
US equity reference data includes active and inactive/delisted symbols where supplied. That reduces survivorship bias; it does not certify any dataset or any reconstructed universe as complete. The us-listing-status lane records dated listing and delisting events for those symbols, so membership on a date can be rebuilt from the events effective on or before it, down to a 2005-01-01 floor. It is not index membership, it carries no delisting reason, and listing dates below that floor are not recoverable from it. Its membership is inclusive of non-common security types. See that lane's row for the security_class column that separates them.
Handle exports as short-lived bearer artifacts
- Download the returned URL with a plain GET. Do not attach the TradePolaris API key to the object-storage request.
- Verify
content_sha256before accepting the file. Versions are immutable, so a pinned re-download should be byte-identical. - Do not log, share or persist the URL (its response is
Cache-Control: no-store). Anyone holding it can download until it expires. - Revoking the API key does not invalidate issued URLs, which stay valid until their TTL ends.
- The export endpoint does not support
as_of. Use a point-in-time CSV query when the filtered result fits the documented row ceiling.
Errors and retries
The default normal-capacity budget is 120 requests per minute per key and can vary by plan. On 429, wait for Retry-After. Retry transient 503 responses with capped exponential backoff.
400- The request asks for unsupported semantics, such as as_of on a dataset that does not declare point-in-time support.
401- The bearer key is missing, malformed, expired, revoked, or unknown.
402 dataset_limit- The monthly distinct-dataset export meter is exhausted.
403- The account is not entitled to Data API access, or the key's source-CIDR policy denies the trusted client address.
404- The dataset does not exist or is outside this key's scope. Private datasets are concealed rather than disclosed with 403.
413- The preview is too large, or a CSV query matches more than 1,000,000 rows. Narrow the query or use Parquet export.
422- One or more parameters failed validation.
429- The per-key request budget is spent. Wait for Retry-After, then retry.
503- A transient fail-closed service guard rejected the request. Retry with backoff.
Key-management routes use account authentication
A Data API key cannot manage keys. These routes need a TradePolaris account session or account JWT, and ownership comes from that identity, never from the request body.
POST /api-keys- Create a key and return its secret once.
GET /api-keys- List credential history and metadata, never secrets.
POST /api-keys/{id}/rotate- Create a same-principal successor with a bounded overlap.
PATCH /api-keys/{id}- Update the label or source-CIDR policy.
DELETE /api-keys/{id}- Revoke immediately while retaining credential history.
One contract for people and machines
This page is server-rendered with semantic headings, tables, code and stable fragment IDs, and the same reference is available as OpenAPI and Markdown.
/data-api/openapi.jsonMarkdownGuide an integration with full context/data-api/docs.mdLifecycleTrack versioned contract changes/data-api/changelog.jsonPython + TypeScriptRun checked server-side clientsexamples/data-api/PostmanImport the generated five-route collection/data-api/tradepolaris-data-api.postman.jsonExcelUse =TP formulas with your own key/excel/manifest.xmlThe root llms.txt links these files so agents can reach the integration contract from the site index.