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

# Build a discovery board

> Show new, graduating and graduated launchpad tokens in three filtered, ranked columns.

A discovery board follows launchpad tokens through their life: just launched, close to graduating, and graduated. `POST /v1/discovery` returns all three columns in one request.

## 1. Request the columns

```bash theme={null}
curl "https://{{API_HOST}}/v1/discovery" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "chains": ["solana"],
    "newPairs": {
      "limit": 30,
      "filters": { "withAtLeastOneSocial": true, "devHoldingPercent": { "max": 10 } }
    },
    "graduating": {
      "limit": 30,
      "filters": { "holderCount": { "min": 100 }, "maxGraduationStaleMinutes": 30 }
    },
    "graduated": {
      "sortBy": "volume24hUsd",
      "limit": 30,
      "filters": { "liquidityUsd": { "min": "10000" } }
    }
  }'
```

Each column takes its own `sortBy`, `order`, `limit` and `filters`. Every filter and sort field is documented in [Filtering and sorting](/concepts/filtering).

The `graduating` and `graduated` columns also apply [minimums of their own](/concepts/filtering#column-defaults), such as 10 holders. Your filters can raise these minimums but not lower them.

## 2. Render the response

```json theme={null}
{
  "columns": [
    { "column": "newPairs", "deltas": [ { "chain": "solana", "address": "…", "rank": 1, "data": { } } ] },
    { "column": "graduating", "deltas": [ ] },
    { "column": "graduated", "deltas": [ ] }
  ]
}
```

Each column's `deltas` is the whole ranked list. Draw the items in `rank` order. `data` is the full token object, so each card can show:

| Card element | Field |
| - | - |
| Name and symbol | `data.name`, `data.symbol` |
| Age | now minus `data.createdAt` |
| Market cap | `data.marketCap.usd` |
| Curve progress | `data.bondingCurvePct` |
| 5-minute volume and trades | `data.stats["5m"].volume.usd`, `data.stats["5m"].buyCount`, `data.stats["5m"].sellCount` |
| Holders | `data.holders.count` |
| Risk badges | `data.holders.devPct`, `data.holders.sniperPct`, `data.holders.bundlerPct`, `data.security.mintDisabled` |
| Links | `data.metadata.twitter`, `data.metadata.website`, `data.metadata.telegram` |

## 3. Keep it fresh

Poll the endpoint on an interval. Every 2 to 5 seconds suits a fast-moving `newPairs` column, and your [request budget](/concepts/rate-limits) leaves plenty of room.

When you redraw, match cards by `chain` + `address`, so a token that moves between ranks animates instead of flickering. A token that has left a column's ranked list is gone from that column; a token that graduated shows up in `graduated`.

Now and then a card's windowed `stats` read zero for a response while the token's full record catches up. Keep the previous values for a card when that happens, and the next refresh corrects it.

## Filter presets

| Preset | Filters |
| - | - |
| Hide likely rugs | `devHoldingPercent: { max: 10 }`, `snipersSupplyPercent: { max: 20 }`, `top10HoldersPercent: { max: 40 }` |
| Socials required | `withAtLeastOneSocial: true` |
| Only active | `lastActivityMinutes: 5` |
| Real liquidity | `liquidityUsd: { min: "5000" }` |
| One launchpad | `protocols: ["pumpfun"]` |
| No copycats | `excludeSymbolOrName: ["test", "scam"]` |

Tune the numbers to your users; these are starting points, not recommendations.

## Several chains

Send more than one slug in `chains`, for example `["solana", "base", "bsc"]`. Each column then ranks tokens from every listed chain as one set. Use `data.chain` to badge each card.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.