# Merkl DeFi Rewards Scraper (`tactful_anvil/merkl-defi-rewards-scraper`) Actor

Scrape every Merkl reward opportunity across all chains: reward APR, TVL, daily rewards in USD, days of campaign left, rewards per $1M deposited (crowding) and a 0-100 opportunity score. HTTP-only, no API key, no login.

- **URL**: https://apify.com/tactful\_anvil/merkl-defi-rewards-scraper.md
- **Developed by:** [Mr Zack](https://apify.com/tactful_anvil) (community)
- **Categories:** Developer tools, Automation, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 opportunity results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Merkl DeFi Rewards Scraper — APR, TVL, Runway & Crowding

Merkl is where a large share of on-chain incentive budgets are actually distributed: **~740 live reward opportunities across every major chain, paying out roughly $470,000 per day** at the time of writing. The app shows you a pretty grid. It does not tell you the two things that decide whether a position is worth taking: **how long the rewards last**, and **how crowded the pool already is**.

This Actor returns **every Merkl opportunity as one clean row** — reward APR, TVL, daily rewards in USD, days of campaign left, rewards paid per $1M already deposited, and a 0-100 opportunity score you can sort by.

No API key, no login, no browser, no residential proxy. HTTP-only, so a full pull takes seconds.

### Who is this for?

- **Yield farmers & DeFi power users** — stop refreshing the Merkl app. Sort by score, filter to the chains you actually bridge to, and see which campaigns die this week before you commit capital.
- **Treasuries & DAO contributors** — benchmark your own incentive program: what APR do comparable pools advertise, and what does it cost per day to hold that TVL?
- **Quant & research desks** — the on-chain incentive market as a dataset: APR, budget and TVL per protocol per chain, pullable on a schedule for time series.
- **AI agents (MCP)** — pay-per-event Actors are callable as tools. *"Which live stablecoin pool on Base pays over 15% and runs for at least two more weeks?"* becomes a single tool call.

### What one row looks like

| Field | Example | Notes |
| --- | --- | --- |
| `name`, `protocol`, `chain`, `action` | `Deposit USDe as collateral…`, `Morpho`, `Base`, `LEND` | `action` is what you must actually do: POOL / LEND / BORROW / HOLD / STAKE / DROP |
| `rewardAprPct` | `4.16` | reward APR as published. `null` when Merkl publishes none — never a fabricated `0` |
| `tvlUsd` | `351099481.95` | depth: a deep pool means your deposit will not move the APR |
| `dailyRewardsUsd` | `40056.84` | USD of rewards flowing per day — proof the budget is real |
| `rewardsPerMillionUsd` | `114.09` | **the crowding signal**: daily rewards ÷ TVL in millions. Two pools can advertise the same APR while one is 100× more contested |
| `daysLeft`, `campaignEndsAt`, `endingWithin7Days` | `6.9`, ISO date, `true` | runway. A 200% APR that ends tomorrow is not an opportunity |
| `daysUntilStart`, `campaignStartsAt` | `1.3`, ISO date | for `SOON` campaigns — get in on day one |
| `tokens`, `rewardTokens` | `["USDe"]`, `["MORPHO"]` | what you deposit vs what you get paid in |
| `depositUrl`, `merklUrl` | direct links | straight to the position, or to Merkl's page |
| `opportunityScore` | `72.4` | see below |

Any value Merkl does not publish comes back `null` — never a fabricated zero. Dust-sized campaigns (a pool paying $0.0035/day) keep their precision instead of being flattened to `0`.

#### How the opportunity score works

Deliberately simple, documented, reproducible — so you can argue with it:

- **45% reward APR**, saturating at 30%. Above that, the number is usually a thin pool or a short burst, not a durable yield.
- **25% runway** — 7+ days left is full marks; a campaign ending within a day scores almost nothing.
- **20% pool depth** — TVL log-scaled $10k → $10M. A tiny pool's advertised APR is fragile; it collapses the moment real money arrives.
- **10% rewards actually flowing** — a published budget that pays out beats one that does not.

`PAST` campaigns and opportunities with no published APR score `null` rather than a misleading number.

### Input examples

The default board — everything live, best first:

```json
{ "statuses": ["LIVE"], "sortBy": "opportunity" }
```

Serious money only, with runway (stablecoin farmer):

```json
{
  "statuses": ["LIVE"],
  "minRewardAprPct": 10,
  "minTvlUsd": 500000,
  "minDaysLeft": 7,
  "searchText": "USDC",
  "sortBy": "apr"
}
```

Least-crowded pools on one chain:

```json
{ "chains": ["Base"], "minDailyRewardsUsd": 100, "sortBy": "rewardsPerMillion" }
```

Get in on day one:

```json
{ "statuses": ["SOON"], "sortBy": "startingSoon" }
```

Exiting before the music stops:

```json
{ "statuses": ["LIVE"], "sortBy": "endingSoon", "maxItems": 50 }
```

### How to schedule this Actor (recommended setup)

Reward campaigns rotate constantly: APRs move as TVL arrives, budgets run dry, and new campaigns land daily. A scheduled run is how you find out **before** your APR quietly halves.

1. Run the Actor once with your input and check the results.
2. On the Actor page open the **…** menu and choose **Schedule Actor** (or go to [Schedules](https://console.apify.com/schedules) → **Create new**).
3. Set the cron expression:
   - `0 */6 * * *` — every 6 hours. Good default: catches new campaigns and APR drops the same day.
   - `0 8 * * *` — once each morning. Cheapest useful cadence.
   - `0 * * * *` — hourly, when you are actively rotating capital between campaigns.
4. Attach a tight saved input (`minTvlUsd`, `minDaysLeft`, a chain filter, `maxItems: 50`) — a filtered run costs a fraction of a cent.
5. Add an integration so the board comes to you: **Integrations → Slack / Discord webhook / Google Sheets / Zapier / Make**, triggered on *Run succeeded*.

**Catching launches:** schedule `{ "statuses": ["SOON"], "sortBy": "startingSoon" }` daily — anything new at the top is a campaign that has not been farmed yet.

**Watching for the exit:** schedule `{ "sortBy": "endingSoon", "maxItems": 30 }` and you get a standing list of positions to unwind this week.

### Why this Actor

- **The only Merkl dataset on the Store** (checked Aug 2026) — everywhere else you get the app's grid, not a queryable table.
- **Signals the API does not give you** — runway, crowding and score are computed here, from a single run. No snapshot, no second run, no extra charge.
- **HTTP-only** — a full 737-row pull finishes in seconds, and no proxy cost is baked into the price.
- **Honest nulls** — placeholder zeros from the upstream API are reported as `null`, so a filter like "APR ≥ 5%" does not silently include unknowns.
- **Pay per event** — a tiny start fee plus a per-row fee. A filtered daily run costs well under a cent; the full live board is about $0.89.
- **No free-plan cap** — free-plan users get the same board as everyone else.

Also hunting **security** payouts? Same treatment for two other money boards: [Immunefi bug bounties](https://apify.com/tactful_anvil/immunefi-bug-bounty-audit-competition-scraper) and [Web3 audit contests across Code4rena, Sherlock, CodeHawks & Cantina](https://apify.com/tactful_anvil/web3-audit-contest-aggregator).

### FAQ

**Is this legal?** The Actor reads Merkl's public API — the same data the public app shows without logging in. No login, no private data, no bypassing access controls. Review Merkl's terms for your specific use case.

**Why is `rewardAprPct` null on some rows?** Merkl publishes `0` for opportunities where no APR is computed yet. A live campaign with a genuine 0% reward APR does not exist, so reporting `0` would be a lie — we report `null` and leave those rows out of APR filters.

**Is `opportunityScore` financial advice?** No. It is an arithmetic ranking of four published numbers, fully documented above so you can disagree with it. Nothing here is investment advice; verify every position yourself before committing capital.

**Does this claim rewards for me?** No. It is read-only data. Claiming happens in your own wallet on Merkl.

**Why does APR here differ from another dashboard?** Merkl's APR counts the incentive rewards it distributes, not the underlying protocol yield. Total return on a position is usually the base yield plus this reward APR.

**Found a bug, or want another field (per-campaign breakdown, historical APR)?** Open a ticket on the **Issues** tab.

### Changelog

- **0.1** — Initial release: every Merkl opportunity across all chains, honest nulls, campaign runway, crowding metric (`rewardsPerMillionUsd`), opportunity score, seven sort orders, pay-per-event.

### Was this Actor useful?

If it saved you from parking capital in a campaign that ended two days later, a short review on the Store page helps other farmers decide whether to try it. Reviews are the only signal buyers have before they spend anything.

If something is wrong instead, please open a ticket on the **Issues** tab rather than leaving it broken — API drift and field requests get fixed.

# Actor input Schema

## `statuses` (type: `array`):

`LIVE` = rewards being distributed right now (default), `SOON` = announced, not started, `PAST` = finished campaigns for historical analysis.

## `chains` (type: `array`):

Filter by chain name as Merkl publishes it, e.g. `Ethereum`, `Base`, `Arbitrum`, `Optimism`. Empty = every chain.

## `protocols` (type: `array`):

Filter by protocol name, e.g. `Morpho`, `Uniswap`, `Aave`, `Pendle`, `Curve`. Empty = every protocol.

## `actions` (type: `array`):

What you have to do to earn: `POOL` (provide liquidity), `LEND`, `BORROW`, `HOLD`, `STAKE`, `DROP`.

## `minRewardAprPct` (type: `integer`):

Only opportunities whose published reward APR is at least this. Opportunities where Merkl publishes no APR are excluded by this filter.

## `minTvlUsd` (type: `integer`):

Skip dust pools. A tiny pool can advertise a huge APR that collapses the moment real money arrives.

## `minDailyRewardsUsd` (type: `integer`):

Only campaigns actually paying out at least this much per day — proof the reward budget is flowing.

## `minDaysLeft` (type: `integer`):

Skip campaigns that end before your capital would even settle.

## `searchText` (type: `string`):

Case-insensitive match on name, protocol, chain, action, token symbols and tags (e.g. `USDC`, `stablecoin`, `ETH`).

## `sortBy` (type: `string`):

Result order.

## `maxItems` (type: `integer`):

Hard cap on returned (and billed) results.

## Actor input object example

```json
{
  "statuses": [
    "LIVE"
  ],
  "sortBy": "opportunity",
  "maxItems": 1000
}
```

# Actor output Schema

## `opportunities` (type: `string`):

Every opportunity returned by the run, one row each. Sorted by the sortBy input (default: opportunity score).

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "statuses": [
        "LIVE"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("tactful_anvil/merkl-defi-rewards-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "statuses": ["LIVE"] }

# Run the Actor and wait for it to finish
run = client.actor("tactful_anvil/merkl-defi-rewards-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "statuses": [
    "LIVE"
  ]
}' |
apify call tactful_anvil/merkl-defi-rewards-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tactful_anvil/merkl-defi-rewards-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/d2KMgLDRvnuMYSsrb/builds/V1ZxlINY4cIuz4k6a/openapi.json
