# Positions Analytics (/analytics/positions) ### Get User Position History Fetch historical position data for a user across opportunities (pools, lending markets, etc.): ```bash GET /v4/analytics/positions/{userAddress}/{chainId?} ``` **Path Parameters:** * **`userAddress`** (required): The user's wallet address * **`chainId`** (optional): Chain ID to filter by **Query Parameters:** * **`includeHistory`** (optional, boolean): Include history snapshots. Defaults to `false` * **`includeMerklAPIData`** (optional, boolean): Include Merkl API data (opportunity ID, APR records). Defaults to `false` * **`maxTimestamp`** (optional, numeric): Maximum timestamp to fetch data until * **`protocol`** (optional, string): Filter by protocol name (e.g., `uniswap`). Case-insensitive partial match * **`identifier`** (optional, string): Filter to a single opportunity identifier (pool address, market id, etc.). Case-insensitive exact match. Scoped in the database, so `first`/`after` paginate over the matched opportunity only * **`indexingIds`** (optional, string\[]): Keep only these indexingIds (comma-separated or repeated). Also scoped in the database. Combined with `identifier` as an intersection * **`first`** (optional, numeric, 1–1000): Number of historical snapshots to return. Defaults to `1000` * **`after`** (optional, string): Cursor for pagination. Use the `endCursor` from the previous response **Examples:** All positions for a user on Ethereum: ```bash GET /v4/analytics/positions/0x1234.../1?includeHistory=true ``` Paginate through results: ```bash GET /v4/analytics/positions/0x1234.../1?first=50&after= ``` A single opportunity, paginated (the filter is applied in the database, so every page is full): ```bash GET /v4/analytics/positions/0x1234.../1?identifier=0xPoolAddress&includeHistory=true&first=50 ``` ### Get Positions by Identifier Fetch the latest position for every user in a given pool or token opportunity: ```bash GET /v4/analytics/positions/by-identifier/{chainId}/{identifier} ``` **Path Parameters:** * **`chainId`** (required): Chain ID to query * **`identifier`** (required): Pool address, token address, market ID, or any opportunity identifier **Example:** ```bash GET /v4/analytics/positions/by-identifier/1/0xPoolAddress ``` ### Enrichment and global states With `includeDisplay=true`, positions are decorated with global-state series (e.g. an AAVE `multiplier`, a CLAMM `pool`). * At the **positionGroup** level, `globalStates` holds series **descriptors** only: `{ type, indexingId }` per series key. * The value **applied to a snapshot** lives on that snapshot: each `latest` (and each `history` entry) carries `globalStates[key] = { blockTimestamp, value }`, the series row at-or-before the snapshot's own timestamp (carry-forward). So read the series descriptor from the group and the applied value from each snapshot under the same key: ```jsonc "globalStates": { "multiplier": { "type": "rebasing_multiplier", "indexingId": "…:multiplier" } }, "positions": { "0x…": { "latest": { "blockTimestamp": 1782180671, "state": { "balance": "…", "rebasedBalance": "…" }, "globalStates": { "multiplier": { "blockTimestamp": 1782178019, "value": { "multiplier": "1069…" } } } } } } ``` # Transactions Analytics (/analytics/transactions) ### Get Transactions Fetch state data associated with specific transaction hashes: ```bash GET /v4/analytics/transactions/{chainId} ``` **Path Parameters:** * **`chainId`** (required): Chain ID to query transactions from **Query Parameters:** * **`txHashes`** (required): Comma-separated transaction hashes (e.g., `0x123abc...,0x456def...`) * **`positionKeyFilter`** (optional): Substring filter for the position key to narrow results **Example:** ```bash GET /v4/analytics/transactions/1?txHashes=0x123abc...,0x456def... ``` ### Get Transaction Hashes by User Fetch all transaction hashes for a user, grouped by opportunity (indexing ID): ```bash GET /v4/analytics/transactions/by-user/{userAddress}/{chainId?} ``` **Path Parameters:** * **`userAddress`** (required): User wallet address * **`chainId`** (optional): Chain ID to filter by **Query Parameters:** * **`first`** (optional, numeric, 1–10000): Number of rows per page. Defaults to `10000` * **`after`** (optional, string): Cursor for pagination (from previous `pageInfo.endCursor`) **Example:** ```bash GET /v4/analytics/transactions/by-user/0x1234.../1?first=100 ``` ## Response Format ### Positions Position endpoints return data grouped by chain ID, then by identifier: ```json { "data": { "1": { "0xPoolAddress": { "positionId-1": { "user": "0x...", "chainId": 1, "identifier": "0xPoolAddress", "positionId": "positionId-1", "timestamp": 1234567890, "state": { ... }, "tokens": [ ... ], "history": [ ... ] } } } }, "pagination": { "positionCount": 10, "totalHistorySnapshots": 150, "pageInfo": { "endCursor": "cursor-string", "hasMoreHistory": true } } } ``` ### Transactions ```json { "data": [ { "stateType": "position", "blockNumber": 123456, "blockTimestamp": 1234567890, "chainId": 1, "storingId": "...", "protocol": "uniswap-v3", "action": "swap", "identifier": "0xPoolAddress", "state": { ... }, "txHashes": ["0x..."] } ], "totalCount": 5 } ``` # Quick start (/analytics/usage) # States Analytics API The Merkl States Analytics API provides detailed position and transaction data for users across different DeFi protocols. This service is particularly useful for building analytics dashboards, tracking position history, and computing PnL (Profit and Loss) for user positions. ## Base URL and Documentation **Base URL:** `https://analytics.merkl.xyz` All analytics endpoints live under the `/v4/analytics` path prefix (e.g. `https://analytics.merkl.xyz/v4/analytics/positions/...`). **Full API Documentation:** [Analytics Reference](/analytics-reference) ## Authentication Every analytics endpoint requires a valid API key. This API uses the same API keys as the [Merkl API](/integrate-merkl/auth), and you can generate one from the [API Keys](/api-keys) page in the UI. Include your API key in the `X-API-Key` header for all requests: ```bash curl 'https://analytics.merkl.xyz/v4/analytics/positions/{userAddress}/{chainId}' \ --header 'X-API-Key: your-api-key-here' ``` ## Use Cases * **Position Tracking**: Monitor user positions across multiple protocols and chains * **PnL Analytics**: Calculate profit and loss using historical position data with `includeHistory=true` and `historyStep=daily` * **Portfolio Visualization**: Display user holdings with enriched token amounts using `includeDisplay=true` * **Protocol Analytics**: Aggregate data by protocol using the `protocol` filter * **Transaction Analysis**: Look up position state changes by transaction hash # Automating campaigns (/crafting-campaigns/create-from-smart-contract) # Automating campaigns The previous pages create a single campaign by hand, building a [configuration](/crafting-campaigns/inputs), [encoding it](/crafting-campaigns/payloads), and [signing the transactions](/crafting-campaigns/transactions). If instead you run **recurring campaigns** (weekly liquidity incentives, biweekly emissions, monthly rewards), or want an onchain gauge to launch them, you don't want a human (or a multisig) repeating those steps every cycle. The clean pattern is to deploy a **middleman smart contract** that bakes the configuration in once and exposes a single function to launch the next campaign with an amount. From then on, your weekly operation is reduced to two steps: 1. Transfer the reward tokens to the middleman. 2. Have a bot call the middleman with the amount. The middleman handles the approval, calls Merkl's `DistributionCreator`, and creates the campaign with the pre-set rules. No multisig signing, no parameter drift between cycles, no human in the loop, and no risk that the wrong campaign config gets pushed. ## Recommended template We recommend using the **StandardMiddleman** contract: 📄 [`StandardMiddleman.sol`](https://github.com/AngleProtocol/merkl-contracts/blob/main/contracts/partners/middleman/StandardMiddleman.sol) It's audited (alongside the rest of the Merkl contracts) and supports the full set of Merkl campaign features. ### How it works The contract holds three pieces of state: * **`defaultParams`**: the full `CampaignParameters` struct used for every campaign created through it (campaign type, target pool, duration, hooks, blacklist/whitelist, etc.). * **`executors`**: an allowlist of addresses authorized to trigger campaign creation (e.g. your bot's hot wallet, or your gauge contract). * **`startTimestampOffset`**: how far in the past the campaign should start, useful for retroactive distributions. The runtime entrypoint is a single function: ```solidity function notifyReward(uint256 amount) external; ``` When called by an authorized executor, it: 1. Verifies the caller is in `executors`. 2. Approves the `DistributionCreator` for `amount` of the reward token (idempotent, handled internally). 3. Calls `DistributionCreator.createCampaign(params)` with `defaultParams`, the supplied `amount`, and `block.timestamp - startTimestampOffset` as the start time. That's it. The campaign launches with the exact parameters baked in at deployment. ### One-time setup Performed by the contract owner: 1. **Deploy** `StandardMiddleman` with the Merkl `DistributionCreator` address. 2. **Configure** the campaign template via `setDefaultParameters(...)`. This is where you bake in the pool, the duration, the customization hooks, and any other Merkl options for your recurring campaigns. 3. **Whitelist** the bot wallet (or the calling contract) via `setExecutor(...)`. After this, the contract is ready. The owner can update parameters or rotate executors at any time, and `recoverToken(...)` is available as an emergency escape hatch. ### Recurring operation Every cycle (e.g. each Monday): 1. Transfer the cycle's reward budget to the middleman. 2. Bot calls `notifyReward(amount)`. A new Merkl campaign is created with the hardcoded rules and the amount you just funded. Nothing else changes between cycles unless the owner explicitly updates the parameters. ## Building the parameters The `defaultParams` you bake into the middleman is the same `CampaignParameters` you'd build for any campaign: exactly what [Understanding campaign configs](/crafting-campaigns/inputs), [Creating the payloads](/crafting-campaigns/payloads), and [Transactions to sign](/crafting-campaigns/transactions) walk through. The easiest way to get the right values is to: 1. Build the configuration for your recurring campaign, in [Merkl Studio](https://studio.merkl.xyz) (see the [campaign creation guide](https://docs.merkl.xyz/distribute-with-merkl/create-a-campaign)) or programmatically (see [Creating the payloads](/crafting-campaigns/payloads)). 2. Copy the struct fields Studio produces for its multisig flow. They map directly to what you pass to `setDefaultParameters(...)`. The [`CampaignParameters` reference](/crafting-campaigns/transactions#3-create-the-campaign) lists each field. ## Use cases ### Recurring institutional emissions A foundation, treasury, or protocol DAO that pays out weekly liquidity incentives can deploy one middleman per pool (or one shared middleman with a more advanced parameter mapping) and cut the operational burden to "send tokens, run bot." Every campaign is verifiably identical to the previous one: auditable, reproducible, and free of human discretion. ### Onchain gauge systems Many protocols rely on gauge systems where users vote onchain to determine reward allocations. The middleman pattern slots in naturally: the gauge calls the middleman's `notifyReward(amount)` function (the standard interface gauge systems already use), and the middleman creates the corresponding Merkl campaign. This combines Merkl's flexibility (campaign types, hooks, customization options, reporting) with the trustlessness of a fully onchain emission flow. Several teams have shipped variations of this pattern: * [Quickswap implementation](https://polygonscan.com/address/0x3a381497813208508689d78c90EC9fb115D5640d#code) * [CrossCurve implementation](https://arbiscan.io/address/0xb665d0B69e91F596B9Dee3016e49136335993Fb8#readProxyContract) If your gauge system calls a different function signature than `notifyReward(uint256)`, you'll want a thin wrapper or a forked middleman with the matching interface. The rest of the logic stays the same. ## Need help? If your setup needs custom parameter logic (per-pool allocation, dynamic durations, multiple reward tokens), reach out to the Merkl team. We can help you adapt the template or design a fit-for-purpose middleman. # Understanding campaign configs (/crafting-campaigns/inputs) # Understanding campaign configs Every Merkl campaign is created from a **configuration**: a single JSON object that fully describes it. Before you can craft a campaign, you need to know what goes into that object and how to fill it in. This page walks through its structure and the schemas that tell you exactly what each part expects. For a higher-level overview of what a configuration is, see the [campaign configuration guide](https://docs.merkl.xyz/merkl-mechanisms/campaignConfiguration). ## Configuration structure A configuration combines a set of **standard fields**, the same on every campaign, with a few **structured inputs** whose shape depends on the choices you make. ### Standard fields These fields are present on most campaigns and take simple values: * `creator`: The address managing the campaign * `rewardToken`: The address of the token distributed as rewards * `distributionChainId`: The chain ID where rewards are distributed * `computeChainId`: The chain ID where user activity is tracked (can differ from `distributionChainId` for cross-chain campaigns) * `startTimestamp`: The start date of the campaign (Unix timestamp) * `endTimestamp`: The end date of the campaign (Unix timestamp) * `amount`: The total amount of rewards to be distributed * `blacklist`: A list of addresses excluded from receiving rewards * `whitelist`: A list of addresses allowed to receive rewards (if set, all others are excluded) You can use [this converter](https://www.unixtimestamp.com/) to convert dates to Unix timestamps. `computeChainId` is the chain whose state Merkl reads to compute rewards. `distributionChainId` is the chain on which rewards are distributed (and therefore where the `createCampaign` transaction is sent). They are often the same, but can differ for cross-chain incentive programs. ### Structured inputs The remaining fields are objects rather than simple values, and their accepted shape changes with the type you pick: * `campaignType`: the [campaign type](https://docs.merkl.xyz/merkl-mechanisms/campaign-types/concentrated-liquidity-mechanisms), together with the `campaignTypeParameters` specific to it * `computeScoreParameters`: the [scoring method](https://docs.merkl.xyz/merkl-mechanisms/scoring) that turns user activity into reward shares * `distributionMethodParameters`: the [distribution method](https://docs.merkl.xyz/merkl-mechanisms/distributions) that controls how the budget is spent over time A concentrated-liquidity campaign expects different parameters than a token-holding one, and a fixed-APR distribution expects different settings than a Dutch auction. To know exactly what each one accepts, you read its **schema**. ## Working with schemas For every campaign type, distribution method, scoring method, hook, and processor, Merkl publishes a **JSON Schema**: a precise, machine-readable description of one input: which fields are required, what type each field is, and which values are accepted. The schemas are generated from the engine itself, so they always reflect exactly what is currently supported. Reach for them whenever you want to: * Discover the campaign types your integration can use * Generate a UI that builds configurations dynamically * Validate a configuration locally before submitting it to the API The easiest way to explore them is the [schema explorer](/resources/schemas), which lets you browse and search every type visually. Programmatically, they live under the [`schemas` tag of the API reference](/api-reference#tag/schemas). The endpoints you'll reach for most when crafting a campaign are: * **`GET /v4/schemas/campaignType`**: every supported `campaignType` (e.g. `60` for `ERC20`, others for `CLAMM`, point programs, etc.), each with a short description and the chain it can run on. * **`GET /v4/schemas/campaignType/{campaignType}`**: the schema of the inputs expected for a given `campaignType`. Fields such as `targetToken` and campaign-type-specific parameters are documented here. * **`GET /v4/schemas/distributionMethod`**: the supported `distributionMethod` values (e.g. `MAX_APR`, `FIX_REWARDS`, etc.) and the settings each one accepts. * **`GET /v4/schemas/computeScoreMethod`**: the available scoring methods and the parameters they take. * **`GET /v4/schemas/processorType`**: the processors (boost, lock, vault wrappers, etc.) that can be plugged into a campaign. A typical flow is: 1. Pick a `campaignType` from `/v4/schemas/campaignType`. 2. Read its schema to learn the shape of its `campaignTypeParameters` (or, for simple campaigns, top-level fields like `targetToken`). 3. Pick a `distributionMethod` from `/v4/schemas/distributionMethod` and read the settings it expects. 4. Assemble everything into a configuration. The [complete example](#a-complete-example) below shows the result. The schemas are the source of truth: they evolve with the engine and always reflect what is currently supported. If a field is missing from the schema it is not (yet) supported; if a new field appears, it is safe to start using it. ## Schema breakdown Some fields appear across many campaign types and deserve a closer look. The sections below walk through the ones that most often need explaining. ### Distribution methods The `distributionMethodParameters` field defines how rewards are distributed over time. **Variable Reward Rate (Dutch Auction):** ```json { "distributionMethod": "DUTCH_AUCTION" } ``` **Fixed APR:** ```json { "distributionMethod": "FIX_APR", "distributionSettings": { "apr": "0.08", "targetToken": "0x...", "rewardTokenPricing": true, "targetTokenPricing": true } } ``` **Capped APR:** ```json { "distributionMethod": "MAX_APR", "distributionSettings": { "apr": "1", "targetToken": "0x...", "rewardTokenPricing": true, "targetTokenPricing": true } } ``` #### Understanding distribution settings parameters **rewardTokenPricing**: * `false`: The campaign distributes a fixed amount of reward tokens per unit (or dollar) of liquidity provided * `true`: The campaign distributes a fixed dollar value of rewards per unit (or dollar) of liquidity provided **targetTokenPricing**: * `false`: Rewards are calculated per unit of liquidity provided * `true`: Rewards are calculated per dollar of liquidity provided We recommend using `targetTokenPricing = true` for most campaigns to ensure consistent reward distribution regardless of liquidity value fluctuations. When both `rewardTokenPricing = true` and `targetTokenPricing = true`, the campaign pays a fixed APR. For most campaign types (such as token holding campaigns), the `targetToken` address is automatically derived from the campaign configuration and doesn't need to be specified manually within the `distributionSettings`. #### Understanding the APR value format The `apr` value in `distributionSettings` represents different metrics depending on your pricing configuration: **Fixed APR** (`targetTokenPricing = true` and `rewardTokenPricing = true`): * The value represents the target APR as a decimal * Example: `"apr": "0.01"` = 1% APR, `"apr": "0.08"` = 8% APR **Token-per-Dollar Rate** (`targetTokenPricing = true` and `rewardTokenPricing = false`): * The value represents the number of reward tokens earned per dollar of liquidity per year * Example: `"apr": "1"` = 1 reward token per year per $1 of liquidity provided * For $1,000 in liquidity: \~2.74 tokens per day (1,000 ÷ 365) * To reward 1 token per day per $1,000 provided, use: `"apr": "0.365"` * Calculation: 1 token/day × 365 days = 365 tokens/year ÷ 1,000 dollars = 0.365 tokens per year per dollar ## A complete example Putting the standard fields and structured inputs together, here's a full configuration for an ERC20 token-holding campaign that pays a capped 8% APR: ```json { "campaignType": 60, "targetToken": "0xBdb9300b7CDE636d9cD4AFF00f6F009fFBBc8EE6", "computeChainId": 8453, "distributionChainId": 8453, "creator": "0xA9DdD91249DFdd450E81E1c56Ab60E1A62651701", "rewardToken": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "startTimestamp": 1778259600, "endTimestamp": 1778864400, "amount": "1000000000", "distributionMethodParameters": { "distributionMethod": "MAX_APR", "distributionSettings": { "apr": "0.08", "rewardTokenPricing": true, "targetTokenPricing": true } } } ``` This example is specific to an ERC20 campaign type (`campaignType: 60`). Other campaign types expect different `campaignTypeParameters`, so always check the [schema](/resources/schemas) for the type you're targeting to know which fields are required. With your configuration ready, the next step is to encode it into the payloads Merkl needs to preview and create your campaign. See [Creating the payloads](/crafting-campaigns/payloads). # Creating the payloads (/crafting-campaigns/payloads) # Creating the payloads Once you've assembled a configuration (see [Understanding campaign configs](/crafting-campaigns/inputs)), the Merkl API turns it into the two payloads you actually use: 1. A **minimal opportunity payload** to preview the metadata Merkl will display for the opportunity. 2. A **full campaign payload**: the encoded `createCampaign` call you broadcast onchain. ## 1. Opportunity metadata payload You don't need a full configuration to preview an opportunity, because a minimal subset of fields is enough (the example below is for an ERC20 campaign type; other types expect different fields). The metadata is what Merkl shows for the opportunity (title, target asset, APR, etc.) before the campaign is even live. ```json { "campaignType": 60, "targetToken": "0xBdb9300b7CDE636d9cD4AFF00f6F009fFBBc8EE6", "computeChainId": 8453, "distributionChainId": 8453, "distributionMethodParameters": { "distributionMethod": "MAX_APR", "distributionSettings": { "apr": "0.08", "rewardTokenPricing": true, "targetTokenPricing": true } } } ``` Submit it to: ```bash POST https://api.merkl.xyz/v4/config/opportunity ``` The response contains the opportunity metadata Merkl will compute for this configuration. Use this endpoint to preview the opportunity, surface its details in your own UI, or validate the inputs before encoding the campaign. Full reference: [`POST /v4/config/opportunity`](/api-reference#tag/config/POST/v4/config/opportunity). ## 2. Full campaign payload The body of this endpoint is simply your **complete configuration**, the one assembled in [Understanding campaign configs](/crafting-campaigns/inputs), wrapped in an **array**, so you can encode several campaigns in a single batch: ```json [ { /* a complete campaign configuration */ }, { /* …another configuration to batch (optional) */ } ] ``` Submit it to: ```bash POST https://api.merkl.xyz/v4/config/encode/batch ``` The response contains the encoded calldata you can hand to a wallet for signing. Full reference: [`POST /v4/config/encode/batch`](/api-reference#tag/config/POST/v4/config/encode/batch). Once you have the encoded payload, head over to [Transactions to sign](/crafting-campaigns/transactions) to learn what to broadcast onchain. ## Encoding and Decoding Configurations The Merkl API also provides a variety of endpoints to convert between campaign configurations and the encoded format used onchain. **Encoding configurations for onchain campaign creation:** These endpoints convert campaign configurations into transaction data for calling the Merkl Distribution Creator contract: * [Encode single campaign from config](https://api.merkl.xyz/docs#tag/config/POST/v4/config/encode) - Generate transaction data for one campaign * [Encode multiple campaigns from their respective configs](https://api.merkl.xyz/docs#tag/config/POST/v4/config/encode/batch) - Generate transaction data for multiple campaigns If you're deploying campaigns from a Gnosis Safe, use these endpoints instead to get Safe-compatible transaction payloads: * [Get Safe payload from a single campaign config](https://api.merkl.xyz/docs#tag/config/POST/v4/config/encode/safe) - Generate Safe transaction payload for one campaign * [Get Safe payload from multiple campaigns config](https://api.merkl.xyz/docs#tag/config/POST/v4/config/encode/batch/safe) - Generate Safe transaction payload for multiple campaigns **Decoding onchain data into configurations:** These endpoints convert various types of onchain and encoded data back into readable configuration objects: * [Decode from onchain campaign ID](https://api.merkl.xyz/docs#tag/config/GET/v4/config/decode/onchain/\{distributionChainId}/\{campaignId}) - Retrieve configuration for an existing campaign * [Get config from database ID](https://api.merkl.xyz/docs#tag/config/GET/v4/config/\{id}) - Retrieve the configuration of an existing campaign from its database ID * [Decode from raw onchain campaign data](https://api.merkl.xyz/docs#tag/config/POST/v4/config/decode/\{distributionChainId}) - Parse encoded campaign data * [Decode from Gnosis Safe payload](https://api.merkl.xyz/docs#tag/config/POST/v4/config/decode/safe) - Extract configuration from Safe transaction payload * [Decode from transaction data](https://api.merkl.xyz/docs#tag/config/GET/v4/config/decode/\{distributionChainId}/\{payload}) - Parse campaign creation transaction data **Previewing campaigns before deployment:** These endpoints allow you to simulate how your campaign will appear and estimate its metrics before deploying it onchain: * [Preview opportunity details](https://api.merkl.xyz/docs#tag/config/post/v4configopportunity) - See how your campaign will be categorized and displayed (opportunity name, grouping, etc.) * [Estimate TVL](https://api.merkl.xyz/docs#tag/config/post/v4configtvl) - Calculate the expected Total Value Locked for your campaign configuration # Transactions to sign (/crafting-campaigns/transactions) # Transactions to sign With your encoded payload ready (see [Creating the payloads](/crafting-campaigns/payloads)), the last step is to send it onchain, which takes up to **three transactions**, in this order: ## 1. Sign Merkl T\&Cs Required **only the first time** a given address creates a campaign. Call `acceptConditions()` on the Merkl distributor. The onchain acceptance is stored against your address and reused for all subsequent campaigns from the same creator, so you don't need to repeat this step on future campaigns. ## 2. Token approval The Merkl distributor contract pulls the reward tokens from your wallet when the campaign is created. You therefore need to grant it an ERC-20 `approve` allowance covering at least the `amount` of reward token specified in your configuration. If you batch several campaigns funded from the same wallet, make sure the approval covers the **sum** of their amounts (or simply use `type(uint256).max` to approve once). ## 3. Create the campaign This is the actual onchain call that registers the campaign with the Merkl distributor. Use the calldata returned by [`POST /v4/config/encode/batch`](/crafting-campaigns/payloads#2-full-campaign-payload) as the transaction `data`, and target the Merkl distributor on the `distributionChainId` from your configuration. The distributor exposes two entrypoints: * `createCampaign(CampaignParameters newCampaign) → bytes32` for a single campaign. * `createCampaigns(CampaignParameters[] campaigns) → bytes32[]` to batch several campaigns in one transaction. Both take the same `CampaignParameters` struct as input: | Field | Type | Description | | ---------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `campaignId` | `bytes32` | Deterministic identifier for the campaign. Leave as `0x0000000000000000000000000000000000000000000000000000000000000000`. The contract assigns it and emits it in the `NewCampaign` event. | | `creator` | `address` | Address credited as the campaign's creator. If set to `0x0000000000000000000000000000000000000000`, the distributor defaults it to the address sending the transaction. | | `rewardToken` | `address` | ERC-20 token used to pay rewards. Must be whitelisted by Merkl. | | `amount` | `uint256` | Total reward amount, in `rewardToken` base units. Must be ≥ the per-epoch minimum for that token. | | `campaignType` | `uint32` | Identifier of the campaign type (e.g. `60` for `ERC20LogProcessor`). See [Understanding campaign configs](/crafting-campaigns/inputs). | | `startTimestamp` | `uint32` | Unix timestamp at which the campaign starts. | | `duration` | `uint32` | Duration in seconds. `endTimestamp = startTimestamp + duration`. | | `campaignData` | `bytes` | The encoding of the campaign-type-specific parameters, produced for you by [`POST /v4/config/encode/batch`](/crafting-campaigns/payloads#2-full-campaign-payload). | The encoder API fills these fields for you from the JSON config you submit, so in practice you just forward the returned calldata to the distributor. Once this transaction is mined, the campaign is live: Merkl's engine will start indexing the target asset and accruing rewards according to the `distributionMethod` you configured. ## Contract ABI The Merkl distributor exposes the `createCampaign` / `createCampaigns` entrypoints, along with the `acceptConditions` call you'll need on your first campaign. Use the full ABI below to interact with it. # Verifying payloads (/crafting-campaigns/verify-payloads) # Verifying payloads Before signing a campaign creation payload, especially one prepared by someone else, you can decode it back into a readable [configuration](/crafting-campaigns/inputs) and confirm exactly what you're approving. The Merkl API exposes decode endpoints for this, whether you're checking a whole Safe batch or a single `createCampaign` transaction. ## Verify a whole Safe payload To check every campaign contained in a Safe transaction batch at once, pass the payload to the [`POST /v4/config/decode/safe`](/api-reference#tag/config/POST/v4/config/decode/safe) endpoint. It returns the decoded configuration of each campaign in the batch. This is the quickest way for a preparer to sanity-check a payload before sharing it, or for a signer to review the whole batch in one call. ## Verify a single `createCampaign` transaction If you're a signer reviewing a payload someone else prepared, you can verify the campaign configuration directly from the transaction data before signing. The `createCampaign` transaction (the last transaction in the batch) contains an array of encoded parameters: ```json "[\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"0x0000000000000000000000000000000000000000\",\"0xD53f905D140dA38FB6505756A5D1eD14599BcdE5\",\"6185567010309278351\",4,1771521860,3600,\"0xcd7b313f6fb732723e6a48935f34d18fd944050bccc526494e54de4b94dc1474\"]" ``` To decode and verify this data: 1. Extract the values from the array. In order, they are the fields of the `CampaignParameters` struct: `campaignId`, `creator`, `rewardToken`, `amount`, `campaignType`, `startTimestamp`, `duration`, and `campaignData`. See [Transactions to sign](/crafting-campaigns/transactions#3-create-the-campaign) for what each field means. 2. Send them to the [`POST /v4/config/decode/{distributionChainId}`](/api-reference#tag/config/POST/v4/config/decode/\{distributionChainId}) endpoint in the request body: ```json { "campaignId": "0x0000000000000000000000000000000000000000000000000000000000000000", "creator": "0x0000000000000000000000000000000000000000", "rewardToken": "0xD53f905D140dA38FB6505756A5D1eD14599BcdE5", "amount": "6185567010309278351", "campaignType": 4, "startTimestamp": 1771521860, "duration": 3600, "campaignData": "0xcd7b313f6fb732723e6a48935f34d18fd944050bccc526494e54de4b94dc1474" } ``` The API returns the full campaign configuration, so you can verify every parameter: the campaign type, the distribution method, and any customization options. Use the [schema explorer](/resources/schemas) to interpret any enum values (such as the `campaignType`) you get back. ## Preview before signing Once you have the decoded configuration, you can also: * [Preview how the campaign will appear](/api-reference#tag/config/POST/v4/config/opportunity) in the Merkl app. * [Estimate the expected TVL](/api-reference#tag/config/POST/v4/config/tvl) for the campaign. Together, these checks ensure you understand exactly what you're signing before approving the transaction. # Auth & rate limits (/integrate-merkl/auth) # Auth & rate limits The Merkl API has a default rate limit of **10 requests per second**. This limit is sufficient for most integration use cases. If your application requires a higher rate limit, please contact the Merkl team to request a custom API key. Once provided, you can include your API key in the `X-API-Key` header for all requests to `api.merkl.xyz`: ```bash X-API-Key: your-api-key-here ``` Example request with API key: ```bash curl -H "X-API-Key: your-api-key-here" https://api.merkl.xyz/v4/opportunities ``` # Finding campaigns (/integrate-merkl/campaigns) # Finding relevant campaigns While the opportunities endpoint provides high-level metrics about what's being incentivized, the campaigns endpoint offers detailed campaign rules (duration, budget, campaign type, customization options, etc.). Use this endpoint to display in-depth campaign information to your users. For a complete list of available filters, see the [campaigns endpoint documentation](/api-reference#tag/campaigns). **Recommended filters:** **By Creator Address** - Find all campaigns created by a specific address: ```bash https://api.merkl.xyz/v4/campaigns?creatorAddress={address} ``` Example: [`https://api.merkl.xyz/v4/campaigns?creatorAddress=0xdef1FA4CEfe67365ba046a7C630D6B885298E210`](https://api.merkl.xyz/v4/campaigns?creatorAddress=0xdef1FA4CEfe67365ba046a7C630D6B885298E210) **By Token Symbol** - Find all campaigns distributing a specific reward token: ```bash https://api.merkl.xyz/v4/campaigns?tokenSymbol={symbol} ``` Example for campaigns distributing $PYTH: [`https://api.merkl.xyz/v4/campaigns?tokenSymbol=PYTH`](https://api.merkl.xyz/v4/campaigns?tokenSymbol=PYTH) **Excluding sub-campaigns** - Find all campaigns except sub-campaigns created by parent campaigns due to [reward forwarding](https://docs.merkl.xyz/merkl-mechanisms/reward-forwarding) ```bash https://api.merkl.xyz/v4/campaigns?excludeSubCampaigns=true ``` **By Campaign List** - Find all campaigns tagged in one of your custom [campaign lists](#tagging-campaigns-with-lists): ```bash https://api.merkl.xyz/v4/campaigns?campaignLists={name}&listOwner={owner} ``` **What are sub-campaigns?** Sub-campaigns are automatically created through [reward forwarding](https://docs.merkl.xyz/merkl-mechanisms/reward-forwarding) when Merkl's engine detects that rewards should be distributed to end users rather than intermediate contracts. **Example:** When incentivizing a Curve pool, if the engine detects that one of the LP token holders is actually a Curve gauge contract, it will automatically create a sub-campaign to properly distribute rewards to the users who staked in that gauge, rather than to the gauge contract itself. **How to identify sub-campaigns:** Check the `campaignId` field in the API response: * **Root campaign**: `campaignId` starts with `0x` (e.g., `0xabc123...`) * **Sub-campaign**: `campaignId` is a numeric value (e.g., `123456789`) **Best practice:** When querying campaign rewards, always use the root campaign ID. The root campaign's rewards data automatically includes all rewards from its sub-campaigns, giving you a complete picture of the total distribution. ## Retrieving both campaign and opportunity data The `campaigns` endpoint provides campaign-specific information but not upstream opportunity data. Similarly, querying for an opportunity doesn't include downstream campaign details. To retrieve all information in a single request: **Search campaigns with related opportunities** - Use `/v4/campaigns` with `withOpportunity=true`: ```bash https://api.merkl.xyz/v4/campaigns?withOpportunity=true ``` **Search opportunities with related campaigns** - Use `/v4/opportunities` with `campaigns=true`: ```bash https://api.merkl.xyz/v4/opportunities?campaigns=true ``` Learn more about [how TVLs, APRs, and daily rewards are computed](https://docs.merkl.xyz/merkl-mechanisms/technical-overview#main-metrics) and why they may differ between the campaign and opportunity levels. ## About campaign IDs Merkl uses two distinct identifier types for campaigns: * **`campaignId`** (format: `0x...`): The onchain identifier of a campaign. Note that this is **not unique** across chains: multiple campaigns on different chains may share the same `campaignId`. You can find this in the [opportunities page](https://app.merkl.xyz/) by selecting an opportunity, viewing campaign details, and navigating to the "Advanced" tab. * **`id`** (format: numeric, e.g., `13972358188887408622`): The unique database identifier used for most API routes. You can retrieve this by querying the [campaigns endpoint](/api-reference#tag/campaigns) with a `campaignId`. You may also find a campaign's database ID under the advanced tab of a campaign on the Merkl app. API V4 ## Tagging campaigns with lists Campaign lists let you group arbitrary campaigns under a name you own (e.g. `featured`, `partners-q2`, `mainnet-launch`), then filter the `/v4/campaigns` endpoint by that tag. Lists are scoped to your API key owner, so two integrators can each maintain a list called `featured` without collision. ### Prerequisites 1. Generate an API key from the [Merkl Developer Portal](/api-keys). 2. Include it as `X-API-Key` on every request to the list endpoints (see [Auth & rate limits](/integrate-merkl/auth)). The `{owner}` path parameter must be the **same address that was used to create the API key**. Requests where `{owner}` doesn't match the address tied to your `X-API-Key` will be rejected. This is what scopes a list (e.g. `featured`) to you and prevents other integrators from writing to it. ### Campaign list ID Lists store campaigns by a deterministic identifier derived from the campaign's chain and onchain `campaignId`: ```ts id = hash(`${chainId}${campaignId}`).toString() ``` You can also retrieve this value directly as the `id` field of any campaign returned by `/v4/campaigns`. ### Add a campaign to a list If the list doesn't exist yet, it's created automatically on the first add. ```bash curl -X POST https://api.merkl.xyz/v4/campaign-lists/{owner}/{name}/add \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d '{ "campaignIds": [ "5821552044504491915" ] }' ``` ### Remove a campaign from a list Same body shape as the add endpoint. ```bash curl -X POST https://api.merkl.xyz/v4/campaign-lists/{owner}/{name}/remove \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d '{ "campaignIds": [ "5821552044504491915" ] }' ``` ### List your lists ```bash https://api.merkl.xyz/v4/campaign-lists/{owner} ``` ### Get the campaigns inside a list ```bash https://api.merkl.xyz/v4/campaign-lists/{owner}/{name} ``` ### Filter campaigns by list Once a list is populated, pass its `name` and `owner` to `/v4/campaigns` to retrieve only the tagged campaigns. Both parameters are required: `campaignLists` selects the list name, `listOwner` scopes it to the address that owns the list. ```bash https://api.merkl.xyz/v4/campaigns?campaignLists={name}&listOwner={owner}&items=100 ``` Example: ```bash https://api.merkl.xyz/v4/campaigns?campaignLists=base-incentives&listOwner=0xEb853F14daDb92752151a087F29ad2690eCb35A4&items=100 ``` # Key-value stores (/integrate-merkl/key-value-stores) # Key-value stores A **key-value store** is an offchain list of `address → value` entries that a campaign reads on every reward computation. They back the dynamic eligibility lists described in [Customization options](https://docs.merkl.xyz/merkl-mechanisms/customization-options): whitelists, blacklists, boosts and referrer maps. Changes propagate within roughly two hours, with no onchain transaction. This page is the **technical reference**: the endpoints you call and the bodies you send to create a store and manage its entries. For the product logic (what whitelists, blacklists and referral programs *do*, how precedence works, and how a store is wired to a campaign), see [Customization options](https://docs.merkl.xyz/merkl-mechanisms/customization-options). Historically, Merkl provisioned every store for you. Allowlisted integrators can now **create and manage their own stores programmatically** with their API key, using the self-serve flow documented below. ## Prerequisites 1. **An API key.** All self-serve requests authenticate with the `X-API-Key` header. See [Auth & rate limits](/integrate-merkl/auth). The store you create is owned by your key's address; only that key can write to it. 2. **A creator quota.** Self-serve creation is allowlisted. The Merkl team grants your address a quota that bounds: * `maxConfigs`: how many stores you may own, * `maxSizeLimit`: the largest a single store may be (number of entries), * `allowedTypes`: which store types you may create (e.g. `WHITELIST`, `BLACKLIST`, `BOOST`, `REFERRER`). To request or adjust a quota, contact the Merkl team with your address and the lists you intend to manage. Without a quota, creation returns `403`. ## Creating a store `POST /v4/key-value-stores/self` Headers: ``` X-API-Key: your-api-key Content-Type: application/json ``` Body: ```json { "key": "my-whitelist", "type": "WHITELIST", "sizeLimit": 50000, "publicRead": true } ``` | Field | Required | Notes | | ------------ | -------- | -------------------------------------------------------------------------------------------------------------------------- | | `key` | yes | A short suffix for your store (`^[a-zA-Z0-9_-]+$`). It is **namespaced to your creator's allowlisted address**. See below. | | `type` | yes | One of your quota's `allowedTypes`. Determines the JSON shape of entry values. | | `sizeLimit` | no | Max entries. Defaults to `min(100000, maxSizeLimit)`; must not exceed your quota's `maxSizeLimit`. | | `publicRead` | no | If `true` (default), anyone can read the entries; if `false`, only you can. | You do **not** set the owner or access mode: the server forces the store's owner to the allowlisted address of your API key's creator and its access mode to `API_KEY` (only your key can write entries). The response returns the store's **canonical key**, the owning address joined to your suffix, e.g. `0xAbC…DeF_my-whitelist`, along with the resolved `type`, `accessMode` (`API_KEY`), `sizeLimit`, `publicRead` and `ownerAddress`. The prefix is the address that holds your creator's quota, which is not necessarily the address you signed up with. Do not derive the canonical key yourself. Always read it from the create response or from `GET /v4/key-value-stores/self`. Use the returned **canonical key** for every later call: adding entries, updating, deleting. The `key` you submitted is only the suffix. Namespacing guarantees your keys never collide with another integrator's. ## Managing your stores | Action | Endpoint | | -------------------------------- | ---------------------------------------- | | List your stores | `GET /v4/key-value-stores/self` | | Update a store | `PATCH /v4/key-value-stores/self/{key}` | | Delete a store (and its entries) | `DELETE /v4/key-value-stores/self/{key}` | `{key}` is the canonical key from the create response. `PATCH` accepts `description`, `publicRead` and `sizeLimit` (never below the store's current entry count, and never above the `maxSizeLimit` of the quota held by that store's own `ownerAddress`). You can only manage stores your creator owns. Every address linked to your creator sees and manages the same stores, whichever of them owns a given store. ## Adding and removing entries Once a store exists, manage its membership with the standard entry endpoints, authenticated with the same API key: `PUT /v4/key-value-stores/{key}/entries/batch`, where `{key}` is your canonical key; up to 1000 entries per request, upsert. Headers: ``` X-API-Key: your-api-key Content-Type: application/json ``` Body: ```json { "entries": [ { "address": "0xA9DdD91249DFdd450E81E1c56Ab60E1A62651701", "value": "{\"isWhitelisted\":true}" } ] } ``` Use `DELETE /v4/key-value-stores/{key}/entries/{address}` to remove a single entry. The `value` is a JSON **string**, and its shape must match the store `type` exactly. It is validated at write time, so a malformed body fails immediately with a `400`: | Type | `value` | Meaning | | ----------- | ----------------------------- | ---------------------------------------------------------------------- | | `WHITELIST` | `{"isWhitelisted": true}` | `address` is eligible | | `BLACKLIST` | `{"isBlacklisted": true}` | `address` is excluded | | `BOOST` | `{"boostBigInt": ""}` | boost for `address`, base 9 (`1×` = `"1000000000"`), strictly positive | | `REFERRER` | `{"referrer": "0x..."}` | `address` is the invitee, `value.referrer` their referrer | | `FORWARDER` | `{"forwardTo": "0x..."}` | forward `address`'s rewards to `forwardTo` | Addresses are stored lowercased, so request casing doesn't matter. Writes are rejected once the store reaches its `sizeLimit`. Full endpoint details are in the [API reference](/api-reference#tag/key-value-store). ## Reading entries `GET /v4/key-value-stores/{key}/entries` lists a store's entries, paginated with `page` and `pageSize` (defaulting to `0` and `100`). Pass `value` to filter by an exact value match. ```bash curl "https://api.merkl.xyz/v4/key-value-stores/{key}/entries?page=0&pageSize=100" ``` Response: ```json { "entries": [ { "address": "0xA9DdD91249DFdd450E81E1c56Ab60E1A62651701", "value": "{\"isWhitelisted\":true}", "createdAt": "2026-06-12T09:00:00.000Z", "updatedAt": "2026-06-12T09:00:00.000Z" } ], "total": 1, "page": 0, "pageSize": 100 } ``` Fetch a single entry with `GET /v4/key-value-stores/{key}/entries/{address}` (returns `404` if absent). If the store was created with `publicRead: true` (the default), anyone can list its entries without authentication, convenient for a public referrer map, but avoid it for a whitelist backing a private LP deal. Otherwise reads require the owner's `X-API-Key`, the same key used to write entries. ## Access modes: who can write Every store has an **access mode** that governs who may write its entries: | Access mode | Who can write | Batch upsert | How it's created | | ----------- | ------------------------------------------------------------- | ------------------- | --------------------------------------- | | `API_KEY` | Only the owner's API key, for **any** address | Yes (≤1000/request) | Self-serve (above) or by the Merkl team | | `PUBLIC` | Any wallet, but only **its own** entry (via wallet signature) | No | By the Merkl team only | Self-serve stores are always `API_KEY`: only your key writes, and it may write any address, which is what powers bulk whitelists, referrer maps, or a creator-managed forwarding store. `PUBLIC` stores are the exception: Merkl provisions them so that **end users** can manage their own entry directly, authenticated by a wallet signature rather than the store owner's key. The canonical example is the global forwarding store below. ## The global forwarding store `global-forwarding` is a single, Merkl-owned `PUBLIC` store of type `FORWARDER`. It lets any wallet redirect **its own** Merkl rewards to another address across every campaign at once. See the [user-facing guide](https://docs.merkl.xyz/earn-with-merkl/earning-with-merkl). Each entry is `address → {"forwardTo": "0x..."}`. Because it is `PUBLIC`, writes do **not** use the store owner's API key. Each wallet authenticates with a **JWT from the wallet-signature login flow** and may only write **its own** entry: a write targeting any other address returns `403`. Batch upsert is not available on `PUBLIC` stores; write one entry at a time. ```bash ADDR=0xYourAddress API=https://api.merkl.xyz/v4 # 1. Get the message to sign curl "$API/auth/nonce/$ADDR" # 2. Sign the returned `message` with your wallet (personal_sign), then log in # (stores a jwt cookie). Connecting your wallet on the Merkl app runs this same flow. curl -X POST "$API/auth/login" -c cookies.txt -H 'Content-Type: application/json' \ -d '{"address":"'$ADDR'","message":"","signature":"0x"}' # 3. Enable forwarding for your own address curl -X PUT "$API/key-value-stores/global-forwarding/entries" -b cookies.txt \ -H 'Content-Type: application/json' \ -d '{"address":"'$ADDR'","value":"{\"forwardTo\":\"0xDestination\"}"}' # Disable forwarding curl -X DELETE "$API/key-value-stores/global-forwarding/entries/$ADDR" -b cookies.txt ``` ### Creator-managed forwarding stores To forward rewards on behalf of others (e.g. routing a vault's or contract's rewards to the real beneficiaries), a campaign creator uses their **own** `FORWARDER` store instead of the global one: create it via [self-serve](#creating-a-store) (or have Merkl provision it), push entries in bulk with your API key, and reference its canonical key from the campaign's forwarding hook. Because it is `API_KEY` mode, you can write any address and use batch upsert. See [Customization options](https://docs.merkl.xyz/merkl-mechanisms/customization-options) for the campaign wiring and precedence rules. ## Wiring a store to a campaign Creating a store does not by itself affect any campaign. A campaign must reference the store's canonical key in its hook for the engine to apply it. See [Customization options](https://docs.merkl.xyz/merkl-mechanisms/customization-options) for how whitelists/blacklists are attached and how precedence works (a non-empty whitelist implicitly excludes every other address), and for the referral boost semantics. ## Common errors | Status | Cause | | ---------------------------------- | -------------------------------------------------------------------------------------- | | `401` | Missing or invalid `X-API-Key`. | | `403`: not allowlisted | No address of your creator has a quota. | | `403`: type not allowed | `type` is not in your quota's `allowedTypes`. | | `403`: quota reached | Your creator already owns `maxConfigs` stores, counted across all of its addresses. | | `403`: not owner | The API key doesn't belong to the store's owner (writes, or reads on a private store). | | `404`: unknown store | No store exists for that key. | | `404`: entry not found | `GET .../entries/{address}` on an address with no entry. | | `400`: malformed value | The `value` doesn't match the store `type`'s required shape. | | `400`: size limit reached | A write would push the store past its `sizeLimit`. | | `400`: sizeLimit exceeds quota | Requested `sizeLimit` is above your `maxSizeLimit`. | | `400`: key already exists | You already have a store with that suffix. | | `400`: sizeLimit below entry count | You tried to lower `sizeLimit` below the store's current number of entries. | # Display Your Native APR (/integrate-merkl/nativeAPR) # Display your native APR Merkl enables protocols to display their native APR directly on the Merkl frontend alongside Merkl incentive APRs. This integration provides users with a comprehensive view of total returns available on your protocol. ## Overview By integrating your native APR data, you can: * Display protocol-specific APRs (e.g., lending yields, staking rewards, pool fees) on Merkl's frontend * Help users understand the full return potential when combining native yields with Merkl incentives * Increase visibility and transparency for your protocol's offerings ## Implementation ### Endpoint Requirements To integrate your native APR data, you must create and host a public API endpoint that returns APR data in the following JSON format: ```json [ { "title": "Protocol Name APR", // Required: String - Display name for the APR (e.g., "Aave Supply APR", "Uniswap Fee APR") "opportunityId": "12345678901234567", // Required: String - The Merkl Opportunity ID (numeric string) "timestamp": 1716900000, // Required: Number - Unix timestamp in seconds when the APR was calculated "value": 5.5, // Required: Number - The APR as a percentage (e.g., 5.5 for 5.5%) "description": "Optional details" // Optional: String - Additional context about the APR calculation }, { "title": "Protocol Name APR", "opportunityId": "98765432109876543", "timestamp": 1716900000, "value": 12.4 } ] ``` ### Field Descriptions * **title**: A clear, descriptive name for the APR type (e.g., "Aave Supply APR", "Compound Borrow APR", "Uniswap V3 Fee APR") * **opportunityId**: The unique numeric identifier for the Merkl opportunity. See [Finding Relevant Opportunities](https://docs.merkl.xyz/integrate-merkl/app#finding-relevant-opportunities) to learn how to retrieve it via the API. * **timestamp**: When the APR value was last calculated or updated, in Unix timestamp format (seconds) * **value**: The APR value as a percentage. For example, use `5.5` to represent 5.5% APR * **description** (optional): Additional information about how the APR is calculated or what it represents The `opportunityId` is the top-level `id` field of each opportunity object returned by the API. Do not confuse it with other `id` fields nested inside the response (e.g., inside the `tokens` array). ### Next Steps Once you have prepared your endpoint: 1. Test your endpoint to ensure it returns data in the correct format 2. Verify that all opportunity IDs are accurate 3. Contact the Merkl team to register your endpoint and complete the integration **Important**: All APR data must be accurate and verifiable. Providing misleading, incorrect, or manipulated data will result in immediate removal of your integration from the Merkl frontend. Ensure that: * APR calculations are accurate and reflect actual protocol performance * Timestamps are current and updated regularly * Values are consistently formatted as percentages If you have questions or need assistance, please reach out to the Merkl team. # Finding opportunities (/integrate-merkl/opportunities) # Finding relevant opportunities The opportunities endpoint provides key metrics like APR, TVL, and daily rewards that you can display in your frontend. Multiple filters are available to query opportunities. For a complete list, see the [opportunities endpoint documentation](/api-reference#tag/opportunities). The `apr` field in each opportunity is already converted to a percentage (`"apr": 50.41` is 50.41% APR) **Recommended filters:** **By Protocol ID** - Find all opportunities for a specific protocol: ```bash https://api.merkl.xyz/v4/opportunities?mainProtocolId={protocol_id} ``` Example for Euler: [`https://api.merkl.xyz/v4/opportunities?mainProtocolId=euler`](https://api.merkl.xyz/v4/opportunities?mainProtocolId=euler) To find your protocol's ID, check existing opportunities on the API or try filtering by name first (`https://api.merkl.xyz/v4/opportunities?name={name}`) **By Explorer Address** - Find opportunities for a specific pool or lending market: ```bash https://api.merkl.xyz/v4/opportunities?explorerAddress={address} ``` Example for the Aave USDT0 market on Plasma: [`https://api.merkl.xyz/v4/opportunities?explorerAddress=0x5D72a9d9A9510Cd8cBdBA12aC62593A58930a948`](https://api.merkl.xyz/v4/opportunities?explorerAddress=0x5D72a9d9A9510Cd8cBdBA12aC62593A58930a948) **By Chain ID** - Find all opportunities on a specific chain: ```bash https://api.merkl.xyz/v4/opportunities?chainId={chain_id} ``` Example for Ethereum: [`https://api.merkl.xyz/v4/opportunities?chainId=1`](https://api.merkl.xyz/v4/opportunities?chainId=1) **By Tags** - Find opportunities across multiple protocols and chains: ```bash https://api.merkl.xyz/v4/opportunities?tags={tag} ``` Example for zkSync Ignite Program: [`https://api.merkl.xyz/v4/opportunities?tags=zksync`](https://api.merkl.xyz/v4/opportunities?tags=zksync) If you need a custom tag for your opportunities, contact us and we'll assign it to your campaigns. Each opportunity has a permanent unique ID that remains unchanged even when multiple successive campaigns are created on the same pool or asset. # Quick start (/integrate-merkl/quickstart) # Quick start Three steps to your first integration on building on top of Merkl API. ## 1. Get an API key Anonymous requests work at a default rate limit of **10 req/sec**. For production, [generate an API key](/api-keys) and send it as the `X-API-Key` header for higher quotas, attribution, and support. ```bash curl https://api.merkl.xyz/v4/opportunities \ -H "X-API-Key: YOUR_API_KEY" ``` See [Auth & rate limits](/integrate-merkl/auth) for details. ## 2. Make your first call Call any endpoint directly over HTTPS, with no SDK required: ```typescript const res = await fetch("https://api.merkl.xyz/v4/opportunities", { headers: { "X-API-Key": "YOUR_API_KEY" }, }); const opportunities = await res.json(); console.log(`${opportunities?.length ?? 0} active opportunities`); ``` See the [API Reference](/api-reference) for the full surface. ## 3. Pick your path Most integrators land here for one of these jobs: The next pages walk you through in more detail how to integrate with the Merkl API. ## Reference * [API Reference](/api-reference): every endpoint, with examples * [Postman collection](https://postman.merkl.xyz): try endpoints interactively * [Status page](https://status.merkl.xyz): uptime and incidents * [llms.txt](/llms.txt): AI-friendly index of the developer portal # API Conventions (/integrate-merkl/reference) # API Conventions ## Identifiers When integrating with the Merkl API, you'll need to know how chains, protocols, and contracts are referenced. * **Chains** are referenced by their `chainId`. See the full list on the [Chains & Contracts](/resources/chains-and-contracts) page. * **Protocols** are referenced by their `protocolId`. See the full list on the [Protocols](/resources/protocols) page. * **Contract addresses**: for distributor and other relevant addresses you'll need when integrating, check the [Chains & Contracts](/resources/chains-and-contracts) page. ## Understanding enums in the API responses The API sometimes returns enum values as integers in its responses. For example, a campaign might show `campaignType: 18`. The full set of types (campaign types, distribution methods, scoring methods, hooks, and more), along with what each value corresponds to, is laid out in the [schema explorer](/resources/schemas). The Merkl API also provides conversion endpoints to decode these enum values programmatically and retrieve all possible values for each enum type. **Available endpoints:** * **List all enums**: [`https://api.merkl.xyz/v4/enums`](https://api.merkl.xyz/v4/enums) - Returns all enum types used in the Merkl API * **Get enum values**: [`https://api.merkl.xyz/v4/enums/HookType`](https://api.merkl.xyz/v4/enums/HookType) - Returns all possible values for a specific enum (e.g., `HookType`) * **Convert enum value**: [`https://api.merkl.xyz/v4/enums/HookType/convert/0`](https://api.merkl.xyz/v4/enums/HookType/convert/0) - Converts a numeric enum value to its human-readable label ## Additional tips **Pagination** - All Merkl API endpoints are paginated. Iterate through pages or increase the page size parameter to retrieve more results (using `&page=`) **Query optimization** - While many API parameters are optional, specifying additional filters can significantly improve query performance. # Statistics & leaderboards (/integrate-merkl/statistics) # Fetching campaign and user statistics The Merkl API provides endpoints to retrieve user leaderboards and reward statistics for individual campaigns or across multiple campaigns distributing the same token. **Recommended endpoints:** * **Campaign leaderboard**: Get all users rewarded in a campaign with [`GET /v4/rewards/`](/api-reference#tag/rewards) * **Token-level rewards**: Check rewards across all campaigns for a token with [`GET /v4/rewards/token/`](/api-reference#tag/rewards) * **Campaign-level rewards**: Check total amount of rewards distributed in a specific campaign with [`GET /v4/rewards/total`](/api-reference#tag/rewards) * **Unclaimed rewards**: Check unclaimed amounts for a campaign with [`GET /v4/rewards/unclaim/`](/api-reference#tag/rewards) * **Historical metrics**: Get TVL and APR history for a campaign with [`GET /v4/campaigns/{id}/metrics`](/api-reference#tag/campaigns) # User rewards (/integrate-merkl/user-rewards) # Integrating user rewards Merkl exposes a family of `/v4/users/{address}/rewards/*` endpoints to read a user's reward state, build claim transactions, and surface where rewards were earned. This page is organized by use case, so pick the endpoint that matches what you need to render. ## Concepts: amount, pending, claimed, proofs Every reward endpoint speaks the same vocabulary. These are the fields you will see on tokens: * **`amount`**: Cumulative reward credited to the user in the live Merkle tree (includes anything already claimed). * **`claimed`**: Cumulative amount the user has already pulled onchain. * **`pending`**: Earned but not yet in any Merkle root. Updates more frequently (\~every 2 hours) than `amount`. Surface it separately, because it is **not** claimable. * **`proofs`**: Merkle proofs required by the `Distributor` contract to claim the current `amount`. Pass them as-is. The **claimable** amount is `amount - claimed`. Whenever a new Merkle root is pushed onchain, `pending` resets to zero because those rewards are now folded into `amount`. Never sum `amount + pending` and display it as claimable. ## Cache bypass: `reloadChainId` After a user claims, Merkl needs to index the claim transaction onchain before `claimed` reflects the new onchain balance. Indexing typically takes up to \~5 minutes, so during that window the cached response keeps showing the old `claimed` value. Pass `reloadChainId={chainId}` to force a fresh read for that chain and reconcile against the onchain state immediately. Supported on `/rewards/summary`, `/rewards/breakdowns`, and the legacy `/v4/users/{address}/rewards`. Use it only when needed: every call with `reloadChainId` bypasses the cache and is more expensive on the server. ``` GET https://api.merkl.xyz/v4/users/0x4F2BF7469Bc38d1aE779b1F4affC588f35E60973/rewards/summary?reloadChainId=324 ``` ## Pick the right endpoint | Use case | Endpoint | Notes | | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------- | | Build a claim transaction | [`GET /v4/users/{address}/rewards/summary`](/api-reference#tag/users/GET/v4/users/\{address}/rewards/summary) | Recommended, fast | | Show USD totals | [`GET /v4/users/{address}/rewards/stats`](/api-reference#tag/users/GET/v4/users/\{address}/rewards/stats) | Lightweight USD totals | | Show full reward breakdowns (small users) | [`GET /v4/users/{address}/rewards/breakdowns`](/api-reference#tag/users/GET/v4/users/\{address}/rewards/breakdowns) | Capped at \~1000 breakdowns | | Show full reward breakdowns (any user, paginated) | [`GET /v4/leaves/{recipient}/breakdowns`](/api-reference#tag/leaves/GET/v4/leaves/\{recipient}/breakdowns) | Paginated, scales further | | List opportunities a user earned on (per chain) | [`GET /v4/users/{address}/rewards/chains/{chainId}/breakdowns`](/api-reference#tag/users/GET/v4/users/\{address}/rewards/chains/\{chainId}/breakdowns) | Per-opportunity, single chain | | List opportunities a user is **currently** earning on | [`GET /v4/users/{address}/rewards/active-opportunities`](/api-reference#tag/users/GET/v4/users/\{address}/rewards/active-opportunities) | LIVE only, APR desc | | Legacy combined endpoint | [`GET /v4/users/{address}/rewards`](/api-reference#tag/users/GET/v4/users/\{address}/rewards) | Backwards compatibility only | ## Build claim transactions: `/rewards/summary` This is the endpoint you want for any "Claim" UI. It returns `amount`, `claimed`, `pending`, and `proofs` per token, grouped by chain, and is the cheapest and fastest server-side path because it skips the breakdown traversal. ``` GET https://api.merkl.xyz/v4/users/{address}/rewards/summary ``` Supports filtering by chain, token, and protocol, plus `reloadChainId` for cache bypass. See the [API reference](/api-reference#tag/users/GET/v4/users/\{address}/rewards/summary) for the full schema. **Example:** [`/v4/users/0x4F2BF7469Bc38d1aE779b1F4affC588f35E60973/rewards/summary`](https://api.merkl.xyz/v4/users/0x4F2BF7469Bc38d1aE779b1F4affC588f35E60973/rewards/summary) ## USD totals: `/rewards/stats` A one-shot endpoint for headline numbers in dashboards or notification copy. ``` GET https://api.merkl.xyz/v4/users/{address}/rewards/stats ``` Returns `totalEarnedUSD`, `pendingUSD`, `claimableUSD`. Supports filtering by chain, token, and protocol. See the [API reference](/api-reference#tag/users/GET/v4/users/\{address}/rewards/stats) for the full schema. **Example:** [`/v4/users/0x4F2BF7469Bc38d1aE779b1F4affC588f35E60973/rewards/stats`](https://api.merkl.xyz/v4/users/0x4F2BF7469Bc38d1aE779b1F4affC588f35E60973/rewards/stats) ## Reward breakdowns: where each reward came from Two endpoints serve breakdowns, depending on how much data you expect. ### Small users: `/rewards/breakdowns` Returns the same `amount` / `claimed` / `pending` / `proofs` shape as `/rewards/summary`, **plus** a `breakdowns` array on every token detailing which campaign each fraction of the reward came from. ``` GET https://api.merkl.xyz/v4/users/{address}/rewards/breakdowns ``` Supports filtering by chain, token, and protocol, plus `reloadChainId` for cache bypass. See the [API reference](/api-reference#tag/users/GET/v4/users/\{address}/rewards/breakdowns) for the full schema. This endpoint is capped at \~1000 breakdowns per response. If a user has more than that on the queried scope, the `breakdowns` field comes back empty. For those users, paginate through `/v4/leaves/{recipient}/breakdowns` instead. **Example:** [`/v4/users/0x4F2BF7469Bc38d1aE779b1F4affC588f35E60973/rewards/breakdowns?chainIds=324`](https://api.merkl.xyz/v4/users/0x4F2BF7469Bc38d1aE779b1F4affC588f35E60973/rewards/breakdowns?chainIds=324) ### Any user, paginated: `/v4/leaves/{recipient}/breakdowns` The scalable path. Returns one page of leaf-level breakdowns at a time for a `(recipient, tokenAddress, distributionChainId)` triple, with pending amounts reconciled from temp leaves. ``` GET https://api.merkl.xyz/v4/leaves/{recipient}/breakdowns ``` Requires a `tokenAddress` and a `distributionChainId`. Supports additional filtering by campaign, opportunity, and protocol, plus pagination via `page` / `items`. See the [API reference](/api-reference#tag/leaves/GET/v4/leaves/\{recipient}/breakdowns) for the full schema. **Example:** first page of breakdowns on Arbitrum for a specific token: `/v4/leaves/0x4F2BF7469Bc38d1aE779b1F4affC588f35E60973/breakdowns?tokenAddress={token}&distributionChainId=42161&page=0&items=50`. ## Per-chain opportunity breakdown: `/rewards/chains/{chainId}/breakdowns` Lists every opportunity a user has rewards on for a single chain. One row per `(opportunity, token)` with `claimed`, `pending`, and a server-computed `claimable`. Useful when expanding a chain row in a dashboard. ``` GET https://api.merkl.xyz/v4/users/{address}/rewards/chains/{chainId}/breakdowns ``` See the [API reference](/api-reference#tag/users/GET/v4/users/\{address}/rewards/chains/\{chainId}/breakdowns) for the full schema. **Example:** [`/v4/users/0x4F2BF7469Bc38d1aE779b1F4affC588f35E60973/rewards/chains/42161/breakdowns`](https://api.merkl.xyz/v4/users/0x4F2BF7469Bc38d1aE779b1F4affC588f35E60973/rewards/chains/42161/breakdowns) ## Active opportunities: `/rewards/active-opportunities` Returns the opportunities where the user is currently earning: only those with LIVE campaigns and a non-zero balance (settled or pending). Aggregated per opportunity, sorted by APR desc. ``` GET https://api.merkl.xyz/v4/users/{address}/rewards/active-opportunities ``` See the [API reference](/api-reference#tag/users/GET/v4/users/\{address}/rewards/active-opportunities) for the full schema. **Example:** [`/v4/users/0x4F2BF7469Bc38d1aE779b1F4affC588f35E60973/rewards/active-opportunities`](https://api.merkl.xyz/v4/users/0x4F2BF7469Bc38d1aE779b1F4affC588f35E60973/rewards/active-opportunities) ### Linking onchain positions: `withStoringIds` Pass `withStoringIds=true` to also get the **storingIds** backing each opportunity, the ids under which the indexer tracks positions. They are the join key between an opportunity and the user's onchain position, which this endpoint does not return itself. ``` GET https://api.merkl.xyz/v4/users/{address}/rewards/active-opportunities?withStoringIds=true ``` Each opportunity then carries: ```json "storingIds": [ { "id": "0xabc…" }, { "id": "0xdef…", "viaSubCampaign": true } ] ``` * **`id`**: an indexing/storingId of the opportunity's LIVE root campaigns, flattened over their full sub-campaign trees. * **`viaSubCampaign`**: present (and `true`) when the id belongs to a sub-campaign only: the position is forwarded or wrapped (e.g. held through a vault) rather than deposited directly. Feed those ids to the analytics positions route to fetch the positions themselves, scoped to this dashboard instead of the whole portfolio: ``` GET https://analytics.merkl.xyz/v4/analytics/positions/{address}?indexingIds={id1},{id2}&includeDisplay=true ``` See [Positions Analytics](/analytics/positions) for that route's parameters and auth. The flag is off by default: resolving storingIds walks each opportunity's campaign tree, so existing consumers keep the endpoint's original latency. Resolution is per-opportunity resilient: an opportunity whose tree cannot be resolved comes back **without** the `storingIds` field rather than failing the request. The two cases are therefore distinguishable: * **`storingIds` absent**: resolution failed for that opportunity (retry it, or fall back to the unscoped positions route). * **`storingIds: []`**: resolved successfully, but no storingId currently backs the opportunity (e.g. no LIVE root campaign). ## Legacy endpoint: `/v4/users/{address}/rewards` Still supported for backwards compatibility. Returns rewards aggregated by chain with breakdowns embedded. Filters are limited to chain only, while the newer endpoints add token and protocol filtering. New integrations should use `/rewards/summary` for claiming and `/rewards/breakdowns` or `/v4/leaves/{recipient}/breakdowns` for breakdown details. See the [API reference](/api-reference#tag/users/GET/v4/users/\{address}/rewards) for the full schema. ``` GET https://api.merkl.xyz/v4/users/{address}/rewards?chainId={chain_id} ``` **Examples:** * Single chain, zkSync: [`/v4/users/0x4F2BF7469Bc38d1aE779b1F4affC588f35E60973/rewards?chainId=324`](https://api.merkl.xyz/v4/users/0x4F2BF7469Bc38d1aE779b1F4affC588f35E60973/rewards?chainId=324) * Multiple chains, zkSync, Ethereum and Arbitrum: [`/v4/users/0x4F2BF7469Bc38d1aE779b1F4affC588f35E60973/rewards?chainId=324,1,42161`](https://api.merkl.xyz/v4/users/0x4F2BF7469Bc38d1aE779b1F4affC588f35E60973/rewards?chainId=324,1,42161) ## Historical rewards For use cases that need the rewards earned by an address between two timestamps (accounting, tax reporting, retroactive analytics, etc.), dedicated API routes are available on demand. These routes are computationally heavy, so they are not publicly exposed to every integrator. Reach out to the Merkl team to request access and describe your use case. To pull the full history of onchain claim transactions for a user, the [`GET /v4/claims`](/api-reference#tag/claims/GET/v4/claims) endpoint is publicly available and easy to query. It supports filters like `recipient`, `chainId`, `campaignId`, `token`, and `root`. Combine them to scope the history exactly to what you need. See the [full filter list and response schema in the API reference](/api-reference#tag/claims). ## Building claim transactions Rewards are claimed through the `Distributor` contract. Find contract addresses for each chain on the [Chains & Contracts](/resources/chains-and-contracts) page. If the `token` or `amount` doesn't match the `proof` when calling the `Distributor` contract, the transaction will revert. Rewards on Merkl are claimable per token. Users can claim rewards for a single token or all tokens at once. ### Distributor contract interface The full source is available in the [`merkl-contracts`](https://github.com/AngleProtocol/merkl-contracts/blob/main/contracts/Distributor.sol) repository. The functions and events most relevant to integrators are: ```solidity interface IDistributor { /// @notice Cumulative claim record stored per (user, token). struct Claim { uint208 amount; uint48 timestamp; bytes32 merkleRoot; } /*//////////////////////////////////// CLAIM ////////////////////////////////////*/ /// @notice Claim rewards for a batch of (user, token) pairs. /// @dev `amounts` are cumulative, so pass the `amount` returned by the API as-is. /// Tokens are sent to `users[i]` (or to a recipient previously set via /// `setClaimRecipient`). Reverts with `InvalidProof` if a leaf does not /// match the active Merkle root. function claim( address[] calldata users, address[] calldata tokens, uint256[] calldata amounts, bytes32[][] calldata proofs ) external; /// @notice Claim with explicit recipients and optional `onClaim` callback data. /// @dev Only `msg.sender` claiming for itself can override the recipient. function claimWithRecipient( address[] calldata users, address[] calldata tokens, uint256[] calldata amounts, bytes32[][] calldata proofs, address[] calldata recipients, bytes[] memory datas ) external; /*//////////////////////////////////// USER ADMIN ////////////////////////////////////*/ /// @notice Authorize / deauthorize an operator to claim on behalf of `user`. /// Pass `address(0)` to allow anyone to claim for the user. function toggleOperator(address user, address operator) external; /// @notice Set a default recipient for `user`'s claims of `token`. /// `token = address(0)` sets the default for all tokens. function setClaimRecipient(address recipient, address token) external; /*//////////////////////////////////// VIEWS ////////////////////////////////////*/ /// @notice Returns the Merkle root currently used to verify proofs. /// Equals `lastTree.merkleRoot` while a dispute window is open. function getMerkleRoot() external view returns (bytes32); /// @notice Cumulative amount already claimed by `user` for `token`. function claimed(address user, address token) external view returns (uint208 amount, uint48 timestamp, bytes32 merkleRoot); /// @notice Operator authorizations (1 = authorized, 0 = not authorized). function operators(address user, address operator) external view returns (uint256); /// @notice Per-(user, token) custom recipient set via `setClaimRecipient`. function claimRecipient(address user, address token) external view returns (address); /*//////////////////////////////////// EVENTS ////////////////////////////////////*/ event Claimed(address indexed user, address indexed token, uint256 amount); event ClaimRecipientUpdated(address indexed user, address indexed token, address indexed recipient); event OperatorToggled(address indexed user, address indexed operator, bool isWhitelisted); event TreeUpdated(bytes32 merkleRoot, bytes32 ipfsHash, uint48 endOfDisputePeriod); } ``` The full ABI (including governance functions, dispute flow, and errors) is available as a JSON file: [Download the Distributor ABI](/images/distributor-abi.json). ### Example claiming script Here is a script to claim all token rewards for a user on a chain. It uses `/rewards/summary`, the recommended endpoint, so the call stays lightweight even for power users. The `Distributor` address is the same on most EVM chains. See the [Chains & Contracts](/resources/chains-and-contracts) page for per-chain values. ```javascript import type { JsonRpcSigner } from '@ethersproject/providers' import { Distributor__factory } from '@sdk' // Or load the ABI from /images/distributor-abi.json const DISTRIBUTOR_ADDRESS = '0x3Ef3D8bA38EBe18DB133cEc108f4D14CE00Dd9Ae' export const claim = async (chainId: number, signer: JsonRpcSigner) => { const res = await fetch( `https://api.merkl.xyz/v4/users/${signer._address}/rewards/summary?chainId=${chainId}`, ) if (!res.ok) throw 'Failed to fetch rewards' const data = await res.json() const users = [] const tokens = [] const amounts = [] const proofs = [] for (const rewards of data) { if (rewards.chain.id !== chainId) continue for (const reward of rewards.rewards) { users.push(signer._address) tokens.push(reward.token.address) amounts.push(reward.amount) proofs.push(reward.proofs) } } if (tokens.length === 0) throw 'No tokens to claim' const contract = Distributor__factory.connect(DISTRIBUTOR_ADDRESS, signer) await (await contract.claim(users, tokens, amounts, proofs)).wait() } ``` # Attribution (/resources/attribution) # Brand assets (/resources/brand-assets) # Chains & Contracts (/resources/chains-and-contracts) # Chains & Contracts ## Chain IDs For non-EVM chains, Merkl uses internal chain IDs that differ from standard chain identifiers. For example, Stellar is assigned chain ID `4`. Use the table below as the reference for chain IDs and contract addresses when integrating with Merkl. ## Smart Contracts Merkl deploys three core contracts on each supported chain. Merkl's EVM contracts have been audited twice by Code4rena: * [November 2025 audit](https://code4rena.com/reports/2025-11-merkl) * [June 2023 audit](https://code4rena.com/reports/2023-06-angle) Merkl's Stellar contracts have been audited by Halborn: Halborn: Merkl Stellar Smart Contract Assessment PDF · April 2026 · 10.6 MB ### Admin Governance and emergency control. Manages whitelisted tokens, fee rates, and dispute resolution for the Distributor. ### Distributor Holds the Merkle roots and lets users claim their rewards. Call `claim()` to pull tokens directly from this contract. ### DistributionCreator Entry point for campaign creators. Operators deposit reward tokens and configure campaign parameters here. On the vast majority of EVM chains, the Distributor is deployed at `0x3Ef3D8bA38EBe18DB133cEc108f4D14CE00Dd9Ae` and the DistributionCreator is deployed at `0x8BB4C975Ff3c250e0ceEA271728547f3802B36Fd`. # Protocols (/resources/protocols) # Protocols These are the protocols currently referenced in the Merkl API. Campaigns can technically be created on top of any protocol. Adding a new protocol (along with its logo) to the API is straightforward, just reach out to the team. # Schema explorer (/resources/schemas) ## About Schemas Every campaign on Merkl is created from a **configuration**: a set of inputs that together describe the campaign: its type, how its rewards are distributed, how scores are computed, and any customizations layered on top. Each of these inputs is described by a [TypeBox](https://github.com/sinclairzx81/typebox) JSON Schema, and this explorer reads them live from the public [`/v4/schemas`](https://api.merkl.xyz/v4/schemas) endpoints so you can discover exactly what each one expects. A configuration is built from these inputs: | Input | What it describes | | ------------------------ | -------------------------------------------------------------- | | **Campaign type** | The activity being rewarded and the parameters it accepts | | **Distribution method** | How a campaign's rewards are spent over time | | **Compute-score method** | How individual contributions are turned into reward shares | | **Hook type** | Eligibility filters, boosts, and other campaign customizations | | **Processor type** | The position processors that can be plugged into a campaign | Pick a group below, search for a type, and expand any entry to inspect its raw JSON Schema, ready to feed into tooling like `ajv`, `openapi-typescript` or `quicktype`. ## Explorer # Merkl API The official API to access data related to Merkl campaigns, built and maintained by Merkl SAS. This API is provided as is, without any warranty of any kind. --- # POST /v4/developer/keys/ _Merkl API_ Create a new API key. The raw key is returned only once. ## Request body **`application/json`** ```json { "type": "object", "properties": { "description": { "minLength": 1, "maxLength": 255, "type": "string" } }, "required": [ "description" ] } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "description": { "minLength": 1, "maxLength": 255, "type": "string" } }, "required": [ "description" ] } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "description": { "minLength": 1, "maxLength": 255, "type": "string" } }, "required": [ "description" ] } ``` ## Responses - **200** Response for status 200 ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/developer/keys/" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # GET /v4/developer/keys/ _Merkl API_ List all API keys for the authenticated user. ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/developer/keys/" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/developer/keys/usage _Merkl API_ Get usage summary across all API keys for the authenticated user. ### Query parameters - `days` (number) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/developer/keys/usage" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/developer/keys/{id}/usage _Merkl API_ Get detailed daily usage for a specific API key. ### Path parameters - `id` (string) _(required)_ ### Query parameters - `days` (number) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/developer/keys/{id}/usage" \ -H "x-api-key: YOUR_API_KEY" ``` --- # PATCH /v4/developer/keys/{id} _Merkl API_ Update description for an API key. ### Path parameters - `id` (string) _(required)_ ## Request body **`application/json`** ```json { "type": "object", "properties": { "description": { "minLength": 1, "maxLength": 255, "type": "string" } } } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "description": { "minLength": 1, "maxLength": 255, "type": "string" } } } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "description": { "minLength": 1, "maxLength": 255, "type": "string" } } } ``` ## Example request ```bash curl -X PATCH "https://api.merkl.xyz//v4/developer/keys/{id}" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # DELETE /v4/developer/keys/{id} _Merkl API_ Revoke an API key. ### Path parameters - `id` (string) _(required)_ ## Example request ```bash curl -X DELETE "https://api.merkl.xyz//v4/developer/keys/{id}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/api-keys/validate _Merkl API_ Validate a developer API key. Returns `{ ok: true }` on success, 401 otherwise. ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/api-keys/validate" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/auth/nonce/{address} _Merkl API_ Generate a unique nonce and message for wallet authentication. ### Path parameters - `address` (string) _(required)_ ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/auth/nonce/{address}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # POST /v4/auth/login _Merkl API_ Authenticate a user and set a JWT cookie for session management. ## Request body **`application/json`** ```json { "type": "object", "properties": { "address": { "minLength": 1, "type": "string" }, "message": { "type": "string" }, "signature": { "minLength": 1, "type": "string" }, "chainId": { "description": "Chain ID of the wallet", "type": "number" } }, "required": [ "address", "message", "signature" ] } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "address": { "minLength": 1, "type": "string" }, "message": { "type": "string" }, "signature": { "minLength": 1, "type": "string" }, "chainId": { "description": "Chain ID of the wallet", "type": "number" } }, "required": [ "address", "message", "signature" ] } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "address": { "minLength": 1, "type": "string" }, "message": { "type": "string" }, "signature": { "minLength": 1, "type": "string" }, "chainId": { "description": "Chain ID of the wallet", "type": "number" } }, "required": [ "address", "message", "signature" ] } ``` ## Responses - **200** Response for status 200 ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/auth/login" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # GET /v4/auth/google/start _Merkl API_ Start the Google OAuth flow for signing in with a Google account. ### Query parameters - `mode` (string | string) _(required)_ ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/auth/google/start?mode=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/auth/google/callback _Merkl API_ Complete the Google OAuth flow and create or refresh the user session. ### Query parameters - `code` (string) - `state` (string) - `error` (string) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/auth/google/callback" \ -H "x-api-key: YOUR_API_KEY" ``` --- # POST /v4/auth/link/wallet _Merkl API_ Link a wallet to the current (Google) session — collapses both into one wallet identity. ### Cookie parameters - `jwt` (string) ## Request body **`application/json`** ```json { "type": "object", "properties": { "address": { "minLength": 1, "type": "string" }, "message": { "type": "string" }, "signature": { "minLength": 1, "type": "string" }, "chainId": { "description": "Chain ID of the wallet", "type": "number" } }, "required": [ "address", "message", "signature" ] } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "address": { "minLength": 1, "type": "string" }, "message": { "type": "string" }, "signature": { "minLength": 1, "type": "string" }, "chainId": { "description": "Chain ID of the wallet", "type": "number" } }, "required": [ "address", "message", "signature" ] } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "address": { "minLength": 1, "type": "string" }, "message": { "type": "string" }, "signature": { "minLength": 1, "type": "string" }, "chainId": { "description": "Chain ID of the wallet", "type": "number" } }, "required": [ "address", "message", "signature" ] } ``` ## Responses - **200** Response for status 200 ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/auth/link/wallet" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # GET /v4/auth/me _Merkl API_ Get authenticated user information including address, profile, and authentication status. ### Cookie parameters - `jwt` (string) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/auth/me" \ -H "x-api-key: YOUR_API_KEY" ``` --- # DELETE /v4/auth/revoke _Merkl API_ Revoke the current user's JWT token and clear the session cookie. ### Cookie parameters - `jwt` (string) ## Example request ```bash curl -X DELETE "https://api.merkl.xyz//v4/auth/revoke" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/block-timestamp/block _Merkl API_ Get the closest block number to a given timestamp on a specific chain ### Query parameters - `chainId` (number) _(required)_ - `timestamp` (number) _(required)_ ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/block-timestamp/block?chainId=×tamp=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/block-timestamp/timestamp _Merkl API_ Get the closest timestamp to a given block number on a specific chain ### Query parameters - `chainId` (number) _(required)_ - `blockNumber` (number) _(required)_ ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/block-timestamp/timestamp?chainId=&blockNumber=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/block-timestamp/bounds _Merkl API_ Get the closest lower and upper bound blocks for a given timestamp from BlockTimestampMapping ### Query parameters - `chainId` (number) _(required)_ - `timestamp` (number) _(required)_ ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/block-timestamp/bounds?chainId=×tamp=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/campaigns/{id} _Merkl API_ Retrieve a campaign by its unique identifier (hash or `distributionChain-campaignId` format) in the `id` path parameter. ### Path parameters - `id` (string) _(required)_ ### Query parameters - `showPrivate` (boolean) ## Responses ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/campaigns/{id}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # POST /v4/campaigns/generate-payload _Merkl API_ Generate an on-chain transaction payload for creating one or more campaigns from the provided data. ## Request body **`application/json`** ```json { "type": "object", "properties": { "creatorAddress": { "type": "string" }, "rewardToken": { "type": "string" }, "distributionChainId": { "anyOf": [ { "format": "numeric", "default": 0, "type": "string" }, { "type": "number" } ] }, "startTimestamp": { "anyOf": [ { "format": "numeric", "default": 0, "type": "string" }, { "type": "number" } ] }, "endTimestamp": { "anyOf": [ { "format": "numeric", "default": 0, "type": "string" }, { "type": "number" } ] }, "campaignsParams": { "type": "object", "patternProperties": { "^(.*)$": { "type": "array", "items": { "additionalProperties": true, "type": "object", "properties": { "amount": { "type": "RegExp", "source": "[0-9]+", "flags": "" } }, "required": [ "amount" ] } } } } }, "required": [ "creatorAddress", "rewardToken", "distributionChainId", "startTimestamp", "endTimestamp", "campaignsParams" ] } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "creatorAddress": { "type": "string" }, "rewardToken": { "type": "string" }, "distributionChainId": { "anyOf": [ { "format": "numeric", "default": 0, "type": "string" }, { "type": "number" } ] }, "startTimestamp": { "anyOf": [ { "format": "numeric", "default": 0, "type": "string" }, { "type": "number" } ] }, "endTimestamp": { "anyOf": [ { "format": "numeric", "default": 0, "type": "string" }, { "type": "number" } ] }, "campaignsParams": { "type": "object", "patternProperties": { "^(.*)$": { "type": "array", "items": { "additionalProperties": true, "type": "object", "properties": { "amount": { "type": "RegExp", "source": "[0-9]+", "flags": "" } }, "required": [ "amount" ] } } } } }, "required": [ "creatorAddress", "rewardToken", "distributionChainId", "startTimestamp", "endTimestamp", "campaignsParams" ] } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "creatorAddress": { "type": "string" }, "rewardToken": { "type": "string" }, "distributionChainId": { "anyOf": [ { "format": "numeric", "default": 0, "type": "string" }, { "type": "number" } ] }, "startTimestamp": { "anyOf": [ { "format": "numeric", "default": 0, "type": "string" }, { "type": "number" } ] }, "endTimestamp": { "anyOf": [ { "format": "numeric", "default": 0, "type": "string" }, { "type": "number" } ] }, "campaignsParams": { "type": "object", "patternProperties": { "^(.*)$": { "type": "array", "items": { "additionalProperties": true, "type": "object", "properties": { "amount": { "type": "RegExp", "source": "[0-9]+", "flags": "" } }, "required": [ "amount" ] } } } } }, "required": [ "creatorAddress", "rewardToken", "distributionChainId", "startTimestamp", "endTimestamp", "campaignsParams" ] } ``` ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/campaigns/generate-payload" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # GET /v4/campaigns/{id}/metrics _Merkl API_ Get historical metrics (TVL, APR, daily rewards) for a campaign, with pagination options. ### Path parameters - `id` (string) _(required)_ ### Query parameters - `tvl` (object) - `apr` (object) - `dailyRewards` (object) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/campaigns/{id}/metrics" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/campaigns/{id}/storing-ids _Merkl API_ Get the storingId(s) backing a campaign as a tree. The root node's `storingIds` belong to the campaign itself, and its `subCampaigns` array recursively lists every sub-campaign. The same storingId may appear in more than one node. Stateless campaigns return an empty `storingIds` array. ### Path parameters - `id` (string) _(required)_ ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/campaigns/{id}/storing-ids" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/campaigns/{id}/linked _Merkl API_ Get every opportunity linked to a campaign — its full chain of parent campaigns up to the root, plus its entire sub-campaign subtree — as a flat list of nodes. Each node carries its `campaignId` and `parentCampaignId` so you can reconstruct the tree at any depth. `self` identifies the requested campaign. ### Path parameters - `id` (string) _(required)_ ### Query parameters - `test` (boolean) - `point` (boolean) - `tokenTypes` (array) - `campaigns` (boolean) - `excludeSubCampaigns` (boolean) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/campaigns/{id}/linked" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/campaigns/tx _Merkl API_ > Get campaign IDs from a creation transaction hash Retrieve the campaign ID(s) created by a given campaign-creation transaction hash. ### Query parameters - `hash` (string) _(required)_ - `chainId` (string | number) _(required)_ ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/campaigns/tx?hash=&chainId=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/campaigns/ _Merkl API_ Search for campaigns matching the criteria provided through query parameters. ### Query parameters - `page` (string | number) - `items` (string | number) - `id` (string) - `creatorTag` (string) - `creatorAddress` (string) - `creatorAddresses` (array) - `creatorId` (string) - `chainId` (string | number) - `search` (string) - `distributionChainIds` (array) - `type` (string) - `types` (array) - `subType` (number) - `campaignId` (string) - `campaignIds` (array) - `mainParameter` (string) - `tokenSymbol` (string) - `tokenAddress` (string) - `test` (boolean) - `tokenTypes` (array) - `point` (boolean) - `opportunityId` (string) - `opportunityIds` (array) - `status` (RegExp) - `startTimestamp` (string) - `endTimestamp` (string) - `withOpportunity` (boolean) - `distributionTypes` (array) - `createdAfter` (Date | string | string | number | null) - `createdBefore` (Date | string | string | number | null) - `excludeSubCampaigns` (boolean) - `onlySubCampaigns` (boolean) - `rootCampaignId` (string) - `parentCampaignId` (string) - `mainProtocolId` (string) - `pauseCompute` (string) - `showPrivate` (boolean) - `listOwner` (string) - `campaignLists` (string) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/campaigns/" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/campaigns/count _Merkl API_ Count the campaigns matching the filters provided through query parameters. ### Query parameters - `page` (string | number) - `items` (string | number) - `id` (string) - `creatorTag` (string) - `creatorAddress` (string) - `creatorAddresses` (array) - `creatorId` (string) - `chainId` (string | number) - `search` (string) - `distributionChainIds` (array) - `type` (string) - `types` (array) - `subType` (number) - `campaignId` (string) - `campaignIds` (array) - `mainParameter` (string) - `tokenSymbol` (string) - `tokenAddress` (string) - `test` (boolean) - `tokenTypes` (array) - `point` (boolean) - `opportunityId` (string) - `opportunityIds` (array) - `status` (RegExp) - `startTimestamp` (string) - `endTimestamp` (string) - `withOpportunity` (boolean) - `distributionTypes` (array) - `createdAfter` (Date | string | string | number | null) - `createdBefore` (Date | string | string | number | null) - `excludeSubCampaigns` (boolean) - `onlySubCampaigns` (boolean) - `rootCampaignId` (string) - `parentCampaignId` (string) - `mainProtocolId` (string) - `pauseCompute` (string) - `showPrivate` (boolean) - `listOwner` (string) - `campaignLists` (string) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/campaigns/count" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/campaigns/aggregate _Merkl API_ Aggregate all campaigns matching the provided filters and return a per-token breakdown of total amounts and USD values. Useful for analyzing program-level reward distribution (e.g. all rewards on a chain, all UNI rewards, or all campaigns by a creator). ### Query parameters - `page` (string | number) - `items` (string | number) - `id` (string) - `creatorTag` (string) - `creatorAddress` (string) - `creatorAddresses` (array) - `creatorId` (string) - `chainId` (string | number) - `search` (string) - `distributionChainIds` (array) - `type` (string) - `types` (array) - `subType` (number) - `campaignId` (string) - `campaignIds` (array) - `mainParameter` (string) - `tokenSymbol` (string) - `tokenAddress` (string) - `test` (boolean) - `tokenTypes` (array) - `point` (boolean) - `opportunityId` (string) - `opportunityIds` (array) - `status` (RegExp) - `startTimestamp` (string) - `endTimestamp` (string) - `withOpportunity` (boolean) - `distributionTypes` (array) - `createdAfter` (Date | string | string | number | null) - `createdBefore` (Date | string | string | number | null) - `excludeSubCampaigns` (boolean) - `onlySubCampaigns` (boolean) - `rootCampaignId` (string) - `parentCampaignId` (string) - `mainProtocolId` (string) - `pauseCompute` (string) - `showPrivate` (boolean) - `listOwner` (string) - `campaignLists` (string) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/campaigns/aggregate" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/campaign-lists/{owner} _Merkl API_ List every named campaign list owned by an address, with its size and last-updated timestamp. ### Path parameters - `owner` (string) _(required)_ ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/campaign-lists/{owner}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/campaign-lists/{owner}/{name} _Merkl API_ Get the campaign IDs in a single named list. Returns 404 if the list does not exist. ### Path parameters - `owner` (string) _(required)_ - `name` (string) _(required)_ ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/campaign-lists/{owner}/{name}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # PUT /v4/campaign-lists/{owner}/{name} _Merkl API_ Replace the campaign IDs in a named list (an empty array deletes the list). Requires the owner's JWT or API key. ### Path parameters - `owner` (string) _(required)_ - `name` (string) _(required)_ ## Request body **`application/json`** ```json { "type": "object", "properties": { "campaignIds": { "description": "Campaign IDs to include in the list. Each entry is either an internal hash (`Campaign.id`) or a `{ distributionChain, campaignId }` composite.", "type": "array", "items": { "anyOf": [ { "minLength": 1, "description": "Internal campaign ID (hash string)", "type": "string" }, { "description": "Composite identifier `{ distributionChain, campaignId }`", "type": "object", "properties": { "distributionChain": { "description": "Distribution chain ID", "type": "number" }, "campaignId": { "minLength": 1, "description": "Onchain campaign ID", "type": "string" } }, "required": [ "distributionChain", "campaignId" ] } ] } } }, "required": [ "campaignIds" ] } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "campaignIds": { "description": "Campaign IDs to include in the list. Each entry is either an internal hash (`Campaign.id`) or a `{ distributionChain, campaignId }` composite.", "type": "array", "items": { "anyOf": [ { "minLength": 1, "description": "Internal campaign ID (hash string)", "type": "string" }, { "description": "Composite identifier `{ distributionChain, campaignId }`", "type": "object", "properties": { "distributionChain": { "description": "Distribution chain ID", "type": "number" }, "campaignId": { "minLength": 1, "description": "Onchain campaign ID", "type": "string" } }, "required": [ "distributionChain", "campaignId" ] } ] } } }, "required": [ "campaignIds" ] } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "campaignIds": { "description": "Campaign IDs to include in the list. Each entry is either an internal hash (`Campaign.id`) or a `{ distributionChain, campaignId }` composite.", "type": "array", "items": { "anyOf": [ { "minLength": 1, "description": "Internal campaign ID (hash string)", "type": "string" }, { "description": "Composite identifier `{ distributionChain, campaignId }`", "type": "object", "properties": { "distributionChain": { "description": "Distribution chain ID", "type": "number" }, "campaignId": { "minLength": 1, "description": "Onchain campaign ID", "type": "string" } }, "required": [ "distributionChain", "campaignId" ] } ] } } }, "required": [ "campaignIds" ] } ``` ## Responses - **200** Response for status 200 ## Example request ```bash curl -X PUT "https://api.merkl.xyz//v4/campaign-lists/{owner}/{name}" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # DELETE /v4/campaign-lists/{owner}/{name} _Merkl API_ Delete a named list entirely. Requires the owner's JWT or API key. No-op if the list does not exist. ### Path parameters - `owner` (string) _(required)_ - `name` (string) _(required)_ ## Responses - **200** Response for status 200 ## Example request ```bash curl -X DELETE "https://api.merkl.xyz//v4/campaign-lists/{owner}/{name}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # POST /v4/campaign-lists/{owner}/{name}/add _Merkl API_ Append campaign IDs to a list, creating it if it does not exist. Requires the owner's JWT or API key. ### Path parameters - `owner` (string) _(required)_ - `name` (string) _(required)_ ## Request body **`application/json`** ```json { "type": "object", "properties": { "campaignIds": { "description": "Campaign IDs to include in the list. Each entry is either an internal hash (`Campaign.id`) or a `{ distributionChain, campaignId }` composite.", "type": "array", "items": { "anyOf": [ { "minLength": 1, "description": "Internal campaign ID (hash string)", "type": "string" }, { "description": "Composite identifier `{ distributionChain, campaignId }`", "type": "object", "properties": { "distributionChain": { "description": "Distribution chain ID", "type": "number" }, "campaignId": { "minLength": 1, "description": "Onchain campaign ID", "type": "string" } }, "required": [ "distributionChain", "campaignId" ] } ] } } }, "required": [ "campaignIds" ] } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "campaignIds": { "description": "Campaign IDs to include in the list. Each entry is either an internal hash (`Campaign.id`) or a `{ distributionChain, campaignId }` composite.", "type": "array", "items": { "anyOf": [ { "minLength": 1, "description": "Internal campaign ID (hash string)", "type": "string" }, { "description": "Composite identifier `{ distributionChain, campaignId }`", "type": "object", "properties": { "distributionChain": { "description": "Distribution chain ID", "type": "number" }, "campaignId": { "minLength": 1, "description": "Onchain campaign ID", "type": "string" } }, "required": [ "distributionChain", "campaignId" ] } ] } } }, "required": [ "campaignIds" ] } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "campaignIds": { "description": "Campaign IDs to include in the list. Each entry is either an internal hash (`Campaign.id`) or a `{ distributionChain, campaignId }` composite.", "type": "array", "items": { "anyOf": [ { "minLength": 1, "description": "Internal campaign ID (hash string)", "type": "string" }, { "description": "Composite identifier `{ distributionChain, campaignId }`", "type": "object", "properties": { "distributionChain": { "description": "Distribution chain ID", "type": "number" }, "campaignId": { "minLength": 1, "description": "Onchain campaign ID", "type": "string" } }, "required": [ "distributionChain", "campaignId" ] } ] } } }, "required": [ "campaignIds" ] } ``` ## Responses - **200** Response for status 200 ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/campaign-lists/{owner}/{name}/add" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # POST /v4/campaign-lists/{owner}/{name}/remove _Merkl API_ Remove the provided campaign IDs from a list. If the list becomes empty, it is deleted. Requires the owner's JWT or API key. ### Path parameters - `owner` (string) _(required)_ - `name` (string) _(required)_ ## Request body **`application/json`** ```json { "type": "object", "properties": { "campaignIds": { "description": "Campaign IDs to include in the list. Each entry is either an internal hash (`Campaign.id`) or a `{ distributionChain, campaignId }` composite.", "type": "array", "items": { "anyOf": [ { "minLength": 1, "description": "Internal campaign ID (hash string)", "type": "string" }, { "description": "Composite identifier `{ distributionChain, campaignId }`", "type": "object", "properties": { "distributionChain": { "description": "Distribution chain ID", "type": "number" }, "campaignId": { "minLength": 1, "description": "Onchain campaign ID", "type": "string" } }, "required": [ "distributionChain", "campaignId" ] } ] } } }, "required": [ "campaignIds" ] } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "campaignIds": { "description": "Campaign IDs to include in the list. Each entry is either an internal hash (`Campaign.id`) or a `{ distributionChain, campaignId }` composite.", "type": "array", "items": { "anyOf": [ { "minLength": 1, "description": "Internal campaign ID (hash string)", "type": "string" }, { "description": "Composite identifier `{ distributionChain, campaignId }`", "type": "object", "properties": { "distributionChain": { "description": "Distribution chain ID", "type": "number" }, "campaignId": { "minLength": 1, "description": "Onchain campaign ID", "type": "string" } }, "required": [ "distributionChain", "campaignId" ] } ] } } }, "required": [ "campaignIds" ] } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "campaignIds": { "description": "Campaign IDs to include in the list. Each entry is either an internal hash (`Campaign.id`) or a `{ distributionChain, campaignId }` composite.", "type": "array", "items": { "anyOf": [ { "minLength": 1, "description": "Internal campaign ID (hash string)", "type": "string" }, { "description": "Composite identifier `{ distributionChain, campaignId }`", "type": "object", "properties": { "distributionChain": { "description": "Distribution chain ID", "type": "number" }, "campaignId": { "minLength": 1, "description": "Onchain campaign ID", "type": "string" } }, "required": [ "distributionChain", "campaignId" ] } ] } } }, "required": [ "campaignIds" ] } ``` ## Responses - **200** Response for status 200 ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/campaign-lists/{owner}/{name}/remove" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # GET /v4/chains/ _Merkl API_ List chains supported and integrated by Merkl. Use query parameters to filter by test chains or other criteria. ### Query parameters - `name` (string) - `test` (boolean) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/chains/" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/chains/{chainId} _Merkl API_ Get a specific chain by its chainId path parameter ### Path parameters - `chainId` (string | number) _(required)_ ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/chains/{chainId}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/claims/ _Merkl API_ Fetch claims filtered by chainId(s), recipient, campaignId, token, root, exact `reason`, and an inclusive `[fromTimestamp, toTimestamp]` range (Unix seconds). Scope by timestamp to keep responses light for recipients with many claims. Returns the 100 most recent claims unless a `recipient` is given, in which case all matching claims are returned. ### Query parameters - `chainId` (string | number) - `chainIds` (array) - `recipient` (string) - `campaignId` (string) - `token` (string) - `root` (string) - `reason` (string) - `fromTimestamp` (string | number) - `toTimestamp` (string | number) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/claims/" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/claims/history/{address} _Merkl API_ Fetch a recipient's historical claims, grouped per claim transaction with amounts aggregated by token. ### Path parameters - `address` (string) _(required)_ ### Query parameters - `chainIds` (array) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/claims/history/{address}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/claims/earnings/{address} _Merkl API_ Aggregate a recipient's claimed rewards into time buckets broken down by token, in USD. Supports `bucket` granularity (day/week/month, default week) and `range` window (7d/30d/all, default all). ### Path parameters - `address` (string) _(required)_ ### Query parameters - `chainIds` (array) - `bucket` (string | string | string) - `range` (string | string | string) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/claims/earnings/{address}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/claims/count _Merkl API_ Returns the total number of unique claimers on a specific chain. ### Query parameters - `chainId` (string | number) _(required)_ ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/claims/count?chainId=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/claims/count/total _Merkl API_ Returns the total number of unique claimers across all chains. ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/claims/count/total" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/config/{id} _Merkl API_ Beta feature (not production ready yet) - Fetch the configuration for a specific campaign by its ID. Use id path parameter. ### Path parameters - `id` (string) _(required)_ ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/config/{id}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # POST /v4/config/diff _Merkl API_ Compute the diff between two config schemas. Returns an array of edit operations (insert/update/delete). ## Request body **`application/json`** ```json { "type": "object", "properties": { "oldConfig": { "description": "The original campaign configuration." }, "newConfig": { "description": "The new campaign configuration to compare against." } }, "required": [ "oldConfig", "newConfig" ] } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "oldConfig": { "description": "The original campaign configuration." }, "newConfig": { "description": "The new campaign configuration to compare against." } }, "required": [ "oldConfig", "newConfig" ] } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "oldConfig": { "description": "The original campaign configuration." }, "newConfig": { "description": "The new campaign configuration to compare against." } }, "required": [ "oldConfig", "newConfig" ] } ``` ## Responses - **200** Override value as JSON ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/config/diff" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # POST /v4/config/diff/{id} _Merkl API_ Compute the diff between the current config of a campaign and a new one. Returns an array of edit operations (insert/update/delete). ### Path parameters - `id` (string) _(required)_ ## Request body **`application/json`** ```json {} ``` **`application/x-www-form-urlencoded`** ```json {} ``` **`multipart/form-data`** ```json {} ``` ## Responses - **200** Override value as JSON ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/config/diff/{id}" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # POST /v4/config/extend _Merkl API_ Beta feature (not production ready yet) - Extends a campaign configuration with the values resolved by the engine. ## Request body **`application/json`** ```json {} ``` **`application/x-www-form-urlencoded`** ```json {} ``` **`multipart/form-data`** ```json {} ``` ## Responses - **200** Response for status 200 ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/config/extend" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # POST /v4/config/opportunity _Merkl API_ Beta feature (not production ready yet) - Computes an opportunity from a campaign configuration ## Request body **`application/json`** ```json {} ``` **`application/x-www-form-urlencoded`** ```json {} ``` **`multipart/form-data`** ```json {} ``` ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/config/opportunity" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # POST /v4/config/tvl _Merkl API_ Beta feature (not production ready yet) - Computes a TVL from a campaign configuration. Requires an API key or an authenticated session. ## Request body **`application/json`** ```json {} ``` **`application/x-www-form-urlencoded`** ```json {} ``` **`multipart/form-data`** ```json {} ``` ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/config/tvl" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # POST /v4/config/encode _Merkl API_ Beta feature (not production ready yet) - Encodes campaign parameters into the transaction payload used to create the campaign on-chain. ### Query parameters - `extend` (boolean) - `signerAddress` (string) ## Request body **`application/json`** ```json {} ``` **`application/x-www-form-urlencoded`** ```json {} ``` **`multipart/form-data`** ```json {} ``` ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/config/encode" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # POST /v4/config/encode/override _Merkl API_ Beta feature (not production ready yet) - Builds the transaction payload used to override (or cancel) an existing campaign on-chain, from pre-encoded on-chain args. Requires campaignId and distributionChainId query parameters. ### Query parameters - `campaignId` (string) _(required)_ - `distributionChainId` (string | number) _(required)_ ## Request body **`application/json`** ```json { "type": "object", "properties": { "campaignId": { "type": "string" }, "creator": { "$id": "address", "title": "Address", "description": "Address", "pattern": "^(0x[a-fA-F0-9]{40}|[1-9A-HJ-NP-Za-km-z]{32,44}|[CG][A-Z0-9]{55}|0x[a-fA-F0-9]{64})$", "type": "string" }, "rewardToken": { "$id": "address", "title": "Address", "description": "Address", "pattern": "^(0x[a-fA-F0-9]{40}|[1-9A-HJ-NP-Za-km-z]{32,44}|[CG][A-Z0-9]{55}|0x[a-fA-F0-9]{64})$", "type": "string" }, "amount": { "type": "string" }, "campaignType": { "type": "integer" }, "startTimestamp": { "$id": "timestamp", "title": "Timestamp", "description": "Unix timestamp in seconds (not milliseconds).", "type": "number" }, "duration": { "type": "integer" }, "campaignData": { "type": "string" } }, "required": [ "campaignId", "creator", "rewardToken", "amount", "campaignType", "startTimestamp", "duration", "campaignData" ], "description": "The new onchain campaign parameters to apply (immutable fields preserved onchain)." } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "campaignId": { "type": "string" }, "creator": { "$id": "address", "title": "Address", "description": "Address", "pattern": "^(0x[a-fA-F0-9]{40}|[1-9A-HJ-NP-Za-km-z]{32,44}|[CG][A-Z0-9]{55}|0x[a-fA-F0-9]{64})$", "type": "string" }, "rewardToken": { "$id": "address", "title": "Address", "description": "Address", "pattern": "^(0x[a-fA-F0-9]{40}|[1-9A-HJ-NP-Za-km-z]{32,44}|[CG][A-Z0-9]{55}|0x[a-fA-F0-9]{64})$", "type": "string" }, "amount": { "type": "string" }, "campaignType": { "type": "integer" }, "startTimestamp": { "$id": "timestamp", "title": "Timestamp", "description": "Unix timestamp in seconds (not milliseconds).", "type": "number" }, "duration": { "type": "integer" }, "campaignData": { "type": "string" } }, "required": [ "campaignId", "creator", "rewardToken", "amount", "campaignType", "startTimestamp", "duration", "campaignData" ], "description": "The new onchain campaign parameters to apply (immutable fields preserved onchain)." } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "campaignId": { "type": "string" }, "creator": { "$id": "address", "title": "Address", "description": "Address", "pattern": "^(0x[a-fA-F0-9]{40}|[1-9A-HJ-NP-Za-km-z]{32,44}|[CG][A-Z0-9]{55}|0x[a-fA-F0-9]{64})$", "type": "string" }, "rewardToken": { "$id": "address", "title": "Address", "description": "Address", "pattern": "^(0x[a-fA-F0-9]{40}|[1-9A-HJ-NP-Za-km-z]{32,44}|[CG][A-Z0-9]{55}|0x[a-fA-F0-9]{64})$", "type": "string" }, "amount": { "type": "string" }, "campaignType": { "type": "integer" }, "startTimestamp": { "$id": "timestamp", "title": "Timestamp", "description": "Unix timestamp in seconds (not milliseconds).", "type": "number" }, "duration": { "type": "integer" }, "campaignData": { "type": "string" } }, "required": [ "campaignId", "creator", "rewardToken", "amount", "campaignType", "startTimestamp", "duration", "campaignData" ], "description": "The new onchain campaign parameters to apply (immutable fields preserved onchain)." } ``` ## Responses - **200** Response for status 200 ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/config/encode/override?campaignId=&distributionChainId=" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # POST /v4/config/encode/reallocate _Merkl API_ Beta feature (not production ready yet) - Builds the transaction payload used to reallocate an existing campaign's unclaimed rewards on-chain (EVM calldata / Stellar XDR). Requires campaignId and distributionChainId query parameters. ### Query parameters - `campaignId` (string) _(required)_ - `distributionChainId` (string | number) _(required)_ ## Request body **`application/json`** ```json { "type": "object", "properties": { "froms": { "description": "Source addresses to reallocate from (the zero address alone reallocates every wallet).", "type": "array", "items": { "type": "string" } }, "to": { "description": "Destination address that receives the reallocated rewards.", "type": "string" }, "caller": { "description": "Address signing the reallocation (creator or authorized operator). Required on Stellar (transaction source), ignored on EVM.", "type": "string" } }, "required": [ "froms", "to" ] } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "froms": { "description": "Source addresses to reallocate from (the zero address alone reallocates every wallet).", "type": "array", "items": { "type": "string" } }, "to": { "description": "Destination address that receives the reallocated rewards.", "type": "string" }, "caller": { "description": "Address signing the reallocation (creator or authorized operator). Required on Stellar (transaction source), ignored on EVM.", "type": "string" } }, "required": [ "froms", "to" ] } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "froms": { "description": "Source addresses to reallocate from (the zero address alone reallocates every wallet).", "type": "array", "items": { "type": "string" } }, "to": { "description": "Destination address that receives the reallocated rewards.", "type": "string" }, "caller": { "description": "Address signing the reallocation (creator or authorized operator). Required on Stellar (transaction source), ignored on EVM.", "type": "string" } }, "required": [ "froms", "to" ] } ``` ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/config/encode/reallocate?campaignId=&distributionChainId=" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # POST /v4/config/encode/safe _Merkl API_ Beta feature (not production ready yet) - Encodes campaign config to Safe transaction payload. Use ?extend=true to validate config extension before encoding. ### Query parameters - `extend` (boolean) - `signerAddress` (string) ## Request body **`application/json`** ```json {} ``` **`application/x-www-form-urlencoded`** ```json {} ``` **`multipart/form-data`** ```json {} ``` ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/config/encode/safe" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # POST /v4/config/encode/batch _Merkl API_ Beta feature (not production ready yet) - Encodes a batch of campaign parameters into transaction payloads. ### Query parameters - `extend` (boolean) - `signerAddress` (string) ## Request body **`application/json`** ```json {} ``` **`application/x-www-form-urlencoded`** ```json {} ``` **`multipart/form-data`** ```json {} ``` ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/config/encode/batch" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # POST /v4/config/encode/batch/safe _Merkl API_ Beta feature (not production ready yet) - Encodes batch campaign configs to Safe transaction payload. Use ?extend=true to validate config extension before encoding. ### Query parameters - `extend` (boolean) - `signerAddress` (string) ## Request body **`application/json`** ```json {} ``` **`application/x-www-form-urlencoded`** ```json {} ``` **`multipart/form-data`** ```json {} ``` ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/config/encode/batch/safe" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # GET /v4/config/decode/onchain/{distributionChainId}/{campaignId} _Merkl API_ Decodes an on-chain campaign configuration. Use distributionChainId and campaignId path parameters. ### Path parameters - `distributionChainId` (string | number) _(required)_ - `campaignId` (string) _(required)_ ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/config/decode/onchain/{distributionChainId}/{campaignId}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # POST /v4/config/decode/{distributionChainId} _Merkl API_ Decodes campaign data from the Distribution contract. Use distributionChainId path parameter and provide on-chain campaign data in body. ### Path parameters - `distributionChainId` (string | number) _(required)_ ## Request body **`application/json`** ```json { "type": "object", "properties": { "campaignId": { "type": "string" }, "creator": { "$id": "address", "title": "Address", "description": "Address", "pattern": "^(0x[a-fA-F0-9]{40}|[1-9A-HJ-NP-Za-km-z]{32,44}|[CG][A-Z0-9]{55}|0x[a-fA-F0-9]{64})$", "type": "string" }, "rewardToken": { "$id": "address", "title": "Address", "description": "Address", "pattern": "^(0x[a-fA-F0-9]{40}|[1-9A-HJ-NP-Za-km-z]{32,44}|[CG][A-Z0-9]{55}|0x[a-fA-F0-9]{64})$", "type": "string" }, "amount": { "type": "string" }, "campaignType": { "type": "integer" }, "startTimestamp": { "$id": "timestamp", "title": "Timestamp", "description": "Unix timestamp in seconds (not milliseconds).", "type": "number" }, "duration": { "type": "integer" }, "campaignData": { "type": "string" } }, "required": [ "campaignId", "creator", "rewardToken", "amount", "campaignType", "startTimestamp", "duration", "campaignData" ], "description": "Onchain campaign data fetched from the Distribution contract" } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "campaignId": { "type": "string" }, "creator": { "$id": "address", "title": "Address", "description": "Address", "pattern": "^(0x[a-fA-F0-9]{40}|[1-9A-HJ-NP-Za-km-z]{32,44}|[CG][A-Z0-9]{55}|0x[a-fA-F0-9]{64})$", "type": "string" }, "rewardToken": { "$id": "address", "title": "Address", "description": "Address", "pattern": "^(0x[a-fA-F0-9]{40}|[1-9A-HJ-NP-Za-km-z]{32,44}|[CG][A-Z0-9]{55}|0x[a-fA-F0-9]{64})$", "type": "string" }, "amount": { "type": "string" }, "campaignType": { "type": "integer" }, "startTimestamp": { "$id": "timestamp", "title": "Timestamp", "description": "Unix timestamp in seconds (not milliseconds).", "type": "number" }, "duration": { "type": "integer" }, "campaignData": { "type": "string" } }, "required": [ "campaignId", "creator", "rewardToken", "amount", "campaignType", "startTimestamp", "duration", "campaignData" ], "description": "Onchain campaign data fetched from the Distribution contract" } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "campaignId": { "type": "string" }, "creator": { "$id": "address", "title": "Address", "description": "Address", "pattern": "^(0x[a-fA-F0-9]{40}|[1-9A-HJ-NP-Za-km-z]{32,44}|[CG][A-Z0-9]{55}|0x[a-fA-F0-9]{64})$", "type": "string" }, "rewardToken": { "$id": "address", "title": "Address", "description": "Address", "pattern": "^(0x[a-fA-F0-9]{40}|[1-9A-HJ-NP-Za-km-z]{32,44}|[CG][A-Z0-9]{55}|0x[a-fA-F0-9]{64})$", "type": "string" }, "amount": { "type": "string" }, "campaignType": { "type": "integer" }, "startTimestamp": { "$id": "timestamp", "title": "Timestamp", "description": "Unix timestamp in seconds (not milliseconds).", "type": "number" }, "duration": { "type": "integer" }, "campaignData": { "type": "string" } }, "required": [ "campaignId", "creator", "rewardToken", "amount", "campaignType", "startTimestamp", "duration", "campaignData" ], "description": "Onchain campaign data fetched from the Distribution contract" } ``` ## Responses - **200** Response for status 200 ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/config/decode/{distributionChainId}" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # GET /v4/config/decode/{distributionChainId}/{payload} _Merkl API_ Decodes campaign from a raw payload. Use distributionChainId and payload (hex string) path parameters. ### Path parameters - `distributionChainId` (string | number) _(required)_ - `payload` (string) _(required)_ ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/config/decode/{distributionChainId}/{payload}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # POST /v4/config/decode/safe _Merkl API_ Decodes a Safe transaction payload back to the original campaign configurations. Returns an array of configs. ## Request body **`application/json`** ```json { "description": "Safe payload containing campaign creation transactions" } ``` **`application/x-www-form-urlencoded`** ```json { "description": "Safe payload containing campaign creation transactions" } ``` **`multipart/form-data`** ```json { "description": "Safe payload containing campaign creation transactions" } ``` ## Responses - **200** Response for status 200 ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/config/decode/safe" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # POST /v4/config/decode/args/createCampaign _Merkl API_ Decodes arguments of the create campaign function back to the original campaign configuration. ## Request body **`application/json`** ```json { "type": "object", "properties": { "distributionChainId": { "description": "Chain ID where the campaign is being created", "anyOf": [ { "format": "numeric", "default": 0, "type": "string" }, { "description": "Chain ID where the campaign is being created", "type": "number" } ] }, "args": { "description": "Arguments of the call to the createCampaign function" } }, "required": [ "distributionChainId", "args" ] } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "distributionChainId": { "description": "Chain ID where the campaign is being created", "anyOf": [ { "format": "numeric", "default": 0, "type": "string" }, { "description": "Chain ID where the campaign is being created", "type": "number" } ] }, "args": { "description": "Arguments of the call to the createCampaign function" } }, "required": [ "distributionChainId", "args" ] } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "distributionChainId": { "description": "Chain ID where the campaign is being created", "anyOf": [ { "format": "numeric", "default": 0, "type": "string" }, { "description": "Chain ID where the campaign is being created", "type": "number" } ] }, "args": { "description": "Arguments of the call to the createCampaign function" } }, "required": [ "distributionChainId", "args" ] } ``` ## Responses - **200** Response for status 200 ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/config/decode/args/createCampaign" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # POST /v4/config/store _Merkl API_ Stores campaign configurations keyed by their hash. Provide the campaign configuration payload in the request body. ## Request body **`application/json`** ```json {} ``` **`application/x-www-form-urlencoded`** ```json {} ``` **`multipart/form-data`** ```json {} ``` ## Responses - **200** Response for status 200 ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/config/store" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # GET /v4/config/hash/{hash} _Merkl API_ Gets a campaign configuration by its hash. If campaignId and distributionChainId are provided, associates the configuration with that campaign. ### Path parameters - `hash` (string) _(required)_ ### Query parameters - `campaignId` (string) - `distributionChainId` (string | number) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/config/hash/{hash}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # PUT /v4/key-value-stores/{key}/entries _Merkl API_ Upsert a single entry. Authenticate with a JWT for user access, or an X-API-Key header for service access. ### Path parameters - `key` (string) _(required)_ ## Request body **`application/json`** ```json { "type": "object", "properties": { "address": { "pattern": "^0x[a-fA-F0-9]{40}$|^0x[a-fA-F0-9]{64}$", "description": "Target Ethereum address (or bytes32 campaignId for APR configs)", "type": "string" }, "value": { "maxLength": 1024, "description": "JSON value (shape gated by config type)", "type": "string" } }, "required": [ "address", "value" ] } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "address": { "pattern": "^0x[a-fA-F0-9]{40}$|^0x[a-fA-F0-9]{64}$", "description": "Target Ethereum address (or bytes32 campaignId for APR configs)", "type": "string" }, "value": { "maxLength": 1024, "description": "JSON value (shape gated by config type)", "type": "string" } }, "required": [ "address", "value" ] } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "address": { "pattern": "^0x[a-fA-F0-9]{40}$|^0x[a-fA-F0-9]{64}$", "description": "Target Ethereum address (or bytes32 campaignId for APR configs)", "type": "string" }, "value": { "maxLength": 1024, "description": "JSON value (shape gated by config type)", "type": "string" } }, "required": [ "address", "value" ] } ``` ## Responses - **200** Response for status 200 ## Example request ```bash curl -X PUT "https://api.merkl.xyz//v4/key-value-stores/{key}/entries" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # GET /v4/key-value-stores/{key}/entries _Merkl API_ List entries. Public for stores configured as publicly readable; otherwise requires authentication. ### Path parameters - `key` (string) _(required)_ ### Query parameters - `page` (string | number) - `pageSize` (string | number) - `value` (string) ### Header parameters - `page` (string | number) - `pageSize` (string | number) - `value` (string) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/key-value-stores/{key}/entries" \ -H "x-api-key: YOUR_API_KEY" \ -H "page: " \ -H "pageSize: " \ -H "value: " ``` --- # PUT /v4/key-value-stores/{key}/entries/batch _Merkl API_ Batch upsert entries. Requires X-API-Key authentication. ### Path parameters - `key` (string) _(required)_ ## Request body **`application/json`** ```json { "type": "object", "properties": { "entries": { "minItems": 1, "maxItems": 1000, "type": "array", "items": { "type": "object", "properties": { "address": { "pattern": "^0x[a-fA-F0-9]{40}$|^0x[a-fA-F0-9]{64}$", "type": "string" }, "value": { "maxLength": 1024, "type": "string" } }, "required": [ "address", "value" ] } } }, "required": [ "entries" ] } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "entries": { "minItems": 1, "maxItems": 1000, "type": "array", "items": { "type": "object", "properties": { "address": { "pattern": "^0x[a-fA-F0-9]{40}$|^0x[a-fA-F0-9]{64}$", "type": "string" }, "value": { "maxLength": 1024, "type": "string" } }, "required": [ "address", "value" ] } } }, "required": [ "entries" ] } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "entries": { "minItems": 1, "maxItems": 1000, "type": "array", "items": { "type": "object", "properties": { "address": { "pattern": "^0x[a-fA-F0-9]{40}$|^0x[a-fA-F0-9]{64}$", "type": "string" }, "value": { "maxLength": 1024, "type": "string" } }, "required": [ "address", "value" ] } } }, "required": [ "entries" ] } ``` ## Responses - **200** Response for status 200 ## Example request ```bash curl -X PUT "https://api.merkl.xyz//v4/key-value-stores/{key}/entries/batch" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # GET /v4/key-value-stores/{key}/entries/{address} _Merkl API_ Get a single entry by address ### Path parameters - `key` (string) _(required)_ - `address` (string) _(required)_ ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/key-value-stores/{key}/entries/{address}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # DELETE /v4/key-value-stores/{key}/entries/{address} _Merkl API_ Delete an entry. Users may delete their own entry; the store owner or an X-API-Key holder may delete any entry. ### Path parameters - `key` (string) _(required)_ - `address` (string) _(required)_ ## Responses - **200** Response for status 200 ## Example request ```bash curl -X DELETE "https://api.merkl.xyz//v4/key-value-stores/{key}/entries/{address}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # POST /v4/key-value-stores/self _Merkl API_ Create a key-value store you own (allowlisted users, X-API-Key). `key` is a suffix; the response returns the canonical, owner-namespaced key used for entry writes, update and delete. ## Request body **`application/json`** ```json { "type": "object", "properties": { "key": { "minLength": 1, "maxLength": 128, "pattern": "^[a-zA-Z0-9_-]{1,128}$", "description": "Unique human-readable key (alphanumeric, hyphens, underscores). Server-namespaced per owner.", "type": "string" }, "description": { "maxLength": 512, "type": "string" }, "type": { "description": "Semantic type — determines the required JSON shape of entry values", "anyOf": [ { "const": "WHITELIST", "type": "string" }, { "const": "BLACKLIST", "type": "string" }, { "const": "BOOST", "type": "string" }, { "const": "REFERRER", "type": "string" }, { "const": "FORWARDER", "type": "string" }, { "const": "APR", "type": "string" } ] }, "sizeLimit": { "minimum": 1, "maximum": 1000000, "description": "Capped at the caller's quota", "type": "number" }, "publicRead": { "default": true, "type": "boolean" } }, "required": [ "key", "type" ] } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "key": { "minLength": 1, "maxLength": 128, "pattern": "^[a-zA-Z0-9_-]{1,128}$", "description": "Unique human-readable key (alphanumeric, hyphens, underscores). Server-namespaced per owner.", "type": "string" }, "description": { "maxLength": 512, "type": "string" }, "type": { "description": "Semantic type — determines the required JSON shape of entry values", "anyOf": [ { "const": "WHITELIST", "type": "string" }, { "const": "BLACKLIST", "type": "string" }, { "const": "BOOST", "type": "string" }, { "const": "REFERRER", "type": "string" }, { "const": "FORWARDER", "type": "string" }, { "const": "APR", "type": "string" } ] }, "sizeLimit": { "minimum": 1, "maximum": 1000000, "description": "Capped at the caller's quota", "type": "number" }, "publicRead": { "default": true, "type": "boolean" } }, "required": [ "key", "type" ] } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "key": { "minLength": 1, "maxLength": 128, "pattern": "^[a-zA-Z0-9_-]{1,128}$", "description": "Unique human-readable key (alphanumeric, hyphens, underscores). Server-namespaced per owner.", "type": "string" }, "description": { "maxLength": 512, "type": "string" }, "type": { "description": "Semantic type — determines the required JSON shape of entry values", "anyOf": [ { "const": "WHITELIST", "type": "string" }, { "const": "BLACKLIST", "type": "string" }, { "const": "BOOST", "type": "string" }, { "const": "REFERRER", "type": "string" }, { "const": "FORWARDER", "type": "string" }, { "const": "APR", "type": "string" } ] }, "sizeLimit": { "minimum": 1, "maximum": 1000000, "description": "Capped at the caller's quota", "type": "number" }, "publicRead": { "default": true, "type": "boolean" } }, "required": [ "key", "type" ] } ``` ## Responses - **200** Response for status 200 ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/key-value-stores/self" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # GET /v4/key-value-stores/self _Merkl API_ List the key-value stores your creator owns (X-API-Key) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/key-value-stores/self" \ -H "x-api-key: YOUR_API_KEY" ``` --- # PATCH /v4/key-value-stores/self/{key} _Merkl API_ Update a key-value store your creator owns, by its canonical key (X-API-Key) ### Path parameters - `key` (string) _(required)_ ## Request body **`application/json`** ```json { "type": "object", "properties": { "description": { "maxLength": 512, "type": "string" }, "publicRead": { "type": "boolean" }, "sizeLimit": { "minimum": 1, "maximum": 1000000, "type": "number" } } } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "description": { "maxLength": 512, "type": "string" }, "publicRead": { "type": "boolean" }, "sizeLimit": { "minimum": 1, "maximum": 1000000, "type": "number" } } } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "description": { "maxLength": 512, "type": "string" }, "publicRead": { "type": "boolean" }, "sizeLimit": { "minimum": 1, "maximum": 1000000, "type": "number" } } } ``` ## Responses - **200** Response for status 200 ## Example request ```bash curl -X PATCH "https://api.merkl.xyz//v4/key-value-stores/self/{key}" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # DELETE /v4/key-value-stores/self/{key} _Merkl API_ Delete a key-value store your creator owns (by canonical key) and all its entries (X-API-Key) ### Path parameters - `key` (string) _(required)_ ## Responses - **200** Response for status 200 ## Example request ```bash curl -X DELETE "https://api.merkl.xyz//v4/key-value-stores/self/{key}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/creators/ _Merkl API_ Retrieves all creators matching the provided query filters ### Query parameters - `id` (string) - `address` (string) - `search` (string) - `page` (string | number) - `items` (string | number) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/creators/" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/creators/{creator} _Merkl API_ Retrieves a specific creator by their ID or slug ### Path parameters - `creator` (string) _(required)_ ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/creators/{creator}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/creators/{creator}/dashboard _Merkl API_ Returns dashboard metrics and overview for a specific creator ### Path parameters - `creator` (string) _(required)_ ### Query parameters - `test` (boolean) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/creators/{creator}/dashboard" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/creators/{creator}/opportunities/count _Merkl API_ Returns the total count of opportunities for a specific creator ### Path parameters - `creator` (string) _(required)_ ### Query parameters - `name` (string) - `search` (string) - `chainId` (RegExp) - `action` (string) - `tokenTypes` (array) - `type` (string) - `tags` (string) - `test` (boolean) - `minimumTvl` (number) - `maximumTvl` (number) - `minimumApr` (number) - `maximumApr` (number) - `minimumDailyRewards` (number) - `maximumDailyRewards` (number) - `minimumCreatedAt` (number) - `maximumCreatedAt` (number) - `status` (RegExp) - `identifier` (string) - `id` (string) - `tokens` (string) - `stablecoin` (boolean) - `rewardTokenSymbol` (string) - `sort` (RegExp) - `order` (RegExp) - `distributionTypes` (array) - `mainProtocolId` (string) - `programSlugs` (string) - `chainName` (RegExp) - `explorerAddress` (string) - `campaignTypes` (RegExp) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/creators/{creator}/opportunities/count" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/creators/{creator}/opportunities _Merkl API_ Returns paginated opportunities for a specific creator. Use page, items, and other query parameters to filter. ### Path parameters - `creator` (string) _(required)_ ### Query parameters - `page` (string | number) - `items` (string | number) - `name` (string) - `search` (string) - `chainId` (RegExp) - `action` (string) - `tokenTypes` (array) - `type` (string) - `tags` (string) - `test` (boolean) - `minimumTvl` (number) - `maximumTvl` (number) - `minimumApr` (number) - `maximumApr` (number) - `minimumDailyRewards` (number) - `maximumDailyRewards` (number) - `minimumCreatedAt` (number) - `maximumCreatedAt` (number) - `status` (RegExp) - `identifier` (string) - `id` (string) - `tokens` (string) - `stablecoin` (boolean) - `rewardTokenSymbol` (string) - `sort` (RegExp) - `order` (RegExp) - `distributionTypes` (array) - `mainProtocolId` (string) - `programSlugs` (string) - `chainName` (RegExp) - `explorerAddress` (string) - `campaignTypes` (RegExp) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/creators/{creator}/opportunities" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/creators/{creator}/campaigns _Merkl API_ Returns all campaigns for a specific creator. Filter by status query parameter. ### Path parameters - `creator` (string) _(required)_ ### Query parameters - `status` (string | string | string) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/creators/{creator}/campaigns" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/creators/campaigns/{campaignId} _Merkl API_ Returns campaign metrics for a specific campaign. Use campaignId path parameter and pagination query parameters. ### Path parameters - `campaignId` (string) _(required)_ ### Query parameters - `tvl` (object) - `apr` (object) - `dailyRewards` (object) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/creators/campaigns/{campaignId}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/creators/{creator}/opportunity/{opportunityId}/metrics _Merkl API_ Returns opportunity metrics for a specific creator and opportunity combination ### Path parameters - `creator` (string) _(required)_ - `opportunityId` (string) _(required)_ ### Query parameters - `test` (boolean) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/creators/{creator}/opportunity/{opportunityId}/metrics" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/creators/{creator}/distributed/over-time _Merkl API_ Token distribution over time for a creator, bucketed and broken down per reward token (token amounts, not USD) ### Path parameters - `creator` (string) _(required)_ ### Query parameters - `range` (string | string | string) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/creators/{creator}/distributed/over-time" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/credits/pricing _Merkl API_ Get points-campaign billing pricing constants (USD) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/credits/pricing" \ -H "x-api-key: YOUR_API_KEY" ``` --- # POST /v4/credits/estimate _Merkl API_ Estimate the credit cost of a points campaign's next run, mirroring the engine pre-check. Returns `estimatedCost` (pro-rated over the next run window, capped at 1 week) and `threshold` (= max(estimatedCost, BASE_COST_PER_WEEK), the minimum balance required for the pre-check to pass). ## Request body **`application/json`** ```json { "type": "object", "properties": { "startTimestamp": { "description": "Campaign start timestamp (unix seconds)", "type": "number" }, "endTimestamp": { "description": "Campaign end timestamp (unix seconds)", "type": "number" }, "computedUntil": { "description": "Last computed-until timestamp (unix seconds). Defaults to startTimestamp.", "type": "number" }, "recipientCount": { "minimum": 0, "description": "Expected recipient count", "type": "number" } }, "required": [ "startTimestamp", "endTimestamp" ] } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "startTimestamp": { "description": "Campaign start timestamp (unix seconds)", "type": "number" }, "endTimestamp": { "description": "Campaign end timestamp (unix seconds)", "type": "number" }, "computedUntil": { "description": "Last computed-until timestamp (unix seconds). Defaults to startTimestamp.", "type": "number" }, "recipientCount": { "minimum": 0, "description": "Expected recipient count", "type": "number" } }, "required": [ "startTimestamp", "endTimestamp" ] } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "startTimestamp": { "description": "Campaign start timestamp (unix seconds)", "type": "number" }, "endTimestamp": { "description": "Campaign end timestamp (unix seconds)", "type": "number" }, "computedUntil": { "description": "Last computed-until timestamp (unix seconds). Defaults to startTimestamp.", "type": "number" }, "recipientCount": { "minimum": 0, "description": "Expected recipient count", "type": "number" } }, "required": [ "startTimestamp", "endTimestamp" ] } ``` ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/credits/estimate" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # POST /v4/credits/estimate/total _Merkl API_ Estimate the total credit cost of a points campaign over its full duration (endTimestamp − startTimestamp). Used at creation time to gate campaign submission when the creator's balance is insufficient. Returns `estimatedCost` (full pro-rated cost) and `threshold` (= max(estimatedCost, BASE_COST_PER_WEEK)). ## Request body **`application/json`** ```json { "type": "object", "properties": { "startTimestamp": { "description": "Campaign start timestamp (unix seconds)", "type": "number" }, "endTimestamp": { "description": "Campaign end timestamp (unix seconds)", "type": "number" }, "computedUntil": { "description": "Last computed-until timestamp (unix seconds). Defaults to startTimestamp.", "type": "number" }, "recipientCount": { "minimum": 0, "description": "Expected recipient count", "type": "number" } }, "required": [ "startTimestamp", "endTimestamp" ] } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "startTimestamp": { "description": "Campaign start timestamp (unix seconds)", "type": "number" }, "endTimestamp": { "description": "Campaign end timestamp (unix seconds)", "type": "number" }, "computedUntil": { "description": "Last computed-until timestamp (unix seconds). Defaults to startTimestamp.", "type": "number" }, "recipientCount": { "minimum": 0, "description": "Expected recipient count", "type": "number" } }, "required": [ "startTimestamp", "endTimestamp" ] } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "startTimestamp": { "description": "Campaign start timestamp (unix seconds)", "type": "number" }, "endTimestamp": { "description": "Campaign end timestamp (unix seconds)", "type": "number" }, "computedUntil": { "description": "Last computed-until timestamp (unix seconds). Defaults to startTimestamp.", "type": "number" }, "recipientCount": { "minimum": 0, "description": "Expected recipient count", "type": "number" } }, "required": [ "startTimestamp", "endTimestamp" ] } ``` ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/credits/estimate/total" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # GET /v4/credits/{creatorId} _Merkl API_ Get credit balance for a creator ### Path parameters - `creatorId` (string) _(required)_ ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/credits/{creatorId}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/credits/{creatorId}/operations _Merkl API_ List credit operations for a creator ### Path parameters - `creatorId` (string) _(required)_ ### Query parameters - `page` (string | number) - `items` (string | number) - `type` (string | string) - `category` (string | string | string | string | string) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/credits/{creatorId}/operations" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/credits/{creatorId}/operations/count _Merkl API_ Get the number of credit operations for a creator, optionally filtered by type ### Path parameters - `creatorId` (string) _(required)_ ### Query parameters - `type` (string | string) - `category` (string | string | string | string | string) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/credits/{creatorId}/operations/count" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/credits/{creatorId}/operations/aggregate _Merkl API_ Get the signed sum of credit operations for a creator. SUBTRACT amounts count as negative, ADD as positive. ### Path parameters - `creatorId` (string) _(required)_ ### Query parameters - `type` (string | string) - `category` (string | string | string | string | string) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/credits/{creatorId}/operations/aggregate" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/credits/{creatorId}/operations/breakdowns _Merkl API_ Get credit operations grouped by category with signed totals. All Auto-claim labels are bucketed together. SUBTRACT amounts count as negative, ADD as positive. ### Path parameters - `creatorId` (string) _(required)_ ### Query parameters - `type` (string | string) - `category` (string | string | string | string | string) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/credits/{creatorId}/operations/breakdowns" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/credits/{creatorId}/paused-campaigns _Merkl API_ Count campaigns paused due to insufficient credits for a creator ### Path parameters - `creatorId` (string) _(required)_ ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/credits/{creatorId}/paused-campaigns" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/enums/ _Merkl API_ List all available enums in the system. Returns enum names, types (string/number), and metadata availability. ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/enums/" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/enums/{enumName} _Merkl API_ Get detailed information about a specific enum including all entries with their values, names, and descriptions (if available). ### Path parameters - `enumName` (string) _(required)_ ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/enums/{enumName}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/enums/{enumName}/convert/{value} _Merkl API_ Convert an enum value bidirectionally. For number enums: provide a number to get its string name. For string enums: provide a string to get its value. Returns the converted value with metadata if available. ### Path parameters - `enumName` (string) _(required)_ - `value` (string) _(required)_ ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/enums/{enumName}/convert/{value}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # POST /v4/enums/{enumName}/batch-convert _Merkl API_ Batch convert multiple enum values at once. Accepts an array of values and returns an array of conversion results. Maximum 100 values per request. ### Path parameters - `enumName` (string) _(required)_ ## Request body **`application/json`** ```json { "type": "object", "properties": { "values": { "description": "Array of values to convert", "minItems": 1, "maxItems": 100, "type": "array", "items": { "anyOf": [ { "type": "string" }, { "type": "number" } ] } } }, "required": [ "values" ] } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "values": { "description": "Array of values to convert", "minItems": 1, "maxItems": 100, "type": "array", "items": { "anyOf": [ { "type": "string" }, { "type": "number" } ] } } }, "required": [ "values" ] } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "values": { "description": "Array of values to convert", "minItems": 1, "maxItems": 100, "type": "array", "items": { "anyOf": [ { "type": "string" }, { "type": "number" } ] } } }, "required": [ "values" ] } ``` ## Responses - **200** Response for status 200 ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/enums/{enumName}/batch-convert" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # GET /v4/bucket/health/proofs _Merkl API_ > Check for stale proof ingestion files Always returns HTTP 200. `status` is `STALE` when any proof file is older than the staleness threshold, `OK` otherwise. Configure a keyword monitor to alert when `STALE` is present. ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/bucket/health/proofs" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/bucket/health/diff _Merkl API_ > Check for stale diff / diff-breakdown ingestion files Always returns HTTP 200. `status` is `STALE` when any diff / diff-breakdown file is older than the staleness threshold, `OK` otherwise. Configure a keyword monitor to alert when `STALE` is present. ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/bucket/health/diff" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/bucket/health/transactions _Merkl API_ > Check for stale transaction and autoclaim ingestion files Always returns HTTP 200. `status` is `STALE` when any transaction and autoclaim file is older than the staleness threshold, `OK` otherwise. Configure a keyword monitor to alert when `STALE` is present. ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/bucket/health/transactions" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/redis/health _Merkl API_ > Check monitored Redis caches for staleness Always returns HTTP 200. `status` is `STALE` when any monitored Redis cache hasn't been refreshed recently enough, `OK` otherwise. Configure a keyword monitor to alert when `STALE` is present. ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/redis/health" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/claims/health _Merkl API_ > Check claim indexing sync across all chains Always returns HTTP 200. `status` is `STALE` when claim indexing has fallen behind: either the latest on-chain claim has sat unprocessed for at least 2 hours (or no processing checkpoint exists yet), or an indexing source is at least 2 hours behind the live chain head. `OK` otherwise. Aggregated over all chains; chains whose status can't be computed (e.g. RPC unreachable) are skipped. Configure a keyword monitor to alert when `STALE` is present. ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/claims/health" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/dynamic-data/health _Merkl API_ > Check the update-dynamic-data job is running across chains Always returns HTTP 200. `status` is `STALE` when the `update-dynamic-data` job (refreshes live opportunity/campaign APR, TVL and dailyRewards) has stopped completing its full run on any chain it runs on — a chain counts as degraded when its last completed run is over 45 minutes old. Chains that have never recorded a run (newly added / not yet scheduled) aren't counted. Configure a keyword monitor to alert when `STALE` is present. ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/dynamic-data/health" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/health/live _Merkl API_ > Liveness probe — pod runtime snapshot Always returns HTTP 200. On the public API the body is `{ status: "OK" }`. On the internal instance it carries the pod's rolling 60s runtime window — mean cores against the cgroup quota, responses served, RSS against the memory limit, worst event-loop drift. Nothing here fails the probe yet: the thresholds that would identify a GC-bound pod are not calibrated against production traffic, so this ships as telemetry first. Every window is also logged as `[RUNTIME] window`. ## Responses ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/health/live" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/health/ready _Merkl API_ > Readiness probe — pod runtime snapshot Always returns HTTP 200. On the public API the body is `{ status: "OK" }`. On the internal instance it carries the pod's rolling 60s runtime window — mean cores against the cgroup quota, responses served, RSS against the memory limit, worst event-loop drift. Nothing here fails the probe yet: the thresholds that would identify a GC-bound pod are not calibrated against production traffic, so this ships as telemetry first. Every window is also logged as `[RUNTIME] window`. ## Responses - **200** Response for status 200 - **503** Response for status 503 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/health/ready" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/historical/campaign/{campaignId} _Merkl API_ > Campaign reward diff between two trees (BETA) **Historical Campaign Diff**

