Signals examples library

Copy-paste recipes for the /v1/signals endpoint in three flavours: curl, Python (requests), and Node / TypeScript.

1. Fetch the latest signals for a ticker

A plain GET returns a JSON array of signal records, newest first. With no date filter you get the most recent 30 (default limit). Each row carries the 0-1 Piotroski score plus the five component families.

curl
curl https://api.vectorfinancials.com/v1/signals/AAPL \
  -H "X-API-Key: vf_sk_your_key_here" \
  -G -d "limit=5"

# Response (newest first):
[
  {
    "ticker": "AAPL",
    "date": "2026-06-16",
    "score": 0.778,
    "components": {
      "piotroski": {
        "positive_ni": true, "positive_ocf": true, "roa_increasing": true,
        "ocf_gt_ni": true, "leverage_decreasing": false,
        "current_ratio_increasing": true, "no_dilution": true,
        "gross_margin_increasing": true, "asset_turnover_increasing": false
      },
      "altman_z": {
        "score": 6.12, "zone": "safe",
        "components": { "x1": 0.28, "x2": 0.19, "x3": 0.22, "x4": 4.85, "x5": 0.88 }
      },
      "beneish_m": { "score": -2.61, "flag": false },
      "sloan_accrual": { "ratio": -0.021, "quality": "high" },
      "regime": { "current": "bull", "confidence": 0.82 }
    },
    "effective_ts": "2026-06-16T00:00:00Z",
    "knowledge_ts": "2026-06-17T06:30:00Z"
  }
]
Python (requests)
import os, requests

H = {"X-API-Key": os.environ["VECTORFIN_API_KEY"]}
rows = requests.get(
    "https://api.vectorfinancials.com/v1/signals/AAPL",
    headers=H,
    params={"limit": 5},
).json()

latest = rows[0]                      # rows are newest-first
print(latest["date"], latest["score"])
Node / TypeScript (fetch)
const H = { "X-API-Key": process.env.VECTORFIN_API_KEY! };

const res = await fetch(
  "https://api.vectorfinancials.com/v1/signals/AAPL?limit=5",
  { headers: H },
);
const rows: SignalRecord[] = await res.json();  // newest first
const latest = rows[0];
console.log(latest.date, latest.score);

2. Filter by date range and limit

Use date_from and date_to (ISO YYYY-MM-DD, both inclusive) to bound the window, and limit (1 to 365, default 30) to cap the number of rows returned. Free-tier keys are clamped to a 90-day look-back and a 30-row cap.

curl
curl https://api.vectorfinancials.com/v1/signals/AAPL \
  -H "X-API-Key: vf_sk_your_key_here" \
  -G \
  -d "date_from=2026-01-01" \
  -d "date_to=2026-03-31" \
  -d "limit=90"
Python (requests)
import os, requests

H = {"X-API-Key": os.environ["VECTORFIN_API_KEY"]}
rows = requests.get(
    "https://api.vectorfinancials.com/v1/signals/AAPL",
    headers=H,
    params={
        "date_from": "2026-01-01",
        "date_to":   "2026-03-31",
        "limit":     90,
    },
).json()
print(len(rows), "rows in Q1 2026")
Node / TypeScript (fetch)
const H = { "X-API-Key": process.env.VECTORFIN_API_KEY! };

const qs = new URLSearchParams({
  date_from: "2026-01-01",
  date_to:   "2026-03-31",
  limit:     "90",
});
const res = await fetch(
  `https://api.vectorfinancials.com/v1/signals/AAPL?${qs}`,
  { headers: H },
);
const rows: SignalRecord[] = await res.json();
console.log(rows.length, "rows in Q1 2026");

3. Interpreting the components object

The top-level score already equals the Piotroski F-Score divided by 9, but you often want the raw diagnostics: count the 9 piotroski booleans yourself, read the Altman Z distress zone, check the Beneish M manipulation flag, read Sloan accrual quality (which may be null), and read the market regime (current may be null) with its confidence.

curl
# jq derives the five diagnostics from a single record
curl -s https://api.vectorfinancials.com/v1/signals/AAPL \
  -H "X-API-Key: vf_sk_your_key_here" -G -d "limit=1" |
