Skip to main content

QTSurfer API Client · Python

CI PyPI Python versions pdoc License

Auto-generated Python client for the QTSurfer API, built from the OpenAPI 3.1 spec with openapi-python-client on top of httpx.

pip install qtsurfer-api-client


Intentionally thin: one function per endpoint, 1:1 with the spec. For workflow orchestration (polling, retries, domain objects, unified errors), use qtsurfer-sdk (coming soon).

  • Sync-first, httpx-powered — same call site shape as the Java/TS siblings.
  • Spec-driven — generated sources fetched from QTSurfer/qtsurfer-api by scripts/regenerate.sh.
  • Fully typedpy.typed marker, mypy --strict clean (consumers see precise types for every request/response).
  • Python 3.11+ — modern type hints, no compat shims.

Installation

pip install qtsurfer-api-client

Or with uv:

uv add qtsurfer-api-client

Quick start

import os

from qtsurfer.api.client import AuthenticatedClient
from qtsurfer.api.client.api.exchange import list_exchanges, list_instruments

client = AuthenticatedClient(
    base_url="https://api.qtsurfer.com/v1",
    token=os.environ["QTSURFER_TOKEN"],
)

exchanges = list_exchanges.sync(client=client)
for ex in exchanges or []:
    print(ex.id, ex.name)

instruments = list_instruments.sync(client=client, exchange_id="binance")
print(f"{len(instruments.data)} instruments on binance ({instruments.meta.segment.value})")

API key → JWT

Every endpoint above expects a short-lived JWT in the Authorization: Bearer … header. Exchange a long-lived API key for one via authenticate:

import os

from qtsurfer.api.client import AuthenticatedClient
from qtsurfer.api.client.api.auth import authenticate

# AuthenticatedClient also drives the apikey header — set prefix="" so it
# sends `X-API-Key: <key>` instead of `Authorization: Bearer <key>`.
apikey_client = AuthenticatedClient(
    base_url="https://api.qtsurfer.com/v1",
    token=os.environ["QTSURFER_APIKEY"],
    prefix="",
    auth_header_name="X-API-Key",
)

token_response = authenticate.sync(client=apikey_client)
jwt = token_response.access_token  # use this in subsequent calls

For production use, prefer the qtsurfer-sdk auth(apikey) helper — it handles token refresh, env-var pickup (QTSURFER_APIKEY), and pluggable token storage so callers don't reinvent any of it on top of the raw client.

Each generated endpoint module exposes four entrypoints:

Function Returns
sync(...) parsed model (or None on a defined error response)
sync_detailed(...) full Response[...] (status, headers, parsed, content)
asyncio(...) parsed model, awaitable
asyncio_detailed(...) full Response[...], awaitable

API surface

Module Operation Method · Path
api.auth authenticate POST /auth/token — exchange API key for a short-lived JWT
api.exchange list_exchanges GET /exchanges
api.exchange list_instruments GET /exchange/{exchangeId}/instruments (default spot segment)
api.exchange list_segment_instruments GET /exchange/{exchangeId}/{segment}/instruments
api.exchange download_tickers GET /exchange/{exchangeId}/tickers/{base}/{quote}
api.exchange download_klines GET /exchange/{exchangeId}/klines/{base}/{quote}
api.strategy get_strategy GET /strategy/{strategyId}
api.strategy list_strategies GET /strategies
api.strategy delete_strategy DELETE /strategy/{strategyId}
api.strategy get_strategy_code GET /strategy/{strategyId}/code
api.strategy validate_strategy POST /strategy/{strategyId}/validate
api.backtesting prepare_backtest POST /backtest/{exchangeId}/{type}/prepare
api.backtesting get_prepare_status GET /backtest/{exchangeId}/{type}/prepare/{jobId}
api.backtesting execute_backtest POST /backtest/{exchangeId}/{type}/execute
api.backtesting cancel_backtest DELETE /backtest/{exchangeId}/{type}/execute/{jobId}
api.backtesting get_backtest_result GET /backtest/{exchangeId}/{type}/execute/{jobId}
api.backtesting execute_sweep POST /backtest/{exchangeId}/{type}/executeSweep/{requestId}
api.backtesting get_sweep_result GET /backtest/{exchangeId}/{type}/executeSweep/{requestId}/{sweepId}
api.backtesting cancel_sweep DELETE /backtest/{exchangeId}/{type}/executeSweep/{requestId}/{sweepId}
api.backtesting get_sweep_sensitivity GET /backtest/{exchangeId}/{type}/executeSweep/{requestId}/{sweepId}/sensitivity