Computes the difference in reward breakdowns for a given campaign between two merkle trees. Each tree is selected either by date (closest root on or before the date, via startDate/endDate) or by an explicit root hash (startRoot/endRoot). Returns per-recipient, per-reason breakdown diffs with non-zero changes only.

Authentication: Requires a valid API key passed via the Authorization header.

⚠️ Beta: this endpoint is in beta. The behavior, response shape and data format may change at any time without notice. Historical data is only available from the configured cutoff date onwards.

### Path parameters - `campaignId` (string) _(required)_ ### Query parameters - `chainId` (string | number) _(required)_ - `startDate` (string) - `endDate` (string) - `startRoot` (string) - `endRoot` (string) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/historical/campaign/{campaignId}?chainId=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/historical/recipient/{recipient} _Merkl API_ > Recipient reward diff between two trees (BETA) **Historical Recipient Diff**

Computes the difference in reward breakdowns for a given recipient between two merkle trees. Each tree is selected either by date (closest root on or before the date, via startDate/endDate) or by an explicit root hash (startRoot/endRoot). Returns per-campaign, per-reason breakdown diffs with non-zero changes only.

Authentication: Requires a valid API key passed via the Authorization header.

⚠️ Beta: this endpoint is in beta. The behavior, response shape and data format may change at any time without notice. Historical data is only available from the configured cutoff date onwards.

