Developer
Free trial · Pro for writev1FinPull API reference
Keyed access to the same pipeline that powers the app: freshness-labeled prices, stored-tick candles, point-in-time features, licensed Parquet datasets, backtests, paper orders, webhooks, and a realtime socket.
Free accounts get a metered read-only trial: Yahoo-sourced symbols, 100 calls per UTC day, and a smaller realtime-socket budget. Pro keys are unmetered and add licensed crypto data, higher WebSocket limits, datasets, backtests and paper orders.
Base URL & versioning
All endpoints are served under https://api.finpull.dev/api/v1. The version is part of the path, so a breaking change ships as a new prefix instead of mutating v1 in place.
# Production
https://api.finpull.dev/api/v1
# Local development
http://localhost:8000/api/v1Symbols accept the spelling your data source uses (BTCUSDT, BTC/USD, AAPL). Responses always contain the canonical form.
Authentication & scopes
Send the key as a bearer token. Keys are generated at Settings → API Keys and shown exactly once; only a SHA-256 hash is stored.
curl "https://api.finpull.dev/api/v1/public/price/BTC/USD" \
-H "Authorization: Bearer $FINPULL_API_KEY"| Scope | What it unlocks | Tier |
|---|---|---|
| read | Market data for free-read (Yahoo) symbols, freshness, webhook event list. Free keys are limited to 100 calls per UTC day. | Free |
| read,write | Everything in read plus dataset builds, backtests, paper orders, and licensed (paid-access) symbols. Write implies read. | Pro |
Keys are opaque random tokens, not JWTs. Revoking a key takes effect on the next request and closes new WebSocket handshakes. Entitlement failures return 402 with an upgrade payload; a key without the required scope gets 403.
Market data
Every payload that carries a price includes a provenance block. Read delay_class before calling a value live: realtime, delayed_seconds, delayed_15m, eod, or daily_ref. Forex is an ECB daily reference rate, not a tradable quote.
| Method | Path | Purpose | Access |
|---|---|---|---|
| GET | /public/symbols | Symbol catalog across every asset class, including rights metadata. | Read |
| GET | /public/symbols/{symbol} | One symbol with venue, asset class, and data-rights flags. | Read |
| GET | /public/price/{symbol} | Latest price with a freshness block. Always read delay_class first. | Read |
| GET | /public/quotes/{symbol} | Latest price plus the rolling 24h range and change. | Read |
| GET | /public/candles/{symbol}?timeframe=1d | Historical OHLC candles. timeframe is 1d | 1w | 1m | 3m | 1y | 5y. | Read |
| GET | /public/tick-candles/{symbol}?timeframe=1h | Bars aggregated from FinPull's tick store on venue session boundaries. timeframe is 1m | 5m | 15m | 1h | 1d; optional session filter. | Read |
| GET | /public/session/{symbol} | Current market session and the venue calendar for the symbol. | Read |
| GET | /public/freshness | Per-feed health: liveness, staleness, rejects, kill-switch state. | Any key |
| GET | /public/freshness/{symbol} | Full provenance for one symbol: source, timestamps, delay class, suspect flag. | Any key |
| GET | /public/features/{symbol}?timeframe=15m | Point-in-time feature snapshot ending at the newest bar. features is a comma-separated group list; limit is 1-500. | Read |
| GET | /public/orderbook/{symbol}?limit=20 | Live Level 2 book for crypto symbols. limit is 1-200. | Read |
| GET | /public/trends | Detected trends, newest first. Filters: symbols, direction, type, timeframe, min_strength, status, limit, offset. | Pro |
| GET | /public/events | Economic and event intelligence. Filters: type, country, importance, symbol, from, to, limit, offset. | Pro |
curl "https://api.finpull.dev/api/v1/public/candles/BTC/USD?timeframe=1d" \
-H "Authorization: Bearer $FINPULL_API_KEY"
curl "https://api.finpull.dev/api/v1/public/freshness/BTC/USD" \
-H "Authorization: Bearer $FINPULL_API_KEY"Datasets
Datasets are Parquet exports of stored ticks with point-in-time availability cutoffs, so a label can never see the future. Builds are asynchronous: POST returns { dataset_id, status: "accepted", estimated_rows, license } and you poll the detail endpoint until status is ready or failed.
| Method | Path | Purpose | Access |
|---|---|---|---|
| POST | /public/datasets | Start a Parquet build. Returns dataset_id, estimated_rows, and the license flags. 202 Accepted. | Pro + write |
| GET | /public/datasets | Every dataset owned by the key's account. | Pro |
| GET | /public/datasets/{dataset_id} | Dataset detail with versions and the latest build job. | Pro |
| GET | /public/datasets/{dataset_id}/download?version=N | A download descriptor: row count, size, checksum, and a URL that expires. | Pro |
| GET | /public/datasets/{dataset_id}/file?version=N | Direct Parquet bytes when the deployment uses local storage instead of object storage. | Pro |
curl -X POST "https://api.finpull.dev/api/v1/public/datasets" \
-H "Authorization: Bearer $FINPULL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"symbol": "BTC/USD",
"timeframe": "1h",
"start": "2026-01-01T00:00:00Z",
"end": "2026-02-01T00:00:00Z",
"features": {"ohlcv": true, "indicators": true, "session": true},
"labels": ["forward_return_1h", "volatility_regime"],
"split": {
"train": {"start": "2026-01-01", "end": "2026-01-21"},
"validation": {"start": "2026-01-22", "end": "2026-01-26"},
"test": {"start": "2026-01-27", "end": "2026-02-01"}
},
"format": "parquet"
}'Export rights are checked per symbol, not per account. Binance crypto is cleared for redistribution and AI training; Yahoo-sourced stocks and futures are display-only and refused. A forward label needs a bar interval at or below its horizon (for example forward_return_1h needs 1h bars or finer).
Backtests & paper trading
Both are write-scope Pro endpoints. Backtests persist results under a run id you can fetch later. Paper orders simulate fills against the same honest, latency-labeled prices the app shows; suspect ticks are excluded from marks and fills.
| Method | Path | Purpose | Access |
|---|---|---|---|
| POST | /public/backtests | Run a backtest and persist it under a run id. 201 Created. | Pro + write |
| GET | /public/backtests/{run_id} | Fetch a stored backtest run. | Pro |
| POST | /public/paper/orders | Place a simulated order. Crypto and stocks only; forex and futures are refused. | Pro + write |
| GET | /public/paper/account | Paper account summary: balance, positions, realized PnL. | Pro |
| GET | /public/webhook-events | The event types a webhook endpoint may subscribe to. | Read |
curl -X POST "https://api.finpull.dev/api/v1/public/paper/orders" \
-H "Authorization: Bearer $FINPULL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"symbol": "BTC/USD", "side": "buy", "quantity": 0.01}'Paper trading accepts crypto and stocks only. Forex reference rates and futures are not paper-tradable and return 422.
Webhooks
Webhooks are a Pro feature managed from the app with a session cookie (not an API key) under /api/v1/webhooks: create, list, patch, rotate the secret, delete, and inspect deliveries. Each account may hold a bounded number of endpoints, and targets must be public http(s) URLs (private ranges are rejected at registration).
| Event | Fires when |
|---|---|
| alert.triggered | One of your price alerts fired. |
| trend.detected | A Trend Radar detector opened a new signal. |
| trend.invalidated | An existing signal hit its invalidation condition. |
| data_quality.incident | A feed went stale or a suspect tick was flagged. |
| event.released | An economic or corporate event was released. |
| paper.order.filled | A paper order simulated a fill. |
| dataset.ready | A dataset build finished and is downloadable. |
Every delivery is a JSON envelope with id, type, api_version, created_at, and data. When a signing secret is set, the request also carries X-FinPull-Signature: t=<unix>,v1=<hex> and X-FinPull-Timestamp, where the digest is HMAC-SHA256 over {timestamp}.{raw_body} using the endpoint secret.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(secret, header, rawBody, toleranceSeconds = 300) {
const parts = Object.fromEntries(
header.split(",").map((part) => part.split("=", 2)),
);
const timestamp = Number(parts.t);
if (!Number.isFinite(timestamp)) return false;
if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false;
const expected = createHmac("sha256", secret)
.update(`${parts.t}.`)
.update(rawBody)
.digest();
const received = Buffer.from(parts.v1 ?? "", "hex");
return received.length === expected.length && timingSafeEqual(received, expected);
}Verify against the exact bytes you received, not a re-serialized object, and reject timestamps outside your tolerance window so a captured delivery cannot be replayed.
WebSocket stream
Connect to wss://api.finpull.dev/api/v1/public/stream with the key as ?api_key= (browsers cannot set handshake headers) or as an Authorization: Bearer header from non-browser clients. Then subscribe by message. Free sockets are capped at 5 symbol subscriptions, 30 messages/minute and 2 concurrent connections, with the data-quality channel only; Pro sockets use the higher configured limits and can subscribe to Trend Radar and events.
| Channel | Needs a symbol | Messages |
|---|---|---|
| ticks | Yes | Price prints for one symbol |
| book | Yes | Level 2 depth snapshots (crypto only) |
| trends | No | Pro. New and invalidated Trend Radar signals |
| events | No | Pro. Released economic and corporate events |
| data_quality | No | Any key. Stale feeds and suspect prints |
const socket = new WebSocket(
"wss://api.finpull.dev/api/v1/public/stream?api_key=" + key,
);
socket.addEventListener("open", () => {
socket.send(JSON.stringify({ action: "subscribe", channel: "ticks", symbol: "BTC/USD" }));
socket.send(JSON.stringify({ action: "subscribe", channel: "trends" }));
});
socket.addEventListener("message", (event) => {
const message = JSON.parse(event.data);
if (message.error) console.warn(message.error);
if (message.status === "subscribed") console.log("ready", message);
});Each subscription is acknowledged with { status: "subscribed", channel, symbol? }. A socket caps out at 20 symbols and 60 messages per minute; exceeding either closes the connection. The legacy { action: "subscribe", symbol } form still subscribes to both ticks and book for that symbol.
SDKs
The official clients wrap every REST resource and the stream, with retries that honor Retry-After and typed errors.
Python
Python 3.11+, synchronous httpx client, optional streaming extra.
pip install finpull-sdk
pip install "finpull-sdk[stream]"TypeScript
Zero dependencies; works in browsers, Node 20+, Bun, and Deno.
npm install @finpull/sdkfrom finpull_sdk import FinPull
fp = FinPull(api_key="...") # defaults to https://api.finpull.dev/api/v1
snapshot = fp.features.get("BTC/USD", timeframe="15m", features=["ohlcv"], limit=100)
for trend in fp.trends.list(direction="bullish")["trends"]:
print(trend["trend_type"], trend["strength"])import { FinPull } from "@finpull/sdk";
const fp = new FinPull({ apiKey: "..." });
const snapshot = await fp.features.get("BTC/USD", {
timeframe: "15m",
features: ["ohlcv"],
limit: 100,
});Errors & rate limits
Errors are JSON with a detail field. A structured detail object may carry code, feature, and upgrade context. Status codes:
| Status | Meaning |
|---|---|
| 400 | Malformed parameters, for example an unsupported timeframe. |
| 401 | Missing, invalid, or revoked API key. |
| 402 | The account tier or symbol license does not allow this call. |
| 403 | The key lacks the required scope (write). |
| 404 | Unknown symbol, dataset, or run id. |
| 409 | Conflict, such as exceeding the webhook endpoint cap. |
| 422 | The payload failed validation, including export-rights checks. |
| 429 | Rate limit hit. Honor Retry-After. |
Default limits per key or account, configurable per deployment:
Data is informational and latency-labeled. Display access is not a dataset license, and only symbols cleared for redistribution may be exported. This reference describes the shipped v1 surface; response schemas are the authoritative contract.