Skip to main content

Auto-generated Python client for the QTSurfer API.

Project description

QTSurfer API Client · Python

CI PyPI Python versions 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 get_exchanges, get_instruments

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

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

instruments = get_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 auth:

import os

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

# 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 = auth.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 auth POST /auth/token — exchange API key for a short-lived JWT
api.exchange get_exchanges GET /exchanges
api.exchange get_instruments GET /exchange/{exchangeId}/instruments (default spot segment)
api.exchange get_segment_instruments GET /exchange/{exchangeId}/{segment}/instruments
api.exchange get_exchange_tickers_hour GET /exchange/{exchangeId}/tickers/{base}/{quote}
api.exchange get_exchange_klines_hour GET /exchange/{exchangeId}/klines/{base}/{quote}
api.strategy get_strategy_status GET /strategy/{strategyId}
api.backtesting prepare_backtesting POST /backtesting/prepare
api.backtesting get_preparation_status GET /backtesting/prepare/{jobId}
api.backtesting execute_backtesting POST /backtesting/execute
api.backtesting cancel_execution POST /backtesting/execute/{jobId}/cancel
api.backtesting get_execution_result GET /backtesting/execute/{jobId}

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, BacktestJobResult, ResultMap, ResponseError, …) live under qtsurfer.api.client.models. get_instruments/get_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_preparation_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.

POST /strategy (postStrategy) 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.

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 get_exchange_tickers_hour

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

response = get_exchange_tickers_hour.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.

Project details


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.98.0.tar.gz (32.9 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.98.0-py3-none-any.whl (71.4 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: qtsurfer_api_client-0.98.0.tar.gz
  • Upload date:
  • Size: 32.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for qtsurfer_api_client-0.98.0.tar.gz
Algorithm Hash digest
SHA256 b2bfe98c0dadb094268a5ce59cb5e6ac8b04aa463dcf08ebc4a74c82bae6cabc
MD5 761c898569664f807b489cf34d9351c5
BLAKE2b-256 7959d4b2848d4188f13af970bd830a189731e94230f55cf45aa55164a8423583

See more details on using hashes here.

Provenance

The following attestation bundles were made for qtsurfer_api_client-0.98.0.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.98.0-py3-none-any.whl.

File metadata

File hashes

Hashes for qtsurfer_api_client-0.98.0-py3-none-any.whl
Algorithm Hash digest
SHA256 fd85624074f17c715307673e6e44254bea6835481af114d2844e351f19aba651
MD5 67c0d7a23de53f3b7a790c5bc091bc2e
BLAKE2b-256 bb536a82d717b367e550e5f5643121e0698a658cb0b9574d2a86440b1e7f0d3a

See more details on using hashes here.

Provenance

The following attestation bundles were made for qtsurfer_api_client-0.98.0-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.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page