Skip to main content

Overview

Prediction market traders need early awareness of events that shift probabilities. Shoal maps news to entities (organizations), so you can track the orgs behind active markets and catch signal spikes before the market reprices.
You’ll need an API key to follow along. See the Quickstart guide to create one.

Endpoints Used

EndpointPurposeReference
/v1/organizations/byOrganizationNameResolve market entities to org IDsDocs
/v1/signal/byOrganizationIdEntity-specific scored signalsDocs
/v1/organizations/:id/signal-historyDetect activity spikes over timeDocs
/v1/signal/topHighest-impact signals across all entitiesDocs
/v1/briefCombined radar+signal for a single entityDocs

Workflow

Step 1: Map Markets to Entities

For each active prediction market, resolve the underlying entity to a Shoal organization ID.
cURL
# Resolve "SEC" to a Shoal org ID
curl "https://api.shoal.xyz/v1/organizations/byOrganizationName?name=SEC" \
  -H "Authorization: Bearer YOUR_API_KEY"

# Resolve "Ethereum"
curl "https://api.shoal.xyz/v1/organizations/byOrganizationName?name=Ethereum" \
  -H "Authorization: Bearer YOUR_API_KEY"
Python
import os, requests

API_KEY = os.environ.get("SHOAL_API_KEY", "YOUR_API_KEY")
HEADERS = {"Authorization": f"Bearer {API_KEY}"}

# Map prediction market entities to Shoal org IDs
market_entities = {
    "Will the SEC approve a Solana ETF?": "SEC",
    "Ethereum above $5000 by June?": "Ethereum",
    "Will Trump sign crypto executive order?": "Trump",
}

entity_map = {}  # entity_name -> org_id

for market, entity_name in market_entities.items():
    r = requests.get(
        "https://api.shoal.xyz/v1/organizations/byOrganizationName",
        headers=HEADERS,
        params={"name": entity_name},
    )
    data = r.json()["data"]
    if data:
        entity_map[entity_name] = data[0]["id"]
        print(f"{entity_name} -> ID {data[0]['id']} (for market: {market})")

print(f"Tracking {len(entity_map)} entities")
JavaScript
const API_KEY = process.env.SHOAL_API_KEY || 'YOUR_API_KEY';
const HEADERS = { Authorization: `Bearer ${API_KEY}` };

const marketEntities = {
  'Will the SEC approve a Solana ETF?': 'SEC',
  'Ethereum above $5000 by June?': 'Ethereum',
  'Will Trump sign crypto executive order?': 'Trump',
};

const entityMap = {};

for (const [market, entityName] of Object.entries(marketEntities)) {
  const res = await fetch(
    `https://api.shoal.xyz/v1/organizations/byOrganizationName?name=${encodeURIComponent(entityName)}`,
    { headers: HEADERS }
  );
  const { data } = await res.json();
  if (data.length > 0) {
    entityMap[entityName] = data[0].id;
    console.log(`${entityName} -> ID ${data[0].id} (for market: ${market})`);
  }
}

console.log(`Tracking ${Object.keys(entityMap).length} entities`);

Step 2: Monitor Entity Signals

Poll /v1/signal/byOrganizationId with since for each entity. High signal scores indicate potential market movers.
cURL
# Use an org ID returned from Step 1
SINCE=$(date -u -v-6H +"%Y-%m-%dT%H:%M:%SZ")  # macOS
# SINCE=$(date -u -d '6 hours ago' +"%Y-%m-%dT%H:%M:%SZ")  # Linux

curl "https://api.shoal.xyz/v1/signal/byOrganizationId?id=YOUR_ORG_ID&since=$SINCE" \
  -H "Authorization: Bearer YOUR_API_KEY"
Python
import time
from datetime import datetime, timedelta, timezone

SCORE_THRESHOLD = 8.0
cursor = (datetime.now(timezone.utc) - timedelta(hours=6)).isoformat()

while True:
    for entity_name, org_id in entity_map.items():
        r = requests.get(
            "https://api.shoal.xyz/v1/signal/byOrganizationId",
            headers=HEADERS,
            params={"id": org_id, "since": cursor},
        )
        signals = r.json()["data"]

        for signal in signals:
            if signal["signal"] >= SCORE_THRESHOLD:
                print(f"HIGH SIGNAL [{entity_name}] score={signal['signal']}: {signal['title']}")
            else:
                print(f"  [{entity_name}] score={signal['signal']}: {signal['title']}")

        if signals:
            latest = signals[0]["latestPostTimestamp"]
            if latest > cursor:
                cursor = latest

    # Also check cross-entity top signals
    top = requests.get(
        "https://api.shoal.xyz/v1/signal/top",
        headers=HEADERS,
    ).json()["data"]

    for s in top[:5]:
        if s["signal"] >= SCORE_THRESHOLD:
            print(f"TOP SIGNAL: {s['title']} (score={s['signal']})")

    time.sleep(300)  # Poll every 5 minutes