### Path parameters - `recipient` (string) _(required)_ ### Query parameters - `chainId` (string | number) _(required)_ - `startDate` (string) - `endDate` (string) - `startRoot` (string) - `endRoot` (string) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/historical/recipient/{recipient}?chainId=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/historical/tree _Merkl API_ > Reward breakdowns of a single tree (BETA) **Historical Tree Breakdowns**

Returns the cumulative reward breakdowns of a single merkle tree, selected either by date (closest root on or before the date) or by an explicit root hash. Results can be filtered by campaignId and/or recipient.

Authentication: Requires a valid API key passed via the Authorization header.

⚠️ Beta: this endpoint is in beta. The behavior, response shape and data format may change at any time without notice. Historical data is only available from the configured cutoff date onwards.

### Query parameters - `chainId` (string | number) _(required)_ - `date` (string) - `root` (string) - `campaignId` (string) - `recipient` (string) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/historical/tree?chainId=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/leaves/{recipient}/breakdowns _Merkl API_ Returns paginated leaf breakdowns for a recipient, token, and chain, including both distributed and pending amounts. Supports filters on campaignId, opportunityId, and protocolId. ### Path parameters - `recipient` (string) _(required)_ ### Query parameters - `tokenAddress` (string) _(required)_ - `distributionChainId` (string | number) _(required)_ - `campaignId` (string) - `opportunityId` (string) - `protocolId` (array) - `page` (string | number) - `items` (string | number) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/leaves/{recipient}/breakdowns?tokenAddress=&distributionChainId=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/leaves/{recipient}/debt _Merkl API_ Returns the outstanding overdistribution clawback debt for an address, per (chain, token): owed, recovered so far, and remaining. Empty array if the address has no configured debt. ### Path parameters - `recipient` (string) _(required)_ ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/leaves/{recipient}/debt" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/roots/ _Merkl API_ Returns the last merkl roots recorded before the provided timestamps. Use chainId and timestamps query parameters to filter results. ### Query parameters - `page` (string | number) - `items` (string | number) - `chainId` (string | number) _(required)_ - `fromTimestamp` (string) _(required)_ - `toTimestamp` (string) _(required)_ ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/roots/?chainId=&fromTimestamp=&toTimestamp=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/roots/live _Merkl API_ Returns all current live Merkl root hashes across all chains. ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/roots/live" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/organizations _Merkl API_ ### Query parameters - `first` (number) - `after` (string) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/organizations" \ -H "x-api-key: YOUR_API_KEY" ``` --- # POST /v4/organizations _Merkl API_ ## Request body **`application/json`** ```json { "type": "object", "properties": { "name": { "minLength": 1, "maxLength": 128, "type": "string" } }, "required": [ "name" ] } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "name": { "minLength": 1, "maxLength": 128, "type": "string" } }, "required": [ "name" ] } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "name": { "minLength": 1, "maxLength": 128, "type": "string" } }, "required": [ "name" ] } ``` ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/organizations" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # POST /v4/organizations/bootstrap _Merkl API_ ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/organizations/bootstrap" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/organizations/permissions _Merkl API_ ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/organizations/permissions" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/organizations/invitations/pending _Merkl API_ ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/organizations/invitations/pending" \ -H "x-api-key: YOUR_API_KEY" ``` --- # POST /v4/organizations/invitations/{invitationId}/accept _Merkl API_ ### Path parameters - `invitationId` (string) _(required)_ ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/organizations/invitations/{invitationId}/accept" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/organizations/{organizationId} _Merkl API_ ### Path parameters - `organizationId` (string) _(required)_ ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/organizations/{organizationId}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # PATCH /v4/organizations/{organizationId} _Merkl API_ ### Path parameters - `organizationId` (string) _(required)_ ## Request body **`application/json`** ```json { "type": "object", "properties": { "name": { "minLength": 1, "maxLength": 128, "type": "string" } }, "required": [ "name" ] } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "name": { "minLength": 1, "maxLength": 128, "type": "string" } }, "required": [ "name" ] } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "name": { "minLength": 1, "maxLength": 128, "type": "string" } }, "required": [ "name" ] } ``` ## Example request ```bash curl -X PATCH "https://api.merkl.xyz//v4/organizations/{organizationId}" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # POST /v4/organizations/{organizationId}/dissolve _Merkl API_ ### Path parameters - `organizationId` (string) _(required)_ ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/organizations/{organizationId}/dissolve" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/organizations/{organizationId}/members _Merkl API_ ### Path parameters - `organizationId` (string) _(required)_ ### Query parameters - `first` (number) - `after` (string) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/organizations/{organizationId}/members" \ -H "x-api-key: YOUR_API_KEY" ``` --- # PATCH /v4/organizations/{organizationId}/members/{userId} _Merkl API_ ### Path parameters - `organizationId` (string) _(required)_ - `userId` (string) _(required)_ ## Request body **`application/json`** ```json { "type": "object", "properties": { "memberGroup": { "anyOf": [ { "const": "ADMIN", "type": "string" }, { "const": "OPERATOR", "type": "string" }, { "const": "VIEWER", "type": "string" } ] } }, "required": [ "memberGroup" ] } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "memberGroup": { "anyOf": [ { "const": "ADMIN", "type": "string" }, { "const": "OPERATOR", "type": "string" }, { "const": "VIEWER", "type": "string" } ] } }, "required": [ "memberGroup" ] } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "memberGroup": { "anyOf": [ { "const": "ADMIN", "type": "string" }, { "const": "OPERATOR", "type": "string" }, { "const": "VIEWER", "type": "string" } ] } }, "required": [ "memberGroup" ] } ``` ## Example request ```bash curl -X PATCH "https://api.merkl.xyz//v4/organizations/{organizationId}/members/{userId}" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # DELETE /v4/organizations/{organizationId}/members/{userId} _Merkl API_ ### Path parameters - `organizationId` (string) _(required)_ - `userId` (string) _(required)_ ## Example request ```bash curl -X DELETE "https://api.merkl.xyz//v4/organizations/{organizationId}/members/{userId}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # POST /v4/organizations/{organizationId}/leave _Merkl API_ ### Path parameters - `organizationId` (string) _(required)_ ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/organizations/{organizationId}/leave" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/organizations/{organizationId}/invitations _Merkl API_ ### Path parameters - `organizationId` (string) _(required)_ ### Query parameters - `first` (number) - `after` (string) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/organizations/{organizationId}/invitations" \ -H "x-api-key: YOUR_API_KEY" ``` --- # POST /v4/organizations/{organizationId}/invitations _Merkl API_ ### Path parameters - `organizationId` (string) _(required)_ ## Request body **`application/json`** ```json { "type": "object", "properties": { "invitee": { "type": "object", "properties": { "kind": { "anyOf": [ { "const": "WALLET", "type": "string" }, { "const": "EMAIL", "type": "string" } ] }, "value": { "minLength": 1, "maxLength": 256, "type": "string" } }, "required": [ "kind", "value" ] }, "memberGroup": { "anyOf": [ { "const": "ADMIN", "type": "string" }, { "const": "OPERATOR", "type": "string" }, { "const": "VIEWER", "type": "string" } ] } }, "required": [ "invitee", "memberGroup" ] } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "invitee": { "type": "object", "properties": { "kind": { "anyOf": [ { "const": "WALLET", "type": "string" }, { "const": "EMAIL", "type": "string" } ] }, "value": { "minLength": 1, "maxLength": 256, "type": "string" } }, "required": [ "kind", "value" ] }, "memberGroup": { "anyOf": [ { "const": "ADMIN", "type": "string" }, { "const": "OPERATOR", "type": "string" }, { "const": "VIEWER", "type": "string" } ] } }, "required": [ "invitee", "memberGroup" ] } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "invitee": { "type": "object", "properties": { "kind": { "anyOf": [ { "const": "WALLET", "type": "string" }, { "const": "EMAIL", "type": "string" } ] }, "value": { "minLength": 1, "maxLength": 256, "type": "string" } }, "required": [ "kind", "value" ] }, "memberGroup": { "anyOf": [ { "const": "ADMIN", "type": "string" }, { "const": "OPERATOR", "type": "string" }, { "const": "VIEWER", "type": "string" } ] } }, "required": [ "invitee", "memberGroup" ] } ``` ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/organizations/{organizationId}/invitations" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # DELETE /v4/organizations/{organizationId}/invitations/{invitationId} _Merkl API_ ### Path parameters - `organizationId` (string) _(required)_ - `invitationId` (string) _(required)_ ## Example request ```bash curl -X DELETE "https://api.merkl.xyz//v4/organizations/{organizationId}/invitations/{invitationId}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/organizations/{organizationId}/user-groups _Merkl API_ ### Path parameters - `organizationId` (string) _(required)_ ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/organizations/{organizationId}/user-groups" \ -H "x-api-key: YOUR_API_KEY" ``` --- # POST /v4/organizations/{organizationId}/user-groups _Merkl API_ ### Path parameters - `organizationId` (string) _(required)_ ## Request body **`application/json`** ```json { "type": "object", "properties": { "name": { "minLength": 1, "maxLength": 128, "type": "string" }, "permissions": { "maxItems": 128, "type": "array", "items": { "pattern": "^[A-Z][A-Z0-9_]*$", "maxLength": 64, "type": "string" } } }, "required": [ "name", "permissions" ] } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "name": { "minLength": 1, "maxLength": 128, "type": "string" }, "permissions": { "maxItems": 128, "type": "array", "items": { "pattern": "^[A-Z][A-Z0-9_]*$", "maxLength": 64, "type": "string" } } }, "required": [ "name", "permissions" ] } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "name": { "minLength": 1, "maxLength": 128, "type": "string" }, "permissions": { "maxItems": 128, "type": "array", "items": { "pattern": "^[A-Z][A-Z0-9_]*$", "maxLength": 64, "type": "string" } } }, "required": [ "name", "permissions" ] } ``` ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/organizations/{organizationId}/user-groups" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # PUT /v4/organizations/{organizationId}/user-groups/{userGroupId}/permissions _Merkl API_ ### Path parameters - `organizationId` (string) _(required)_ - `userGroupId` (string) _(required)_ ## Request body **`application/json`** ```json { "type": "object", "properties": { "permissions": { "maxItems": 128, "type": "array", "items": { "pattern": "^[A-Z][A-Z0-9_]*$", "maxLength": 64, "type": "string" } } }, "required": [ "permissions" ] } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "permissions": { "maxItems": 128, "type": "array", "items": { "pattern": "^[A-Z][A-Z0-9_]*$", "maxLength": 64, "type": "string" } } }, "required": [ "permissions" ] } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "permissions": { "maxItems": 128, "type": "array", "items": { "pattern": "^[A-Z][A-Z0-9_]*$", "maxLength": 64, "type": "string" } } }, "required": [ "permissions" ] } ``` ## Example request ```bash curl -X PUT "https://api.merkl.xyz//v4/organizations/{organizationId}/user-groups/{userGroupId}/permissions" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # DELETE /v4/organizations/{organizationId}/user-groups/{userGroupId} _Merkl API_ ### Path parameters - `organizationId` (string) _(required)_ - `userGroupId` (string) _(required)_ ## Example request ```bash curl -X DELETE "https://api.merkl.xyz//v4/organizations/{organizationId}/user-groups/{userGroupId}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # POST /v4/organizations/{organizationId}/user-groups/{userGroupId}/members _Merkl API_ ### Path parameters - `organizationId` (string) _(required)_ - `userGroupId` (string) _(required)_ ## Request body **`application/json`** ```json { "type": "object", "properties": { "userId": { "minLength": 1, "type": "string" } }, "required": [ "userId" ] } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "userId": { "minLength": 1, "type": "string" } }, "required": [ "userId" ] } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "userId": { "minLength": 1, "type": "string" } }, "required": [ "userId" ] } ``` ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/organizations/{organizationId}/user-groups/{userGroupId}/members" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # DELETE /v4/organizations/{organizationId}/user-groups/{userGroupId}/members/{userId} _Merkl API_ ### Path parameters - `organizationId` (string) _(required)_ - `userGroupId` (string) _(required)_ - `userId` (string) _(required)_ ## Example request ```bash curl -X DELETE "https://api.merkl.xyz//v4/organizations/{organizationId}/user-groups/{userGroupId}/members/{userId}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/node _Merkl API_ > Check if an address was already labelled as specific by the Merkl Engine. Check if a given address was already labelled as specific by the Merkl Engine. Use this endpoint to check for forwarders, pools, etc. ### Query parameters - `chainId` (string | number) - `addressOrId` (string) _(required)_ ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/node?addressOrId=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/node/protocols _Merkl API_ Get the list of protocols that have indexed node sources available. ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/node/protocols" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/node/protocol/{protocol}/chains _Merkl API_ Get the chains configured for a specific protocol. ### Path parameters - `protocol` (string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string) _(required)_ ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/node/protocol/{protocol}/chains" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/node/last-block _Merkl API_ Get the most recent indexed block. If a protocol is provided, returns it for that protocol on the given chain; otherwise returns it for the chain across all protocols. ### Query parameters - `chainId` (string | number) _(required)_ - `protocol` (string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string) ## Responses ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/node/last-block?chainId=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/node/oldest-block _Merkl API_ Get the oldest indexed block. If a protocol is provided, returns it for that protocol on the given chain; otherwise returns it for the chain across all protocols. ### Query parameters - `chainId` (string | number) _(required)_ - `protocol` (string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string) ## Responses ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/node/oldest-block?chainId=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/node/block-difference _Merkl API_ Get the block difference (oldest to current). If a protocol is provided, returns it for that protocol on the given chain; otherwise returns it for the chain across all protocols. ### Query parameters - `chainId` (string | number) _(required)_ - `protocol` (string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string | string) ## Responses ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/node/block-difference?chainId=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/offchain-billing/self/owed _Merkl API_ Your creator's off-chain owed position (X-API-Key): credit balance, pending accrual, owed, and a per-campaign pending breakdown. Scoped to your own key. ## Responses - **200** Off-chain owed position for a client ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/offchain-billing/self/owed" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/offchain-billing/self/pending _Merkl API_ Just your creator's pending offchain accrual (X-API-Key) — the USD not yet debited. ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/offchain-billing/self/pending" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/offchain-billing/self/operations _Merkl API_ Your creator's offchain accrual debits (X-API-Key), newest first. ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/offchain-billing/self/operations" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/opportunities/{id} _Merkl API_ Get a unique opportunity by ID (hash) or composite identifier (chainId-type-identifier). Use query parameters to include campaigns or filter results. ### Path parameters - `id` (string) _(required)_ ### Query parameters - `test` (boolean) - `point` (boolean) - `tokenTypes` (array) - `campaigns` (boolean) - `excludeSubCampaigns` (boolean) ## Responses - **200** Response for status 200 - **404** Response for status 404 - **500** Response for status 500 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/opportunities/{id}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/opportunities/{id}/storing-ids _Merkl API_ Get the storingId(s) backing an opportunity, grouped by root campaign. Returns one tree per root campaign matching the requested statuses (default `LIVE`; past root campaigns are excluded). Each node lists its own storingIds, with sub-campaigns nested under `subCampaigns`. Sub-campaigns of a matching root are not status-filtered. The same storingId may appear in more than one tree. Stateless campaigns return an empty `storingIds` array. ### Path parameters - `id` (string) _(required)_ ### Query parameters - `campaignStatus` (RegExp) ## Responses - **200** Response for status 200 - **404** Response for status 404 - **500** Response for status 500 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/opportunities/{id}/storing-ids" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/opportunities/{id}/campaigns _Merkl API_ Get a unique opportunity by ID (hash) or composite identifier (chainId-type-identifier), including related campaigns. Use query parameters to filter results. ### Path parameters - `id` (string) _(required)_ ### Query parameters - `test` (boolean) - `point` (boolean) - `tokenTypes` (array) - `campaigns` (boolean) - `excludeSubCampaigns` (boolean) ## Responses - **200** Response for status 200 - **404** Response for status 404 - **500** Response for status 500 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/opportunities/{id}/campaigns" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/opportunities/{id}/apr-records _Merkl API_ Get historical APR records for an opportunity. ### Path parameters - `id` (string) _(required)_ ### Query parameters - `page` (string | number) - `items` (string | number) - `from` (number) - `to` (number) - `grain` (string | string | string | string) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/opportunities/{id}/apr-records" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/opportunities/{id}/tvl-records _Merkl API_ Get historical TVL records for an opportunity. ### Path parameters - `id` (string) _(required)_ ### Query parameters - `page` (string | number) - `items` (string | number) - `from` (number) - `to` (number) - `grain` (string | string | string | string) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/opportunities/{id}/tvl-records" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/opportunities/{id}/native-apr-records _Merkl API_ Get historical native APR records for an opportunity. ### Path parameters - `id` (string) _(required)_ ### Query parameters - `page` (string | number) - `items` (string | number) - `from` (number) - `to` (number) - `grain` (string | string | string | string) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/opportunities/{id}/native-apr-records" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/opportunities/ _Merkl API_ **Retrieve Multiple Opportunities**

