~hedgewatch

// api_reference

REST API for quant_api (tier 2+) subscribers. returns published 13F anomaly signals as JSON. all endpoints require a bearer token; all responses are UTF-8 JSON.

base_urlhttps://hedgewatch.ioversionv1

overview

hedgewatch surfaces institutional 13F filing anomalies — positions where a fund used the obsolete thousands convention, making micro-cap positions appear 1,000x smaller on every dollar-assuming screener. the REST API gives you programmatic access to every published signal: raw filing values, true position size, filing convention receipts, and direct EDGAR links.

all endpoints are read-only. write operations (API key management, webhook configuration) are performed through the dashboard. this API is informational only — nothing in these responses constitutes investment advice.

signals are published within seconds of detection. the detector runs 24/7, polling SEC EDGAR every 60 seconds. use GET /api/v1/signals/latest for a live feed or configure a webhook from the dashboard to receive push delivery.

authentication

every request must include an Authorization header with a bearer token. API keys are created and revoked from dashboard → api. keys are shown once at creation time — store them securely.

curl https://hedgewatch.io/api/v1/signals/latest \
  -H "Authorization: Bearer hw_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Authorizationheader · requiredformat: "Bearer hw_<key>". keys are scoped to your account and tied to your subscription tier.
never include your API key in client-side JavaScript or public repositories. rotate a compromised key immediately from the dashboard — revocation takes effect within seconds.

rate_limits

each API key is limited to 1,000 requests per day on a rolling 24-hour window. when the quota is exceeded the API returns 429 with a Retry-After header indicating seconds until the quota resets.

HTTP/1.1 429 Too Many Requests
Retry-After: 37214

{
  "error": "rate limit exceeded"
}
Retry-Afterresponse header · secondsnumber of seconds to wait before retrying. only present on 429 responses.
paginating through historical signals efficiently: the cursor-based pagination in GET /api/v1/signals returns up to 20 rows per request. a full season (~50 signals) costs ~3 requests. use date range filtering (from / to) to narrow before paginating.

endpoints

get/api/v1/signals/latest

returns the 20 most recently published signals, ordered by detected_at descending. no query parameters accepted. use this endpoint for a lightweight live feed or polling loop.

curl https://hedgewatch.io/api/v1/signals/latest \
  -H "Authorization: Bearer hw_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
response
{
  "data": [
    {
      "id": "f8c3de3d-1fea-4d7c-a8b3-9c23cc1c5f3d",
      "detected_at": "2026-05-14T14:30:07.000Z",
      "fund_name": "[REDACTED]",
      "ticker": "SILC",
      "cusip": "82657T101",
      "flag": "HIDDEN_POSITION",
      "shares": 160000,
      "conviction_pct": 2.0,
      "reported_value": 2315,
      "value_usd": 2315000,
      "market_cap_bucket": "MICRO",
      "edgar_filing_url": "https://www.sec.gov/Archives/edgar/data/1423958/...",
      "filing_convention": "thousands",
      "ticker_source": "openfigi",
      "unit_ratio": 0.001,
      "schema_version": 2
    }
    // ... up to 20 signals
  ],
  "count": 20
}
get/api/v1/signals

paginated signal query with filtering. returns up to 20 results per page. pass the cursor value from a response into the next request to advance the page. all filters are combinable.

paramtypedescription
fromstring · optionalISO date (YYYY-MM-DD). filter signals detected on or after this date (inclusive, midnight UTC).
tostring · optionalISO date (YYYY-MM-DD). filter signals detected on or before this date (inclusive, 23:59:59 UTC).
tickerstring · optionalcase-insensitive partial match against ticker or fund_name. e.g. "SILC" or "Meridian".
flagstring · optionalexact flag type. one of: HIDDEN_POSITION, ROW_ANOMALY, OVERSTATED_POSITION.
conviction_minnumber · optionalminimum conviction_pct threshold (0.0–100.0). all published signals have already passed the 0.10% floor — sort rather than filter aggressively.
cursorstring · optionalopaque pagination cursor from the previous response. do not modify — cursors are base64-encoded position tokens.
# first page — HIDDEN_POSITION signals from Q1 2026, conviction >= 0.5%
curl "https://hedgewatch.io/api/v1/signals?from=2026-01-01&to=2026-03-31&flag=HIDDEN_POSITION&conviction_min=0.5" \
  -H "Authorization: Bearer hw_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

# next page — pass the cursor from the previous response
curl "https://hedgewatch.io/api/v1/signals?cursor=eyJkZXRlY3RlZF9hdCI6Ij..." \
  -H "Authorization: Bearer hw_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
