Merkl Developer Portal logo

ID types in the Merkl API

A reference for the different IDs used across the Merkl API endpoints — campaigns, opportunities, tokens, chains, protocols.

The Merkl API uses several distinct ID types. Many of them are stringified numbers and look interchangeable, but they aren't: passing one where another is expected typically returns an empty array ([]) silently. This page explains each ID type, how to recognize it, and which endpoints accept which.

TL;DR table

ID typeFormatExampleUsed as
Internal campaign ID (Campaign.id)numeric hash, ≤20 digits13896840291241714382Response field campaignId (in records, breakdowns), query ?id=, ?rootCampaignId=, ?parentCampaignId=
Onchain campaign ID (Campaign.campaignId)32-byte hex, 0x…0x93cf385c2a446a7596820601caf25e63bfc4939efa9a89a6f21563b58ee54eb1Response field onChainCampaignId, query ?campaignId=, ?campaignIds=, path /rewards/campaign/:campaignId/list
Internal opportunity ID (Opportunity.id)numeric hash, ≤20 digits7673870997270712040Response field id, query ?opportunityId=, ?id=, path /opportunities/:id
Composite opportunity ID (path-only){chainId}-{type}-{identifier}1-ERC20LOGPROCESSOR-0x35Cbe8542E70fa2f7F9cDF129F19e593F4b4f560Path /opportunities/:id only, as an alternative to the numeric form. Not accepted in query parameters.
Opportunity identifier (mainParameter)free-form, often an EVM address0x35Cbe8542E70fa2f7F9cDF129F19e593F4b4f560Query ?identifier=, ?mainParameter=, ?explorerAddress=
Token ID (Token.id)numeric hash, ≤20 digits3460729690457214808Response field id on token objects, and underlyingTokenId on a wrapper; query ?id=, path /tokens/:id
Token shorthand (path-only){chainId}-{address}1-0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48Path /tokens/:id and /tokens/:id/allowance/... only. Server resolves it to the numeric Token.id. Not accepted in ?id= query.
Chain IDsmall integer1 (Ethereum), 42161 (Arbitrum)Query ?chainId=, path /chains/:chainId
Protocol sluglowercase stringaave, morpho, uniswapQuery ?mainProtocolId=, path /protocols/:id
EVM address20-byte hex, 0x…0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48Query ?address=, ?explorerAddress=, ?recipient=, ?creatorAddress=

The two campaign IDs

The single most common source of confusion: the field called campaignId in API responses is not what the ?campaignId= query parameter expects.

A campaign object has both:

{
  "id": "13896840291241714382",
  "campaignId": "0x93cf385c2a446a7596820601caf25e63bfc4939efa9a89a6f21563b58ee54eb1",
  "...": "..."
}
Field in responseDB columnWhat it is
idCampaign.idInternal numeric hash, derived from Hash(distributionChainId-campaignId). Stable per chain.
campaignIdCampaign.campaignIdOnchain campaign hash emitted by the distributor contract. The same value can exist on multiple chains.

Some response shapes (e.g. rewardsRecord.breakdowns) project Campaign.id under a field also named campaignId, with onChainCampaignId for the hex hash. When in doubt, look at the format: 32-byte 0x… is onchain, ≤20-digit numeric is internal.

Which endpoints take which?

  • GET /v4/opportunities?campaignId=onchain hash (0x…)
  • GET /v4/campaigns?campaignId=onchain hash (0x…)
  • GET /v4/campaigns?id=internal numeric ID
  • GET /v4/rewards/campaigns?campaignId=onchain hash (paired with chainId)
  • GET /v4/rewards/campaign/:campaignId/listonchain hash
  • GET /v4/claims?campaignId=onchain hash

Path-only forms

Two ID types are accepted only in path parameters, never in query filters. They exist as ergonomic shortcuts so you can hit /v4/opportunities/:id or /v4/tokens/:id without first looking up the numeric ID.

Composite opportunity ID

{chainId}-{type}-{identifier}, e.g. 1-ERC20LOGPROCESSOR-0x35Cbe8542E70fa2f7F9cDF129F19e593F4b4f560.

  • chainId: numeric chain id (1, 42161, …)
  • type: opportunity type, either the enum name (ERC20LOGPROCESSOR, CLAMM, AAVE_NET_LENDING, …) or its numeric value
  • identifier: the opportunity's mainParameter (often a pool/vault/market address)
# Both resolve to the same opportunity
GET https://api.merkl.xyz/v4/opportunities/7673870997270712040
GET https://api.merkl.xyz/v4/opportunities/1-ERC20LOGPROCESSOR-0x35Cbe8542E70fa2f7F9cDF129F19e593F4b4f560

The composite form is not accepted in ?id= or any other query filter, only on the /opportunities/:id path.