This endpoint enables you to search for opportunities by providing specific criteria through query parameters.

#### Request Examples ##### List opportunities by protocol name ```bash curl "https://api.merkl.xyz/v4/opportunities?name=Euler" ``` Returns all opportunities associated with the Euler protocol, including aggregate metrics. ##### Filter by tag ```bash curl "https://api.merkl.xyz/v4/opportunities?tags=zksync" ``` Returns opportunities tagged with "zksync" across all supported protocols. ### Query parameters - `page` (string | number) - `items` (string | number) - `name` (string) - `search` (string) - `campaignId` (string) - `creatorSlug` (string) - `chainId` (RegExp) - `action` (string) - `tokenTypes` (array) - `point` (boolean) - `type` (string) - `creatorAddress` (string) - `tags` (string) - `test` (boolean) - `minimumTvl` (number) - `maximumTvl` (number) - `minimumApr` (number) - `maximumApr` (number) - `minimumDailyRewards` (number) - `maximumDailyRewards` (number) - `minimumCreatedAt` (number) - `maximumCreatedAt` (number) - `status` (RegExp) - `identifier` (string) - `id` (string) - `campaigns` (boolean) - `tokens` (string) - `stablecoin` (boolean) - `rewardTokenSymbol` (string) - `sort` (RegExp) - `order` (RegExp) - `distributionTypes` (array) - `mainProtocolId` (string) - `programSlugs` (string) - `chainName` (RegExp) - `excludeSubCampaigns` (boolean) - `explorerAddress` (string) - `withInvalids` (boolean) - `campaignTypes` (RegExp) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/opportunities/" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/opportunities/count _Merkl API_ Get the count of opportunities corresponding to the query. ### Query parameters - `page` (string | number) - `items` (string | number) - `name` (string) - `search` (string) - `campaignId` (string) - `creatorSlug` (string) - `chainId` (RegExp) - `action` (string) - `tokenTypes` (array) - `point` (boolean) - `type` (string) - `creatorAddress` (string) - `tags` (string) - `test` (boolean) - `minimumTvl` (number) - `maximumTvl` (number) - `minimumApr` (number) - `maximumApr` (number) - `minimumDailyRewards` (number) - `maximumDailyRewards` (number) - `minimumCreatedAt` (number) - `maximumCreatedAt` (number) - `status` (RegExp) - `identifier` (string) - `id` (string) - `campaigns` (boolean) - `tokens` (string) - `stablecoin` (boolean) - `rewardTokenSymbol` (string) - `sort` (RegExp) - `order` (RegExp) - `distributionTypes` (array) - `mainProtocolId` (string) - `programSlugs` (string) - `chainName` (RegExp) - `excludeSubCampaigns` (boolean) - `explorerAddress` (string) - `withInvalids` (boolean) - `campaignTypes` (RegExp) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/opportunities/count" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/opportunities/bins/apr _Merkl API_ Get the APR distribution of opportunities matching the query, grouped into bins. ### Query parameters - `page` (string | number) - `items` (string | number) - `name` (string) - `search` (string) - `campaignId` (string) - `creatorSlug` (string) - `chainId` (RegExp) - `action` (string) - `tokenTypes` (array) - `point` (boolean) - `type` (string) - `creatorAddress` (string) - `tags` (string) - `test` (boolean) - `minimumTvl` (number) - `maximumTvl` (number) - `minimumApr` (number) - `maximumApr` (number) - `minimumDailyRewards` (number) - `maximumDailyRewards` (number) - `minimumCreatedAt` (number) - `maximumCreatedAt` (number) - `status` (RegExp) - `identifier` (string) - `id` (string) - `campaigns` (boolean) - `tokens` (string) - `stablecoin` (boolean) - `rewardTokenSymbol` (string) - `sort` (RegExp) - `order` (RegExp) - `distributionTypes` (array) - `mainProtocolId` (string) - `programSlugs` (string) - `chainName` (RegExp) - `excludeSubCampaigns` (boolean) - `explorerAddress` (string) - `withInvalids` (boolean) - `campaignTypes` (RegExp) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/opportunities/bins/apr" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/opportunities/bins/tvl _Merkl API_ Get the TVL distribution of opportunities matching the query, grouped into bins. ### Query parameters - `page` (string | number) - `items` (string | number) - `name` (string) - `search` (string) - `campaignId` (string) - `creatorSlug` (string) - `chainId` (RegExp) - `action` (string) - `tokenTypes` (array) - `point` (boolean) - `type` (string) - `creatorAddress` (string) - `tags` (string) - `test` (boolean) - `minimumTvl` (number) - `maximumTvl` (number) - `minimumApr` (number) - `maximumApr` (number) - `minimumDailyRewards` (number) - `maximumDailyRewards` (number) - `minimumCreatedAt` (number) - `maximumCreatedAt` (number) - `status` (RegExp) - `identifier` (string) - `id` (string) - `campaigns` (boolean) - `tokens` (string) - `stablecoin` (boolean) - `rewardTokenSymbol` (string) - `sort` (RegExp) - `order` (RegExp) - `distributionTypes` (array) - `mainProtocolId` (string) - `programSlugs` (string) - `chainName` (RegExp) - `excludeSubCampaigns` (boolean) - `explorerAddress` (string) - `withInvalids` (boolean) - `campaignTypes` (RegExp) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/opportunities/bins/tvl" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/opportunities/bins/daily-rewards _Merkl API_ Get the daily rewards distribution of opportunities matching the query, grouped into bins. ### Query parameters - `page` (string | number) - `items` (string | number) - `name` (string) - `search` (string) - `campaignId` (string) - `creatorSlug` (string) - `chainId` (RegExp) - `action` (string) - `tokenTypes` (array) - `point` (boolean) - `type` (string) - `creatorAddress` (string) - `tags` (string) - `test` (boolean) - `minimumTvl` (number) - `maximumTvl` (number) - `minimumApr` (number) - `maximumApr` (number) - `minimumDailyRewards` (number) - `maximumDailyRewards` (number) - `minimumCreatedAt` (number) - `maximumCreatedAt` (number) - `status` (RegExp) - `identifier` (string) - `id` (string) - `campaigns` (boolean) - `tokens` (string) - `stablecoin` (boolean) - `rewardTokenSymbol` (string) - `sort` (RegExp) - `order` (RegExp) - `distributionTypes` (array) - `mainProtocolId` (string) - `programSlugs` (string) - `chainName` (RegExp) - `excludeSubCampaigns` (boolean) - `explorerAddress` (string) - `withInvalids` (boolean) - `campaignTypes` (RegExp) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/opportunities/bins/daily-rewards" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/opportunities/aggregate/{field} _Merkl API_ Aggregate opportunities by a specific field (chainId, protocolId, tags, etc.). ### Path parameters - `field` (string | string | string) _(required)_ ### Query parameters - `page` (string | number) - `items` (string | number) - `name` (string) - `search` (string) - `campaignId` (string) - `creatorSlug` (string) - `chainId` (RegExp) - `action` (string) - `tokenTypes` (array) - `point` (boolean) - `type` (string) - `creatorAddress` (string) - `tags` (string) - `test` (boolean) - `minimumTvl` (number) - `maximumTvl` (number) - `minimumApr` (number) - `maximumApr` (number) - `minimumDailyRewards` (number) - `maximumDailyRewards` (number) - `minimumCreatedAt` (number) - `maximumCreatedAt` (number) - `status` (RegExp) - `identifier` (string) - `id` (string) - `campaigns` (boolean) - `tokens` (string) - `stablecoin` (boolean) - `rewardTokenSymbol` (string) - `sort` (RegExp) - `order` (RegExp) - `distributionTypes` (array) - `mainProtocolId` (string) - `programSlugs` (string) - `chainName` (RegExp) - `excludeSubCampaigns` (boolean) - `explorerAddress` (string) - `withInvalids` (boolean) - `campaignTypes` (RegExp) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/opportunities/aggregate/{field}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/opportunities/aggregate/max/{field} _Merkl API_ Get the maximum value for a specific field across filtered opportunities. ### Path parameters - `field` (string | string | string) _(required)_ ### Query parameters - `page` (string | number) - `items` (string | number) - `name` (string) - `search` (string) - `campaignId` (string) - `creatorSlug` (string) - `chainId` (RegExp) - `action` (string) - `tokenTypes` (array) - `point` (boolean) - `type` (string) - `creatorAddress` (string) - `tags` (string) - `test` (boolean) - `minimumTvl` (number) - `maximumTvl` (number) - `minimumApr` (number) - `maximumApr` (number) - `minimumDailyRewards` (number) - `maximumDailyRewards` (number) - `minimumCreatedAt` (number) - `maximumCreatedAt` (number) - `status` (RegExp) - `identifier` (string) - `id` (string) - `campaigns` (boolean) - `tokens` (string) - `stablecoin` (boolean) - `rewardTokenSymbol` (string) - `sort` (RegExp) - `order` (RegExp) - `distributionTypes` (array) - `mainProtocolId` (string) - `programSlugs` (string) - `chainName` (RegExp) - `excludeSubCampaigns` (boolean) - `explorerAddress` (string) - `withInvalids` (boolean) - `campaignTypes` (RegExp) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/opportunities/aggregate/max/{field}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/opportunities/aggregate/min/{field} _Merkl API_ Get the minimum value for a specific field across filtered opportunities. ### Path parameters - `field` (string | string | string) _(required)_ ### Query parameters - `page` (string | number) - `items` (string | number) - `name` (string) - `search` (string) - `campaignId` (string) - `creatorSlug` (string) - `chainId` (RegExp) - `action` (string) - `tokenTypes` (array) - `point` (boolean) - `type` (string) - `creatorAddress` (string) - `tags` (string) - `test` (boolean) - `minimumTvl` (number) - `maximumTvl` (number) - `minimumApr` (number) - `maximumApr` (number) - `minimumDailyRewards` (number) - `maximumDailyRewards` (number) - `minimumCreatedAt` (number) - `maximumCreatedAt` (number) - `status` (RegExp) - `identifier` (string) - `id` (string) - `campaigns` (boolean) - `tokens` (string) - `stablecoin` (boolean) - `rewardTokenSymbol` (string) - `sort` (RegExp) - `order` (RegExp) - `distributionTypes` (array) - `mainProtocolId` (string) - `programSlugs` (string) - `chainName` (RegExp) - `excludeSubCampaigns` (boolean) - `explorerAddress` (string) - `withInvalids` (boolean) - `campaignTypes` (RegExp) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/opportunities/aggregate/min/{field}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/sofr-ratchet/campaign/{campaignId} _Merkl API_ Current SOFR-spread-ratchet rate for a campaign, resolved from its own stored distribution settings (opportunityId, anchor, curve). Returns `campaignApr` (the baseline target or the boost gap, per the campaign's curve) plus the shared context: frozen mean TVL, last/next recalc, live SOFR, and the baseline/boosted APYs. ### Path parameters - `campaignId` (string) _(required)_ ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/sofr-ratchet/campaign/{campaignId}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/sofr-ratchet/opportunity/{id} _Merkl API_ Current SOFR-spread-ratchet rate for an opportunity at an explicit anchor: frozen 14-day-mean TVL with last/next recalc, live SOFR, and the resulting baseline/boosted APYs. Computed on the fly from the same shared logic the engine distributes against. ### Path parameters - `id` (string) _(required)_ ### Query parameters - `anchor` (number) _(required)_ - `at` (number) - `period` (number) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/sofr-ratchet/opportunity/{id}?anchor=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # POST /v4/overrides/check _Merkl API_ Validate if an override is allowed. Checks: (1) rewardToken cannot change, (2) amount cannot change, (3) startTimestamp cannot change after campaign starts, (4) endTimestamp must be in future. ## Request body **`application/json`** ```json { "type": "object", "properties": { "oldConfig": {}, "newConfig": {} }, "required": [ "oldConfig", "newConfig" ] } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "oldConfig": {}, "newConfig": {} }, "required": [ "oldConfig", "newConfig" ] } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "oldConfig": {}, "newConfig": {} }, "required": [ "oldConfig", "newConfig" ] } ``` ## Responses - **200** Response for status 200 ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/overrides/check" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # GET /v4/overrides/{id}/previous-configs _Merkl API_ > Get previous campaign configs Get all previous configs for a campaign, showing the opportunity each config was linked to before overrides ### Path parameters - `id` (string) _(required)_ ## Responses - **200** Array of previous campaign configurations with their linked opportunities ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/overrides/{id}/previous-configs" \ -H "x-api-key: YOUR_API_KEY" ``` --- # POST /v4/campaigns/{id}/access/grant _Merkl API_ Grant access to a private campaign for a specific address. ## Request body **`application/json`** ```json { "type": "object", "properties": { "userAddress": { "pattern": "^0x[a-fA-F0-9]{40}$", "description": "Ethereum address to grant/revoke access", "type": "string" }, "canManageAccess": { "description": "Whether the user can manage (grant/revoke) access for others", "default": false, "type": "boolean" } }, "required": [ "userAddress" ] } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "userAddress": { "pattern": "^0x[a-fA-F0-9]{40}$", "description": "Ethereum address to grant/revoke access", "type": "string" }, "canManageAccess": { "description": "Whether the user can manage (grant/revoke) access for others", "default": false, "type": "boolean" } }, "required": [ "userAddress" ] } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "userAddress": { "pattern": "^0x[a-fA-F0-9]{40}$", "description": "Ethereum address to grant/revoke access", "type": "string" }, "canManageAccess": { "description": "Whether the user can manage (grant/revoke) access for others", "default": false, "type": "boolean" } }, "required": [ "userAddress" ] } ``` ## Responses - **200** Response for status 200 ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/campaigns/{id}/access/grant" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # DELETE /v4/campaigns/{id}/access/revoke _Merkl API_ Revoke access to a private campaign for a specific address. ## Request body **`application/json`** ```json { "type": "object", "properties": { "userAddress": { "pattern": "^0x[a-fA-F0-9]{40}$", "description": "Ethereum address to grant/revoke access", "type": "string" }, "canManageAccess": { "description": "Whether the user can manage (grant/revoke) access for others", "default": false, "type": "boolean" } }, "required": [ "userAddress" ] } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "userAddress": { "pattern": "^0x[a-fA-F0-9]{40}$", "description": "Ethereum address to grant/revoke access", "type": "string" }, "canManageAccess": { "description": "Whether the user can manage (grant/revoke) access for others", "default": false, "type": "boolean" } }, "required": [ "userAddress" ] } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "userAddress": { "pattern": "^0x[a-fA-F0-9]{40}$", "description": "Ethereum address to grant/revoke access", "type": "string" }, "canManageAccess": { "description": "Whether the user can manage (grant/revoke) access for others", "default": false, "type": "boolean" } }, "required": [ "userAddress" ] } ``` ## Responses - **200** Response for status 200 ## Example request ```bash curl -X DELETE "https://api.merkl.xyz//v4/campaigns/{id}/access/revoke" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # GET /v4/campaigns/{id}/access/list _Merkl API_ List all addresses currently granted access to a private campaign. ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/campaigns/{id}/access/list" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/programs/ _Merkl API_ **Retrieve Multiple Programs**