jq '.[0].components | {
  f_score:  ([.piotroski[] | select(. == true)] | length),
  z_zone:   .altman_z.zone,
  m_flag:   .beneish_m.flag,
  accruals: (.sloan_accrual.quality // "n/a"),
  regime:   (.regime.current // "n/a"),
  regime_conf: .regime.confidence
}'
Python (requests)
import os, requests

H = {"X-API-Key": os.environ["VECTORFIN_API_KEY"]}
rec = requests.get(
    "https://api.vectorfinancials.com/v1/signals/AAPL",
    headers=H, params={"limit": 1},
).json()[0]

c = rec["components"]
f_score  = sum(1 for v in c["piotroski"].values() if v)   # 0..9
z_zone   = c["altman_z"]["zone"]                           # safe|grey|distress
m_flag   = c["beneish_m"]["flag"]                          # True => risk
sloan    = c.get("sloan_accrual")
accruals = sloan["quality"] if sloan else None             # may be null
regime   = (c.get("regime") or {}).get("current")          # bull|bear|sideways|None
reg_conf = (c.get("regime") or {}).get("confidence")

print(f"F-Score {f_score}/9 | Z={z_zone} | M-flag={m_flag} | accruals={accruals} | regime={regime} ({reg_conf})")
Node / TypeScript (fetch)
const H = { "X-API-Key": process.env.VECTORFIN_API_KEY! };
const res = await fetch(
  "https://api.vectorfinancials.com/v1/signals/AAPL?limit=1",
  { headers: H },
);
const rec: SignalRecord = (await res.json())[0];

const c = rec.components;
const fScore = Object.values(c.piotroski).filter(Boolean).length; // 0..9
const zZone  = c.altman_z.zone;                                   // safe|grey|distress
const mFlag  = c.beneish_m.flag;                                  // true => risk
const accr   = c.sloan_accrual?.quality ?? null;                 // may be null
const regime = c.regime?.current ?? null;                        // bull|bear|sideways|null
console.log({ fScore, zZone, mFlag, accr, regime });

4. Bitemporal / point-in-time views

Signals are append-only and bitemporal: there can be several rows for the same date, distinguished by knowledge_ts (when we computed that version). For the current view, take the row with the latest knowledge_ts per date. For a look-ahead-free backtest, instead keep only rows whose knowledge_ts is on or before your as-of timestamp before reducing, so you never use data you could not have known yet.

curl
# Pull a window, then reduce client-side to the latest knowledge_ts per date
curl -s https://api.vectorfinancials.com/v1/signals/AAPL \
  -H "X-API-Key: vf_sk_your_key_here" \
  -G -d "date_from=2026-06-01" -d "date_to=2026-06-16" -d "limit=365" |
jq 'group_by(.date) | map(max_by(.knowledge_ts))'
Python (requests)
import os, requests
from collections import defaultdict

H = {"X-API-Key": os.environ["VECTORFIN_API_KEY"]}
rows = requests.get(
    "https://api.vectorfinancials.com/v1/signals/AAPL",
    headers=H,
    params={"date_from": "2026-06-01", "date_to": "2026-06-16", "limit": 365},
).json()

# Current view: latest knowledge_ts wins per date.
latest = {}
for r in rows:
    d = r["date"]
    if d not in latest or r["knowledge_ts"] > latest[d]["knowledge_ts"]:
        latest[d] = r

# Look-ahead-free backtest: filter BEFORE reducing.
as_of = "2026-06-10T00:00:00Z"
pit = {}
for r in rows:
    if r["knowledge_ts"] <= as_of:
        d = r["date"]
        if d not in pit or r["knowledge_ts"] > pit[d]["knowledge_ts"]:
            pit[d] = r
Node / TypeScript (fetch)
const H = { "X-API-Key": process.env.VECTORFIN_API_KEY! };
const res = await fetch(
  "https://api.vectorfinancials.com/v1/signals/AAPL?date_from=2026-06-01&date_to=2026-06-16&limit=365",
  { headers: H },
);
const rows: SignalRecord[] = await res.json();

// Current view: latest knowledge_ts per date.
const latest = new Map<string, SignalRecord>();
for (const r of rows) {
  const prev = latest.get(r.date);
  if (!prev || r.knowledge_ts > prev.knowledge_ts) latest.set(r.date, r);
}

// Look-ahead-free backtest: filter on knowledge_ts before reducing.
const asOf = "2026-06-10T00:00:00Z";
const pit = new Map<string, SignalRecord>();
for (const r of rows) {
  if (r.knowledge_ts > asOf) continue;
  const prev = pit.get(r.date);
  if (!prev || r.knowledge_ts > prev.knowledge_ts) pit.set(r.date, r);
}

5. Multi-ticker screening

There is no batch endpoint yet, so you discover the universe with GET /v1/tickers and then loop, calling GET /v1/signals/{ticker}?limit=1 for each to collect the latest score. Mind your plan rate limit (a 429 returns a Retry-After header); add a small delay or backoff for large universes.

curl
# Discover the ticker universe...
curl -s https://api.vectorfinancials.com/v1/tickers \
  -H "X-API-Key: vf_sk_your_key_here" | jq '.total'

# ...then loop one ticker at a time (no batch endpoint):
for T in AAPL MSFT NVDA; do
  curl -s "https://api.vectorfinancials.com/v1/signals/$T?limit=1" \
    -H "X-API-Key: vf_sk_your_key_here" |
    jq -r '.[0] | "\(.ticker) \(.score)"'
done
Python (requests)
import os, requests

VF = "https://api.vectorfinancials.com"
H = {"X-API-Key": os.environ["VECTORFIN_API_KEY"]}

universe = requests.get(f"{VF}/v1/tickers", headers=H).json()["tickers"]

# No batch endpoint yet — loop one request per ticker.
scores = {}
for t in universe[:50]:
    rows = requests.get(f"{VF}/v1/signals/{t}", headers=H,
                        params={"limit": 1}).json()
    if rows:
        scores[t] = rows[0]["score"]

top = sorted(scores.items(), key=lambda kv: kv[1], reverse=True)[:10]
print(top)
Node / TypeScript (fetch)
const VF = "https://api.vectorfinancials.com";
const H = { "X-API-Key": process.env.VECTORFIN_API_KEY! };

const { tickers } = await (await fetch(`${VF}/v1/tickers`, { headers: H })).json();

// No batch endpoint yet — loop one request per ticker.
const scores: Record<string, number> = {};
for (const t of tickers.slice(0, 50)) {
  const rows: SignalRecord[] = await (
    await fetch(`${VF}/v1/signals/${t}?limit=1`, { headers: H })
  ).json();
  if (rows.length) scores[t] = rows[0].score;
}
const top = Object.entries(scores).sort((a, b) => b[1] - a[1]).slice(0, 10);
console.log(top);