response
{
  "data": [
    {
      "id": "f8c3de3d-1fea-4d7c-a8b3-9c23cc1c5f3d",
      "detected_at": "2026-05-14T14:30:07.000Z",
      "fund_name": "[REDACTED]",
      "ticker": "SILC",
      "cusip": "82657T101",
      "flag": "HIDDEN_POSITION",
      "shares": 160000,
      "conviction_pct": 2.0,
      "reported_value": 2315,
      "value_usd": 2315000,
      "market_cap_bucket": "MICRO",
      "edgar_filing_url": "https://www.sec.gov/Archives/edgar/data/...",
      "filing_convention": "thousands",
      "ticker_source": "openfigi",
      "unit_ratio": 0.001,
      "schema_version": 2
    }
    // ...
  ],
  "count": 20,
  "cursor": "eyJkZXRlY3RlZF9hdCI6IjIwMjYtMDUtMTRUMTQ6MzA6MDcuMDAwWiIsImlkIjoiZjhjM2RlM2QifQ==",
  "has_more": true
}
cursor pagination:the cursor encodes the last row's detected_at and id, enabling stable keyset pagination even under concurrent inserts. cursors from one query should not be mixed with different filter parameters.
get/api/v1/signals/{id}

fetch a single signal by UUID. returns all standard fields plus one audit-trail field exclusive to this endpoint: raw_xml_snippet (the exact XML fragment extracted from the SEC filing, allowing direct verification against the EDGAR source).

curl https://hedgewatch.io/api/v1/signals/f8c3de3d-1fea-4d7c-a8b3-9c23cc1c5f3d \
  -H "Authorization: Bearer hw_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
response
{
  "data": {
    "id": "f8c3de3d-1fea-4d7c-a8b3-9c23cc1c5f3d",
    "detected_at": "2026-05-14T14:30:07.000Z",
    "fund_name": "[REDACTED]",
    "ticker": "SILC",
    "cusip": "82657T101",
    "flag": "HIDDEN_POSITION",
    "shares": 160000,
    "conviction_pct": 2.0,
    "reported_value": 2315,
    "value_usd": 2315000,
    "market_cap_bucket": "MICRO",
    "edgar_filing_url": "https://www.sec.gov/Archives/edgar/data/...",
    "filing_convention": "thousands",
    "ticker_source": "openfigi",
    "unit_ratio": 0.001,
    "qend_price": 14.47,
    "convention_probes": 14,
    "schema_version": 2,
    "raw_xml_snippet": "<ns1:ssCusip>82657T101</ns1:ssCusip><ns1:value>2315</ns1:value>..."
  }
}

signal_object

full field reference for the signal object returned by all three endpoints. fields marked * are only present on GET /api/v1/signals/{id}.

fieldtypedescription
idstring (uuid)unique signal identifier.
detected_atstring (ISO 8601 UTC)timestamp when the detector published the signal.
fund_namestringinstitutional manager name as filed with SEC EDGAR.
tickerstring | nullexchange ticker resolved from CUSIP. null when resolution fails.
cusipstring | null9-character CUSIP from the 13F-HR filing.
flag"HIDDEN_POSITION" | "ROW_ANOMALY" | "OVERSTATED_POSITION"HIDDEN_POSITION — whole filing used thousands convention, making positions appear 1000x too small on screeners. ROW_ANOMALY — single row 1000x smaller than the filing's own convention. OVERSTATED_POSITION — position 1000x too large (phantom position).
sharesnumber | nullraw share count as reported in the filing.
conviction_pctnumber | nullposition size as % of the issuer's market cap. for HIDDEN_POSITION, the share of the company that is invisible to screeners. any published signal has already passed the 0.10% floor — sort rather than filter.
reported_valuenumber | nullraw value exactly as filed in the 13F-HR XML (dollars or thousands, per the filer's convention).
value_usdnumber | nulltrue position size in USD under the filing's detected convention. this is the number screeners should show but don't.
market_cap_bucket"MICRO" | "SMALL" | "MID" | nullissuer market cap tier at detection time. MICRO < $300M, SMALL $300M–$2B, MID $2B–$10B. large-cap positions are filtered before signal math.
edgar_filing_urlstring | nulldirect URL to the SEC EDGAR filing index page for this 13F-HR submission.
filing_convention"thousands" | "dollars" | nullunit convention detected for this filing. thousands = filer used the pre-2023 convention; dollars = correct per EDGAR Release 22.4.
unit_rationumber | nullratio of reported_value to value_usd. 0.001 for a thousands-filer (reported 1000x too small), 1000 for an overstated position.
ticker_sourcestring | nullhow the ticker was resolved. "openfigi" = CUSIP lookup via OpenFIGI (most reliable); "name_search" = fund name heuristic (lower confidence).
schema_versionnumber | nulldetector schema version that produced this signal. v2 signals have schema_version=2 and include the full receipt fields above.
raw_xml_snippet*string | nullexcerpt of the original 13F-HR XML containing the anomalous entry. only returned by GET /api/v1/signals/{id}.

errors

all error responses are JSON with a single error string field. match on the error string for machine-readable handling — HTTP status codes alone are not always sufficient to distinguish the failure cause.

statuserrorcause
400"invalid cursor"the cursor value is malformed or tampered.
401"missing or invalid authorization header"authorization header is absent or does not begin with "Bearer ".
401"missing api key"bearer prefix present but key value is empty.
401"invalid api key"key does not match any record in the database.
401"api key revoked"key was manually revoked from the dashboard.
403"subscription not active"your subscription is canceled or pending.
403"tier2 subscription required"API access requires a quant_api (tier 2) subscription.
404"signal not found"no published signal exists for this id.
429"rate limit exceeded"daily request quota (1,000 req/day) exhausted. check Retry-After header for reset time in seconds.
500"internal server error"upstream database error. retry with exponential backoff.
on 500 responses, retry with exponential backoff (e.g. 1s → 2s → 4s, max 3 attempts). these are transient DB errors. note: the rate limit counter is incremented before the signal query runs, so a 500 still counts against your daily quota.

python_quickstart

a complete Python script for pulling signals into a pandas DataFrame, computing hidden dollar exposure, ranking by conviction, and fetching the raw XML audit trail for deep inspection. requires requests and pandas.

"""
HedgeWatch Python quickstart — pandas-ready signal analysis.
Requires: pip install requests pandas
"""

import requests
import pandas as pd

API_KEY = "hw_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
BASE_URL = "https://hedgewatch.io"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}