This endpoint enables you to search for programs by providing specific criteria through query parameters.

### Query parameters - `page` (string | number) - `items` (string | number) - `name` (string) - `slugs` (string) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/programs/" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/programs/{idOrSlug} _Merkl API_ **Retrieve a Program by id or slug**

This endpoint enables you to search for a program by providing its unique ID or slug identifier in the idOrSlug path parameter.

### Path parameters - `idOrSlug` (string) _(required)_ ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/programs/{idOrSlug}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/programs/count _Merkl API_ Get the total count of all programs. ### Query parameters - `page` (string | number) - `items` (string | number) - `name` (string) - `slugs` (string) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/programs/count" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/protocols/ _Merkl API_ List protocols supported and integrated by Merkl. Use query parameters to filter by chain ID, tags, or other criteria. ### Query parameters - `page` (string | number) - `items` (string | number) - `id` (string) - `test` (boolean) - `ids` (array) - `name` (string) - `tags` (array) - `opportunityTag` (string) - `opportunityChain` (string | number) - `status` (RegExp) - `tokenTypes` (array) - `excludeSubCampaigns` (boolean) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/protocols/" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/protocols/count _Merkl API_ Get the number of protocols corresponding to the query. Use query parameters to filter the count. ### Query parameters - `page` (string | number) - `items` (string | number) - `id` (string) - `test` (boolean) - `ids` (array) - `name` (string) - `tags` (array) - `opportunityTag` (string) - `opportunityChain` (string | number) - `status` (RegExp) - `tokenTypes` (array) - `excludeSubCampaigns` (boolean) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/protocols/count" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/protocols/{id} _Merkl API_ Get a protocol by its ID or name using id path parameter. This endpoint searches by protocol ID first, then falls back to name. ### Path parameters - `id` (string) _(required)_ ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/protocols/{id}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/reallocations/breakdown _Merkl API_ **Retrieve Reallocation Breakdown**

