Download files
Prepare complete UTC dates, purchase authorization, and download daily files directly from R2.
On this page
Choose your data
A date is available only after its complete daily file has passed publication validation. Zero-record days still have a schema-valid file. Missing is never treated as empty. Coverage bounds are descriptive; inventory preview checks each date.
The publisher uploads complete daily files. Today is unavailable, including for subscriptions. Tasks fix the exact daily file versions. Repaired inventory does not silently change an existing task.
| Option | Example / rule |
|---|---|
| Market & product | binance_spot · trades. Select supported combinations from the catalog. |
| Symbols | BTCUSDT, ETHUSDT — use the market’s instrument identifiers. |
| UTC dates | start / end are inclusive past UTC dates. Current dates and partial-day retail requests are rejected. |
| Format | Use a format declared by the selected product, such as Parquet. |
| Missing partitions | allowPartial=False rejects incomplete dates. True skips whole unavailable dates; it never sells part of a day. |
How a download works
- Preview & create
Preview reports fileCount, totalBytes and incomplete dates. If canCreate is true, create a free preparation task with an Idempotency-Key. Save both the key and task id for retries.
- Wait & list files
Poll until ready. Failed preparation includes an error and can be retried. Read file pages using nextCursor until null; ready does not mean paid or downloaded.
- Quote & authorize
Request a task quote and explicitly confirm its total to purchase seven-day download authorization. A paid-purchase API key is required to spend your balance; ordinary keys can use existing authorization.
- Get a link & download
Request a URL for each file immediately before downloading. The response contains url and expiresAt; default validity is five minutes. Download with a separate HTTP GET.
File metadata & temporary links
Use the paginated task manifest; each file covers one complete UTC day. Request up to 100 signed URLs per batch using POST /downloads/{id}/urls. URLs expire in five minutes or at the original authorization expiry, whichever is earlier. Keep their query strings private.
| Field | Meaning |
|---|---|
| id / name | File identifier for signing, and a suggested local filename. |
| from / until | The file’s UTC interval, inclusive / exclusive. |
| size / records | Compressed bytes and record count, returned as strings. |
| format / schemaVersion | File encoding and exact schema revision. |
| sha256 / quality | Content checksum and published quality state. |
Python download with verification
This version verifies compressed bytes and SHA-256 before renaming each .part file. It follows every manifest page and streams files without loading the entire archive into memory.
import os, time, uuid, hashlibfrom 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") checksum, size = hashlib.sha256(), 0 # 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) checksum.update(chunk) size += len(chunk) if size != int(item["size"]) or checksum.hexdigest() != item["sha256"]: raise ValueError(f"Verification failed: {item['id']}") partial.replace(target) print(target) cursor = page.get("nextCursor") if not cursor: breakRetries & costs
Reuse the task id and download authorization after interruption. Refresh signed links within the original seven days. After expiry, request and explicitly purchase a new quote at the repeat rate. A purchase retry with the same Idempotency-Key and body cannot debit twice.
First authorization is $0.05/GB; renewed authorization for previously purchased dates is $0.02/GB across tasks. Chargeable bytes come from immutable inventory sizes, not actual transfer or SDK reports. You are charged upon explicit authorization purchase even if you never finish downloading. One GB is 1,000,000,000 compressed bytes. Subscription configuration grants no rights; payments remain closed. Public monthly samples are separate and free.