All requests require an API key via the
X-API-Key header. Reads accept a publishable key (pk_live_) from browser code or a secret key (sk_live_) from your backend. See Authentication for details.Overview
GET /v2/streams/merkle_proofs returns Merkle proofs for a wallet across one or more token streams. Each proof contains everything needed to call claim() on the stream’s smart contract: the cumulative allocation amount, the proof array, the contract address, and the chain ID.
Use this endpoint when building a claim UI on a partner website or in any frontend that needs to submit on-chain claim transactions.
The proof data itself is public — anyone can verify it on-chain, and the claim() call it feeds is permissionless. The API key gates only the fetch: reading proofs from the Earn API needs a key, spending them on-chain does not.
This endpoint returns proofs for token-based streams only. Point-based streams do not have on-chain Merkle trees.
Endpoint
streamIds parameter:
string
required
The user’s EVM wallet address.
string[]
required
One or more stream UUIDs to fetch proofs for. Repeat the parameter for multiple streams. You can find your stream IDs via Get Streams or from your Turtle dashboard.
Response Example
Claim parameters
The response includes everything needed to callclaim() on-chain. Three fields map directly to the contract’s parameters:
The
timestamp identifies which Merkle root the proof was generated against. The contract validates the proof against that root. If the timestamp doesn’t match a committed root, the transaction reverts. See Claim Rewards for the full integration guide.
Response Fields
StreamMerkleProof[]
required
Array of Merkle proofs, one per requested stream where the wallet has an allocation.
StreamMerkleProof
uuid
The stream this proof belongs to.
integer
Decimal EVM chain ID where the stream contract is deployed (e.g.
8453 for Base, 1 for Ethereum).string
The stream contract address to call
claim() on.string
Total cumulative allocation in raw token units. This is the total ever allocated to the wallet, not the unclaimed balance. The contract tracks what has already been claimed. Call
getRewardToken() on the stream contract to look up the token’s decimals for display formatting.string
ISO 8601 timestamp of the Merkle root this proof was generated against. Required for the
claim() call. The contract uses it to look up the correct root hash for verification. Must be converted to Unix epoch seconds (uint40) before passing to the contract.string[]
Array of
bytes32 hashes for Merkle verification, passed directly to the contract’s claim() function.string
The Merkle root hash this proof was generated against. Informational only; the contract resolves the root from the
timestamp parameter, so you don’t need to pass this on-chain.Operational Notes
Cumulative amount model
Cumulative amount model
The
amount field is cumulative. It represents the wallet’s total allocation across all snapshots, not a per-snapshot delta. The on-chain contract tracks how much has already been claimed and releases the difference when claim() is called.Proof caching
Proof caching
Proofs are recomputed on each snapshot cycle. Between snapshots, the same proof data is returned. There is no need to poll this endpoint. Fetch once when the user is ready to claim.
Empty proofs array
Empty proofs array
If the wallet has no allocation in any of the requested streams, the
proofs array will be empty. This is not an error. It means the wallet is not a participant in those streams.Using proofs on-chain
Using proofs on-chain
Pass
amount, timestamp, and proof directly to the stream contract’s claim() function. See Claim Rewards for the full on-chain integration guide.Error Handling
Missing required parameters
Missing required parameters
Status Code: 400 Bad RequestSolution: Ensure both
wallet and at least one streamIds parameter are present.Missing or invalid API key
Missing or invalid API key
Status Code: 401 UnauthorizedA key that is present but invalid, inactive, or expired also returns
401, with "Invalid API key", "API key is inactive", or "API key has expired".Solution: Send a valid key on the X-API-Key header.Origin not allowed
Origin not allowed
Status Code: 403 ForbiddenPublishable keys (
pk_live_) are checked against the origin allowlist configured on the key. A request from an origin that is not on the list — including a server-side call that sends no Origin or Referer header — is rejected.Solution: Call from an allowlisted browser origin, or use a secret key (sk_live_) from your backend.Rate limit exceeded
Rate limit exceeded
Status Code: 429 Too Many RequestsUsage is metered per key, hourly and monthly. Every response carries the hourly budget in the
X-RateLimit-* headers; keys with a monthly limit also get X-Monthly-* headers. A monthly overrun returns "Monthly usage limit exceeded" instead.Solution: Back off until the window resets — hourly limits also return Retry-After — or ask the Turtle team for a higher limit. See Rate limits.Unexpected internal error
Unexpected internal error
Status Code: 500 Internal Server ErrorSolution: Retry the request and contact Turtle if the issue persists.