Returns aggregated reallocation amounts grouped by recipient and campaign.

Filters only successful reallocation operations (reason = "reallocation").

Use query parameters to filter by chain, recipients, and campaigns.

Authentication: Requires either a valid x-api-key header or the back-office bearer secret.

### Query parameters - `chainId` (string | number) _(required)_ - `to` (array) - `campaignIds` (array) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/reallocations/breakdown?chainId=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/reallocations/report/{campaignId} _Merkl API_ > Retrace a campaign's reallocations (BETA) **Reallocation Report**

Retraces every reallocation ever applied to a campaign, from its latest merkle tree — the newest root whose tree the tree service holds, named in atRoot. The distribution chain is inferred from the campaign ID.

A reallocation is a two-step protocol recorded in the tree, and it never changes a campaign's total. Step 1 shrinks the original recipient's leaves to what it had already claimed and stages a marker at the zero address (tmp_reallocation_{from}_{to}). Step 2 drains that marker and either credits the destination (reallocation for a creator request, unclaimedRewards for a wipe sweep) or returns the reward to the original recipient (fail_reallocation / fail_wipe) when it claimed in between.

Drained markers survive at amount zero, so the senders roster and the inFlight, returned and per-destination settled amounts are exact.

Not available here: per-sender settled and stagedAt (walk is null). A destination's leaf aggregates every sender and a drained marker has no amount left, so recovering those means one tree read per root — see POST /v4/reallocations/report/{campaignId}/walk.