Token shorthand

{chainId}-{address}, e.g. 1-0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48. The server splits on -, looks up the token, and resolves to its canonical numeric Token.id.

# Both resolve to the same token
GET https://api.merkl.xyz/v4/tokens/3460729690457214808
GET https://api.merkl.xyz/v4/tokens/1-0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48

Like the composite opportunity ID, this shorthand only works on path parameters (/tokens/:id, /tokens/:id/allowance/...). The ?id= query expects the canonical numeric form (and accepts a comma-separated list).

Both forms read the token index. An address Merkl has never indexed is a 404, whichever form you use.

Resolving a wrapper's underlying token

A Merkl token wrapper pays out a different token on claim. Every token payload carries the link as underlyingTokenId, a canonical Token.id:

{
  "id": "8152336397138133356",
  "symbol": "WXPL",
  "address": "0x461B1d4ae26B4f4CB627c18aD85F223Ef8a0dd18",
  "underlyingTokenId": "9177007823639843523"
}

The field appears wherever a token does — on rewardToken in /v4/campaigns, on the tokens in /v4/opportunities, on /v4/tokens/reward, and on user rewards. It is absent when the token is not a wrapper.

Token.id is a hash, so the address cannot be derived from it. Resolve it with a second call:

GET https://api.merkl.xyz/v4/tokens/9177007823639843523
{
  "id": "9177007823639843523",
  "symbol": "WXPL",
  "address": "0x6100E367285b01F48D07953803A2d8dCA5D19873",
  "decimals": 18,
  "chainId": 9745
}

Or resolve both in one call with returnUnderlying:

GET https://api.merkl.xyz/v4/tokens/8152336397138133356?returnUnderlying=true
{
  "id": "8152336397138133356",
  "symbol": "WXPL",
  "underlyingTokenId": "9177007823639843523",
  "underlying": { "id": "9177007823639843523", "address": "0x6100E367285b01F48D07953803A2d8dCA5D19873" }
}

underlying is null when the token is not a wrapper.

Resolve several at once with the query form, which takes the canonical numeric ids only:

GET https://api.merkl.xyz/v4/tokens?id=9177007823639843523&id=3460729690457214808

Neither route needs an API key or campaign ownership. /v4/tokens/wrappers answers a different question — every wrapper one creator distributes — and needs a creatorId or creatorAddress.

Looking up an opportunity from a campaign

If a customer hands you a campaign hash and you want the matching opportunity, the most reliable path is:

# By onchain campaign ID
https://api.merkl.xyz/v4/opportunities?campaignId=0x93cf385c2a446a7596820601caf25e63bfc4939efa9a89a6f21563b58ee54eb1

# Or by the underlying pool/vault/market address (often easier)
https://api.merkl.xyz/v4/opportunities?explorerAddress=0x35Cbe8542E70fa2f7F9cDF129F19e593F4b4f560

Once you have an opportunity, you have its id (internal), which can be used directly:

https://api.merkl.xyz/v4/opportunities/7673870997270712040

Side-by-side example

A single opportunity response surfaces every ID type at once. Here is an annotated excerpt:

{
  "id": "7673870997270712040",                    // internal Opportunity.id (use with /opportunities/:id)
  "chainId": 1,                                    // chain ID (Ethereum)
  "type": "ERC20LOGPROCESSOR",
  "identifier": "0x35Cbe8542E70fa2f7F9cDF129F19e593F4b4f560",  // mainParameter, the vault address here
  "explorerAddress": "0x35Cbe8542E70fa2f7F9cDF129F19e593F4b4f560",
  "protocol": { "id": "morpho" },                  // protocol slug, NOT a numeric ID
  "tokens": [
    {
      "id": "3460729690457214808",                 // internal Token.id
      "chainId": 1,
      "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
      "symbol": "USDC"
    }
  ],
  "rewardsRecord": {
    "breakdowns": [
      {
        "campaignId": "13896840291241714382",      // internal Campaign.id (numeric hash)
        "onChainCampaignId": "0x93cf385c2a446a7596820601caf25e63bfc4939efa9a89a6f21563b58ee54eb1",
        "id": "26e95ebc-b90e-414e-84ed-acacc424faf2"    // breakdown row UUID, internal
      }
    ]
  }
}

Quick sanity rules

  • A 32-byte hex string (0x + 64 hex chars) is an onchain campaign ID.
  • A 20-byte hex string (0x + 40 hex chars) is an EVM address (token, pool, vault, or recipient).
  • A 1-20 digit decimal string is an internal hash (campaign, opportunity, or token, depending on context).
  • A small integer (1, 137, 42161, …) is a chain ID.
  • A lowercase word (aave, morpho) is a protocol slug.

If a query returns [], double-check the ID format against this table before assuming the data isn't there.