---
title: "Start a Zama encryption job"
method: POST
path: "/v4/confidential-rewards/zama"
tags: ["Rewards"]
---

# Start a Zama encryption job

`POST /v4/confidential-rewards/zama`

## Request

- Base URL: `https://api.merkl.xyz` (the document declares no server; this is the origin it was published from)
- URL: `https://api.merkl.xyz/v4/confidential-rewards/zama`
- Auth: `apiKey` (a scheme the document does not define)

Starts a background job that FHE-encrypts a campaign's leaderboard page and returns a job ID
immediately. Poll `GET /v4/confidential-rewards/zama/jobs/{jobId}`; once `status` is `completed`,
`result` holds the response described below. Encryption is too slow for one request, which is why
it is a job.

The job ID is a hash of the request, so repeating a request joins the running or finished job
instead of encrypting twice. A failed job is retried. Job state lives in Redis for 30 minutes.

The job's `result` is 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 the campaign's
reward-airdrop contract as both the consuming contract and the submitting `msg.sender`, alongside
the chain ID and the chain's fhEVM ACL address. That address is the CREATE2 clone Zama's airdrop
factory deploys for the onchain campaign ID, so it is known before deployment. Both are echoed
back as `contractAddress` and `userAddress`. 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`. The check covers the whole campaign, not
the page: one overflowing amount fails every page with a 422 that carries a campaign-wide count,
never a recipient or an amount. It 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 served, to any caller
with access to the campaign: the recipient list and count are cleartext by design, and no field
reduces to one recipient's amount. `truncatedDust`, `excludedZeroAmount` and the overflow check
are whole-campaign, never per page.

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` union: 0-indexed page number
  - string, integer
  - integer: 0-indexed page number
- `items` union: Number of items returned by page
  - string, integer
  - integer: Number of items returned by page
- `chainId` union, required: Distribution chain ID of the Merkl campaign (numeric, e.g. `1` for Ethereum).
  - string, numeric
  - number: Distribution chain ID of the Merkl campaign (numeric, e.g. `1` for Ethereum).
- `campaignId` string, required: **Onchain campaign ID** (32-byte hex hash, e.g. `0x93cf385c...`). NOT the internal `Campaign.id`.
- `fheChainId` union: Chain the encrypted inputs will be consumed on, if different from `chainId`. The proof binds this chain's ID and fhEVM ACL address, so it must match the chain the distributor is deployed on. Defaults to `chainId`.
  - string, numeric
  - number: Chain the encrypted inputs will be consumed on, if different from `chainId`. The proof binds this chain's ID and fhEVM ACL address, so it must match the chain the distributor is deployed on. Defaults to `chainId`.
- `fheType` union: FHE type to encrypt amounts into. `euint64` is what ERC-7984 confidential tokens use and is the default; it caps a single amount at 2^64-1, so raw 18-decimal wei amounts above ~18.44 tokens need `amountDecimals` or a wider type.
  - string
  - string
  - string
- `amountDecimals` union: Denomination to express amounts in, as a number of decimals. Omit for raw reward-token wei. Set below the reward token's own decimals to rescale (e.g. `6` on an 18-decimal token divides by 1e12) so amounts fit `euint64`. Rescaling truncates: the response reports the exact dust dropped.
  - string, numeric
  - number: Denomination to express amounts in, as a number of decimals. Omit for raw reward-token wei. Set below the reward token's own decimals to rescale (e.g. `6` on an 18-decimal token divides by 1e12) so amounts fit `euint64`. Rescaling truncates: the response reports the exact dust dropped.
- `hide` boolean: If true, excludes the campaign creator, the Merkl multisig and the Merkl dumper.

## Response `200`

Response for status 200

- object
  - `jobId` string, required: Job ID to poll for the encrypted leaderboard

## Changes

- **2026-10-09** `8909b59dc0ff`: 1 breaking
  - for the `query` request parameter `items`, the max was decreased from `1000.00` to `64.00`
- **2026-10-09** `c8b105eb3f1d`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/merkl/apis/merkl-api/changes/v4/confidential-rewards/zama/post.md)

---

[API](https://skmtc.dev/merkl/apis/merkl-api.md) - [All operations](https://skmtc.dev/merkl/apis/merkl-api/llms.txt) - [OpenAPI document](https://skmtc.dev/merkl/apis/merkl-api/revisions/60ffe2c7d887?raw)
