Skip to main content

Overview

Before outreach, know what a prospect’s company has been doing. Shoal enrichment matches company names against 5,000+ tracked organizations and surfaces recent radar and signal events — filtered to where the org is the primary event owner, not just mentioned as a participant.

How It Works

  1. You provide a list of contacts (name + company)
  2. Each company is matched against Shoal’s tracked organizations
  3. Matched orgs get recent radar and signal events attached
  4. Events are filtered to primary ownership (not just mentioned as participant)

Quick Start (CLI)

# Generate a sample contacts file
shoal enrich --example > contacts.json

# Enrich and print to stdout
shoal enrich contacts.json

# Write to file (progress in terminal, JSON to file)
shoal enrich contacts.json --output enriched.json

# Fewer events per company (saves credits)
shoal enrich contacts.json --output enriched.json --limit 3

Input Format

A JSON array where each object needs at minimum a company field. The name field is optional and passed through to the output.
contacts.json
[
  { "name": "sarah.chen@uniswap.org", "company": "Uniswap" },
  { "name": "james.wright@aave.com", "company": "Aave" },
  { "name": "m.santos@chainlink.com", "company": "Chainlink" },
  { "name": "alex.kumar@arbitrum.io", "company": "Arbitrum" },
  { "name": "lisa.park@lido.fi", "company": "Lido" },
  { "name": "d.nguyen@optimism.io", "company": "Optimism" },
  { "name": "rachel.kim@eigenlayer.xyz", "company": "EigenLayer" },
  { "name": "tom.bell@pendle.finance", "company": "Pendle" },
  { "name": "nina.vogt@layerzero.network", "company": "LayerZero" },
  { "name": "carlos.reyes@jupiter.ag", "company": "Jupiter" }
]
This is the same file generated by shoal enrich --example.

Full Example Output

Below is a realistic enriched result for 4 of the contacts above. Each contact gets matched, shoal_org, and filtered radar/signal arrays appended.
[
  {
    "name": "sarah.chen@uniswap.org",
    "company": "Uniswap",
    "matched": true,
    "shoal_org": { "id": 526, "label": "Uniswap", "type": "project" },
    "radar": [
      {
        "id": 48201,
        "title": "Uniswap v4 hooks deployed to mainnet",
        "eventCategory": "product_development",
        "eventSubcategory": "product_launch",
        "globalSummary": "Uniswap Labs has deployed the v4 hook framework to Ethereum mainnet, enabling custom pool logic including dynamic fees, limit orders, and TWAMM functionality.",
        "bulletSummary": [
          "v4 hooks now live on mainnet with 12 audited hook templates",
          "Custom fee tiers and concentrated liquidity strategies enabled",
          "Initial TVL migration of $180M from v3 pools"
        ],
        "eventOwner": [
          { "id": 526, "label": "Uniswap", "type": "project" }
        ],
        "eventParticipants": [],
        "latestPostTimestamp": "2026-03-12T16:45:00Z"
      },
      {
        "id": 48315,
        "title": "Uniswap Foundation partners with Circle for cross-chain USDC routing",
        "eventCategory": "partnership",
        "eventSubcategory": "integration",
        "globalSummary": "The Uniswap Foundation and Circle have announced a partnership to enable native cross-chain USDC routing through Uniswap's interface, leveraging CCTP for bridgeless swaps.",
        "bulletSummary": [
          "Native USDC routing across 8 chains via CCTP integration",
          "Eliminates bridged USDC fragmentation for Uniswap users",
          "Expected to reduce cross-chain swap costs by 40-60%"
        ],
        "eventOwner": [
          { "id": 526, "label": "Uniswap", "type": "project" }
        ],
        "eventParticipants": [
          { "id": 891, "label": "Circle", "type": "project" }
        ],
        "latestPostTimestamp": "2026-03-10T11:20:00Z"
      }
    ],
    "signal": []
  },
  {
    "name": "m.santos@chainlink.com",
    "company": "Chainlink",
    "matched": true,
    "shoal_org": { "id": 42, "label": "Chainlink", "type": "project" },
    "radar": [
      {
        "id": 47998,
        "title": "Chainlink CCIP reaches $10B in cross-chain value transferred",
        "eventCategory": "product_development",
        "eventSubcategory": "milestone",
        "globalSummary": "Chainlink's Cross-Chain Interoperability Protocol has surpassed $10 billion in total cross-chain value transferred, with adoption accelerating across DeFi and institutional protocols.",
        "bulletSummary": [
          "$10B milestone reached across 12 supported blockchains",
          "Institutional adoption growing with 5 new TradFi integrations in Q1",
          "Average daily transfer volume up 3x quarter-over-quarter"
        ],
        "eventOwner": [
          { "id": 42, "label": "Chainlink", "type": "project" }
        ],
        "eventParticipants": [],
        "latestPostTimestamp": "2026-03-11T09:30:00Z"
      }
    ],
    "signal": [
      {
        "id": 91204,
        "score": 8,
        "summary": "CCIP adoption accelerating across DeFi protocols — multiple new integrations and rising transfer volumes suggest Chainlink is consolidating its position as the default cross-chain layer.",
        "eventOwner": [
          { "id": 42, "label": "Chainlink", "type": "project" }
        ],
        "createdAt": "2026-03-11T12:00:00Z"
      }
    ]
  },
  {
    "name": "james.wright@aave.com",
    "company": "Aave",
    "matched": true,
    "shoal_org": { "id": 318, "label": "Aave", "type": "project" },
    "radar": [
      {
        "id": 48102,
        "title": "Aave GHO stablecoin expands to Arbitrum and Base",
        "eventCategory": "product_development",
        "eventSubcategory": "product_launch",
        "globalSummary": "Aave has deployed its GHO stablecoin to Arbitrum and Base networks, expanding the overcollateralized stablecoin beyond Ethereum mainnet for the first time.",
        "bulletSummary": [
          "GHO now mintable on Arbitrum and Base via Aave v3 markets",
          "Cross-chain GHO transfers enabled through Chainlink CCIP",
          "Initial borrow cap set at $50M per chain"
        ],
        "eventOwner": [
          { "id": 318, "label": "Aave", "type": "project" }
        ],
        "eventParticipants": [
          { "id": 42, "label": "Chainlink", "type": "project" },
          { "id": 204, "label": "Arbitrum", "type": "project" }
        ],
        "latestPostTimestamp": "2026-03-09T14:15:00Z"
      }
    ],
    "signal": []
  },
  {
    "name": "rachel.kim@eigenlayer.xyz",
    "company": "EigenLayer",
    "matched": false
  }
]
Notice that:
  • Uniswap has 2 radar events where it is the eventOwner
  • Chainlink has 1 radar + 1 signal event — the signal includes a score and narrative summary
  • Aave has 1 radar event — even though Chainlink and Arbitrum are participants, Aave is the owner so it surfaces here
  • EigenLayer returned matched: false because it isn’t in Shoal’s tracked org database yet

