System is still under construction and will be released in Nov 05, please be patient untill then

Developer

Free trial · Pro for writev1

FinPull 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.

Base URL
# Production
https://api.finpull.dev/api/v1

# Local development
http://localhost:8000/api/v1

Symbols 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
curl "https://api.finpull.dev/api/v1/public/price/BTC/USD" \
  -H "Authorization: Bearer $FINPULL_API_KEY"
ScopeWhat it unlocksTier
readMarket data for free-read (Yahoo) symbols, freshness, webhook event list. Free keys are limited to 100 calls per UTC day.Free
read,writeEverything 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.

MethodPathPurposeAccess
GET/public/symbolsSymbol 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=1dHistorical OHLC candles. timeframe is 1d | 1w | 1m | 3m | 1y | 5y.Read
GET/public/tick-candles/{symbol}?timeframe=1hBars 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/freshnessPer-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=15mPoint-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=20Live Level 2 book for crypto symbols. limit is 1-200.Read
GET/public/trendsDetected trends, newest first. Filters: symbols, direction, type, timeframe, min_strength, status, limit, offset.Pro
GET/public/eventsEconomic and event intelligence. Filters: type, country, importance, symbol, from, to, limit, offset.Pro
cURL - candles and freshness
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.

MethodPathPurposeAccess
POST/public/datasetsStart a Parquet build. Returns dataset_id, estimated_rows, and the license flags. 202 Accepted.Pro + write
GET/public/datasetsEvery 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=NA download descriptor: row count, size, checksum, and a URL that expires.Pro
GET/public/datasets/{dataset_id}/file?version=NDirect Parquet bytes when the deployment uses local storage instead of object storage.Pro
Create a build
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.

MethodPathPurposeAccess
POST/public/backtestsRun 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/ordersPlace a simulated order. Crypto and stocks only; forex and futures are refused.Pro + write
GET/public/paper/accountPaper account summary: balance, positions, realized PnL.Pro
GET/public/webhook-eventsThe event types a webhook endpoint may subscribe to.Read
Paper order
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).

EventFires when
alert.triggeredOne of your price alerts fired.
trend.detectedA Trend Radar detector opened a new signal.
trend.invalidatedAn existing signal hit its invalidation condition.
data_quality.incidentA feed went stale or a suspect tick was flagged.
event.releasedAn economic or corporate event was released.
paper.order.filledA paper order simulated a fill.
dataset.readyA 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.

Verify a delivery (Node)
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.

ChannelNeeds a symbolMessages
ticksYesPrice prints for one symbol
bookYesLevel 2 depth snapshots (crypto only)
trendsNoPro. New and invalidated Trend Radar signals
eventsNoPro. Released economic and corporate events
data_qualityNoAny key. Stale feeds and suspect prints
Browser WebSocket
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
pip install finpull-sdk
pip install "finpull-sdk[stream]"

TypeScript

Zero dependencies; works in browsers, Node 20+, Bun, and Deno.

npm
npm install @finpull/sdk
Python
from 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"])
TypeScript
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:

StatusMeaning
400Malformed parameters, for example an unsupported timeframe.
401Missing, invalid, or revoked API key.
402The account tier or symbol license does not allow this call.
403The key lacks the required scope (write).
404Unknown symbol, dataset, or run id.
409Conflict, such as exceeding the webhook endpoint cap.
422The payload failed validation, including export-rights checks.
429Rate limit hit. Honor Retry-After.

Default limits per key or account, configurable per deployment:

Market data reads60 / minute
Dataset builds6 / minute
Backtests20 / minute
Paper orders30 / minute
Trend list60 / minute
Event list120 / minute
Webhook management60 / minute
API key management30 / minute

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.