Quick start
An API key, a Python script, your first historical file.
On this page
1. Get your key
Sign in and create an API key in Data access. Enable paid download purchases only if this key should spend your balance. Save TICKFOLD_API_KEY; the secret is shown only once.
2. Run the Python example
Install requests with python -m pip install requests. Replace the example dates with published historical days, then run this script. It checks inventory, creates a task, and downloads every file directly from Cloudflare R2.
download.py
import os, time, uuidfrom pathlib import Pathimport requests API = "/api/v1"client = requests.Session()client.headers["Authorization"] = "Bearer " + os.environ["TICKFOLD_API_KEY"] def api(method, path, **kwargs): response = client.request(method, API + path, timeout=30, **kwargs) response.raise_for_status() return response.json() # Choose complete, published historical UTC days.request = { "venue": "binance_spot", "type": "trades", "product": "trades", "symbols": ["BTCUSDT"], "representation": "normalized", "format": "parquet", "allowPartial": False, "start": "2026-10-01", "end": "2026-10-01",}preview = api("POST", "/downloads/preview", json=request)if not preview["canCreate"]: raise RuntimeError(preview) # Save request_id and task id to resume this request later.request_id = str(uuid.uuid4())task = api("POST", "/downloads", json=request, headers={"Idempotency-Key": request_id})print("Task:", task["id"])for _ in range(60): if task["status"] == "ready": break if task["status"] == "failed": raise RuntimeError(task.get("error")) time.sleep(2) task = api("GET", "/downloads/" + task["id"])else: raise TimeoutError("Still processing; check the saved task id later") # Your key needs the separate paid-purchase permission.quote = api("POST", f"/downloads/{task['id']}/quote")print(quote)if input("Charge $" + quote["totalUsd"] + " for seven-day authorization? Type yes: ") != "yes": raise SystemExit("No purchase made")api("POST", f"/downloads/{task['id']}/authorizations", json={"quoteId": quote["quoteId"], "maxAmountUsd": quote["totalUsd"]}, headers={"Idempotency-Key": "purchase-" + quote["quoteId"]}) output = Path("tickfold-data") / task["id"]output.mkdir(parents=True, exist_ok=True)cursor = Nonewhile True: page = api("GET", f"/downloads/{task['id']}/files", params={"limit": 100, **({"cursor": cursor} if cursor else {})}) for item in page["items"]: link = api("POST", f"/downloads/{task['id']}/files/{item['id']}/url") target = output / Path(item["name"]).name partial = target.with_name(target.name + ".part") # Separate request: never send your Tickfold API key to R2. with requests.get(link["url"], stream=True, timeout=(10, 120)) as r: r.raise_for_status() with partial.open("wb") as f: for chunk in r.iter_content(1024 * 1024): if chunk: f.write(chunk) partial.replace(target) print(target) cursor = page.get("nextCursor") if not cursor: break3. Open your files
Retail purchases use complete past UTC dates. Each market, symbol, product and UTC date has one daily R2 file. A seven-day authorization includes free pause, resume, retries and link refreshes; these do not extend its expiry.
Need a hand with your data?Contact support