> ## Documentation Index
> Fetch the complete documentation index at: https://docs.shoal.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Exchange Asset Intelligence

> Track news for exchange-listed assets via entity monitoring

## Overview

Exchanges, trading desks, and portfolio managers need structured intelligence on listed assets. Shoal's entity model lets you map tokens and projects to organizations and monitor them for regulatory, security, and development events — all through a single API.

<Frame>
  <img src="https://mintcdn.com/shoalresearch/E1hQrw-PO667gkzT/images/exchange-integration.svg?fit=max&auto=format&n=E1hQrw-PO667gkzT&q=85&s=1c973c9b9b5825eb1631ed53b47e68bb" alt="Exchange asset intelligence integration showing Shoal events flowing into exchange monitoring workflows" width="1512" height="771" data-path="images/exchange-integration.svg" />
</Frame>

<Note>
  You'll need an API key to follow along. See the [Quickstart](/quickstart) guide to create one.
</Note>

## Endpoints Used

| Endpoint                                                                                    | Purpose                             | Reference                                                 |
| ------------------------------------------------------------------------------------------- | ----------------------------------- | --------------------------------------------------------- |
| [`/v1/organizations/byOrganizationName`](/api-reference/organizations/by-organization-name) | Map token names to org IDs          | [Docs](/api-reference/organizations/by-organization-name) |
| [`/v1/brief/batch`](/api-reference/brief/batch)                                             | Monitor up to 25 assets in one call | [Docs](/api-reference/brief/batch)                        |
| [`/v1/radar/byCategory`](/api-reference/radar/by-category)                                  | Regulatory & security events        | [Docs](/api-reference/radar/by-category)                  |
| [`/v1/signal/byOrganizationId`](/api-reference/signal/by-organization-id)                   | Per-asset signal feed               | [Docs](/api-reference/signal/by-organization-id)          |

## Workflow

### Step 1: Map Your Portfolio

Resolve each asset to a Shoal org ID via name lookup and store the mapping.

```bash cURL theme={null}
curl "https://api.shoal.xyz/v1/organizations/byOrganizationName?name=Ethereum" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```python Python theme={null}
import os, requests

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

# Map exchange-listed assets to Shoal org IDs
assets = ["Ethereum", "Solana", "Chainlink", "Aave", "Uniswap", "Arbitrum"]
asset_map = {}  # asset_name -> org_id

for asset in assets:
    r = requests.get(
        "https://api.shoal.xyz/v1/organizations/byOrganizationName",
        headers=HEADERS,
        params={"name": asset},
    )
    data = r.json()["data"]
    if data:
        asset_map[asset] = str(data[0]["id"])
        print(f"{asset} -> ID {data[0]['id']}")
    else:
        print(f"{asset} -> not found")

print(f"Mapped {len(asset_map)}/{len(assets)} assets")
```

```javascript JavaScript theme={null}
const API_KEY = process.env.SHOAL_API_KEY || 'YOUR_API_KEY';
const HEADERS = { Authorization: `Bearer ${API_KEY}` };

const assets = ['Ethereum', 'Solana', 'Chainlink', 'Aave', 'Uniswap', 'Arbitrum'];
const assetMap = {};

for (const asset of assets) {
  const res = await fetch(
    `https://api.shoal.xyz/v1/organizations/byOrganizationName?name=${encodeURIComponent(asset)}`,
    { headers: HEADERS }
  );
  const { data } = await res.json();
  if (data.length > 0) {
    assetMap[asset] = data[0].id;
    console.log(`${asset} -> ID ${data[0].id}`);
  } else {
    console.log(`${asset} -> not found`);
  }
}

console.log(`Mapped ${Object.keys(assetMap).length}/${assets.length} assets`);
```

### Step 2: Batch-Poll for Updates

Use `/v1/brief/batch` with all org IDs and `since` for efficient single-call monitoring when batch brief is available on your plan.

```bash cURL theme={null}
# Use the IDs returned from Step 1
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"
```

```python Python theme={null}
import time
from datetime import datetime, timedelta, timezone

ids = ",".join(asset_map.values())
cursor = (datetime.now(timezone.utc) - timedelta(hours=24)).isoformat()