Authentication: Requires either a valid x-api-key header or the back-office bearer secret.

⚠️ Beta: this endpoint is in beta. The behavior, response shape and data format may change at any time without notice.

### Path parameters - `campaignId` (string) _(required)_ ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/reallocations/report/{campaignId}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # POST /v4/reallocations/report/{campaignId}/walk _Merkl API_ > Start a reallocation walk job (BETA) **Start a Reallocation Walk**

Starts a background walk of the historical merkle trees and returns a job ID immediately. Poll GET /v4/reallocations/jobs/{jobId}; once status is completed, result holds the report with each sender's settled amount and stagedAt waves filled in.

Amounts come from each marker's positive jumps between consecutive roots — a marker goes 0 → staged → 0, possibly over several waves, so only the rises are staging events.

No window to position. It covers the campaign's whole root history: settled + returned + inFlight only ever grows, so equal endpoints prove nothing happened between them and the walk bisects straight to the roots where a wave landed. Cost is O(waves × log roots) tree reads rather than one per root — a few dozen for a typical campaign, however long the chain's history.

A walk that cannot account for every wave returns walk.complete: false with an incompleteReason, and omits per-sender settled rather than reporting an understated amount as exact. That happens if it exhausts its snapshot budget, if the markers disagree with the staged total (a reallocation leaf edited outside the producer), or if the tree service holds no tree for the older roots a wave landed in.

Partly converted history. The tree service serves the roots its backfill has converted, so the walk covers the newest run of roots it can read and says in incompleteReason how many older ones it had to drop. When that run still starts before the campaign's first wave the report is exact all the same, and walk.oldestRoot names the oldest tree actually read.

The job ID is the campaign, so repeating a request joins the running or finished job instead of starting a second walk — that dedup runs before any tree read. A failed job is retried. Job state lives in Redis for 30 minutes.

Authentication: Requires either a valid x-api-key header or the back-office bearer secret.

⚠️ Beta: this endpoint is in beta. The behavior, response shape and data format may change at any time without notice.

### Path parameters - `campaignId` (string) _(required)_ ## Responses - **200** Response for status 200 ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/reallocations/report/{campaignId}/walk" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/reallocations/jobs/{jobId} _Merkl API_ > Poll a reallocation walk job (BETA) **Poll a Reallocation Walk**

Returns the status of a walk started by POST /v4/reallocations/report/{campaignId}/walk. progress is a percentage of the waves attributed; once status is completed, result holds the report. Check result.walk.complete before trusting per-sender amounts.

State lives in Redis for 30 minutes and is not persisted, so an unknown or expired job ID answers 404 — start a new walk. Any pod can serve the poll, not just the one running the walk.

Authentication: Requires either a valid x-api-key header or the back-office bearer secret.

⚠️ Beta: this endpoint is in beta. The behavior, response shape and data format may change at any time without notice.

### Path parameters - `jobId` (string) _(required)_ ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/reallocations/jobs/{jobId}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/referral/code _Merkl API_ Returns the transaction to register user as a referrer on-chain and some additional state using chainId, referralKey, and address query parameters ### Query parameters - `chainId` (number) _(required)_ - `referralKey` (string) _(required)_ - `address` (string) _(required)_ ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/referral/code?chainId=&referralKey=&address=
" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/referral/redeem _Merkl API_ Returns the transaction to redeem a referral code on-chain and some additional state using chainId, referralKey, and code query parameters ### Query parameters - `chainId` (number) _(required)_ - `referralKey` (string) _(required)_ - `code` (string) _(required)_ ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/referral/redeem?chainId=&referralKey=&code=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/rewards/ _Merkl API_ Returns all the addresses that received rewards for a given campaign, sorted by descending amounts. Use query parameters to filter by campaign ID, chain ID, and pagination. ### Query parameters - `page` (string | number) - `items` (string | number) - `chainId` (string | number) _(required)_ - `campaignId` (string) _(required)_ - `hide` (boolean) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/rewards/?chainId=&campaignId=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/rewards/total _Merkl API_ Returns the total amount distributed for a given campaign. Use query parameters to specify campaign ID and chain ID. ### Query parameters - `page` (string | number) - `items` (string | number) - `chainId` (string | number) _(required)_ - `campaignId` (string) _(required)_ - `hide` (boolean) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/rewards/total?chainId=&campaignId=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/rewards/count _Merkl API_ Returns the number of recipients that earned rewards in a given campaign. Use query parameters to specify campaign ID and chain ID. ### Query parameters - `page` (string | number) - `items` (string | number) - `chainId` (string | number) _(required)_ - `campaignId` (string) _(required)_ - `hide` (boolean) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/rewards/count?chainId=&campaignId=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/rewards/rank _Merkl API_ Returns the rank of a user in a campaign leaderboard, based on reward amount. Returns an empty array if the rank is not yet available. ### Query parameters - `chainId` (string | number) _(required)_ - `campaignId` (string) _(required)_ - `userAddress` (string) _(required)_ - `hide` (boolean) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/rewards/rank?chainId=&campaignId=&userAddress=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/rewards/campaign/{campaignId}/list _Merkl API_ Get rewards for a specific campaign by campaignId path parameter and list of addresses ### Path parameters - `campaignId` (string) _(required)_ ### Query parameters - `addresses` (array) _(required)_ ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/rewards/campaign/{campaignId}/list?addresses=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/rewards/campaign/{campaignId}/mine _Merkl API_ Returns **your own** rewards for one campaign — settled, claimed, still-claimable and pending — with the per-reason rows behind them, plus the campaign and its `opportunityId`. Pass the `recipient` you are asking about, and authenticate as it: a JWT for that wallet, or an API key whose creator holds the address. Any other address receives 401. Because the response only ever contains one address's own amounts, it serves campaigns the leaderboard routes refuse: campaigns that hide their leaderboard, and campaigns paying a reward token Merkl hides from the reward endpoints. Campaign privacy still applies — a private campaign is visible only to its creator, an address holding a grant, and its own recipients. `chainId` is only needed when the same on-chain campaignId exists on several chains; the route returns 400 naming those chains when it does. ### Path parameters - `campaignId` (string) _(required)_ ### Query parameters - `chainId` (string | number) - `recipient` (string) _(required)_ ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/rewards/campaign/{campaignId}/mine?recipient=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/rewards/token/ _Merkl API_ Returns all the addresses that received rewards of a given token, sorted by descending amounts. Use query parameters to specify token address, chain ID, and pagination. ### Query parameters - `page` (string | number) - `items` (string | number) - `chainId` (string | number) _(required)_ - `address` (string) _(required)_ - `recipient` (string) - `excludeAddresses` (string | array) - `test` (boolean) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/rewards/token/?chainId=&address=
" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/rewards/token/total _Merkl API_ Returns the total amount distributed for a given token. Use query parameters to specify token address and chain ID. ### Query parameters - `page` (string | number) - `items` (string | number) - `chainId` (string | number) _(required)_ - `address` (string) _(required)_ - `recipient` (string) - `excludeAddresses` (string | array) - `test` (boolean) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/rewards/token/total?chainId=&address=
" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/rewards/token/count _Merkl API_ Returns the number of recipients that earned rewards in a given token. Use query parameters to specify token address and chain ID. ### Query parameters - `page` (string | number) - `items` (string | number) - `chainId` (string | number) _(required)_ - `address` (string) _(required)_ - `recipient` (string) - `excludeAddresses` (string | array) - `test` (boolean) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/rewards/token/count?chainId=&address=
" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/rewards/token/unclaimed _Merkl API_ Returns the total unclaimed amount (distributed minus claimed) for a given token. Use query parameters to specify token address and chain ID. ### Query parameters - `page` (string | number) - `items` (string | number) - `chainId` (string | number) _(required)_ - `address` (string) _(required)_ - `recipient` (string) - `excludeAddresses` (string | array) - `test` (boolean) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/rewards/token/unclaimed?chainId=&address=
" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/rewards/unclaim/ _Merkl API_ Returns the total of unclaimed rewards for given campaigns. Use query parameters to specify chain ID and campaign IDs. ### Query parameters - `chainId` (string | number) _(required)_ - `campaignIds` (string | array) _(required)_ ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/rewards/unclaim/?chainId=&campaignIds=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # POST /v4/rewards/unclaim/batch _Merkl API_ Returns unclaimed rewards for campaigns across multiple chains in a single call. Pass an array of `{ campaignId, distributionChainId }` objects. ## Request body **`application/json`** ```json { "type": "object", "properties": { "campaigns": { "type": "array", "items": { "type": "object", "properties": { "campaignId": { "description": "Onchain campaign ID (32-byte hex hash). See [ID types](/integrate-merkl/id-types).", "type": "string" }, "distributionChainId": { "description": "Distribution chain ID (numeric, e.g. `1` for Ethereum). See [ID types](/integrate-merkl/id-types).", "anyOf": [ { "format": "numeric", "default": 0, "type": "string" }, { "description": "Distribution chain ID (numeric, e.g. `1` for Ethereum). See [ID types](/integrate-merkl/id-types).", "type": "number" } ] } }, "required": [ "campaignId", "distributionChainId" ] } } }, "required": [ "campaigns" ], "additionalProperties": false } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "campaigns": { "type": "array", "items": { "type": "object", "properties": { "campaignId": { "description": "Onchain campaign ID (32-byte hex hash). See [ID types](/integrate-merkl/id-types).", "type": "string" }, "distributionChainId": { "description": "Distribution chain ID (numeric, e.g. `1` for Ethereum). See [ID types](/integrate-merkl/id-types).", "anyOf": [ { "format": "numeric", "default": 0, "type": "string" }, { "description": "Distribution chain ID (numeric, e.g. `1` for Ethereum). See [ID types](/integrate-merkl/id-types).", "type": "number" } ] } }, "required": [ "campaignId", "distributionChainId" ] } } }, "required": [ "campaigns" ], "additionalProperties": false } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "campaigns": { "type": "array", "items": { "type": "object", "properties": { "campaignId": { "description": "Onchain campaign ID (32-byte hex hash). See [ID types](/integrate-merkl/id-types).", "type": "string" }, "distributionChainId": { "description": "Distribution chain ID (numeric, e.g. `1` for Ethereum). See [ID types](/integrate-merkl/id-types).", "anyOf": [ { "format": "numeric", "default": 0, "type": "string" }, { "description": "Distribution chain ID (numeric, e.g. `1` for Ethereum). See [ID types](/integrate-merkl/id-types).", "type": "number" } ] } }, "required": [ "campaignId", "distributionChainId" ] } } }, "required": [ "campaigns" ], "additionalProperties": false } ``` ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/rewards/unclaim/batch" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # GET /v4/confidential-rewards/zama _Merkl API_ Returns a campaign's leaderboard with every reward amount FHE-encrypted for Zama's confidential distribution: `(recipient, encryptedAmount, inputProof)` triples ready for `FHE.fromExternal`. Merkl performs the encryption, so the distributor never receives a cleartext amount. **Only amounts are confidential.** Recipient addresses are returned in cleartext — the distributor must know who to pay. The participant set and count are therefore visible. **The proof binding is fixed by this route**, not parameters. Every proof binds one Merkl-fixed address as both the consuming contract and the submitting `msg.sender`, alongside the chain ID and the chain's fhEVM ACL address. Both are echoed back as `contractAddress` and `userAddress`, so you can always see what your proofs are bound to. The address is currently a placeholder, so proofs are structurally valid but NOT redeemable on-chain — they exercise the pipeline only. A proof cannot be rebound afterwards; it has to be re-encrypted. **One proof covers several recipients** — `recipientsPerProof` (32 for `euint64`) entries share an `inputProof`, grouped by `proofId`. Submit a group together, or pass the same proof in each of several transactions; both verify. **Amounts must fit the FHE type.** `euint64` (the ERC-7984 type, and the default) caps one amount at 2^64-1, which is only ~18.44 tokens of an 18-decimal reward. Use `amountDecimals` to request a coarser denomination, or a wider `fheType`. Overflow returns 422 and never wraps. Amounts are settled committed rewards, aggregated per recipient across reasons. Uncommitted engine state (TempLeaves) is excluded. Private campaigns are refused. Campaigns that hide their leaderboard ARE supported — the encryption is what keeps the leaderboard hidden. Results are ordered by recipient address, which is independent of the amounts — an amount-ordered response would reveal the full ranking even though every amount is encrypted. Not configurable, and it makes paging repeatable. ### Query parameters - `page` (string | number) - `items` (string | number) - `chainId` (string | number) _(required)_ - `campaignId` (string) _(required)_ - `fheChainId` (string | number) - `fheType` (string | string | string) - `amountDecimals` (string | number) - `hide` (boolean) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/confidential-rewards/zama?chainId=&campaignId=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/schemas/ _Merkl API_ > List schema groups (BETA) **List schema groups**

Returns every schema group exposed by this API. A group binds an enum (e.g. CampaignType) to a family of TypeBox JSON Schemas describing the inputs the Merkl engine accepts for each value of that enum.

Current groups:

  • campaignType — config inputs of a campaign of a given CampaignType.
  • campaignTypeExtended — same as campaignType plus distributionChainId and campaignId.
  • distributionMethod — parameters of a given DistributionMethod.
  • computeScoreMethod — parameters of a given ComputeScoreMethod.
  • hookType — parameters of a given HookType.
  • processorType — config of a given ProcessorType.

Pair this endpoint with /v4/enums for the enum value ↔ key mapping.

⚠️ Beta: response shape may change without notice.

## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/schemas/" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/schemas/{groupName} _Merkl API_ > List schemas in a group (BETA) **List schemas in a group**

Returns every entry of the enum backing the given group. Each entry contains the addressable value, the enum key and an optional human-readable description. Use one of these values (or the key) as :typeId in GET /v4/schemas/:groupName/:typeId.

Example: GET /v4/schemas/distributionMethod returns the list of DistributionMethod values for which a JSON Schema is available.

Responds 404 if groupName is not a known group — see GET /v4/schemas for the list of valid groups.

### Path parameters - `groupName` (string) _(required)_ ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/schemas/{groupName}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/schemas/{groupName}/{typeId} _Merkl API_ > Get raw JSON Schema for a typed input (BETA) **Get raw JSON Schema for a typed input**

Returns the raw TypeBox JSON Schema describing the inputs accepted for a specific entry of a group. The returned object is a standard JSON Schema document and can be consumed directly by tooling such as ajv, openapi-typescript, json-schema-to-ts or quicktype.

:typeId accepts either:

  • the enum value — e.g. 1 for CampaignType.INVALID, or FIX_APR for DistributionMethod.FIX_APR;
  • the enum key — e.g. INVALID for CampaignType.INVALID.

Examples:

  • GET /v4/schemas/campaignType/1
  • GET /v4/schemas/distributionMethod/FIX_APR
  • GET /v4/schemas/hookType/APIBOOST

Responds 404 if either groupName is unknown or typeId does not match any value/key of the underlying enum.

⚠️ Beta: the returned schema may evolve as the engine evolves.

