# TradePolaris Data API integration guide

> Public, read-only integration documentation for the Kappa Data API. The API itself is available to approved private-preview accounts.

- Human documentation: https://tradepolaris.com/data-api/docs
- OpenAPI 3.1 document: https://tradepolaris.com/data-api/openapi.json
- Base URL: https://api.tradepolaris.com/platform/v1
- Authentication: `Authorization: Bearer tp_live_...`
- Last reviewed: 2026-07-30

## Recommended integration sequence

1. Ask the user to create a key in the authenticated Data API console at https://tradepolaris.com/data-api. Never ask them to paste the secret into chat.
2. Read `GET /datasets?q=<term>` to resolve a dataset slug. Search first; do not enumerate the full catalog.
3. Inspect its schema before choosing filters: read `GET /datasets/{name}/schema` before setting symbol, date, column, or `as_of`.
4. Use `GET /datasets/{name}/query` for bounded JSON rows or complete-or-rejected CSV.
5. Use `GET /datasets/{name}/export` for an immutable full-version Parquet artifact, then verify `content_sha256`.
6. Pin `version` whenever reproducibility matters.

The API accepts structured filters, not SQL. Never claim that `as_of` works unless the resolved schema says `capabilities.point_in_time_supported: true`. Never send an API key to an export URL; that URL is already a short-lived bearer artifact.

## Authentication and scopes

Send a Data API key as an HTTP bearer token on every API request. Secrets are shown once when created. A `curated` key sees shared curated datasets only; an `all` key can also see datasets owned by the same account. Up to five keys may be active per account. Keys can expire, rotate with a bounded overlap, be restricted to source CIDRs, and be revoked immediately.

Kappa is in private preview. Public documentation does not grant data access; the account must be entitled before a key authenticates.

## Quick start

```bash
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"
```

```python
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_sha256
```

## Endpoints

All paths below are relative to `https://api.tradepolaris.com/platform/v1`.

### GET /datasets — Find 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.

Parameters:
- `q` (query; string): Search dataset names and human labels. For example, q=apple matches us-eq-aapl-1d.
- `asset_class` (query; enum): Optional family filter: equity, intl, futures, fx, crypto, fundamentals, options, news, positioning, or macro.
- `limit` (query; integer · 1–500, default 200): Maximum catalog rows in this page.
- `offset` (query; integer · ≥0, default 0): Zero-based catalog offset.

Returns:
- `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.

### GET /datasets/{name}/preview — Preview 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.

Parameters:
- `name` (path; string, required): Dataset slug returned by GET /datasets.
- `version` (query; integer · ≥1): Pinned dataset version. Omit to use the latest version.
- `max_points` (query; integer, default 500): Requested preview density. Values are safely clamped to 10–2,000 points.

Returns:
- `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.

### GET /datasets/{name}/schema — Inspect the schema

Read columns, dtypes, coverage, detected time and symbol fields, and the producer-declared capability contract before querying rows.

Parameters:
- `name` (path; string, required): Dataset slug returned by GET /datasets.
- `version` (query; integer · ≥1): Pinned dataset version. Omit to use the latest version.

Returns:
- `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.

### GET /datasets/{name}/query — Query 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.

Parameters:
- `name` (path; string, required): Dataset slug returned by GET /datasets.
- `version` (query; integer · ≥1): Pinned dataset version. Omit to use the latest version.
- `symbol` (query; string · ≤32 chars): Exact symbol filter when the schema declares a symbol column.
- `start` (query; ISO date): Inclusive lower bound on the dataset's effective date field.
- `end` (query; ISO date): Inclusive upper bound on the dataset's effective date field.
- `as_of` (query; YYYY-MM-DD): Point-in-time knowledge bound. Use only when schema.capabilities.point_in_time_supported is true; unsupported datasets return 400.
- `columns` (query; comma-separated string): Projected column names.
- `limit` (query; integer · 1–5,000, default 500): Maximum rows in a JSON page.
- `offset` (query; integer · ≥0, default 0): JSON page offset.
- `tail` (query; boolean, default false): For JSON, keep the newest bounded window instead of the oldest one. CSV is already the complete filtered result.
- `format` (query; json | csv, default json): JSON returns a bounded page. CSV returns the complete filtered result or rejects it before sending partial bytes.

Returns:
- `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.

### GET /datasets/{name}/export — Export 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.

Parameters:
- `name` (path; string, required): Dataset slug returned by GET /datasets.
- `version` (query; integer · ≥1): Pinned dataset version. Omit to use the latest version.
- `as_of` (query; unsupported): Do not send this parameter. The endpoint returns 400 rather than silently exporting unfiltered history; use /query with as_of.

Returns:
- `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 and completeness

- Catalog: advance `offset` while the response header `X-Has-More` is `true`.
- JSON query: advance `offset` while `truncated` is `true`; `total_matched` is the exact filtered count.
- `tail=true`: returns the newest bounded window. It is selection, not arbitrary ordering.
- CSV query: a successful response is complete and carries `X-Export-Complete: true`; matches above 1,000,000 rows fail before any partial CSV is sent.

## Point-in-time requests

`as_of=YYYY-MM-DD` means “rows known by the end of that UTC date” only on schemas that explicitly declare point-in-time support. Inspect `capabilities.time_field`, `capabilities.availability_time_field`, `capabilities.revision_model`, and the response's `knowledge_time_min` / `knowledge_time_max`. An `as_of` earlier than retained coverage is not evidence that no event occurred. The complete lane contract is https://tradepolaris.com/kappa-capability-matrix.json.

For SEC 13F history, treat each retained accession (including amendments) as a filing-time observation, not an amendment-resolved canonical portfolio. Keep Put, Call, and unflagged rows distinct; `pct_of_filing_value` is within one accession, and `value_usd` is the normalized value field. Multi-entity filers and ADD-type amendments require consumer-side interpretation.

## Export handling

The export response itself is `Cache-Control: no-store`. Treat its URL as a secret until it expires. Revoking the API key does not invalidate a URL that was already minted. Download with a plain GET, verify `content_sha256`, and avoid logging or persisting the URL.

## Errors and retry behavior

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

The normal request budget is per key and plan-dependent (120 requests per minute by default). On `429`, honor `Retry-After`. Retry transient `503` responses with capped exponential backoff. Do not retry `400`, `401`, `402`, `403`, `404`, `413`, or `422` without changing the request or account state.

## Key-management routes

Key-management routes live under `https://api.tradepolaris.com/platform/v1/api-keys` and require an authenticated TradePolaris account session or account JWT; a Data API key cannot call them. The public integration contract is the five read-only dataset endpoints above.