while True:
    r = requests.get(
        "https://api.shoal.xyz/v1/brief/batch",
        headers=HEADERS,
        params={"ids": ids, "since": cursor, "compact": "true"},
    )
    data = r.json()

    for org in data["organizations"]:
        total = org["counts"]["radar"] + org["counts"]["signal"]
        if total > 0:
            print(f"{org['label']}: {org['counts']['radar']} radar, {org['counts']['signal']} signals")
            for event in org.get("radar", []):
                print(f"  [{event['eventCategory']}] {event['title']}")
    time.sleep(900)  # Poll every 15 minutes
```

```javascript JavaScript theme={null}
const ids = Object.values(assetMap).join(',');
let cursor = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();

async function pollAssets() {
  const res = await fetch(
    `https://api.shoal.xyz/v1/brief/batch?ids=${ids}&since=${cursor}&compact=true`,
    { headers: HEADERS }
  );
  const data = await res.json();

  for (const org of data.organizations) {
    const total = org.counts.radar + org.counts.signal;
    if (total > 0) {
      console.log(`${org.label}: ${org.counts.radar} radar, ${org.counts.signal} signals`);
      for (const event of org.radar || []) {
        console.log(`  [${event.eventCategory}] ${event.title}`);
      }
    }
  }
}

setInterval(pollAssets, 15 * 60 * 1000); // Poll every 15 minutes
```

### Step 3: Filter by Risk Category

Use `/v1/radar/byCategory` for `regulation_legal` and `security_incident` to surface compliance-relevant events.

```bash cURL theme={null}
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

# Regulatory events
curl "https://api.shoal.xyz/v1/radar/byCategory?category=regulation_legal&since=$SINCE" \
  -H "Authorization: Bearer YOUR_API_KEY"

# Security incidents
curl "https://api.shoal.xyz/v1/radar/byCategory?category=security_incident&since=$SINCE" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```python Python theme={null}
RISK_CATEGORIES = ["regulation_legal", "security_incident"]
since = (datetime.now(timezone.utc) - timedelta(hours=24)).isoformat()

portfolio_ids = set(asset_map.values())

for category in RISK_CATEGORIES:
    r = requests.get(
        "https://api.shoal.xyz/v1/radar/byCategory",
        headers=HEADERS,
        params={"category": category, "since": since},
    )
    events = r.json()["data"]

    # Filter to only portfolio assets
    relevant = [e for e in events if str(e.get("organizationId")) in portfolio_ids]

    if relevant:
        print(f"\n{category.upper()} ({len(relevant)} events):")
        for event in relevant:
            print(f"  {event['title']} (significance: {event['signal']})")
```

```javascript JavaScript theme={null}
const RISK_CATEGORIES = ['regulation_legal', 'security_incident'];
const since = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
const portfolioIds = new Set(Object.values(assetMap).map(String));

for (const category of RISK_CATEGORIES) {
  const res = await fetch(
    `https://api.shoal.xyz/v1/radar/byCategory?category=${category}&since=${since}`,
    { headers: HEADERS }
  );
  const { data } = await res.json();

  const relevant = data.filter(e => portfolioIds.has(String(e.organizationId)));

  if (relevant.length > 0) {
    console.log(`\n${category.toUpperCase()} (${relevant.length} events):`);
    for (const event of relevant) {
      console.log(`  ${event.title} (significance: ${event.signal})`);
    }
  }
}
```

## Example Output

The [`/v1/brief/batch`](/api-reference/brief/batch) response groups events by organization. See the [full schema](/api-reference/brief/batch) for all available fields.

```json theme={null}
{
  "organizations": [
    {
      "id": 15,
      "label": "Solana",
      "counts": { "radar": 1, "signal": 3 },
      "radar": [
        {
          "eventCategory": "regulation_legal",
          "title": "SEC delays decision on Solana ETF application",
          "signal": 8,
          "latestPostTimestamp": "2026-03-09T11:30:00Z"
        }
      ]
    },
    {
      "id": 27,
      "label": "Aave",
      "counts": { "radar": 0, "signal": 1 },
      "radar": []
    }
  ],
  "next_cursor": null
}
```

## Tips

<Note>
  The `since` parameter is required on batch and polling endpoints. Always pass it to avoid fetching stale data.
</Note>

* Use `compact=true` on batch calls to reduce payload size.
* Poll regulatory categories (`regulation_legal`, `security_incident`) more frequently than general radar — these events often require immediate action.
* Use `/v1/organizations/:id/signal-history` to detect unusual activity on a specific asset before it hits mainstream news.
* The batch endpoint supports up to 25 org IDs per call — group assets by priority tier if you have more.
