Skip to main content

JavaScript Examples

This page provides JavaScript examples using the native fetch API for all major API operations.

Setup

Create a utility module for API requests:
// ticksupply.js
const API_KEY = process.env.TICKSUPPLY_API_KEY;
const BASE_URL = "https://api.ticksupply.com";

async function apiRequest(method, path, options = {}) {
  const headers = {
    "X-Api-Key": API_KEY,
    ...options.headers
  };
  
  const response = await fetch(`${BASE_URL}${path}`, {
    method,
    headers,
    ...options
  });
  
  // Handle rate limiting
  if (response.status === 429) {
    const retryAfter = parseInt(response.headers.get("Retry-After") || "30");
    console.log(`Rate limited. Waiting ${retryAfter}s...`);
    await sleep(retryAfter * 1000);
    return apiRequest(method, path, options);
  }
  
  if (!response.ok) {
    const error = await response.json();
    throw new Error(`API Error: ${error.error.code} - ${error.error.message}`);
  }
  
  if (response.status === 204) {
    return null;
  }
  
  return response.json();
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

export { apiRequest, sleep };

Catalog operations

List exchanges

async function listExchanges() {
  return apiRequest("GET", "/v1/exchanges");
}

// Usage
const exchanges = await listExchanges();
exchanges.forEach(exchange => {
  console.log(`${exchange.code}: ${exchange.display_name}`);
});

List instruments

async function listInstruments(exchange, search = null, limit = null) {
  const params = new URLSearchParams();
  if (search) params.set("search", search);
  if (limit) params.set("limit", limit);
  
  const queryString = params.toString();
  const path = `/v1/exchanges/${exchange}/instruments${queryString ? `?${queryString}` : ""}`;
  
  return apiRequest("GET", path);
}

// Usage
const result = await listInstruments("binance", "BTC", 10);
result.items.forEach(pair => {
  console.log(`${pair.symbol}: ${pair.base}/${pair.quote}`);
});

Get datastreams

async function getDatastreams(exchange, instrument, streamType) {
  const params = new URLSearchParams({
    exchange,
    instrument,
    stream_type: streamType
  });

  return apiRequest("GET", `/v1/datastreams?${params}`);
}

// Usage
const result = await getDatastreams("binance", "BTCUSDT", "trade");
result.items.forEach(ds => {
  console.log(`Datastream ${ds.datastream_id}: ${ds.wire_format}`);
});

Subscription operations

Create a subscription

async function createSubscription(datastreamId, options = {}) {
  const { idempotencyKey } = options;

  const payload = { datastream_id: datastreamId };

  const headers = { "Content-Type": "application/json" };
  if (idempotencyKey) headers["Idempotency-Key"] = idempotencyKey;
  
  return apiRequest("POST", "/v1/subscriptions", {
    headers,
    body: JSON.stringify(payload)
  });
}

// Usage
const subscription = await createSubscription(123, {
  idempotencyKey: crypto.randomUUID()
});
console.log(`Created subscription: ${subscription.id}`);
console.log(`Status: ${subscription.status}`);

List subscriptions with pagination

async function* iterateSubscriptions(limit = 50) {
  let pageToken = null;
  
  while (true) {
    const params = new URLSearchParams({ limit: String(limit) });
    if (pageToken) params.set("page_token", pageToken);
    
    const result = await apiRequest("GET", `/v1/subscriptions?${params}`);
    
    for (const item of result.items) {
      yield item;
    }
    
    pageToken = result.next_page_token;
    if (!pageToken) break;
  }
}

async function listAllSubscriptions() {
  const subscriptions = [];
  for await (const sub of iterateSubscriptions()) {
    subscriptions.push(sub);
  }
  return subscriptions;
}

// Usage
const subscriptions = await listAllSubscriptions();
subscriptions.forEach(sub => {
  console.log(`${sub.id}: ${sub.status}`);
});

Subscription lifecycle management

async function getSubscription(subscriptionId) {
  return apiRequest("GET", `/v1/subscriptions/${subscriptionId}`);
}

async function pauseSubscription(subscriptionId) {
  await apiRequest("POST", `/v1/subscriptions/${subscriptionId}/pause`);
  console.log(`Paused subscription: ${subscriptionId}`);
}

async function resumeSubscription(subscriptionId) {
  await apiRequest("POST", `/v1/subscriptions/${subscriptionId}/resume`);
  console.log(`Resumed subscription: ${subscriptionId}`);
}

async function deleteSubscription(subscriptionId, idempotencyKey = null) {
  const headers = {};
  if (idempotencyKey) headers["Idempotency-Key"] = idempotencyKey;
  
  await apiRequest("DELETE", `/v1/subscriptions/${subscriptionId}`, { headers });
  console.log(`Deleted subscription: ${subscriptionId}`);
}

async function getSubscriptionSpans(subscriptionId) {
  return apiRequest("GET", `/v1/subscriptions/${subscriptionId}/spans`);
}

// Usage
const subId = "sub_550e8400e29b41d4a716446655440000";

const sub = await getSubscription(subId);
console.log(`Status: ${sub.status}`);

await pauseSubscription(subId);

const spans = await getSubscriptionSpans(subId);
spans.forEach(span => {
  console.log(`Span: ${span.started_at} - ${span.ended_at || "ongoing"}`);
});

await resumeSubscription(subId);

Export operations

Create an export

async function createExport(datastreamId, startTimeNs, endTimeNs, idempotencyKey = null) {
  const payload = {
    datastream_id: datastreamId,
    start_time: String(startTimeNs),
    end_time: String(endTimeNs)
  };
  
  const headers = { "Content-Type": "application/json" };
  if (idempotencyKey) headers["Idempotency-Key"] = idempotencyKey;
  
  return apiRequest("POST", "/v1/exports", {
    headers,
    body: JSON.stringify(payload)
  });
}

// Usage - export last 24 hours
const nowNs = BigInt(Date.now()) * 1_000_000n;
const dayAgoNs = nowNs - (86400n * 1_000_000_000n);

const exportJob = await createExport(
  123,
  dayAgoNs,
  nowNs,
  crypto.randomUUID()
);
console.log(`Created export: ${exportJob.id}`);

Wait for export completion

async function waitForExport(exportId, pollInterval = 5000, timeout = 300000) {
  const startTime = Date.now();
  
  while (Date.now() - startTime < timeout) {
    const result = await apiRequest("GET", `/v1/exports/${exportId}`);
    const status = result.status;
    
    console.log(`Status: ${status}`);
    
    if (status === "succeeded") {
      return result;
    } else if (status === "failed") {
      throw new Error(`Export failed: ${result.reason || "Unknown error"}`);
    }
    
    await sleep(pollInterval);
  }
  
  throw new Error(`Export did not complete within ${timeout}ms`);
}

// Usage
const completedExport = await waitForExport(exportJob.id);
console.log(`Export completed at: ${completedExport.finished_at}`);

Download export

import { writeFile } from "fs/promises";

async function downloadExport(exportId, outputDir = ".") {
  // Get presigned URLs for all artifacts
  const result = await apiRequest("GET", `/v1/exports/${exportId}/download`);

  console.log(`Downloading ${result.count} file(s), ${result.total_bytes.toLocaleString()} bytes total...`);

  const downloadedFiles = [];
  for (const artifact of result.artifacts) {
    const outputPath = `${outputDir}/${artifact.filename}`;
    console.log(`  Downloading ${artifact.filename} (${artifact.bytes.toLocaleString()} bytes)...`);

    const response = await fetch(artifact.url);
    if (!response.ok) {
      throw new Error(`Download failed: ${response.status}`);
    }

    const buffer = await response.arrayBuffer();
    await writeFile(outputPath, Buffer.from(buffer));
    downloadedFiles.push(outputPath);
  }

  console.log(`Downloaded ${downloadedFiles.length} file(s)`);
  return downloadedFiles;
}

// Usage
const files = await downloadExport(exportJob.id, "./exports");

Availability

Check data availability

async function getAvailability(datastreamId) {
  const params = new URLSearchParams({ datastream_id: String(datastreamId) });
  return apiRequest("GET", `/v1/availability?${params}`);
}

// Usage
const availability = await getAvailability(123);
console.log(`Datastream ID: ${availability.datastream.datastream_id}`);
console.log(`Last updated: ${availability.updated_at}`);
availability.ranges.forEach(range => {
  console.log(`  ${range.from} - ${range.to}: ~${range.rows_estimate.toLocaleString()} rows`);
});

Complete workflow example

// complete-example.js
import { writeFile } from "fs/promises";

const API_KEY = process.env.TICKSUPPLY_API_KEY;
const BASE_URL = "https://api.ticksupply.com";

async function apiRequest(method, path, options = {}) {
  const headers = { "X-Api-Key": API_KEY, ...options.headers };
  const response = await fetch(`${BASE_URL}${path}`, { method, headers, ...options });
  
  if (response.status === 429) {
    const retryAfter = parseInt(response.headers.get("Retry-After") || "30");
    await new Promise(r => setTimeout(r, retryAfter * 1000));
    return apiRequest(method, path, options);
  }
  
  if (!response.ok) {
    const error = await response.json();
    throw new Error(`${error.error.code}: ${error.error.message}`);
  }
  
  return response.status === 204 ? null : response.json();
}

async function main() {
  // 1. Find BTCUSDT trade datastream
  console.log("Finding BTCUSDT trade datastream...");
  const datastreamResult = await apiRequest("GET",
    "/v1/datastreams?exchange=binance&instrument=BTCUSDT&stream_type=trade"
  );
  const datastreamId = datastreamResult.items[0].datastream_id;
  console.log(`Found datastream ID: ${datastreamId}`);
  
  // 2. Create subscription
  console.log("Creating subscription...");
  const subscription = await apiRequest("POST", "/v1/subscriptions", {
    headers: {
      "Content-Type": "application/json",
      "Idempotency-Key": crypto.randomUUID()
    },
    body: JSON.stringify({ datastream_id: datastreamId })
  });
  console.log(`Subscription created: ${subscription.id}`);

  // 3. Wait for data collection
  console.log("Waiting 60 seconds for data collection...");
  await new Promise(r => setTimeout(r, 60000));

  // 4. Create export
  const nowNs = BigInt(Date.now()) * 1_000_000n;
  const hourAgoNs = nowNs - (3600n * 1_000_000_000n);

  console.log("Creating export...");
  const exportJob = await apiRequest("POST", "/v1/exports", {
    headers: {
      "Content-Type": "application/json",
      "Idempotency-Key": crypto.randomUUID()
    },
    body: JSON.stringify({
      datastream_id: datastreamId,
      start_time: String(hourAgoNs),
      end_time: String(nowNs)
    })
  });
  console.log(`Export created: ${exportJob.id}`);
  
  // 5. Wait for completion
  console.log("Waiting for export to complete...");
  while (true) {
    const status = (await apiRequest("GET", `/v1/exports/${exportJob.id}`)).status;
    console.log(`Status: ${status}`);
    
    if (status === "succeeded") break;
    if (status === "failed") throw new Error("Export failed!");
    
    await new Promise(r => setTimeout(r, 5000));
  }
  
  // 6. Download all artifacts
  console.log("Downloading...");
  const downloadResult = await apiRequest("GET", `/v1/exports/${exportJob.id}/download`);

  for (const artifact of downloadResult.artifacts) {
    console.log(`  Downloading ${artifact.filename}...`);
    const downloadResponse = await fetch(artifact.url);
    const buffer = await downloadResponse.arrayBuffer();
    await writeFile(artifact.filename, Buffer.from(buffer));
  }

  console.log(`Downloaded ${downloadResult.count} file(s)`);
}

main().catch(console.error);
Run with:
TICKSUPPLY_API_KEY=your_key node complete-example.js