@qtsurfer/api-client
    Preparing search index...

    @qtsurfer/api-client

    @qtsurfer/api-client

    Auto-generated TypeScript API client for the QTSurfer API, produced from the OpenAPI 3.1 spec with @hey-api/openapi-ts.

    This package is intentionally thin: one function per operation, 1:1 with the spec. For workflow orchestration (polling, retries, domain objects, unified errors), use @qtsurfer/sdk.

    • Tree-shakeable standalone functions.
    • Full type safety for requests, responses, and error shapes.
    • Native fetch based client via @hey-api/client-fetch.
    • Works in Node.js >=20, modern browsers, Deno, and Bun.
    pnpm add @qtsurfer/api-client
    # or
    npm install @qtsurfer/api-client
    import { client, listExchanges, prepareBacktest } from '@qtsurfer/api-client';

    client.setConfig({
    baseUrl: 'https://api.qtsurfer.net/v1', // Staging beta server
    headers: {
    Authorization: `Bearer ${process.env.QTSURFER_TOKEN}`,
    },
    });

    const { data: exchanges, error } = await listExchanges();
    if (error) throw error;

    console.log(exchanges);

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

    import { authenticate } from '@qtsurfer/api-client';

    const { data, error } = await authenticate({
    baseUrl: 'https://api.qtsurfer.net/v1', // Staging beta server
    headers: { 'X-API-Key': process.env.QTSURFER_APIKEY! },
    });
    if (error) throw error;

    const { access_token: jwt } = data; // feed to client.setConfig() for the rest
    console.log(data.scopes); // e.g. { exchanges: ['read'], backtests: ['read', 'execute'] }

    An invalid, revoked, or expired key returns 401. Where a deployment requires scopes, a key with none returns 403 with AuthTokenError.code === 'apikey_without_scopes', and no token is minted. Create a key with at least one resource/action scope in the web app; retrying the same scope-less key does not fix the rejection. Read the code from error and the status from response.

    For production use, prefer the @qtsurfer/sdk authenticate(apikey) helper — it returns a session that refreshes the JWT transparently, reads QTSURFER_APIKEY from the environment, and supports pluggable token stores so callers don't reinvent that plumbing.

    All operations are exported as standalone functions; every operation accepts an Options object and returns { data, error, response }.

    The table is exhaustive: src/generated/ is produced from the OpenAPI spec, so all 48 operations the spec declares are exported. The rows below describe spec version 0.128.37, which is versioned independently of this package.

    Function Method Path Purpose
    authenticate POST /auth/token Exchange an API key for a short-lived JWT
    getAccount GET /account Read the authenticated account tier and limits, including the full-grid sweep limit
    getAccountUsage GET /account/usage Read current resource and storage use
    listExchanges GET /exchanges List the available exchanges
    listInstruments GET /exchange/{exchangeId}/instruments List CEX instruments or DEX pools in the default segment
    listSegmentInstruments GET /exchange/{exchangeId}/{segment}/instruments List CEX instruments or DEX pools in a discovered segment
    downloadTickers GET /exchange/{exchangeId}/tickers/{base}/{quote} Download one hour of tickers as a Lastra segment
    downloadKlines GET /exchange/{exchangeId}/klines/{base}/{quote} Download one hour of klines as a Lastra segment
    listStrategies GET /strategies List your registered strategies, optionally including deleted strategies and their deletion times
    compileStrategy POST /strategy Compile and register a strategy
    validateStrategy POST /strategy/{strategyId}/validate Check that a registered strategy can actually run
    getStrategy GET /strategy/{strategyId} Get a strategy by id, including its validation state
    deleteStrategy DELETE /strategy/{strategyId} Release a registered strategy
    getStrategyCode GET /strategy/{strategyId}/code Get a registered strategy's source, if you still have one to read
    prepareBacktest POST /backtest/{exchangeId}/{type}/prepare Prepare backtest data
    getPrepareStatus GET /backtest/{exchangeId}/{type}/prepare/{jobId} Get the status of a prepare job
    executeSweep POST /backtest/{exchangeId}/{type}/executeSweep/{requestId} Execute a parameter sweep over prepared data
    getSweepResult GET /backtest/{exchangeId}/{type}/executeSweep/{requestId}/{sweepId} Get sweep progress and results
    cancelSweep DELETE /backtest/{exchangeId}/{type}/executeSweep/{requestId}/{sweepId} Cancel a running parameter sweep
    getSweepSensitivity GET /backtest/{exchangeId}/{type}/executeSweep/{requestId}/{sweepId}/sensitivity Get sweep sensitivity surfaces
    getSweepRunEquityCurve GET /backtest/{exchangeId}/{type}/executeSweep/{requestId}/{sweepId}/runs/{runIx}/equityCurve Get one sweep trial's equity curve
    executeBacktest POST /backtest/{exchangeId}/{type}/execute Execute a compiled strategy against a prepared dataset
    cancelBacktest DELETE /backtest/{exchangeId}/{type}/execute/{jobId} Cancel a running backtest execution
    getBacktestResult GET /backtest/{exchangeId}/{type}/execute/{jobId} Get the result of a backtest execution job
    listDatasets GET /datasets List your datasets, optionally including deleted datasets and their deletion times
    createDataset POST /datasets Create a dataset and obtain its first upload session
    deleteDataset DELETE /datasets/{datasetId} Soft-delete a dataset
    getDataset GET /datasets/{datasetId} Get dataset metadata
    openDatasetUpload POST /datasets/{datasetId}/uploads Open an upload session for a later dataset version
    finalizeDatasetUpload POST /datasets/{datasetId}/uploads/{uploadId}/finalize Queue ingest after uploading a file
    getDatasetUpload GET /datasets/{datasetId}/uploads/{uploadId} Get an upload's ingestion state
    importDataset POST /datasets/imports Create a dataset by importing CEX market history or DEX pool history
    getDatasetImport GET /datasets/{datasetId}/imports/{importId} Get an import's fetch and ingestion state
    startLive POST /strategy/{strategyId}/live Start a compiled strategy on a live feed; request a sandbox run with sandbox: true, set its warm-up replay window with warmFrom, simulate fills and equity with paper, or request a plain WebSocket stream URL
    getLive GET /strategy/{strategyId}/live Read a strategy's current or most recent live run
    stopLive DELETE /strategy/{strategyId}/live Request a strategy's active live run to stop
    listLive GET /live Page all live runs owned by the authenticated account
    listPublicLive GET /live/public Page publicly visible live runs
    getLiveRun GET /live/{runId} Read one owned run by its id, including its latest stats and last-change time
    updateLive PATCH /live/{runId} Change a run's visibility, name, or description
    updateLiveParams PUT /live/{runId}/params Change parameters while a run stays active
    rotateLiveStream POST /live/{runId}/stream Replace a run's stream URL and retire the old one
    revokeLiveStream DELETE /live/{runId}/stream Permanently revoke a run's stream URL
    sendLiveCommand POST /live/{runId}/commands Send a command to a running strategy without restarting it
    getLiveRunSignals GET /live/{runId}/signals Page retained signals by time, instrument, or signal type (paper items only when output is mix)
    getLiveRunPaper GET /live/{runId}/paper Read simulated accounts, open positions, equity, and KPIs for a paper-trading run
    getLiveRunPaperEquity GET /live/{runId}/paper/equity Page the run's retained paper equity curve, optionally by quote currency or start time
    mintLiveConnectionToken POST /live/token Mint a short-lived Centrifugo connection token

    All generated types (Exchange, InstrumentDetail, BacktestJobResult, PrepareJobState, ResultMap, LiveRunStats, LiveRunDetail, LiveRunWithStream, LiveStreamUrl, LiveStreamRevoked, LivePaper, LivePaperAccount, LivePaperEquityPage, SendLiveCommandRequest, LiveCommandResult, etc.) are re-exported from the root.

    Exchange.venueType is cex or dex; segments lists its catalogues and liveSegments the segments a live run can consume. Both instrument operations return InstrumentCatalogueResponse, a union of InstrumentListResponse and PoolListResponse, distinguished by meta.venueType. Pool id is the address used to start a DEX live run; symbol is its signal pair. Unknown prices, volumes, and pool creation dates are absent. DEX live sources require one pool address and type: 'kline'. New requests use cex/dex; the server still accepts the older cx/dx spelling.

    DatasetImportRequest accepts type: 'cex' with a cex block (exchange, segment: 'spot', dataType: 'ticker' | 'klines'), or type: 'dex' with its dex block. CEX import and kline preparation support all fifteen cadence labels from 1s through 1q.

    AuthTokenResponse.scopes and Account.scopes use KeyScopes, a resource-to-actions map such as { exchanges: ['read'], backtests: ['read', 'execute'] }; a key with none reports {}. A scope is one resource and one action; resources with no actions are omitted. The API reports these grants without refusing individual operations for missing scopes, although token issuance can reject a key with no scopes as described above.

    Hourly downloads cover the last seven days. Lastra content is application/vnd.qtsurfer.lastra and its ts column uses microseconds (X-Ts-Unit: micros); Parquet timestamps use milliseconds. Inspect response.headers for X-Data-Status: provisional and X-Data-Through: provisional bytes may grow and must be fetched again to obtain the stored hour. Some deployments instead answer recent hours with 404 NOT_YET_AVAILABLE and Retry-After. ResponseError.reason distinguishes that case from NO_DATA, OUTSIDE_WINDOW (400, with next describing a dataset import), and TEMPORARILY_UNAVAILABLE (503, with Retry-After). Treat unknown reasons like an absent reason, and add caller-specific fields such as a dataset name to a suggested ErrorNextCall.

    sendLiveCommand sends a command to every execution behind an owned live run. The strategy must implement the engine's CommandRequestHandler; 202 means the command was accepted, not that the strategy has completed handling it:

    import { sendLiveCommand } from '@qtsurfer/api-client';

    const { data, error } = await sendLiveCommand({
    path: { runId },
    body: { command: 'flatten', properties: { instrument: 'BTC/USDT' } },
    });
    if (error) throw error;
    console.log(data.commandId, data.effectiveAtMs);

    The default client points to the staging server. Override via setConfig or by passing options inline:

    import { client, listExchanges } from '@qtsurfer/api-client';

    // Global
    client.setConfig({
    baseUrl: 'https://api.qtsurfer.net/v1', // Staging beta server
    });

    // Per-call
    await listExchanges({
    baseUrl: 'https://api.qtsurfer.net/v1', // Staging beta server
    headers: { 'X-Request-Id': '...' },
    });

    To build your own isolated client (e.g. per-tenant), use createClient from @hey-api/client-fetch.

    Each function returns a discriminated union. Narrow via error before using data:

    const { data, error } = await prepareBacktest({
    body: {
    /* PrepareRequest */
    },
    });

    if (error) {
    console.error(error.code, error.message);
    return;
    }

    console.log(data.jobId);

    The src/generated/ directory is a committed artifact produced from the OpenAPI spec hosted at QTSurfer/qtsurfer-api.

    pnpm install
    pnpm generate # runs @hey-api/openapi-ts against the remote spec
    pnpm lint # tsc --noEmit
    pnpm build # emits dist/ with .js + .d.ts

    Configuration lives in openapi-ts.config.ts. To generate against a local checkout instead, change input to a relative path (e.g. ../qtsurfer-api/openapi.yaml).

    Script Description
    pnpm generate Regenerate the client from the OpenAPI spec
    pnpm lint Type-check without emitting
    pnpm build Compile to dist/

    Apache-2.0 — see LICENSE.