Using with AI Agents (MCP)

The same enrichment is available as the enrich MCP tool via shoal-mcp.
Tool: enrich
Input: { "contacts": [...], "limit": 5 }
Works in Claude Code, Cursor, Windsurf — any MCP-compatible agent. Example prompt:
“Enrich these 20 contacts and tell me which companies have the most recent activity. Suggest an outreach angle for the top 3.”
See CLI & MCP setup for configuration.

API-Only Approach

For custom pipelines, use two API calls per company: resolve the name, then fetch events.
cURL
# Step 1: Resolve company to org ID
curl "https://api.shoal.xyz/v1/organizations/byOrganizationName?name=Uniswap" \
  -H "Authorization: Bearer YOUR_API_KEY"

# Step 2: Get org with embedded events
curl "https://api.shoal.xyz/v1/organizations/byOrganizationId?id=526&include=radar,signal&includeLimit=5" \
  -H "Authorization: Bearer YOUR_API_KEY"
Python
import os, requests

API_KEY = os.environ["SHOAL_API_KEY"]
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
BASE = "https://api.shoal.xyz/v1"

contacts = [
    {"name": "sarah.chen@uniswap.org", "company": "Uniswap"},
    {"name": "m.santos@chainlink.com", "company": "Chainlink"},
]

seen = {}
for contact in contacts:
    company = contact["company"]
    if company in seen:
        contact.update(seen[company])
        continue

    # Resolve name to org
    r = requests.get(f"{BASE}/organizations/byOrganizationName",
                     headers=HEADERS, params={"name": company})
    orgs = r.json().get("data", [])
    if not orgs:
        seen[company] = {"matched": False}
        contact.update(seen[company])
        continue

    org_id = orgs[0]["id"]

    # Fetch with events
    r = requests.get(f"{BASE}/organizations/byOrganizationId",
                     headers=HEADERS,
                     params={"id": org_id, "include": "radar,signal", "includeLimit": 5})
    data = r.json()["data"]
    result = {
        "matched": True,
        "shoal_org": {"id": data["id"], "label": data["label"]},
        "radar": data.get("radar", []),
        "signal": data.get("signal", []),
    }
    seen[company] = result
    contact.update(result)
JavaScript
const API_KEY = process.env.SHOAL_API_KEY;
const HEADERS = { Authorization: `Bearer ${API_KEY}` };
const BASE = 'https://api.shoal.xyz/v1';

const contacts = [
  { name: 'sarah.chen@uniswap.org', company: 'Uniswap' },
  { name: 'm.santos@chainlink.com', company: 'Chainlink' },
];

const seen = {};
for (const contact of contacts) {
  const { company } = contact;
  if (seen[company]) { Object.assign(contact, seen[company]); continue; }

  const searchRes = await fetch(
    `${BASE}/organizations/byOrganizationName?name=${encodeURIComponent(company)}`,
    { headers: HEADERS }
  );
  const orgs = (await searchRes.json()).data;
  if (!orgs.length) { seen[company] = { matched: false }; Object.assign(contact, seen[company]); continue; }

  const orgRes = await fetch(
    `${BASE}/organizations/byOrganizationId?id=${orgs[0].id}&include=radar,signal&includeLimit=5`,
    { headers: HEADERS }
  );
  const data = (await orgRes.json()).data;
  seen[company] = {
    matched: true,
    shoal_org: { id: data.id, label: data.label },
    radar: data.radar || [],
    signal: data.signal || [],
  };
  Object.assign(contact, seen[company]);
}

Tips

  • The CLI deduplicates by company name automatically — duplicates don’t cost extra credits
  • Use --limit to control event depth vs credit cost (default 5)
  • Pipe to jq for filtering: shoal enrich contacts.json | jq '[.[] | select(.matched)]'
  • Combine with shoal brief org <id> for deeper dives on high-value prospects
  • For large lists (100+ contacts), consider the API-only approach with your own rate limiting