PairScan API Documentation
Complete REST API reference for PairScan โ authentication, endpoints, rate limits, response formats, and code examples in curl and Python.
Last updated:
PairScan offers a REST API for programmatic access to the same data you see on the site: daily screening results, detailed per-pair metrics, backtests, charts, and depeg events. The API is built for trading bots, backtest pipelines, and custom dashboards.
Quick start
# 1. Get a key on Settings โ API access (requires Personal tier or higher)
# 2. Make your first request:
curl -H "Authorization: Bearer rr_your_key" \
https://pairscan.io/api/v1/screen/latest
Authentication
Every request needs an API key in the Authorization header:
Authorization: Bearer rr_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Generate a key on Settings โ API access. Available from the Personal tier. Each user has one key; rotating it revokes the previous one immediately.
Treat the key like a password. It grants access to your account's data and consumes your quota. If a key is compromised, rotate it on the settings page.
Base URL
https://pairscan.io/api/v1
Rate limits
| Tier | Requests per day |
|---|---|
| Personal | 200 |
| Pro | 2000 |
Only compute endpoints count โ the ones that run a backtest in real time (/pair/{a}/{b} and /pair/{a}/{b}/chart.png). Endpoints serving cached data (/screen/latest, /pairs, /watchlist) and metadata (/me) don't count against your quota.
The limit resets at 00:00 UTC. Over the ceiling, the endpoint returns 429 Too Many Requests with a Retry-After header. Your current remaining quota is always visible via GET /me.
Response codes
| Code | Meaning |
|---|---|
| 200 | Success (body is JSON, or PNG for charts) |
| 400 | Bad ticker or parameter format |
| 401 | Key missing, invalid, or revoked |
| 403 | Your tier is below what this endpoint requires |
| 404 | Pair not found, or no data yet |
| 409 | Another computation is in progress โ retry shortly |
| 429 | Daily quota exhausted |
Error bodies are plain text describing the problem. Success bodies are JSON (except charts, which are image/png).
Endpoints
GET /me
Your account info and current quota state. Doesn't count against quota.
Tier: Personal+
curl -H "Authorization: Bearer rr_your_key" \
https://pairscan.io/api/v1/me
Response:
{
"email": "[email protected]",
"tier": "personal",
"tier_expires_at": "2026-07-14T00:00:00+00:00",
"is_lifetime": false,
"quota": {
"limit_per_day": 200,
"used_today": 12,
"remaining_today": 188,
"resets_at": "2026-06-14T24:00:00Z (00:00 UTC)"
}
}
GET /screen/latest
The latest full screen โ all surviving pairs with metrics and backtest. Served from cache (the cron refreshes it every 6 hours). Doesn't count against quota.
Tier: Personal+
curl -H "Authorization: Bearer rr_your_key" \
https://pairscan.io/api/v1/screen/latest
Response:
{
"generated_at": "2026-06-14T12:00:00+00:00",
"n_pairs_screened": 333,
"n_survivors": 30,
"survivors": [
{
"sector": "memes_majors",
"pair": ["BONK", "FLOKI"],
"score": 0.61,
"metrics": {
"position": 0.07,
"zone": "bottom",
"hurst": 0.32,
"adf_pvalue": 0.0079,
"range_width_pct": 43.2,
"touches_low": 4,
"touches_high": 3,
"z_score": -1.54,
"r_current": -1.75
},
"backtest": {
"A": {"growth_pct": 62.9, "n_trades": 1, "max_drawdown_pct": 14.9},
"B": {"growth_pct": -4.1, "n_trades": 1, "max_drawdown_pct": 3.9}
}
}
]
}
The survivors field contains the full structure of each pair as written in the screen. Zones: bottom (low in the range โ signal to buy A using B), top (high), mid (middle).
GET /pairs
A compact list of surviving pairs from the latest screen โ key fields only. Use it to scan candidates quickly, then request /pair/{a}/{b} for details. Doesn't count against quota.
Tier: Personal+
curl -H "Authorization: Bearer rr_your_key" \
https://pairscan.io/api/v1/pairs
Response:
{
"generated_at": "2026-06-14T12:00:00+00:00",
"count": 30,
"pairs": [
{
"pair": "BONK/FLOKI",
"a": "BONK",
"b": "FLOKI",
"sector": "memes_majors",
"zone": "bottom",
"position": 0.07,
"score": 0.61
}
]
}
GET /pair/{a}/{b}
A full live snapshot of one pair โ metrics, signal, both backtests (A-leg and B-leg start), and per-leg peg status. Runs the same engine as the website's pair page. Counts as 1 request.
Tier: Personal+
Query parameters:
| Parameter | Values | Default | Description |
|---|---|---|---|
bt |
90, 180, 360 |
360 |
Backtest window in days |
curl -H "Authorization: Bearer rr_your_key" \
"https://pairscan.io/api/v1/pair/SOL/XRP?bt=360"
Response:
{
"pair": "SOL/XRP",
"base_a": "SOL",
"base_b": "XRP",
"asset_class_a": "crypto",
"asset_class_b": "crypto",
"generated_at": "2026-06-14T16:54:21+00:00",
"passes_filter": true,
"filter_reason": "",
"metrics": {
"position": 0.17,
"zone": "bottom",
"z_score": -0.86,
"hurst": 0.42,
"adf_pvalue": 0.0138,
"trend_slope": 0.148,
"range_width": null,
"touches_low": 3,
"touches_high": 3,
"volume_a": 99534445.06,
"volume_b": 42057718.32,
"p5": 4.029,
"p95": 4.404,
"p15": null,
"p85": null,
"r_current": 4.054
},
"backtest": {
"history_days": 360,
"A": {"base_leg": "SOL", "growth_pct": 0.0, "n_trades": 0, "max_drawdown_pct": 0.0, "buy_hold": null},
"B": {"base_leg": "XRP", "growth_pct": -14.6, "n_trades": 1, "max_drawdown_pct": 29.7, "buy_hold": null}
},
"peg": {
"A": null,
"B": null
}
}
The peg field is non-null only for tokenized assets (xStocks, tokenized metals/treasuries). For native crypto it's null.
GET /pair/{a}/{b}/chart.png
A PNG chart of the pair's log-ratio with percentile bands and trade markers. The same render as the pair page. Counts as 1 request.
Tier: Personal+
Query parameters: same as /pair/{a}/{b} (bt).
curl -H "Authorization: Bearer rr_your_key" \
"https://pairscan.io/api/v1/pair/SOL/XRP/chart.png?bt=360" \
-o sol_xrp.png
Returns image/png. Client cache is 15 minutes.
GET /watchlist
Your watchlist pairs with their current notification state. Read from the DB โ doesn't count against quota. Handy for a bot to mirror the same signals you get via email/Telegram.
Tier: Personal+
curl -H "Authorization: Bearer rr_your_key" \
https://pairscan.io/api/v1/watchlist
Response:
{
"count": 2,
"items": [
{
"pair": "SOL/XRP",
"a": "SOL",
"b": "XRP",
"notify_approach": false,
"notify_in_zone": true,
"last_notified_state": "in_bottom",
"created_at": "2026-06-01T10:00:00+00:00"
}
]
}
GET /peg-events
Currently active sustained depeg events. The same detector that drives the email alerts: any tokenized ticker continuously unhealthy for โฅ12h with โฅ6 samples. Doesn't count against quota.
Tier: Pro
curl -H "Authorization: Bearer rr_your_key" \
https://pairscan.io/api/v1/peg-events
Response:
{
"events": [
{
"ticker": "XAUT",
"asset_class": "tokenized_metal",
"first_unhealthy": "2026-06-13T08:00:00+00:00",
"last_unhealthy": "2026-06-14T16:00:00+00:00",
"hours_unhealthy": 32,
"median_drift_pct": -1.8,
"sample_count": 33,
"reference_source": "chainlink+yfinance"
}
],
"checked_at": "2026-06-14T16:55:00+00:00"
}
An empty events array means there are no active depegs.
Full example: Python
A minimal requests client that finds bottom-zone pairs and pulls their details:
import requests
API = "https://pairscan.io/api/v1"
KEY = "rr_your_key"
HEADERS = {"Authorization": f"Bearer {KEY}"}
# 1. Grab the compact pairs list (free โ no quota cost)
pairs = requests.get(f"{API}/pairs", headers=HEADERS).json()
# 2. Filter to the bottom zone โ accumulation candidates
bottom = [p for p in pairs["pairs"] if p["zone"] == "bottom"]
print(f"Found {len(bottom)} bottom-zone pairs")
# 3. Pull a full snapshot for each (costs 1 request each)
for p in bottom[:5]:
detail = requests.get(
f"{API}/pair/{p['a']}/{p['b']}",
headers=HEADERS,
params={"bt": 360},
).json()
m = detail["metrics"]
bt = detail["backtest"]["A"]
print(
f"{detail['pair']}: position={m['position']:.2f} "
f"hurst={m['hurst']:.2f} adf={m['adf_pvalue']:.4f} "
f"backtest={bt['growth_pct']:.1f}% ({bt['n_trades']} trades)"
)
# 4. Check remaining quota
me = requests.get(f"{API}/me", headers=HEADERS).json()
print(f"Quota: {me['quota']['used_today']}/{me['quota']['limit_per_day']}")
Metrics glossary
| Field | What it means |
|---|---|
position |
Where the current ratio sits in its historical range (0 = floor, 1 = ceiling). < 0 or > 1 means it's outside the historical bounds |
zone |
bottom (< 0.2), top (> 0.8), mid (in between) |
hurst |
Hurst exponent. < 0.5 = mean-reverting, > 0.5 = trending |
adf_pvalue |
Augmented Dickey-Fuller p-value for stationarity. < 0.05 = statistically significant stationarity |
z_score |
How many standard deviations the current ratio is from its mean |
range_width_pct |
Width of the historical range, in percent |
touches_low / touches_high |
How many times the ratio touched the lower / upper boundary |
r_current |
Current value of the log-ratio |
growth_pct |
Coin-count growth over the backtest window (accumulation, not USD) |
n_trades |
Number of trades in the backtest |
max_drawdown_pct |
Maximum drawdown during the run |
For a detailed explanation of the methodology, filters, and backtest, see the Methodology page.
The API is under active development. Response shapes may gain new fields (existing fields are stable). This is not financial advice โ it's an analytical tool.