Twenty of the spec's twenty-one operations, all reachable through qtsurfer.api.client.api as listed. The exception is compileStrategy (POST /strategy), whose text/plain request body openapi-python-client does not support, so no module is generated for it — call it through the underlying httpx client.

Exact module/function names are produced from operationId in the OpenAPI spec. Run scripts/regenerate.sh to refresh and check src/qtsurfer/api/client/_generated/api/ for the authoritative listing.

All generated model types (Exchange, InstrumentDetail, InstrumentCoverage, CoverageWindow, JobState, PrepareJobState, StrategyState, StrategyLinks, BacktestJobResult, ResultMap, ResponseError, …) live under qtsurfer.api.client.models. list_instruments/list_segment_instruments return an InstrumentListResponse (HAL envelope: data + meta + _links), not a bare list — each InstrumentDetail.coverage carries per-data-type CoverageWindows instead of flat dataFrom/dataTo. A single-instrument get_prepare_status returns a PrepareJobState — always terminal (status: Completed), with a coverage_ratio and a per-hour hours_without_data breakdown to act on instead of polling. get_strategy returns a StrategyState, whose validation field (not_validated / pending / passed / failed) reports the outcome of the most recent validate_strategy check rather than a compile job status.

A full StrategyState (from get_strategy, and from validate_strategy's already-validated 200) carries an optional field_links (_links on the wire) — a StrategyLinks with a code: HalLink pointing at get_strategy_code. validate_strategy's 202 omits it, since a check that just started has nothing to link to yet. list_strategies returns every strategy you've registered and not deleted, most recently compiled first, but deliberately without each one's validation state — check that per strategy with get_strategy. delete_strategy removes a strategy from both get_strategy and list_strategies; it doesn't touch backtests already run against it, and re-submitting the same source afterwards registers a new strategy under a new id. get_strategy_code's 404 covers two indistinguishable cases: an id never registered by you, or one that resolves only through a shared/marketplace reference with no source of its own.

POST /strategy (compileStrategy) is currently omitted by the generator because the spec declares its request body as text/plain and openapi-python-client only emits JSON / form / multipart bodies. Call it directly via the underlying httpx client (client.get_httpx_client().post("/strategy", content=src, headers={"Content-Type": "text/plain"})) until the spec is restructured. The other api.strategy operations have no such restriction and generate normally.

Binary downloads (/exchange/{ex}/tickers|klines/{base}/{quote})

These endpoints return raw Lastra bytes (default) or Parquet (format=parquet). The generated sync() helpers parse the response body as JSON and will raise on binary payloads; use sync_detailed() and read response.content directly:

from qtsurfer.api.client import AuthenticatedClient
from qtsurfer.api.client.api.exchange import download_tickers

client = AuthenticatedClient(base_url="https://api.qtsurfer.com/v1", token=token)

response = download_tickers.sync_detailed(
    client=client,
    exchange_id="binance",
    base="BTC",
    quote="USDT",
    hour="2026-01-15T10",
)
with open("BTC_USDT_2026-01-15_h10.lastra", "wb") as f:
    f.write(response.content)

For very large segments, drop down to the underlying httpx.Client (client.get_httpx_client()) and stream:

with client.get_httpx_client().stream(
    "GET",
    "/exchange/binance/klines/BTC/USDT",
    params={"hour": "2026-01-15T10", "format": "parquet"},
) as r:
    r.raise_for_status()
    with open("out.parquet", "wb") as f:
        for chunk in r.iter_bytes():
            f.write(chunk)

Configuring the client

Both Client and AuthenticatedClient accept the standard hooks of the upstream generator:

from qtsurfer.api.client import AuthenticatedClient

client = AuthenticatedClient(
    base_url="https://api.qtsurfer.com/v1",
    token=token,
    timeout=httpx.Timeout(30.0),
    verify_ssl=True,
    headers={"X-Request-Id": "..."},
    raise_on_unexpected_status=True,
)

Need per-call customisation (e.g. swap the underlying httpx.Client for one with a custom transport)? Use client.with_httpx_client(my_httpx_client) or client.set_httpx_client(...).

Regenerating the client

The src/qtsurfer/api/client/_generated/ directory is a committed build artifact produced from the OpenAPI spec hosted at QTSurfer/qtsurfer-api. Never hand-edit it.

uv sync                    # install pinned dev deps
./scripts/regenerate.sh    # fetch spec + regenerate + sync pyproject version
uv run ruff check src/ tests/
uv run mypy src/
uv run pytest -v

Generator configuration lives in codegen.config.yaml. The spec URL is hard-coded in scripts/regenerate.sh; point it at a tag/commit for fully reproducible builds.

Development

Command Description
uv sync Install dependencies from uv.lock
./scripts/regenerate.sh Re-fetch spec + regenerate client
uv run ruff check src/ tests/ Lint
uv run ruff format src/ tests/ Format
uv run mypy src/ Type-check (--strict)
uv run pytest -v Run tests
uv run python -m build Build wheel + sdist into dist/

Versioning

pyproject.toml's version field is kept in lockstep with the info.version of the OpenAPI spec by scripts/regenerate.sh. Tags pushed to main (vX.Y.Z) trigger the PyPI publish workflow via OIDC trusted publishing.

License

Apache-2.0 — see LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

qtsurfer_api_client-0.109.2.tar.gz (60.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

qtsurfer_api_client-0.109.2-py3-none-any.whl (135.3 kB view details)

Uploaded Python 3

File details

Details for the file qtsurfer_api_client-0.109.2.tar.gz.

File metadata

  • Download URL: qtsurfer_api_client-0.109.2.tar.gz
  • Upload date:
  • Size: 60.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for qtsurfer_api_client-0.109.2.tar.gz
Algorithm Hash digest
SHA256 a1844d94c466f5fb84d8229fb209f86285b57f2a43e7ea36e958032c64807668
MD5 526cf76a0ff1584c6b3816baf03fe6dd
BLAKE2b-256 2568ac01aee95cde6ebf2dec9ae442ad65f020774a173d8a414461be3dcb0bd5

See more details on using hashes here.

Provenance

The following attestation bundles were made for qtsurfer_api_client-0.109.2.tar.gz:

Publisher: publish.yml on QTSurfer/api-client-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file qtsurfer_api_client-0.109.2-py3-none-any.whl.

File metadata

File hashes

Hashes for qtsurfer_api_client-0.109.2-py3-none-any.whl
Algorithm Hash digest
SHA256 834798582fbf25b3be486c43fc7df73093fb49975c0a7840edc634d647ba028e
MD5 14933ab1901485bb58c139b3103e5c8c
BLAKE2b-256 2e7460e010be471dbbb3739c8dcf18e61392bad463257d08c167ba1d06bbe9bb

See more details on using hashes here.

Provenance

The following attestation bundles were made for qtsurfer_api_client-0.109.2-py3-none-any.whl:

Publisher: publish.yml on QTSurfer/api-client-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.115.1

2 files

0.111.2

2 files

0.110.3

2 files

0.110.1

2 files

This release

0.109.2 This release

2 files

0.107.0

2 files

0.106.0

2 files

0.102.0

2 files

0.99.2

2 files

0.99.1

2 files

0.98.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page