Swolecharts
Live crypto data · 10 exchanges
Log inSign up

API examples: Python, JavaScript and Google Sheets

Fetch seven days of BTC open interest in USD at hourly steps. Each recipe makes one anonymous request; no account or package installation is needed.

One bounded history request

API reference · OpenAPI · API terms and attribution

BTC OI JSON source: t holds Unix milliseconds and v holds OI USD. Pair each timestamp with its value at the same array index. All output times below are UTC.

A healthy empty history returns no data rows (Sheets keeps its header). db=false or an error means unavailable, even with HTTP 200. Null means missing: Python keeps None, JavaScript keeps null, and Sheets leaves a blank cell. Never treat blanks as zero in analysis. Inspect gaps and retain the source URL alongside your analysis.

These are one-shot examples, with no polling or automatic Sheets refresh. Network failures propagate as errors; Python and JavaScript stop waiting after 15 seconds. Apps Script uses Google's service execution limits; UrlFetchApp has no custom timeout option.

An optional X-API-Key request header raises the shared API request budget. Keep private keys in server-side configuration; never embed them in browser code or sheet formulas. Anonymous limits are shared by client network, including Google-hosted sheet requests. On HTTP 429, stop and respect Retry-After before a later request; these snippets do not retry. Cache results rather than repeatedly recalculating cells. See keys and current limits.

Python: standard library

Save as btc_oi.py and run python btc_oi.py. No packages needed.

import json
from datetime import datetime, timezone
from urllib.request import Request, urlopen
from urllib.error import HTTPError

URL = "https://swolecharts.com/api/series/oi?base=BTC&range=7d&step=1h"
try:
    with urlopen(Request(URL, headers={"User-Agent": "swolecharts-example/1.0"}), timeout=15) as response:
        data = json.load(response)
except HTTPError as error:
    raise RuntimeError(
        f"HTTP {error.code}; Retry-After: {error.headers.get('Retry-After')}"
    ) from error
if data.get("db") is False or data.get("error"):
    raise RuntimeError("History unavailable; not an empty success")
rows = [(datetime.fromtimestamp(t / 1000, timezone.utc).isoformat(), v)
        for t, v in zip(data["t"], data["v"])]
print({"source": URL, "gaps": data.get("gaps"), "rows": rows})

JavaScript: browser or Node 18+

Paste into your browser developer console, or save as btc_oi.js and run node btc_oi.js.

async function btcOi() {
  const url = "https://swolecharts.com/api/series/oi?base=BTC&range=7d&step=1h";
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), 15000);
  try {
    const response = await fetch(url, {signal: controller.signal});
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}; Retry-After: ${response.headers.get("Retry-After")}`);
    }
    const data = await response.json();
    if (data.db === false || data.error) {
      throw new Error("History unavailable; not an empty success");
    }
    const rows = data.t.map((t, i) => [new Date(t).toISOString(), data.v[i] ?? null]);
    return {source: url, gaps: data.gaps, rows};
  } finally {
    clearTimeout(timer);
  }
}
btcOi().then(console.log).catch(console.error);

Google Sheets: Apps Script

In a sheet, open Extensions > Apps Script, replace the editor contents, and save. In an empty cell enter =BTC_OI() with room for two columns and up to 169 rows.

Use a bound Apps Script custom function, not IMPORTDATA on JSON. UTC times are ISO text, so the sheet timezone cannot shift them. Blank OI cells represent missing data; a header-only result is healthy empty history. This function does not install a refresh trigger.

/** @customfunction */
function BTC_OI() {
  const url = "https://swolecharts.com/api/series/oi?base=BTC&range=7d&step=1h";
  const response = UrlFetchApp.fetch(url, {muteHttpExceptions: true});
  const status = response.getResponseCode();
  if (status < 200 || status >= 300) {
    const headers = response.getAllHeaders();
    const name = Object.keys(headers).find(k => k.toLowerCase() === "retry-after");
    throw new Error(`HTTP ${status}; Retry-After: ${headers[name] || "unspecified"}`);
  }
  const data = JSON.parse(response.getContentText());
  if (data.db === false || data.error) {
    throw new Error("History unavailable; not an empty success");
  }
  return [["UTC time", "OI USD"], ...data.t.map((t, i) => [
    new Date(t).toISOString(), data.v[i] ?? ""
  ])];
}

Python: CSV variant

Save as btc_oi_csv.py and run python btc_oi_csv.py. Same history, CSV transport.

CSV begins with a variable number of # comment lines before time_utc,unix_ms,v. Filter every leading comment, not a fixed header count. Filtering removes provenance from the table: keep the captured source URL and gap comments alongside exported analysis. CSV UTC times are already ISO text; blank values stay missing. CSV does not expose the JSON db flag, so an empty CSV with unknown coverage is rejected as unconfirmed availability; use JSON to distinguish that state.

import csv
import json
from itertools import dropwhile
from urllib.request import Request, urlopen
from urllib.error import HTTPError

URL = "https://swolecharts.com/api/series/oi?base=BTC&range=7d&step=1h&format=csv"
try:
    with urlopen(Request(URL, headers={"User-Agent": "swolecharts-example/1.0"}), timeout=15) as response:
        text = response.read().decode("utf-8")
        content_type = response.headers.get("Content-Type", "")
except HTTPError as error:
    raise RuntimeError(
        f"HTTP {error.code}; Retry-After: {error.headers.get('Retry-After')}"
    ) from error
# Errors can be JSON even when CSV was requested.
if "json" in content_type:
    raise RuntimeError(f"History unavailable: {json.loads(text)}")
comments = []
def leading_comment(line):
    if line.startswith("#"):
        comments.append(line)
        return True
    return False
reader = csv.DictReader(dropwhile(leading_comment, text.splitlines()))
rows = [(r["time_utc"], None if r["v"] == "" else float(r["v"])) for r in reader]
if not rows and "# gaps: unknown" in comments:
    raise RuntimeError("Empty CSV with unknown coverage; availability unconfirmed")
if any(line.startswith("# error:") for line in comments):
    raise RuntimeError("History unavailable; not an empty success")
print({"source": URL, "provenance": comments, "rows": rows})

Apps Script references

The Sheets recipe uses UrlFetchApp.fetch with muteHttpExceptions and HTTPResponse to check status before parsing JSON.