JavaScript
const SCORE_THRESHOLD = 8.0;
let cursor = new Date(Date.now() - 6 * 60 * 60 * 1000).toISOString();

async function monitorSignals() {
  for (const [entityName, orgId] of Object.entries(entityMap)) {
    const res = await fetch(
      `https://api.shoal.xyz/v1/signal/byOrganizationId?id=${orgId}&since=${cursor}`,
      { headers: HEADERS }
    );
    const { data } = await res.json();

    for (const signal of data) {
      const prefix = signal.signal >= SCORE_THRESHOLD ? 'HIGH SIGNAL' : ' ';
      console.log(`${prefix} [${entityName}] score=${signal.signal}: ${signal.title}`);
    }

    if (data.length > 0 && data[0].latestPostTimestamp > cursor) {
      cursor = data[0].latestPostTimestamp;
    }
  }

  // Cross-entity top signals
  const topRes = await fetch(
    'https://api.shoal.xyz/v1/signal/top',
    { headers: HEADERS }
  );
  const { data: topData } = await topRes.json();
  for (const s of topData.slice(0, 5)) {
    if (s.signal >= SCORE_THRESHOLD) {
      console.log(`TOP SIGNAL: ${s.title} (score=${s.signal})`);
    }
  }
}

setInterval(monitorSignals, 5 * 60 * 1000); // Poll every 5 minutes

Step 3: Detect Spikes

Use /v1/organizations/:id/signal-history to compare today’s activity to baseline. A sudden spike often precedes market repricing.
cURL
# Use an org ID returned from Step 1
curl "https://api.shoal.xyz/v1/organizations/YOUR_ORG_ID/signal-history?days=7" \
  -H "Authorization: Bearer YOUR_API_KEY"
Python
for entity_name, org_id in entity_map.items():
    r = requests.get(
        f"https://api.shoal.xyz/v1/organizations/{org_id}/signal-history",
        headers=HEADERS,
        params={"days": 7},
    )
    history = r.json()["data"]

    if len(history) < 2:
        continue

    today_count = history[-1]["count"]
    avg_count = sum(d["count"] for d in history[:-1]) / (len(history) - 1)

    if avg_count > 0 and today_count > avg_count * 2:
        print(f"SPIKE [{entity_name}] today={today_count} vs avg={avg_count:.1f} ({today_count/avg_count:.1f}x)")
    else:
        print(f"  [{entity_name}] today={today_count}, avg={avg_count:.1f}")
JavaScript
for (const [entityName, orgId] of Object.entries(entityMap)) {
  const res = await fetch(
    `https://api.shoal.xyz/v1/organizations/${orgId}/signal-history?days=7`,
    { headers: HEADERS }
  );
  const { data: history } = await res.json();

  if (history.length < 2) continue;

  const todayCount = history[history.length - 1].count;
  const prior = history.slice(0, -1);
  const avgCount = prior.reduce((sum, d) => sum + d.count, 0) / prior.length;

  if (avgCount > 0 && todayCount > avgCount * 2) {
    console.log(`SPIKE [${entityName}] today=${todayCount} vs avg=${avgCount.toFixed(1)} (${(todayCount / avgCount).toFixed(1)}x)`);
  } else {
    console.log(`  [${entityName}] today=${todayCount}, avg=${avgCount.toFixed(1)}`);
  }
}

Example Output

High-score signal response:
{
  "data": [
    {
      "title": "SEC Commissioner signals openness to Solana ETF approval",
      "signal": 9.2,
      "eventCategory": "regulation_legal",
      "latestPostTimestamp": "2026-03-09T16:45:00Z"
    }
  ]
}
Signal history showing spike:
{
  "data": [
    { "date": "2026-03-03", "count": 3 },
    { "date": "2026-03-04", "count": 5 },
    { "date": "2026-03-05", "count": 2 },
    { "date": "2026-03-06", "count": 4 },
    { "date": "2026-03-07", "count": 3 },
    { "date": "2026-03-08", "count": 6 },
    { "date": "2026-03-09", "count": 28 }
  ]
}

Tips

The since parameter is required on signal polling endpoints. Always pass it to avoid fetching stale data.
  • Combine /v1/signal/top with entity-specific monitoring for both broad and focused coverage.
  • Use a signal score threshold (e.g., >8.0) as an alert trigger — these events have the highest probability of moving markets.
  • Signal history spikes of 2x+ above the 7-day average are strong indicators of imminent market movement.
  • Poll every 5 minutes for active trading, or every 15-60 minutes for daily monitoring.