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
| Endpoint | Purpose | Reference |
|---|
/v1/organizations/byOrganizationName | Resolve market entities to org IDs | Docs |
/v1/signal/byOrganizationId | Entity-specific scored signals | Docs |
/v1/organizations/:id/signal-history | Detect activity spikes over time | Docs |
/v1/signal/top | Highest-impact signals across all entities | Docs |
/v1/brief | Combined radar+signal for a single entity | Docs |
Workflow
Step 1: Map Markets to Entities
For each active prediction market, resolve the underlying entity to a Shoal organization ID.
# 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"
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")
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.
# 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"
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
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.
# 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"
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}")
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.