def iter_signals(from_date=None, to_date=None, ticker=None,
                 flag=None, conviction_min=None):
    params = {k: v for k, v in {
        "from": from_date, "to": to_date,
        "ticker": ticker, "flag": flag,
        "conviction_min": conviction_min,
    }.items() if v is not None}
    cursor = None
    while True:
        if cursor:
            params["cursor"] = cursor
        resp = requests.get(f"{BASE_URL}/api/v1/signals",
                            headers=HEADERS, params=params)
        resp.raise_for_status()
        body = resp.json()
        yield from body["data"]
        if not body["has_more"]:
            break
        cursor = body["cursor"]


# ── 1. pull Q1 2026 hidden-position signals into a DataFrame ────────────────
signals = list(iter_signals(
    from_date="2026-01-01",
    to_date="2026-03-31",
    flag="HIDDEN_POSITION",
    conviction_min=0.1,
))
df = pd.DataFrame(signals)
df["detected_at"] = pd.to_datetime(df["detected_at"], utc=True)

# ── 2. true position size is value_usd (already in full dollars) ────────────
# sort by conviction — bigger % of company owned is a stronger signal
top = (
    df.sort_values("conviction_pct", ascending=False)
    .head(10)
    [["fund_name", "ticker", "conviction_pct", "value_usd", "market_cap_bucket"]]
)
print(top.to_string(index=False))

# ── 3. aggregate hidden value by fund ───────────────────────────────────────
by_fund = (
    df.groupby("fund_name")["value_usd"]
    .sum()
    .sort_values(ascending=False)
    .head(20)
)
print(by_fund)

# ── 4. fetch receipts for the highest-conviction signal ─────────────────────
top_id = df.sort_values("conviction_pct", ascending=False).iloc[0]["id"]
detail = requests.get(f"{BASE_URL}/api/v1/signals/{top_id}",
                      headers=HEADERS).json()["data"]
print("unit_ratio:", detail["unit_ratio"])
print("filing_convention:", detail["filing_convention"])
print("raw_xml_snippet:\n", detail["raw_xml_snippet"])

// key field notes for quants

  • conviction_pct— the position's size as a percentage of the company's market cap, computed from the true (convention-corrected) value. published signals clear a 0.10% floor; raise it with conviction_min=1 to surface only the highest-conviction stakes.
  • hidden_usd = value_usd − reported_value — the approximate dollar magnitude hidden by the unit convention. value_usd is the true position size; reported_value is the raw as-filed number (for a thousands-convention filer, value_usd ≈ reported_value × 1000).
  • market_cap_bucket = "MICRO" — the most actionable category. micro-cap 1000x mismatches often represent meaningful hidden stakes that move the underlying price.
  • unit_ratio + ticker_source (from /signals/{id}) — unit_ratio is reported_value ÷ (shares × quarter-end price): ≈0.001 confirms a thousands-convention filing, ≈1.0 a dollars filing. ticker_source is "openfigi" (CUSIP lookup, most reliable) or "name_search" (name heuristic) — weight the latter lower in your own scoring.