Every API here is available over the APIs.io API and to AI agents over MCP.
{
"openapi": "3.1.0",
"info": {
"title": "patternfetch",
"version": "0.3.0",
"description": "Agent-first API + MCP: a stock, ETF, or crypto ticker + timeframe -> a token-compact market-state brief (compact candles, chart/candlestick patterns, support/resistance, regime, interpreted indicators, and a one-line summary). Covers US stocks & ETFs (Yahoo, split & dividend adjusted, delayed/EOD) and crypto (Binance spot, realtime) in one schema. Built so an LLM agent never dumps raw OHLCV into context. Free tier: keyless POST /v1/demo, free MCP tools/list, and a free key with $3.00 starter credit (300 briefs) via POST /v1/keys — no card. The credit is staged: at least $0.50 is usable immediately, and confirming your email address releases the remainder (where confirmation is not required, the full $3.00 lands at signup). Confirming never gates API access; it only releases the rest of the credit. Studio subscription: $19/month with 2,500 briefs ($25 of usage) included, then pay-per-call. Agents can also connect via OAuth (one-click, auto free-tier key) — see /.well-known/oauth-protected-resource. Impersonal market data, NOT investment advice.",
"termsOfService": "https://patternfetch.com/terms",
"contact": {
"name": "patternfetch",
"url": "https://patternfetch.com/docs",
"email": "mail@patternfetch.com"
}
},
"servers": [
{
"url": "https://patternfetch.com"
}
],
"security": [
{
"bearerAuth": []
},
{
"apiKeyAuth": []
}
],
"components": {
"securitySchemes": {
"bearerAuth": {
"type": "http",
"scheme": "bearer",
"description": "API key from POST /v1/keys (or an OAuth-minted token). Header: Authorization: Bearer pf_..."
},
"apiKeyAuth": {
"type": "apiKey",
"in": "header",
"name": "Authorization",
"description": "The SAME credential and the SAME header as bearerAuth (Authorization: Bearer pf_...), spelled as an apiKey-in-header scheme. Both are listed because x402/agentcash discovery crawlers only recognise an API key when the scheme's type is literally `apiKey` — with `type: http` alone, every protected operation was reported as `auth mode missing` and the whole API read as unmonetized. The two entries describe one header, so the OR between them is exact."
}
},
"schemas": {
"Query": {
"type": "object",
"required": [
"ticker",
"timeframe"
],
"properties": {
"ticker": {
"type": "string",
"example": "AAPL",
"description": "Stock, ETF, or crypto ticker (e.g. AAPL, SPY, BTC/USDT). A bare symbol is always the US listing: BTC is a listed ETF, not Bitcoin - use BTC/USDT for the coin. Affected responses carry a notice field."
},
"timeframe": {
"type": "string",
"enum": [
"1m",
"5m",
"15m",
"30m",
"1h",
"4h",
"1d",
"1w"
]
},
"limit": {
"type": "integer",
"minimum": 20,
"maximum": 1000,
"description": "Candles to analyze (default 200) NOTE: values above the maximum are CLAMPED, not rejected — limit:5000 returns 200 with 1000 candles. Values below the minimum are a 400."
},
"market": {
"type": "string",
"enum": [
"crypto",
"stock"
],
"description": "Asset-class hint. Omitted = inferred from the symbol, which is exactly where an ambiguous ticker goes wrong: bare \"BTC\" is a real US security, not Bitcoin. Send market:'crypto' with BTC/USDT, or market:'stock' to force the equity leg. Any other value is a 400."
}
}
},
"MultiQuery": {
"type": "object",
"required": [
"ticker"
],
"properties": {
"ticker": {
"type": "string",
"example": "BTC/USDT",
"description": "Stock, ETF, or crypto ticker (e.g. AAPL, SPY, BTC/USDT). A bare symbol is always the US listing: BTC is a listed ETF, not Bitcoin - use BTC/USDT for the coin. Affected responses carry a notice field."
},
"timeframes": {
"type": "array",
"items": {
"type": "string",
"enum": [
"1m",
"5m",
"15m",
"30m",
"1h",
"4h",
"1d",
"1w"
]
},
"description": "Timeframes to compare (default [\"1h\",\"4h\",\"1d\"]); max 4, deduped, shortest to longest."
},
"limit": {
"type": "integer",
"minimum": 20,
"maximum": 1000,
"description": "Candles per timeframe (default 200)"
},
"market": {
"type": "string",
"enum": [
"crypto",
"stock"
],
"description": "Asset-class hint. Omitted = inferred from the symbol, which is exactly where an ambiguous ticker goes wrong: bare \"BTC\" is a real US security, not Bitcoin. Send market:'crypto' with BTC/USDT, or market:'stock' to force the equity leg. Any other value is a 400."
}
}
},
"AnalogsQuery": {
"allOf": [
{
"$ref": "#/components/schemas/Query"
},
{
"type": "object",
"properties": {
"window": {
"type": "integer",
"minimum": 2,
"maximum": 500,
"description": "Shape-match window length (default 32)"
},
"horizon": {
"type": "integer",
"minimum": 2,
"maximum": 500,
"description": "Forward horizon for the outcome distribution (default 20)"
}
}
},
{
"type": "object",
"properties": {
"market": {
"type": "string",
"enum": [
"crypto",
"stock"
],
"description": "Asset-class hint. Omitted = inferred from the symbol, which is exactly where an ambiguous ticker goes wrong: bare \"BTC\" is a real US security, not Bitcoin. Send market:'crypto' with BTC/USDT, or market:'stock' to force the equity leg. Any other value is a 400."
}
}
}
]
},
"Brief": {
"type": "object",
"description": "Token-compact market-state brief. Returned by /v1/brief and /v1/demo.",
"properties": {
"header": {
"type": "object",
"properties": {
"sym": {
"type": "string",
"example": "BTC/USDT"
},
"tf": {
"type": "string",
"example": "4h"
},
"src": {
"type": "string",
"example": "binance",
"enum": [
"binance",
"vwap-composite",
"yahoo",
"twelvedata"
],
"description": "The venue that ACTUALLY served this series — not the one routing would have guessed. A failover shows up here."
},
"n": {
"type": "integer",
"example": 200
},
"t0": {
"type": "integer",
"description": "first candle open time (epoch ms)"
},
"t1": {
"type": "integer",
"description": "last candle open time (epoch ms)"
},
"asOf": {
"type": "integer",
"description": "Epoch ms of the last bar in the series. What the brief is actually AS OF — not when it was requested."
},
"delay": {
"type": "string",
"enum": [
"realtime",
"delayed/EOD"
],
"description": "Freshness class of the serving source. Equity sources are delayed/EOD; crypto is realtime."
},
"adjusted": {
"type": "boolean",
"description": "Present (true) only for equity sources, meaning the series is split/dividend adjusted. Omitted entirely for crypto."
}
}
},
"codec": {
"type": "object",
"description": "Compact candle codec.",
"properties": {
"rows": {
"type": "string",
"description": "Per-candle o,h,l,c,v joined by ';' (no per-row timestamp)."
},
"sax": {
"type": "string",
"description": "a-g SAX shape signature (<=8 chars)."
},
"precision": {
"type": "integer",
"description": "Decimal precision used in rows."
}
}
},
"analysis": {
"type": "object",
"properties": {
"patterns": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Pattern name. Open set — new detectors add names, so do not treat this as a closed enum."
},
"confidence": {
"type": "number",
"description": "geometric quality score 0..1 (NOT a probability of profit)"
},
"at": {
"type": "integer"
},
"span": {
"type": "integer"
},
"evidence": {
"$ref": "#/components/schemas/PatternEvidence"
}
}
}
},
"levels": {
"type": "object",
"properties": {
"support": {
"type": "array",
"items": {
"type": "object",
"properties": {
"price": {
"type": "number"
},
"strength": {
"type": "number"
}
}
}
},
"resistance": {
"type": "array",
"items": {
"type": "object",
"properties": {
"price": {
"type": "number"
},
"strength": {
"type": "number"
}
}
}
}
}
},
"regime": {
"type": "object",
"properties": {
"trend": {
"type": "string",
"enum": [
"up",
"down",
"range"
]
},
"strength": {
"type": "number"
},
"volPct": {
"type": "number",
"description": "ATR as % of price"
}
}
},
"indicators": {
"type": "object",
"description": "Interpreted indicator states, keyed by indicator name. OPEN-ENDED: the code returns a map, so do not assume only rsi/ema are present.",
"properties": {
"rsi": {
"type": "object",
"properties": {
"v": {
"type": "number"
},
"state": {
"type": "string",
"enum": [
"overbought",
"oversold",
"neutral"
]
}
}
},
"ema": {
"type": "object",
"properties": {
"v": {
"type": "number"
},
"state": {
"type": "string"
}
}
}
},
"additionalProperties": true
},
"nl": {
"type": "string",
"description": "One-line natural-language summary the agent can act on."
}
}
},
"disclaimer": {
"type": "string"
},
"methodologyUrl": {
"type": "string",
"example": "https://patternfetch.com/methodology"
},
"notice": {
"type": "array",
"items": {
"type": "string"
},
"description": "Caveats that change how the payload should be read — today the cross-listing warning (e.g. \"BTC\" is a US security here, not Bitcoin). Serialized as the FIRST key so it is seen before the data it qualifies. Absent when there is nothing to warn about."
}
}
},
"ScanRequest": {
"type": "object",
"description": "Optional filters for the screener (POST /v1/scan). Every field is optional; an absent field puts no constraint on that axis. A filter-less scan returns the top of the universe (limit defaults to 50).",
"properties": {
"assetClass": {
"type": "string",
"enum": [
"stock",
"crypto",
"all"
],
"description": "Restrict to US stocks/ETFs, crypto pairs, or all (default all)."
},
"regime": {
"type": "string",
"enum": [
"up",
"down",
"range"
],
"description": "Only tickers whose current regime trend matches (up=uptrend, down=downtrend, range=sideways)."
},
"tf": {
"type": "string",
"enum": [
"1m",
"5m",
"15m",
"30m",
"1h",
"4h",
"1d",
"1w"
],
"description": "Only signals computed on this timeframe (the universe is currently precomputed at 1d)."
},
"pattern": {
"type": "string",
"description": "Require the ticker's top pattern to be this one, e.g. double_bottom, double_top, head_and_shoulders, bullish_engulfing, bearish_engulfing, hammer."
},
"minLift": {
"type": "number",
"minimum": -1,
"maximum": 1,
"description": "Drop tickers whose top pattern does not beat its OWN pattern-free baseline by at least this many rate points (0.02 = 2pp; 0 = at or above baseline; negative values allowed). PREFER THIS OVER minBaseRate: a raw base rate is not comparable across bullish and bearish rows, so minBaseRate:0.55 mostly selects bullish patterns in a rising universe before any of them carries information, whereas minLift selects patterns that measurably add something. Rows whose evidence carries no baseline are always excluded — absence of a lift is not a lift of 0."
},
"minBaseRate": {
"type": "number",
"minimum": 0,
"maximum": 1,
"description": "Drop tickers whose top-pattern backtested base rate is below this (0..1). Kept for existing consumers; see minLift for the drift-free equivalent, which is the better filter for an unbiased shortlist."
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 500,
"description": "Max rows to return (default 50)."
}
}
},
"ScanResultRow": {
"type": "object",
"description": "One ranked discovery row from the screener.",
"properties": {
"sym": {
"type": "string",
"example": "AAPL",
"description": "Ticker (stock/ETF symbol or crypto pair)."
},
"tf": {
"type": "string",
"example": "1d",
"description": "Timeframe the signal was computed on."
},
"assetClass": {
"type": "string",
"enum": [
"stock",
"crypto"
]
},
"regime": {
"type": "string",
"enum": [
"up",
"down",
"range"
],
"description": "Current regime trend."
},
"pattern": {
"type": [
"string",
"null"
],
"description": "Top pattern name, or null if none."
},
"baseRate": {
"type": [
"number",
"null"
],
"description": "Backtested directional base rate 0..1 — the primary ranking key; null when no sufficiently-powered evidence bucket exists (ranks last)."
},
"ci95": {
"type": [
"number",
"null"
],
"description": "95% confidence-interval half-width on baseRate."
},
"n": {
"type": [
"integer",
"null"
],
"description": "Independent historical occurrences behind the base rate."
},
"scope": {
"type": [
"string",
"null"
],
"description": "Asset-class corpus the base rate was measured over, e.g. 'US stocks & ETFs' or 'major crypto pairs'."
},
"confidence": {
"type": [
"number",
"null"
],
"description": "Geometric fit score 0..1 of the top pattern (secondary ranking key)."
},
"asOf": {
"type": [
"string",
"null"
],
"example": "2026-07-18",
"description": "ISO date (UTC) of the last bar behind this signal. Freshness, and the final ranking tie-break."
},
"baseline": {
"type": [
"number",
"null"
],
"description": "Direction-matched pattern-free rate over the same timeframe and horizon: what the SAME bet pays with no pattern present. null on rows built from an evidence table without baselines."
},
"lift": {
"type": [
"number",
"null"
],
"description": "baseRate - baseline, in rate points. THE COMPARABLE NUMBER: baseRate alone is not comparable between bullish and bearish rows, because in a drifting universe a bullish pattern starts ahead and a bearish one starts behind with no information involved. Negative means the pattern underperforms its own baseline. null when no baseline exists."
},
"liftCi95": {
"type": [
"number",
"null"
],
"description": "95% confidence-interval half-width on lift. |lift| <= liftCi95 means the edge is not distinguishable from zero at this sample size."
},
"liftReading": {
"type": [
"string",
"null"
],
"enum": [
"above-baseline",
"below-baseline",
"indistinguishable-from-baseline",
null
],
"description": "Plain reading of lift against its interval, so a caller does not have to redo the statistics. Most rows in a healthy universe are indistinguishable-from-baseline; that is an honest result, not a missing value."
}
}
},
"ScanResponse": {
"type": "object",
"description": "Ranked screener results. Returned by POST /v1/scan. Rows are ordered by baseRate desc, then confidence desc, then timeframe, then symbol.",
"properties": {
"asOf": {
"type": [
"string",
"null"
],
"description": "ISO date the universe was last precomputed."
},
"tfs": {
"type": "array",
"items": {
"type": "string"
},
"description": "Timeframes present in the scanned universe."
},
"universe": {
"type": "integer",
"description": "Number of tickers scanned."
},
"count": {
"type": "integer",
"description": "Number of rows returned after filtering + limit."
},
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScanResultRow"
}
},
"note": {
"type": "string",
"description": "Impersonal-data / not-a-prediction note."
},
"disclaimer": {
"type": "string"
}
}
},
"Error": {
"type": "object",
"description": "Uniform error envelope. Every non-2xx JSON response from /v1 has this shape; `error` is a stable machine code and `message` is human text that may change.",
"required": [
"error",
"message"
],
"properties": {
"error": {
"type": "string",
"enum": [
"INVALID_TICKER",
"INVALID_TIMEFRAME",
"INVALID_PARAM",
"INSUFFICIENT_DATA",
"UNAUTHORIZED",
"PAYMENT_REQUIRED",
"RATE_LIMITED",
"PAYLOAD_TOO_LARGE",
"UPSTREAM_ERROR",
"NOT_FOUND",
"GEO_BLOCKED",
"NOT_CONFIGURED"
],
"description": "Stable error code. Branch on THIS, never on the message or the HTTP status alone."
},
"message": {
"type": "string",
"description": "Human-readable explanation. Wording is not part of the contract."
}
}
},
"WatchSubscription": {
"type": "object",
"description": "A watchlist subscription. The signing secret is NOT part of this projection — it is returned exactly once, by the create call.",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"ticker": {
"type": "string",
"example": "BTC/USDT"
},
"timeframe": {
"type": "string",
"example": "1h"
},
"market": {
"type": [
"string",
"null"
],
"enum": [
"crypto",
"stock",
null
],
"description": "Asset-class hint, or null to infer from the symbol."
},
"url": {
"type": "string",
"format": "uri",
"description": "HTTPS callback target."
},
"intervalSec": {
"type": "integer",
"description": "Poll interval actually in force (>= the timeframe floor)."
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"lastPollAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"lastDeliveryAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "Last time a callback was actually SENT (a poll that finds no change sends nothing)."
},
"nextPollAt": {
"type": "string",
"format": "date-time"
},
"failCount": {
"type": "integer",
"description": "Consecutive delivery failures; drives exponential backoff."
},
"status": {
"type": "string",
"enum": [
"active",
"paused",
"disabled"
],
"description": "'paused' recovers by itself (insufficient_credit resumes after a top-up); 'disabled' needs a new subscription."
},
"statusReason": {
"type": [
"string",
"null"
],
"example": "insufficient_credit"
}
}
},
"NewsletterState": {
"type": "object",
"description": "Consent record. The status is DERIVED from the three timestamps, never stored as a fourth field that could disagree with them.",
"properties": {
"status": {
"type": "string",
"enum": [
"none",
"pending",
"subscribed",
"unsubscribed"
]
},
"optInAt": {
"type": [
"integer",
"null"
],
"description": "Epoch ms of the opt-in (timestamp 1 of 3)."
},
"confirmedAt": {
"type": [
"integer",
"null"
],
"description": "Epoch ms of the double-opt-in confirmation (timestamp 2 of 3)."
},
"unsubAt": {
"type": [
"integer",
"null"
],
"description": "Epoch ms of the unsubscribe (timestamp 3 of 3)."
}
}
},
"LiftEvidence": {
"type": "object",
"description": "The pattern's measured edge over a pattern-free baseline. THIS is the number that says whether the pattern carries information, as opposed to merely riding a drifting market.",
"properties": {
"baseline": {
"type": "number",
"description": "Direction-matched unconditional rate: P(up) for a bullish pattern, P(down) for a bearish one."
},
"baselineN": {
"type": "integer",
"description": "Windows behind the baseline."
},
"lift": {
"type": "number",
"description": "hitRate - baseline, in rate points. Can be negative."
},
"ci95": {
"type": "number",
"description": "95% confidence-interval half-width on lift."
},
"ci95Clustered": {
"type": "number",
"description": "Half-width widened for overlapping windows. Present when the occurrences are not independent; it is the more honest of the two."
},
"informative": {
"type": "boolean",
"description": "Whether lift is distinguishable from zero given the interval. False is a normal, honest outcome — most patterns most of the time."
},
"reading": {
"type": "string",
"enum": [
"above-baseline",
"below-baseline",
"indistinguishable-from-baseline"
],
"description": "Plain-language verdict, so a caller does not have to redo the statistics."
}
}
},
"PatternEvidence": {
"type": "object",
"description": "Backtested evidence for a detected pattern. Absent when the pattern has no sufficiently-powered evidence bucket — an omitted `evidence` means \"we have not measured this\", never \"it does not work\".",
"properties": {
"hitRate": {
"type": "number",
"description": "Directional hit rate over the horizon, 0..1."
},
"horizon": {
"type": "integer",
"description": "Bars forward the outcome was measured over."
},
"n": {
"type": "integer",
"description": "Independent historical occurrences."
},
"scope": {
"type": "string",
"example": "US stocks & ETFs",
"description": "Corpus the rate was measured over."
},
"ci95": {
"type": "number",
"description": "95% confidence-interval half-width on hitRate."
},
"lift": {
"$ref": "#/components/schemas/LiftEvidence"
}
}
},
"OutcomeDistribution": {
"type": "object",
"description": "FULL forward-return distribution, losers included. Never a single headline number.",
"properties": {
"n": {
"type": "integer",
"description": "Matches contributing a forward return."
},
"horizon": {
"type": "integer",
"description": "Bars forward."
},
"winRate": {
"type": "number"
},
"median": {
"type": "number"
},
"mean": {
"type": "number"
},
"min": {
"type": "number"
},
"max": {
"type": "number"
}
}
},
"AnalogsResponse": {
"type": "object",
"description": "In-series analogs: the latest window matched against earlier windows of the SAME series.",
"properties": {
"notice": {
"type": "array",
"items": {
"type": "string"
},
"description": "Cross-listing caveat when the symbol is ambiguous. Derived here from the ROUTING decision (which is the decision that can be wrong), not from a serving source — this endpoint reports no src."
},
"sym": {
"type": "string"
},
"tf": {
"type": "string"
},
"window": {
"type": "integer",
"description": "Window length actually used."
},
"distribution": {
"$ref": "#/components/schemas/OutcomeDistribution"
},
"matches": {
"type": "integer",
"description": "Analog windows found after de-duplication."
},
"note": {
"type": "string"
},
"disclaimer": {
"type": "string"
}
}
},
"MultiResponse": {
"type": "object",
"description": "One brief per timeframe plus a cross-timeframe alignment read.",
"properties": {
"sym": {
"type": "string"
},
"views": {
"type": "array",
"items": {
"type": "object",
"properties": {
"tf": {
"type": "string"
},
"trend": {
"type": "string",
"enum": [
"up",
"down",
"range"
]
},
"strength": {
"type": "number"
},
"nl": {
"type": "string"
}
}
}
},
"alignment": {
"type": "object",
"properties": {
"trends": {
"type": "object",
"additionalProperties": {
"type": "string",
"enum": [
"up",
"down",
"range"
]
},
"description": "Trend per timeframe."
},
"aligned": {
"t
# --- truncated at 32 KB (95 KB total) ---
# Full source: https://artifacts.apis.io/patternfetch-com/openapi/_original/patternfetch-com-api-openapi.json