๐Ÿ‘€ PairScan

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.