### Path parameters - `groupName` (string) _(required)_ - `typeId` (string) _(required)_ ## Responses ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/schemas/{groupName}/{typeId}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/templates/ _Merkl API_ Get all templates filtered by query parameters ### Query parameters - `protocolId` (string) - `search` (string) - `action` (array) - `page` (string | number) - `items` (string | number) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/templates/" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/templates/count _Merkl API_ Count templates matching the query filters ### Query parameters - `protocolId` (string) - `search` (string) - `action` (array) - `page` (string | number) - `items` (string | number) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/templates/count" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/templates/{id} _Merkl API_ Find one template by ID (UUID), slug or name ### Path parameters - `id` (string) _(required)_ ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/templates/{id}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/tokens/prices/history _Merkl API_ Get historical price snapshots for a token, newest first. Resolve by symbol (optionally scoped to a chainId) or by exact chainId+address. Snapshots are stored on change, so pass `fill=carry-forward` for one row per day of the range. ### Query parameters - `symbol` (string) - `address` (string) - `chainId` (string | number) - `from` (string | integer) - `to` (string | integer) - `fill` (string) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/tokens/prices/history" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/tokens/wrappers _Merkl API_ List every Merkl token wrapper a creator distributes, one entry per wrapper and distribution chain, live campaigns first. Wrappers are discovered through the campaigns that distribute them, so a wrapper deployed but never used in a campaign is not returned. Only static data is served here — read the holder, balances and allowance from chain. ### Query parameters - `creatorId` (string) - `creatorAddress` (string) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/tokens/wrappers" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/tokens/reward _Merkl API_ Get all tokens that are accepted as reward tokens across all chains. ### Query parameters - `chainId` (RegExp) - `search` (string) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/tokens/reward" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/tokens/reward/{chainId} _Merkl API_ Get the list of tokens that are accepted as reward tokens on a given chain. ### Path parameters - `chainId` (string | number) _(required)_ ### Query parameters - `tokenTypes` (array) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/tokens/reward/{chainId}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # POST /v4/tokens/whitelist _Merkl API_ Submit a token whitelist request for review. ## Request body **`application/json`** ```json { "type": "object", "properties": { "explainer": { "maxLength": 500, "description": "Short explanation of the token to add.", "type": "string" }, "address": { "maxLength": 66, "description": "Token address in checksum format.", "type": "string" }, "chainId": { "description": "Chain ID of the network where the token is deployed.", "anyOf": [ { "format": "numeric", "default": 0, "type": "string" }, { "description": "Chain ID of the network where the token is deployed.", "type": "number" } ] }, "iconUrl": { "maxLength": 1024, "description": "Publicly accessible HTTPS URL for the token icon. Mutually exclusive with iconFile.", "type": "string" }, "iconFile": { "type": "object", "properties": { "data": { "maxLength": 1398102, "description": "Base64-encoded icon file bytes (max 1 MB raw).", "type": "string" }, "mimeType": { "description": "Image MIME type (e.g. image/png).", "type": "string" }, "filename": { "maxLength": 255, "description": "Original filename (e.g. token.svg).", "type": "string" } }, "required": [ "data", "mimeType", "filename" ] }, "protocolName": { "maxLength": 100, "description": "Name of the protocol submitting the request.", "type": "string" }, "requesterType": { "description": "Whether the requester submits as an individual or a company. Required.", "anyOf": [ { "const": "individual", "type": "string" }, { "const": "company", "type": "string" } ] }, "companyName": { "maxLength": 100, "description": "Company name — required when requesterType is 'company'.", "type": "string" }, "telegramHandle": { "maxLength": 100, "description": "Telegram handle to notify once the token is whitelisted.", "type": "string" }, "displaySymbol": { "maxLength": 50, "description": "Override symbol shown in the UI. Defaults to on-chain symbol.", "type": "string" }, "coingeckoApiId": { "maxLength": 100, "description": "CoinGecko API ID for price sourcing and APR/TVL calculation.", "type": "string" }, "tokenName": { "maxLength": 100, "description": "Token name, resolved client-side.", "type": "string" }, "tokenSymbol": { "maxLength": 50, "description": "Token symbol, resolved client-side.", "type": "string" } }, "required": [ "explainer", "address", "chainId", "protocolName", "requesterType", "telegramHandle" ] } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "explainer": { "maxLength": 500, "description": "Short explanation of the token to add.", "type": "string" }, "address": { "maxLength": 66, "description": "Token address in checksum format.", "type": "string" }, "chainId": { "description": "Chain ID of the network where the token is deployed.", "anyOf": [ { "format": "numeric", "default": 0, "type": "string" }, { "description": "Chain ID of the network where the token is deployed.", "type": "number" } ] }, "iconUrl": { "maxLength": 1024, "description": "Publicly accessible HTTPS URL for the token icon. Mutually exclusive with iconFile.", "type": "string" }, "iconFile": { "type": "object", "properties": { "data": { "maxLength": 1398102, "description": "Base64-encoded icon file bytes (max 1 MB raw).", "type": "string" }, "mimeType": { "description": "Image MIME type (e.g. image/png).", "type": "string" }, "filename": { "maxLength": 255, "description": "Original filename (e.g. token.svg).", "type": "string" } }, "required": [ "data", "mimeType", "filename" ] }, "protocolName": { "maxLength": 100, "description": "Name of the protocol submitting the request.", "type": "string" }, "requesterType": { "description": "Whether the requester submits as an individual or a company. Required.", "anyOf": [ { "const": "individual", "type": "string" }, { "const": "company", "type": "string" } ] }, "companyName": { "maxLength": 100, "description": "Company name — required when requesterType is 'company'.", "type": "string" }, "telegramHandle": { "maxLength": 100, "description": "Telegram handle to notify once the token is whitelisted.", "type": "string" }, "displaySymbol": { "maxLength": 50, "description": "Override symbol shown in the UI. Defaults to on-chain symbol.", "type": "string" }, "coingeckoApiId": { "maxLength": 100, "description": "CoinGecko API ID for price sourcing and APR/TVL calculation.", "type": "string" }, "tokenName": { "maxLength": 100, "description": "Token name, resolved client-side.", "type": "string" }, "tokenSymbol": { "maxLength": 50, "description": "Token symbol, resolved client-side.", "type": "string" } }, "required": [ "explainer", "address", "chainId", "protocolName", "requesterType", "telegramHandle" ] } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "explainer": { "maxLength": 500, "description": "Short explanation of the token to add.", "type": "string" }, "address": { "maxLength": 66, "description": "Token address in checksum format.", "type": "string" }, "chainId": { "description": "Chain ID of the network where the token is deployed.", "anyOf": [ { "format": "numeric", "default": 0, "type": "string" }, { "description": "Chain ID of the network where the token is deployed.", "type": "number" } ] }, "iconUrl": { "maxLength": 1024, "description": "Publicly accessible HTTPS URL for the token icon. Mutually exclusive with iconFile.", "type": "string" }, "iconFile": { "type": "object", "properties": { "data": { "maxLength": 1398102, "description": "Base64-encoded icon file bytes (max 1 MB raw).", "type": "string" }, "mimeType": { "description": "Image MIME type (e.g. image/png).", "type": "string" }, "filename": { "maxLength": 255, "description": "Original filename (e.g. token.svg).", "type": "string" } }, "required": [ "data", "mimeType", "filename" ] }, "protocolName": { "maxLength": 100, "description": "Name of the protocol submitting the request.", "type": "string" }, "requesterType": { "description": "Whether the requester submits as an individual or a company. Required.", "anyOf": [ { "const": "individual", "type": "string" }, { "const": "company", "type": "string" } ] }, "companyName": { "maxLength": 100, "description": "Company name — required when requesterType is 'company'.", "type": "string" }, "telegramHandle": { "maxLength": 100, "description": "Telegram handle to notify once the token is whitelisted.", "type": "string" }, "displaySymbol": { "maxLength": 50, "description": "Override symbol shown in the UI. Defaults to on-chain symbol.", "type": "string" }, "coingeckoApiId": { "maxLength": 100, "description": "CoinGecko API ID for price sourcing and APR/TVL calculation.", "type": "string" }, "tokenName": { "maxLength": 100, "description": "Token name, resolved client-side.", "type": "string" }, "tokenSymbol": { "maxLength": 50, "description": "Token symbol, resolved client-side.", "type": "string" } }, "required": [ "explainer", "address", "chainId", "protocolName", "requesterType", "telegramHandle" ] } ``` ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/tokens/whitelist" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # GET /v4/users/{address}/rewards _Merkl API_ Get the rewards associated to a user on a given chain. #### Request Examples ##### Fetch rewards for a user wallet on a specific chain ```bash curl "https://api.merkl.xyz/v4/users/0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb/rewards?chainId=1" ``` Use the returned proofs to build a claim transaction [integrating-user-reward](https://docs.merkl.xyz/integrate-merkl/app#integrating-user-reward) Retrieve user rewards aggregated by chain. Use query parameters to filter by chain IDs, reward type, and claimability. ### Path parameters - `address` (string) _(required)_ ### Query parameters - `chainId` (array) _(required)_ - `reloadChainId` (string | number) - `test` (boolean) - `claimableOnly` (boolean) - `breakdownPage` (string | number) - `type` (string | string | string) - `showPrivate` (boolean) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/users/{address}/rewards?chainId=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/users/{address}/protocols/{protocolId}/rewards _Merkl API_ Retrieve claimable rewards for a user scoped to a single protocol. Breakdowns and amounts reflect only campaigns attributed to the requested protocol. The returned `proofs` and `root` still claim the full per-token leaf on-chain. ### Path parameters - `address` (string) _(required)_ - `protocolId` (string) _(required)_ ### Query parameters - `chainId` (array) _(required)_ - `reloadChainId` (string | number) - `test` (boolean) - `claimableOnly` (boolean) - `breakdownPage` (string | number) - `type` (string | string | string) - `showPrivate` (boolean) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/users/{address}/protocols/{protocolId}/rewards?chainId=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/users/{address}/terms _Merkl API_ Check if a user address has signed Merkl's terms for a specific chain. Provide the user's wallet address and chain ID. ### Path parameters - `address` (string) _(required)_ ### Query parameters - `chainId` (string | number) _(required)_ ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/users/{address}/terms?chainId=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/users/{address}/recipient/{token} _Merkl API_ Get the recipient address for a given user address and specific token. ### Path parameters - `address` (string) _(required)_ - `token` (string) _(required)_ ### Query parameters - `chainId` (string | number) _(required)_ ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/users/{address}/recipient/{token}?chainId=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/users/{address}/operator/{operatorAddress} _Merkl API_ Check if an address is an operator for a given user address. ### Path parameters - `address` (string) _(required)_ - `operatorAddress` (string) _(required)_ ### Query parameters - `chainId` (string | number) _(required)_ ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/users/{address}/operator/{operatorAddress}?chainId=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/users/{address}/wrapped _Merkl API_ Get a summary of a user's claimed rewards, including their most-used protocol and most-claimed token by USD value. ### Path parameters - `address` (string) _(required)_ ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/users/{address}/wrapped" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/users/{address}/rewards/stats _Merkl API_ Lightweight USD totals for a user — total earned, currently claimable, and pending. Supports optional filters: chainId, chains, tokens, protocols, isTest, isPreTGE. ### Path parameters - `address` (string) _(required)_ ### Query parameters - `chainId` (array) - `tokens` (array) - `protocols` (array) - `tags` (array) - `isTest` (boolean) - `isPreTGE` (boolean) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/users/{address}/rewards/stats" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/users/{address}/rewards/summary _Merkl API_ Per-chain rewards rollup with USD totals and per-token raw amounts. A lightweight alternative to /rewards that omits full campaign breakdowns. Supports optional filters: chainId, chains, tokens, protocols, isTest, isPreTGE. ### Path parameters - `address` (string) _(required)_ ### Query parameters - `chainId` (array) - `tokens` (array) - `protocols` (array) - `tags` (array) - `isTest` (boolean) - `isPreTGE` (boolean) - `reloadChainId` (string | number) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/users/{address}/rewards/summary" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/users/{address}/rewards/active-opportunities _Merkl API_ Active opportunities for a user — LIVE status with rewards still claimable or pending. Aggregated by opportunity and sorted by APR descending. Settled amounts reflect recent distribution state and may be up to 30 minutes behind; pending amounts are computed live. `withStoringIds=true` additionally returns the storingIds backing each opportunity, for mapping the caller's on-chain positions (analytics positions route). ### Path parameters - `address` (string) _(required)_ ### Query parameters - `chainId` (array) - `tokens` (array) - `protocols` (array) - `tags` (array) - `isTest` (boolean) - `isPreTGE` (boolean) - `withStoringIds` (boolean) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/users/{address}/rewards/active-opportunities" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/users/{address}/rewards/chains/{chainId}/breakdowns _Merkl API_ Per-chain opportunity breakdown for a user. Aggregated by opportunity with one row per token carrying claimed, pending, and server-computed claimable amounts. ### Path parameters - `address` (string) _(required)_ - `chainId` (string | number) _(required)_ ### Query parameters - `tokens` (array) - `protocols` (array) - `tags` (array) - `isTest` (boolean) - `isPreTGE` (boolean) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/users/{address}/rewards/chains/{chainId}/breakdowns" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/users/{address}/rewards/breakdowns _Merkl API_ Per-chain rewards with full opportunity breakdowns and proofs. Returns the same shape as `/v4/users/{address}/rewards` but with the breakdowns included. Limited to roughly 1000 breakdowns per response; responses exceeding this limit omit the breakdowns. For high-volume users, paginate via `/v4/leaves/{recipient}/breakdowns` instead. ### Path parameters - `address` (string) _(required)_ ### Query parameters - `tags` (string) - `chainIds` (array) - `reloadChainId` (string | number) - `test` (boolean) - `claimableOnly` (boolean) - `showPrivate` (boolean) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/users/{address}/rewards/breakdowns" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/volume/entries _Merkl API_ List volume entries, filterable by program, clientId, chainId and a timestamp range (all optional, AND-combined). Paginated via page/items. Each entry includes its parent VolumeConfig. ### Query parameters - `page` (string | number) - `items` (string | number) - `program` (string) - `clientId` (string) - `chainId` (string | number) - `fromTimestamp` (string | number) - `toTimestamp` (string | number) ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/volume/entries" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/volume/user _Merkl API_ Aggregated volume for a single user (recipient), filterable by program, clientId, chainId and a timestamp range (all optional, AND-combined). Returns the summed amountUSD and matching entry count. ### Query parameters - `recipient` (string) _(required)_ - `program` (string) - `clientId` (string) - `chainId` (string | number) - `fromTimestamp` (string | number) - `toTimestamp` (string | number) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/volume/user?recipient=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # POST /v4/volume/boost _Merkl API_ Compute a boost per address: an address with at least one VolumeEntry in a config matching the optional clientId and program filters gets a 1x boost (base 1e9). Addresses without a matching entry are omitted; the zero address is always included with "0". ### Query parameters - `clientId` (string) - `program` (string) ## Request body **`application/json`** ```json { "type": "object", "properties": { "addresses": { "maxItems": 500, "description": "Wallet addresses to look up (case-insensitive). Up to 500 per request.", "type": "array", "items": { "type": "string" } } }, "required": [ "addresses" ] } ``` **`application/x-www-form-urlencoded`** ```json { "type": "object", "properties": { "addresses": { "maxItems": 500, "description": "Wallet addresses to look up (case-insensitive). Up to 500 per request.", "type": "array", "items": { "type": "string" } } }, "required": [ "addresses" ] } ``` **`multipart/form-data`** ```json { "type": "object", "properties": { "addresses": { "maxItems": 500, "description": "Wallet addresses to look up (case-insensitive). Up to 500 per request.", "type": "array", "items": { "type": "string" } } }, "required": [ "addresses" ] } ``` ## Responses - **200** Response for status 200 ## Example request ```bash curl -X POST "https://api.merkl.xyz//v4/volume/boost" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` --- # GET /v4/volume/facets _Merkl API_ List the available programs and client ids to filter volume entries by. ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://api.merkl.xyz//v4/volume/facets" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /graphql/v1 _Merkl API_ GraphQL endpoint. POST a query to execute it, or open the URL in a browser to load GraphiQL for interactive exploration of the schema. ## Example request ```bash curl -X GET "https://api.merkl.xyz//graphql/v1" \ -H "x-api-key: YOUR_API_KEY" ``` --- # POST /graphql/v1 _Merkl API_ GraphQL endpoint. POST a query to execute it, or open the URL in a browser to load GraphiQL for interactive exploration of the schema. ## Example request ```bash curl -X POST "https://api.merkl.xyz//graphql/v1" \ -H "x-api-key: YOUR_API_KEY" ``` --- # PUT /graphql/v1 _Merkl API_ GraphQL endpoint. POST a query to execute it, or open the URL in a browser to load GraphiQL for interactive exploration of the schema. ## Example request ```bash curl -X PUT "https://api.merkl.xyz//graphql/v1" \ -H "x-api-key: YOUR_API_KEY" ``` --- # DELETE /graphql/v1 _Merkl API_ GraphQL endpoint. POST a query to execute it, or open the URL in a browser to load GraphiQL for interactive exploration of the schema. ## Example request ```bash curl -X DELETE "https://api.merkl.xyz//graphql/v1" \ -H "x-api-key: YOUR_API_KEY" ``` --- # PATCH /graphql/v1 _Merkl API_ GraphQL endpoint. POST a query to execute it, or open the URL in a browser to load GraphiQL for interactive exploration of the schema. ## Example request ```bash curl -X PATCH "https://api.merkl.xyz//graphql/v1" \ -H "x-api-key: YOUR_API_KEY" ``` --- # Merkl Analytics API API for tracking DeFi positions, analyzing transactions, and computing PnL across protocols --- # GET /v4/analytics/health/live _Merkl Analytics API_ > Liveness probe — pod runtime snapshot Always returns HTTP 200. On the public API the body is `{ status: "OK" }`. On the internal instance it carries the pod's rolling 60s runtime window — mean cores against the cgroup quota, responses served, RSS against the memory limit, worst event-loop drift. Nothing here fails the probe yet: the thresholds that would identify a GC-bound pod are not calibrated against production traffic, so this ships as telemetry first. Every window is also logged as `[RUNTIME] window`. ## Responses ## Example request ```bash curl -X GET "https://analytics.merkl.xyz/v4/analytics/health/live" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/analytics/health/ready _Merkl Analytics API_ > Readiness probe — pod runtime snapshot Always returns HTTP 200. On the public API the body is `{ status: "OK" }`. On the internal instance it carries the pod's rolling 60s runtime window — mean cores against the cgroup quota, responses served, RSS against the memory limit, worst event-loop drift. Nothing here fails the probe yet: the thresholds that would identify a GC-bound pod are not calibrated against production traffic, so this ships as telemetry first. Every window is also logged as `[RUNTIME] window`. ## Responses - **200** Response for status 200 - **503** Response for status 503 ## Example request ```bash curl -X GET "https://analytics.merkl.xyz/v4/analytics/health/ready" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/analytics/positions/{userAddress}/{chainId} _Merkl Analytics API_ > Get user position history Fetch historical position data for a user across opportunities. **Auth:** a valid `x-api-key` developer key, or an authenticated JWT identity (cookie or Bearer) — both grant the same read access. **Response:** - `data[chainId][identifier][indexingId]`: Wrapped positions grouped by chain ID → identifier → indexingId. Each positionGroup carries shared metadata (`protocol`, `action`, `stateType`, `globalStates`, `tokens`) and a `positions` map keyed by positionId. - `latest` is reported **as of the indexing checkpoint**: its `(blockNumber, blockTimestamp)` are advanced to how far indexing has completed (state unchanged through there), never beyond it — so the state is never presented as more current than what's indexed. - `globalStates` (populated per snapshot when `includeDisplay=true`): at the positionGroup level it holds series **descriptors** only (`type`, `indexingId`). The value applied to a snapshot lives on that snapshot's own `globalStates[key] = { blockTimestamp, value }` — the series row at-or-before the snapshot's timestamp (carry-forward) — so each snapshot (`latest` + each `history` entry) is self-describing. - `debt`: present and `true` when the group's amounts are owed, not held (a borrow) — sum values across groups by subtracting these. Set once per group: it follows from the processor. - `positions[positionId].params`: position-invariant fields hoisted out of the snapshots, emitted **once per position**. Shape follows `stateType`; CLAMM carries `tickLower`/`tickUpper` (and `salt` when the persisted state has one), which is why they are no longer inside each snapshot's `state`. `poolId` and `tokenId` are not emitted at all: `poolId` is the positionGroup's `identifier`, and `tokenId` is the last segment of the positionId key. - `pagination.positionCount`: Number of positions returned - `pagination.totalHistorySnapshots`: Total history snapshots (when > 0) - `pagination.pageInfo`: Cursor-based pagination info (when applicable) **PnL (`includePnl=true`):** adds a running `pnl` block to every snapshot's `display` — cost basis, per-event cashflow, PnL, ROI, all USD at each event's own daily-close price — A position's own PnL is its `latest.display.pnl`; there is no duplicate block on the position. Every `history` row is one capital event, so a row's state delta is the cashflow and everything else is PnL. PnL therefore needs the **complete** series: the flag implies `includeDisplay` and `includeHistory`, and **disregards `first`/`after`**, since `first` pages rows newest-first portfolio-wide and drops the oldest writes — exactly the cost basis. The walk is row-capped; if it stops early, or a `minTimestamp` anchor opens the series, the block reports `basis: "truncated"`. Scope large portfolios with `identifier` or `indexingIds`. `summary` carries the request's aggregates: `total` plus `byIdentifier` nested chainId → identifier, mirroring `data`. ROI is recomputed from the aggregate, never averaged, and divides by **mean capital at risk** (the basis time-weighted over the time it was funded) — so it survives a position closing, where the net basis goes negative, and it is not inflated by capital recycled through the position. The per-position means are summed, which is exact only where the positions' windows coincide. A borrow's cost basis is capital *received*, so `costBasisUsd` subtracts it and reports net capital committed. Always read `positionsIncluded`/`positionsExcluded` — PnL cannot be computed for every position (unsupported `stateType`, unpriced token), and a total that omits some says so there rather than looking complete. Excluded: protocol fees (CLAMM state carries liquidity only) and Merkl incentives (not position states). **Examples:** - All positions on chain 1: `GET /v4/analytics/positions/0xUserAddress/1` - With history: `GET /v4/analytics/positions/0xUserAddress/1?includeHistory=true` - With USD display values: `GET /v4/analytics/positions/0xUserAddress/1?includeDisplay=true` - With PnL: `GET /v4/analytics/positions/0xUserAddress/1?includePnl=true&identifier=0xPoolAddress` - Filter by protocol: `GET /v4/analytics/positions/0xUserAddress/1?protocol=uniswap` - Filter by identifier: `GET /v4/analytics/positions/0xUserAddress/1?identifier=0xPoolAddress` ### Path parameters - `userAddress` (string) _(required)_ - `chainId` (string | number) _(required)_ ### Query parameters - `includeHistory` (boolean) _(required)_ - `includeDisplay` (boolean) _(required)_ - `includePnl` (boolean) _(required)_ - `minTimestamp` (string | number) - `daily` (boolean) _(required)_ - `first` (string | number) - `protocol` (string) - `identifier` (string) - `indexingIds` (array) - `after` (string) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://analytics.merkl.xyz/v4/analytics/positions/{userAddress}/{chainId}?includeHistory=&includeDisplay=&includePnl=&daily=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/analytics/positions/{userAddress} _Merkl Analytics API_ > Get user position history Fetch historical position data for a user across opportunities. **Auth:** a valid `x-api-key` developer key, or an authenticated JWT identity (cookie or Bearer) — both grant the same read access. **Response:** - `data[chainId][identifier][indexingId]`: Wrapped positions grouped by chain ID → identifier → indexingId. Each positionGroup carries shared metadata (`protocol`, `action`, `stateType`, `globalStates`, `tokens`) and a `positions` map keyed by positionId. - `latest` is reported **as of the indexing checkpoint**: its `(blockNumber, blockTimestamp)` are advanced to how far indexing has completed (state unchanged through there), never beyond it — so the state is never presented as more current than what's indexed. - `globalStates` (populated per snapshot when `includeDisplay=true`): at the positionGroup level it holds series **descriptors** only (`type`, `indexingId`). The value applied to a snapshot lives on that snapshot's own `globalStates[key] = { blockTimestamp, value }` — the series row at-or-before the snapshot's timestamp (carry-forward) — so each snapshot (`latest` + each `history` entry) is self-describing. - `debt`: present and `true` when the group's amounts are owed, not held (a borrow) — sum values across groups by subtracting these. Set once per group: it follows from the processor. - `positions[positionId].params`: position-invariant fields hoisted out of the snapshots, emitted **once per position**. Shape follows `stateType`; CLAMM carries `tickLower`/`tickUpper` (and `salt` when the persisted state has one), which is why they are no longer inside each snapshot's `state`. `poolId` and `tokenId` are not emitted at all: `poolId` is the positionGroup's `identifier`, and `tokenId` is the last segment of the positionId key. - `pagination.positionCount`: Number of positions returned - `pagination.totalHistorySnapshots`: Total history snapshots (when > 0) - `pagination.pageInfo`: Cursor-based pagination info (when applicable) **PnL (`includePnl=true`):** adds a running `pnl` block to every snapshot's `display` — cost basis, per-event cashflow, PnL, ROI, all USD at each event's own daily-close price — A position's own PnL is its `latest.display.pnl`; there is no duplicate block on the position. Every `history` row is one capital event, so a row's state delta is the cashflow and everything else is PnL. PnL therefore needs the **complete** series: the flag implies `includeDisplay` and `includeHistory`, and **disregards `first`/`after`**, since `first` pages rows newest-first portfolio-wide and drops the oldest writes — exactly the cost basis. The walk is row-capped; if it stops early, or a `minTimestamp` anchor opens the series, the block reports `basis: "truncated"`. Scope large portfolios with `identifier` or `indexingIds`. `summary` carries the request's aggregates: `total` plus `byIdentifier` nested chainId → identifier, mirroring `data`. ROI is recomputed from the aggregate, never averaged, and divides by **mean capital at risk** (the basis time-weighted over the time it was funded) — so it survives a position closing, where the net basis goes negative, and it is not inflated by capital recycled through the position. The per-position means are summed, which is exact only where the positions' windows coincide. A borrow's cost basis is capital *received*, so `costBasisUsd` subtracts it and reports net capital committed. Always read `positionsIncluded`/`positionsExcluded` — PnL cannot be computed for every position (unsupported `stateType`, unpriced token), and a total that omits some says so there rather than looking complete. Excluded: protocol fees (CLAMM state carries liquidity only) and Merkl incentives (not position states). **Examples:** - All positions on chain 1: `GET /v4/analytics/positions/0xUserAddress/1` - With history: `GET /v4/analytics/positions/0xUserAddress/1?includeHistory=true` - With USD display values: `GET /v4/analytics/positions/0xUserAddress/1?includeDisplay=true` - With PnL: `GET /v4/analytics/positions/0xUserAddress/1?includePnl=true&identifier=0xPoolAddress` - Filter by protocol: `GET /v4/analytics/positions/0xUserAddress/1?protocol=uniswap` - Filter by identifier: `GET /v4/analytics/positions/0xUserAddress/1?identifier=0xPoolAddress` ### Path parameters - `userAddress` (string) _(required)_ - `chainId` (string | number) _(required)_ ### Query parameters - `includeHistory` (boolean) _(required)_ - `includeDisplay` (boolean) _(required)_ - `includePnl` (boolean) _(required)_ - `minTimestamp` (string | number) - `daily` (boolean) _(required)_ - `first` (string | number) - `protocol` (string) - `identifier` (string) - `indexingIds` (array) - `after` (string) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://analytics.merkl.xyz/v4/analytics/positions/{userAddress}?includeHistory=&includeDisplay=&includePnl=&daily=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/analytics/positions/by-identifier/{chainId}/{identifier} _Merkl Analytics API_ > Get positions by opportunity identifier Fetch the latest position for every user in a given opportunity. **Response:** - `data[indexingId]`: Wrapped positions keyed by indexingId. Each positionGroup carries shared metadata (`protocol`, `action`, `stateType`, `globalStates`, `tokens`) and a `positions` map keyed by positionId. Each position entry carries its `user` field; multiple users may share the same indexingId positionGroup. - `latest` is reported **as of the indexing checkpoint**: its `(blockNumber, blockTimestamp)` are advanced to how far indexing has completed (state unchanged through there), never beyond it — so the state is never presented as more current than what's indexed. - `globalStates`: at the positionGroup level it holds series **descriptors** only (`type`, `indexingId`) — fetch the values from `GET /v4/analytics/global-states`. This route returns raw states; it runs no global-state enrichment. - `display.unpricedReason` (when `valueUsd` is absent): which step gave up — `no_price` (the token mapped but has no daily close at or before that day), `no_token_mapped` (no declared token denominates the amount, e.g. a market-keyed identifier), `unresolved_amount` (global-state enrichment produced none), `unsupported_state` (no valuation rule for the `stateType`). A price gap and an unsupported shape both read as "no value" without it. - `debt`: present and `true` when the group's amounts are owed, not held (a borrow) — sum values across groups by subtracting these. Set once per group: it follows from the processor. - `positions[positionId].params`: position-invariant fields hoisted out of the snapshots, emitted **once per position**. Shape follows `stateType`; CLAMM carries `tickLower`/`tickUpper` (and `salt` when the persisted state has one), which is why they are no longer inside each snapshot's `state`. `poolId` and `tokenId` are not emitted at all: `poolId` is the positionGroup's `identifier`, and `tokenId` is the last segment of the positionId key. - `pagination.positionCount`: Total number of position entries returned - `pagination.recipientsCount`: Number of distinct user addresses across all positions **Examples:** - Latest positions: `GET /v4/analytics/positions/by-identifier/1/0xPoolAddress` ### Path parameters - `chainId` (string | number) _(required)_ - `identifier` (string) _(required)_ ### Query parameters - `includeDisplay` (boolean) _(required)_ ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://analytics.merkl.xyz/v4/analytics/positions/by-identifier/{chainId}/{identifier}?includeDisplay=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/analytics/positions/at/{storingId} _Merkl Analytics API_ > Get closest position states at a timestamp Return the closest position state(s) to a Unix timestamp for a storingId. `order=DESC` (default) returns the last state at or before the timestamp; `order=ASC` returns the first state at or after it. Each entry mirrors the gRPC `StateV2` shape, so the response carries the raw `state` blob unmodified. **Examples:** - Latest state ≤ T: `GET /v4/analytics/positions/at/?timestamp=1748635620` - First state ≥ T: `GET /v4/analytics/positions/at/?timestamp=1748635620&order=ASC` - One specific user: `GET /v4/analytics/positions/at/?timestamp=1748635620&positionKey=0xUser` ### Path parameters - `storingId` (string) _(required)_ ### Query parameters - `timestamp` (string | number) _(required)_ - `order` (string | string) _(required)_ - `positionKey` (string) - `positionGranularityKey` (string) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://analytics.merkl.xyz/v4/analytics/positions/at/{storingId}?timestamp=&order=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/analytics/transactions/{chainId} _Merkl Analytics API_ > Get states by transaction hashes (V2) Retrieves position states associated with the given transaction hashes from the V2 position_states table. Returns enriched state data including protocol, action, and token metadata derived from state descriptions. ### Path parameters - `chainId` (string | number) _(required)_ ### Query parameters - `txHashes` (string) _(required)_ - `positionKeyFilter` (string) - `timestamps` (string) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://analytics.merkl.xyz/v4/analytics/transactions/{chainId}?txHashes=" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/analytics/transactions/by-user/{userAddress}/{chainId} _Merkl Analytics API_ > Get tx hashes by user, grouped by indexing ID Returns all transaction hashes recorded for a given user address, grouped by indexing ID (opportunity). Supports optional chain filtering via the chainId path parameter. Results are cursor-paginated: pass `first` to set page size and `after` (from `pageInfo.endCursor`) to fetch the next page. Cursor encodes `(block_number, position_id)` — ordering is block_number DESC. ### Path parameters - `userAddress` (string) _(required)_ - `chainId` (string | number) _(required)_ ### Query parameters - `first` (string | number) - `after` (string) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://analytics.merkl.xyz/v4/analytics/transactions/by-user/{userAddress}/{chainId}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/analytics/global-states/{indexingId} _Merkl Analytics API_ ### Path parameters - `indexingId` (string) _(required)_ ### Query parameters - `fromBlock` (string | number) - `toBlock` (string | number) ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://analytics.merkl.xyz/v4/analytics/global-states/{indexingId}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/analytics/global-states/{indexingId}/latest _Merkl Analytics API_ ### Path parameters - `indexingId` (string) _(required)_ ## Responses ## Example request ```bash curl -X GET "https://analytics.merkl.xyz/v4/analytics/global-states/{indexingId}/latest" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/analytics/states-meta/find-by-token/{chainId}/{identifier} _Merkl Analytics API_ > Find storing_ids by token / identifier Search the `state_set_descriptions` table for every storing_id whose `raw_config` references the given token contract or opportunity identifier. Pass `chainId=0` to search across all chains. ### Path parameters - `chainId` (string | number) _(required)_ - `identifier` (string) _(required)_ ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://analytics.merkl.xyz/v4/analytics/states-meta/find-by-token/{chainId}/{identifier}" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/analytics/states-meta/{storingId}/block-range _Merkl Analytics API_ > Get indexed block / timestamp range for a storing_id Return the min/max indexed `block_number` and `block_timestamp` for a storing_id. All four values are 0 when the storing_id has no indexed state yet. ### Path parameters - `storingId` (string) _(required)_ ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://analytics.merkl.xyz/v4/analytics/states-meta/{storingId}/block-range" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/analytics/states-meta/{storingId}/setDescription _Merkl Analytics API_ > Get raw processor config (set description) for a storing_id Return the raw processor config stored in the `state_set_description` table for the given storing_id. ### Path parameters - `storingId` (string) _(required)_ ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://analytics.merkl.xyz/v4/analytics/states-meta/{storingId}/setDescription" \ -H "x-api-key: YOUR_API_KEY" ``` --- # GET /v4/analytics/states-meta/address-tags/{chainId} _Merkl Analytics API_ > Batch resolve address tags Resolve human-readable labels for up to 100 identifiers on a single chain. `identifier` may be any resolvable value: owner address, pool address, market id, bytes32 poolId, etc. Misses return `addressTag: null` — partial resolution is normal for plain EOAs. Input order is preserved in the response. **Example:** ``` GET /v4/analytics/states-meta/address-tags/8453?identifiers=0x7Ce5F356a10BBF528618259404543BB1e6412B69,0xRandomEOA ``` ### Path parameters - `chainId` (string | number) _(required)_ ### Query parameters - `identifiers` (string) _(required)_ ## Responses - **200** Response for status 200 ## Example request ```bash curl -X GET "https://analytics.merkl.xyz/v4/analytics/states-meta/address-tags/{chainId}?identifiers=" \ -H "x-api-key: YOUR_API_KEY" ```