Overview
BD teams need early signals on partnerships, funding rounds, and product launches. Shoal’s radar and signal endpoints surface these events automatically, ranked by significance — so you can act on opportunities before they’re widely known.
Deliver Alerts Where Your Team Works
Slack Real-time radar and signal cards in your BD channel, plus /shoal brief slash commands.
Telegram Daily digest of top signal events ranked by score — perfect for mobile-first teams.
You’ll need an API key to follow along. See the Quickstart guide to create one.
Endpoints Used
Endpoint Purpose Reference /v1/radar/byCategoryPartnership & acquisition events Docs /v1/signal/topHighest-impact events across all orgs Docs /v1/brief/batchCombined view for a watchlist of orgs Docs /v1/organizations/allBrowse/search the org directory Docs /v1/organizations/byOrganizationNameLook up org by name Docs
Workflow
Step 1: Build a Watchlist
Search for organizations by name and collect their IDs into a watchlist.
curl "https://api.shoal.xyz/v1/organizations/byOrganizationName?name=Chainlink" \
-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 } " }
# Resolve org names to IDs
watchlist_names = [ "Chainlink" , "Arbitrum" , "Aave" , "Lido" ]
watchlist_ids = []
for name in watchlist_names:
r = requests.get(
"https://api.shoal.xyz/v1/organizations/byOrganizationName" ,
headers = HEADERS ,
params = { "name" : name},
)
data = r.json()[ "data" ]
if data:
watchlist_ids.append( str (data[ 0 ][ "id" ]))
print ( f " { name } -> ID { data[ 0 ][ 'id' ] } " )
print ( f "Watchlist IDs: { ',' .join(watchlist_ids) } " )
const API_KEY = process . env . SHOAL_API_KEY || 'YOUR_API_KEY' ;
const HEADERS = { Authorization: `Bearer ${ API_KEY } ` };
const watchlistNames = [ 'Chainlink' , 'Arbitrum' , 'Aave' , 'Lido' ];
const watchlistIds = [];
for ( const name of watchlistNames ) {
const res = await fetch (
`https://api.shoal.xyz/v1/organizations/byOrganizationName?name= ${ encodeURIComponent ( name ) } ` ,
{ headers: HEADERS }
);
const { data } = await res . json ();
if ( data . length > 0 ) {
watchlistIds . push ( data [ 0 ]. id );
console . log ( ` ${ name } -> ID ${ data [ 0 ]. id } ` );
}
}
console . log ( `Watchlist IDs: ${ watchlistIds . join ( ',' ) } ` );
Step 2: Poll for BD-Relevant Events
Call the radar category endpoints on a schedule with the since parameter to get only new events.
# Partnership events in the last 24 hours
SINCE = $( date -u -v-24H +"%Y-%m-%dT%H:%M:%SZ" ) # macOS
# SINCE=$(date -u -d '24 hours ago' +"%Y-%m-%dT%H:%M:%SZ") # Linux
curl "https://api.shoal.xyz/v1/radar/byCategory?category=partnership&since= $SINCE " \
-H "Authorization: Bearer YOUR_API_KEY"
# Funding rounds
curl "https://api.shoal.xyz/v1/radar/byCategory?category=funding&since= $SINCE " \
-H "Authorization: Bearer YOUR_API_KEY"
import time
from datetime import datetime, timedelta, timezone
cursor = (datetime.now(timezone.utc) - timedelta( hours = 24 )).isoformat()
while True :
# Check partnership events
partnerships = requests.get(
"https://api.shoal.xyz/v1/radar/byCategory" ,
headers = HEADERS ,
params = { "category" : "partnership" , "since" : cursor},
).json()[ "data" ]
# Check funding events
funding = requests.get(
"https://api.shoal.xyz/v1/radar/byCategory" ,
headers = HEADERS ,
params = { "category" : "funding" , "since" : cursor},
).json()[ "data" ]
for event in partnerships + funding:
print ( f "[ { event[ 'eventCategory' ] } ] { event[ 'title' ] } " )
if partnerships or funding:
all_events = partnerships + funding
cursor = max (e[ "latestPostTimestamp" ] for e in all_events)
time.sleep( 900 ) # Poll every 15 minutes
let cursor = new Date ( Date . now () - 24 * 60 * 60 * 1000 ). toISOString ();
async function pollBDEvents () {
const categories = [ 'partnership' , 'funding' ];
for ( const category of categories ) {
const res = await fetch (
`https://api.shoal.xyz/v1/radar/byCategory?category= ${ category } &since= ${ cursor } ` ,
{ headers: HEADERS }
);
const { data } = await res . json ();
for ( const event of data ) {
console . log ( `[ ${ event . eventCategory } ] ${ event . title } ` );
}
if ( data . length > 0 ) {
const latest = data [ 0 ]. createdAt ;
if ( latest > cursor ) cursor = latest ;
}
}
}
setInterval ( pollBDEvents , 15 * 60 * 1000 ); // Poll every 15 minutes
Step 3: Get a Daily Briefing
Use /v1/brief/batch with your watchlist IDs for a single-call portfolio update.
# Use the IDs returned from Step 1 (e.g. from byOrganizationName lookups)
SINCE = $( date -u -v-24H +"%Y-%m-%dT%H:%M:%SZ" ) # macOS
# SINCE=$(date -u -d '24 hours ago' +"%Y-%m-%dT%H:%M:%SZ") # Linux
curl "https://api.shoal.xyz/v1/brief/batch?ids=YOUR_ORG_IDS&since= $SINCE &compact=true" \
-H "Authorization: Bearer YOUR_API_KEY"
ids = "," .join(watchlist_ids)
since = (datetime.now(timezone.utc) - timedelta( hours = 24 )).isoformat()
r = requests.get(
"https://api.shoal.xyz/v1/brief/batch" ,
headers = HEADERS ,
params = { "ids" : ids, "since" : since, "compact" : "true" },
)
data = r.json()
for org in data[ "organizations" ]:
radar_count = org[ "counts" ][ "radar" ]
signal_count = org[ "counts" ][ "signal" ]
if radar_count > 0 or signal_count > 0 :
print ( f " { org[ 'label' ] } : { radar_count } radar, { signal_count } signals" )
for event in org.get( "radar" , []):
print ( f " [ { event[ 'eventCategory' ] } ] { event[ 'title' ] } " )
print ( f "Credits used: { data[ 'creditCost' ] } " )
const ids = watchlistIds . join ( ',' );
const since = new Date ( Date . now () - 24 * 60 * 60 * 1000 ). toISOString ();
const briefRes = await fetch (
`https://api.shoal.xyz/v1/brief/batch?ids= ${ ids } &since= ${ since } &compact=true` ,
{ headers: HEADERS }
);
const briefData = await briefRes . json ();
for ( const org of briefData . organizations ) {
const { radar , signal } = org . counts ;
if ( radar > 0 || signal > 0 ) {
console . log ( ` ${ org . label } : ${ radar } radar, ${ signal } signals` );
for ( const event of org . radar || []) {
console . log ( ` [ ${ event . eventCategory } ] ${ event . title } ` );
}
}
}
console . log ( `Credits used: ${ briefData . creditCost } ` );
Example Output
The /v1/brief/batch response groups events by organization. See the full schema for all available fields.
{
"organizations" : [
{
"id" : 42 ,
"label" : "Chainlink" ,
"counts" : { "radar" : 2 , "signal" : 5 },
"radar" : [
{
"eventCategory" : "partnership" ,
"title" : "Chainlink CCIP integration with major TradFi institution" ,
"signal" : 9 ,
"latestPostTimestamp" : "2026-03-09T14:22:00Z"
},
{
"eventCategory" : "funding" ,
"title" : "Chainlink ecosystem fund announces new cohort" ,
"signal" : 7 ,
"latestPostTimestamp" : "2026-03-09T09:15:00Z"
}
]
}
],
"creditCost" : 4
}
Lead Enrichment
Enrich your CRM contacts with Shoal intelligence before outreach — match company names to tracked organizations and surface recent events.
Lead & Company Enrichment Full walkthrough with examples: input format, CLI usage, MCP tool, and API-only approach.
Tips
The since parameter is required on batch and polling endpoints. Always pass it to avoid fetching stale data and to minimize credit usage.
Use compact=true on batch calls to reduce payload size and stay under your credit budget.
Poll every 15-60 minutes depending on how time-sensitive your BD pipeline is.
Use the signal score from /v1/signal/top to prioritize outreach — higher scores indicate more significant events.
Combine partnership radar with signal spikes to identify the highest-conviction opportunities.