npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@qtsurfer/api-client

v0.29.0

Published

Auto-generated TypeScript API client for the QTSurfer API (from OpenAPI 3.1 spec)

Readme

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

Installation

pnpm add @qtsurfer/api-client
# or
npm install @qtsurfer/api-client

Quick start

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);

API key → JWT

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.

API surface

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.49, 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 at an allowed cadence; the 202 includes the effective cadence and a plan refusal is typed 402 | | 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 | Import CEX market history or DEX pool history at a plan-allowed cadence; the 202 reports its cadence when known and plan refusals are typed 402 | | 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 | Read a paper account equity or instrument PnL curve with shaping and paging options | | mintLiveConnectionToken | POST | /live/token | Mint a short-lived Centrifugo connection token |

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

Venue discovery, history, and scopes

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. Protected operations reject a valid token whose key lacks the needed scope with a typed 403 InsufficientScopeError (code: 'insufficient_scope', required: 'resource:action'). Update the key's grants and obtain a fresh token; retrying the same token cannot add a grant. This is distinct from POST /auth/token returning 403 apikey_without_scopes before issuing a token.

ResponseError.retryable says whether resending the same request may succeed; honor Retry-After when present. JobState.retryable reports whether a failed execution may succeed if submitted again.

const { data, error, response } = await listDatasets();
if (error) {
  if (response?.status === 403 && error.code === 'insufficient_scope' && 'required' in error) {
    console.error(`Grant ${error.required} to this API key and obtain a fresh token.`);
  } else if ('retryable' in error && error.retryable) {
    console.error('Retry this request only after any Retry-After delay.');
  } else {
    throw error;
  }
} else {
  console.log(data?.datasets);
}

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);

Configuring the client

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.

Error handling

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);

Regenerating the client

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

Development

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

License

Apache-2.0 — see LICENSE.