Getting Started
This walks through the shortest path from nothing to a confirmed disbursement: register as a Funding Party, mint a credential, stand up an agent, and send your first payment. Each step links out to the page that covers it in depth.
Expect about 30 minutes once the four prerequisites below are in place. They're worth settling first, because each one blocks the walkthrough rather than slowing it down, and the first two are things only we can give you:
| Prerequisite | Why |
|---|---|
| Your deployment's platform host | Written <platform-host> throughout this documentation, and every request below goes to it. Which one you use is settled in Choosing your platform host immediately below — don't start step 1 with the placeholder unresolved. |
| Your wallet address on the onboarding allowlist | /onboard and key-minting are gated on it. An unconfigured allowlist closes both routes entirely — ask us to add your address before you start. |
| A host with a real GPU | Proving is client-side and mandatory; there is no platform-side fallback and no software path fast enough to use. See step 2b. |
| A funder wallet holding the token, plus gas | You'll approve the pool to spend it in step 2c. |
Recipients also have to be eligible before you can pay them, which normally means each one signs in once — step 2 covers what to do when that isn't going to happen.
Pick your custody track
Where the funder's private key lives is the one thing that genuinely differs between deployments, and it changes exactly two steps of this walkthrough: how you approve the pool (2c) and how the agent is configured (3). Everything else is identical.
| Key lives | Approval is | Extra process | Use for | |
|---|---|---|---|---|
| Local key | a file on the agent host | a cast send you run | none | development only |
| DFNS | your DFNS org | a console decision | approver | production |
| Fireblocks | your Fireblocks vault | a console decision | cosigner | production |
| Enclave | a Nitro enclave on your host | signed inside the enclave | none | production |
Choose here and the page follows you: every toggle below is this same selector, the choice survives a reload, and it lands in the URL — so you can hand a colleague a link that opens on your track.
- Local key (dev)
- DFNS
- Fireblocks
- Enclave
You need: a funder private key you're willing to put in a file on the agent host, and Foundry for the one cast send.
The agent signs with the key directly. Nothing gates it, so treat this as a development posture only — the platform refuses DISBURSE_SIGNER=local-key in config mode for that reason. Start here to get a disbursement working end to end, then move the key.
You need: a DFNS organization, a secp256k1 wallet, two identities (agent and approver), a Wallets:Sign gating policy, and a second host to run the approver on.
This is the most complete of the production tracks — the funder key never exists on the agent host, and every signature is gated by a policy that a separate process votes on. It's also the only one with holder and roster support. Provision it first: Delegated Custody with DFNS, then come back to step 1.
You need: a Fireblocks workspace, an API key and secret, the vault account holding the funder balance, and its asset id.
Covers the funder only — disbursements are authorized by your existing Fireblocks transaction policy, and the cosigner you already run is what approves them. There is no holder or roster equivalent today, so account operations and roster attestation still need a different signer.
You need: a host that supports AWS Nitro Enclaves, and the enclave built, running and attested before the agent starts.
The funder key is generated inside the enclave and never leaves it, so there's no custodian and no second process — the trade is that you operate the enclave yourself. See the deployment runbook for standing one up.
Choosing your platform host
There is no shared public endpoint. <platform-host> is one of exactly two things, and which one you're on determines the chain and the tokens you'll be moving:
| Use | <platform-host> | Chain |
|---|---|---|
| Evaluating, or integrating against a sandbox | dev-ligeroclear.ligetron.com | Base Sepolia, test USDC |
| Production | the hostname we provision for your organization, given to you with your allowlisting | as contracted |
Pick one and use it everywhere — the two share no accounts, keys, rosters or pool state, so a credential minted on one is meaningless on the other. Everything in this walkthrough works identically on the sandbox, which is where a first integration should be built.
Confirm the host before anything else; it costs one unauthenticated request, and it's the cheapest way to catch a typo or a DNS/VPN problem before you're debugging it through a failed login:
BASE=https://dev-ligeroclear.ligetron.com # or your production host
curl -fsS "$BASE/api/health/db"
{ "status": "healthy", "database": "connected" }
Anything else — a connection failure, an HTML error page, a 502 — is the host, not you, and none of the steps below will work until it answers this. Note that the agent's own configuration wants $BASE/agent-edge rather than the bare host; the snippets that need it say so.
The agent binary can do its own onboarding: disburse-agent onboard registers the Funding Party, mints the credential and writes a filled-in agent.env — see One command. The steps below are what it does, and remain the reference for anything you want to drive yourself.
1. Onboard as a Funding Party
One-time, SIWX-session-gated. You'll need a session cookie first — see Getting a session, and note the walletPub caveat there, which is what makes eligibility work. This registers your Funding Party account and its funder wallet, and that is all it does:
curl -X POST "$BASE/api/employers/register" \
-H "Cookie: sessionId=<your-session-cookie>" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Inc",
"email": "finance@acme.example"
}'
{
"success": true,
"alreadyRegistered": false
}
Idempotent on the wallet: a second call answers alreadyRegistered: true and changes nothing. One email per wallet, though — reusing an address that belongs to a different wallet is a 409.
You do not declare recipients here, and there is no step where you list them. /api/disburse resolves each recipient as it pays them, so an eligible address is payable whether or not you ever named it.
POST /api/employers/onboard is the same call plus a payroll roster — don't reach for itIt accepts a wallet_addresses array and inserts each one as an employee. That is one-way: a wallet is an employer or an employee and never both, no route removes the row, and so an address you put there can never register, mint a credential, or run balance/transfer/send. It buys you nothing for disbursement either, since payability comes from eligibility rather than from the roster. Use /register unless you specifically want payroll rows, and build those with POST /api/employers/add-employee where the consequence is in front of you.
1b. Mint an API key
curl -X POST "$BASE/api/employers/keys" \
-H "Cookie: sessionId=<your-session-cookie>" \
-H "Content-Type: application/json" \
-d '{ "capability": "disburse", "name": "payroll-scheduler" }'
{
"success": true,
"id": 12,
"apiKey": "lgk_live_...",
"keyPrefix": "lgk_live_ab12",
"capability": "disburse",
"name": "payroll-scheduler",
"createdAt": "2026-08-17T12:00:00.000Z"
}
Store apiKey now: it's shown exactly once. The rest of this walkthrough refers to it as $LIGERO_API_KEY, so put it somewhere your shell can reach and keep that shell open:
export LIGERO_API_KEY=lgk_live_... # the apiKey field above, in full
# Confirm you copied it correctly, and see what it's allowed to do:
curl -fsS -X POST "$BASE/api/employers/verify-key" \
-H "Authorization: Bearer $LIGERO_API_KEY"
{ "valid": true, "walletAddress": "0xYourFundingPartyWallet", "platform": "evm", "capability": "disburse" }
A 401 {"valid": false} means the value is truncated, has whitespace in it, or belongs to another deployment — worth settling here rather than three steps later, where the same key produces a download failure or an agent that won't start. Copy it in full: the keyPrefix in the response is only the first few characters, for identifying the key in logs, and is not something you can authenticate with.
This is the credential your disbursement agent uses to authorize every disburse call. Keys are capability-scoped and additive: minting this one doesn't revoke any other key on your wallet, and you can mint separate ones later for account operations (transfer, send). Details on how this key is used, and the other credentials involved, are in Authentication & API Access.
Coming back to a Funding Party you already registered
Nothing above needs redoing on a second visit, and neither step punishes you for repeating it. /register is idempotent — it answers alreadyRegistered: true and leaves your account alone.
Keys are the part worth knowing your way around, because the one thing you cannot do is recover a plaintext. List what your wallet has ever held:
curl -fsS "$BASE/api/employers/keys" -H "Cookie: sessionId=<your-session-cookie>"
{
"success": true,
"keys": [
{ "id": 12, "keyPrefix": "lgk_live_ab12", "name": "payroll-scheduler", "capability": "disburse",
"createdAt": "2026-08-17T12:00:00.000Z", "lastUsedAt": "2026-08-30T09:14:22.000Z", "revokedAt": null }
]
}
Metadata only, newest first, revoked keys included — it answers "what has ever had authority over my wallet", which a filtered view wouldn't. lastUsedAt is the useful column when you're working out which of several keys a running agent is actually holding, and keyPrefix is what identifies a key in your logs.
If you've lost a plaintext, mint a replacement (1b) — that's the only route, since the secret exists nowhere after the response that returned it. The old key keeps working until you revoke it, which is deliberate: bring the new one up first, confirm it, then retire the old one by id:
curl -fsS -X DELETE "$BASE/api/employers/keys/12" -H "Cookie: sessionId=<your-session-cookie>"
Revocation is immediate and idempotent, and returns the same {"success": true} for an id that was already revoked, never existed, or belongs to someone else — so a success here is not evidence that the key you meant was the key you hit. Check with the list call above.
2. Recipients become eligible at first login
You don't whitelist recipients directly. A recipient's wallet public key (W, distinct from the address you just submitted) is only recoverable from their own sign-in signature, so eligibility is written automatically the first time each recipient signs in. If you're integrating before recipients have signed in yet, or need to repair a gap, see Eligibility → Repairing or bulk-draining eligibility.
That login is where W comes from, so waiting for it never resolves — and the same signature is what would give them an encryption key P, which an MPC signer cannot produce reproducibly either. Attest both yourself instead: Employees in DFNS custody.
2b. Install the agent
The agent is a single self-contained executable. It embeds its own runtime, the WASM prover, the verifier circuit and the prover worker, so on the host that runs it there is nothing else to install: no Node, no npm install, no GPU addon to compile.
Downloads authenticate with the API key you minted in step 1b — that's why this step comes after it. No access to Ligero's source repository is required, and nothing has to be sent to you out of band: the two calls that produce that key (1 and 1b) need only a browser session, so a customer starting from nothing can always reach a binary. Any capability works here, including one you minted for something else — the download is gated on holding a valid key, not on which one. Without a usable key the endpoint answers 401, which is the same answer a typo gives you.
PLAT=linux-x64 # or: darwin-arm64 · windows-x64
PLATFORM_URL="$BASE/agent-edge" # note the /agent-edge suffix
AUTH="Authorization: Bearer $LIGERO_API_KEY" # the key from step 1b
# Resolve the version once, so the binary and its checksum come from the same release.
VER=$(curl -fsS -H "$AUTH" "$PLATFORM_URL/download/$PLAT?redirect=false" | jq -r .version)
BIN="disburse-agent-$VER-$PLAT"
curl -fL -H "$AUTH" --get --data-urlencode "version=$VER" \
"$PLATFORM_URL/download/$PLAT" -o "$BIN"
curl -fL -H "$AUTH" --get --data-urlencode "version=$VER" --data-urlencode "kind=checksum" \
"$PLATFORM_URL/download/$PLAT" -o "$BIN.sha256"
sha256sum -c "$BIN.sha256" # macOS: shasum -a 256 -c "$BIN.sha256"
chmod +x "$BIN" && mv "$BIN" disburse-agent
Three details are worth not rediscovering. latest is a pointer resolved per request, so requesting it separately for the binary and the checksum can straddle a release and leave you with a mismatch — resolve version once, as above, and pass it on every call. Pass it with --data-urlencode, because a release may carry SemVer build metadata (1.2.3+build.7) and a literal + in a query string decodes to a space. And the endpoint answers 302 to a short-lived storage URL, so follow redirects (-L) but do not forward the Authorization header to the target — object storage rejects a request carrying both a signed URL and a bearer token. curl drops the header across hosts, so the commands above are safe as written; if your client doesn't, use ?redirect=false and fetch the returned URL yourself.
The downloads.md support file served with each release at /download/support/<name> covers the rest, including Windows and the other support files (agent.env.example, the exhaustive environment reference).
On a fresh Linux host
Proving is always client-side and GPU-mandatory; there is no platform-side fallback. A stock Ubuntu image has no Vulkan runtime, and without one the driver quietly falls back to a software renderer — correct, but far too slow for payroll:
sudo apt-get update
sudo apt-get install -y libvulkan1 mesa-vulkan-drivers libxext6 libegl1
The agent detects this case and reports accelerator.kind: "software" with ready: false rather than proving slowly, so a ready: false on an otherwise healthy host almost always means these libraries are missing or the NVIDIA Vulkan ICD can't load. In Docker, use the NVIDIA runtime with NVIDIA_DRIVER_CAPABILITIES=graphics.
Not needed for normal use — the download above is the supported path, and the binary is identical. If you have source access and want to build it yourself, a bare Ubuntu host needs the toolchain the build uses (the build needs Node, bun and Deno; the resulting binary still needs none of them):
sudo apt-get install -y curl unzip git # the installers below need unzip
curl -fsSL https://deno.land/install.sh | sh # Deno: compiles the binary
curl -fsSL https://bun.sh/install | bash # bun: runs the esbuild tree-shake step
export PATH="$HOME/.deno/bin:$HOME/.bun/bin:$PATH"
git clone https://github.com/ligeroinc/ligeroclear.git && cd ligeroclear
npm install # or: bun install
deno run -A scripts/build-agent-deno.ts # -> dist/disburse-agent
Both are required: the build tree-shakes the dependency graph with esbuild under bun, then deno compile embeds the result. Node 20+ is needed for the repo's own scripts.
One command instead of steps 1–3: disburse-agent onboard
Everything above this point except the download — registering, minting the credential, and assembling the agent's configuration — is one command of the binary you just installed.
It comes after the download rather than before it because downloads are gated on a Funding Party key (step 1b): you mint one key by hand to get the binary, and from there the binary does the rest. If you're standing up a second deployment, you already hold that key and this is your whole first three steps.
cd /srv/ligero # see the note on write access below
export ONBOARD_PRIVATE_KEY=0x... # the Funding Party wallet's key. Signs the login, nothing else.
./disburse-agent onboard \
--platform https://<platform-host> \
--out ./agent.env
✅ onboarded 0xYourFundingPartyWallet on https://<platform-host>
registered as a new Funding Party
minted a disburse credential: lgk_live_JUKwsYNA… (id 113)
wrote ./agent.env (mode 0600)
| Replaces | Still yours to do |
|---|---|
1. Onboard as a Funding Party — including the SIWX login, with walletPub sent correctly | 2c. Fund the funder and approve the pool — on-chain transactions signed by the funder wallet |
| 1b. Mint an API key | Choosing a funder signer: the written config leaves DISBURSE_FUNDER_PRIVATE_KEY blank, with the delegated-custody alternative spelled out in comments |
The export DISBURSE_* block in step 3 | Recipient eligibility (step 2) |
Then continue at 2c, and start the agent with the file it wrote:
set -a; . ./agent.env; set +a
./disburse-agent
It takes no recipient list, and there is no flag for one. onboard registers the identity and writes no roster, because /api/disburse resolves each recipient at the moment it pays them: any address that is eligible is payable, whether or not you ever named it. Building a payroll roster is a separate, deliberate act — POST /api/employers/add-employee — and worth knowing it is one-way: a rostered address becomes an employee, a wallet is an employer or an employee and never both, and no route removes the row. So a wallet you roster can never register, mint a credential, or run balance/transfer/send.
The credential it mints is a new one, additive to any key you already hold (including the one you downloaded the binary with, which stays valid). Its plaintext is returned by the platform exactly once, so the file --out names is the only copy — which is why the command refuses to overwrite an existing --out rather than destroying whatever key was in it, and writes mode 0600.
A hardware wallet or an MPC-custodied Funding Party wallet can't hand its key to ONBOARD_PRIVATE_KEY. Sign in on the web app instead, copy the sessionId cookie, and pass --session <sessionId>; everything else is identical. (Signing locally is otherwise preferable: it's what lets the command record your wallet public key W, per the caution here.)
The binary carries write access to its own working directory and nowhere else — no other role of it writes anything, so that's the whole filesystem authority it holds. cd to where the config should live and keep --out beneath it; the default ./agent.env always works. A path outside it fails before anything is minted, never after.
--json prints a machine-readable summary instead (keyId, keyPrefix, roster counts, the config path) and never contains the credential itself. ./disburse-agent onboard --help documents the rest, including --name, --email, --chain and --chain-id.
2c. Fund the funder and approve the pool
Settlement calls fund() on the pool, which does transferFrom(funder, pool, amount). So the funder wallet must hold the token and have approved the pool as spender — approval is not implied by holding a balance, and it's the single most common first-run failure.
This is the first of the two steps that depend on your custody track — the toggle below is the same one, already set to whatever you picked at the top.
- Local key (dev)
- DFNS
- Fireblocks
- Enclave
export PATH="$HOME/.foundry/bin:$PATH"
RPC=https://sepolia.base.org
USDC=0x036CbD53842c5426634e7929541eC2318f3dCF7e # Base Sepolia USDC
POOL=<your deployment's pool address>
# The funder approves the pool to spend its USDC (once; set it comfortably above one payroll).
cast send --private-key $FUNDER_KEY --rpc-url $RPC \
$USDC "approve(address,uint256)" $POOL 1000000000
The funder needs a little native gas for that one approve transaction, and nothing after it: settlement is submitted by the relayer, and the pool pulls the funds, so the funder never sends another transaction.
approve(pool, amount) is a transaction signed by your DFNS wallet, so the Wallets:Sign policy you created pauses it exactly like a disbursement — expect to approve it in the DFNS console. Doing it from the dashboard is fine here; the approver process doesn't have to be running yet.
The funder still needs a little native gas for that one transaction. Send it to the wallet's address before you try, or the approval succeeds and the broadcast doesn't.
Full sequence, including creating the wallet and the policy: Delegated Custody with DFNS → Fund the wallet.
approve(pool, amount) is a transaction signed from your Fireblocks vault account, so your transaction-authorization policy gates it the same way it gates a disbursement — expect to authorize it in the Fireblocks console.
The vault account needs a little native gas for that one transaction, and nothing after it.
The funder key is generated and held inside a Nitro enclave, so the approve is signed there rather than by you. It still needs native gas at the funder address, and the enclave has to be running and attested before it can sign anything — see the deployment runbook for standing one up.
Check both before you disburse — allowance and balance each have to cover the batch:
cast call $USDC "allowance(address,address)(uint256)" $FUNDER $POOL --rpc-url $RPC
cast call $USDC "balanceOf(address)(uint256)" $FUNDER --rpc-url $RPC
| Failure at step 5 | Cause |
|---|---|
ERC20: transfer amount exceeds allowance | No approval, or it's been consumed by earlier runs. |
ERC20: transfer amount exceeds balance | The funder holds less than the batch total. |
Each disbursement consumes from both the balance and the allowance, so a funder that exactly covers one payroll will fail the next one. /api/validate (step 4) reports the allowance it can see, which is the cheap way to catch this before proving.
3. Run a disbursement agent
The agent is a process you run on your own infrastructure; it needs a real GPU accelerator and outbound reach to the platform's /agent-edge. It takes its configuration from the environment, so write that configuration to a file once rather than re-typing exports into whichever shell you happen to be in:
umask 077 # the file below holds a live credential
cat > agent.env <<EOF
DISBURSE_PLATFORM_URL=$BASE/agent-edge
DISBURSE_PLATFORM_TOKEN=$LIGERO_API_KEY
DISBURSE_PLATFORM_VERIFY_URL=$BASE/api/employers/verify-key
EOF
DISBURSE_PLATFORM_TOKEN is the key from 1b — the same credential you download with and call /api/disburse with, not a separate deployment token.
Then add the funder signer. This is the one block that differs by custody, and the agent refuses to start half-configured rather than falling back to a local key — so an incomplete block here is a startup error, never a silent downgrade:
- Local key (dev)
- DFNS
- Fireblocks
- Enclave
cat >> agent.env <<'EOF'
DISBURSE_FUNDER_PRIVATE_KEY=0x...
EOF
Nothing else is needed — an unset DISBURSE_SIGNER means local-key. This is a development shortcut: the key sits in a file next to the process that spends it, which is exactly what the other three tabs exist to avoid.
cat >> agent.env <<'EOF'
DISBURSE_SIGNER=dfns
DISBURSE_DFNS_AUTH_TOKEN_FILE=/etc/ligero/dfns-agent.token
DISBURSE_DFNS_CRED_ID=<the agent's User Action Signing credential id>
DISBURSE_DFNS_CREDENTIAL_PRIVATE_KEY_FILE=/etc/ligero/dfns-agent.key
DISBURSE_DFNS_WALLET_ID=wa-...
DISBURSE_DFNS_APPROVAL_POLICY_ID=plc-...
EOF
No funder key appears anywhere — that's the point. The credential private key above is not the funder key; it signs the per-request challenge that authenticates the API call.
You also need a second process, the approver, on a different host. Full walkthrough: Delegated Custody with DFNS.
cat >> agent.env <<'EOF'
DISBURSE_SIGNER=fireblocks
DISBURSE_FIREBLOCKS_API_KEY=<api user key>
DISBURSE_FIREBLOCKS_API_SECRET_FILE=/etc/ligero/fireblocks.key
DISBURSE_FIREBLOCKS_VAULT_ACCOUNT_ID=0
DISBURSE_FIREBLOCKS_ASSET_ID=USDC_BASE_SEPOLIA
EOF
No funder key on the host; the vault signs. You also need a second process, the cosigner, on a different host — it recomputes each request and rejects what doesn't match, which is what makes the vault's approval meaningful.
cat >> agent.env <<'EOF'
DISBURSE_SIGNER=enclave
ENCLAVE_CID=<vsock CID of the running enclave>
EOF
The key is generated inside the enclave and never leaves it, so there's no credential to place and no second process to run — the isolation boundary is local rather than organizational. Standing the enclave up and attesting it is a host-provisioning job; see the deployment runbook.
Then start the agent:
set -a; . ./agent.env; set +a # export every variable in the file
./disburse-agent
onboard writes for youIf you used onboard, agent.env already exists, fully populated and 0600, and you can skip straight to sourcing it. The block above is the same file written by hand — useful when you want to see what's in it, or to add the variables the full reference covers.
Confirm it's ready before sending traffic:
curl http://127.0.0.1:8787/api/readyz
Full configuration reference, including production-grade signer options (enclave, Fireblocks, DFNS), is in Disbursement → Configuring the agent.
DISBURSE_FUNDER_PRIVATE_KEY is a development shortcut. If your funder key lives in DFNS, follow Delegated Custody with DFNS instead of this step: the key stays inside your DFNS organization, and the agent is joined by a second process that approves what it asks to sign. The rest of this walkthrough is unchanged.
4. Dry-run a batch
Validate recipients and allowance without proving or settling anything:
curl -X POST http://127.0.0.1:8787/api/validate \
-H "Authorization: Bearer lgk_live_..." \
-H "Content-Type: application/json" \
-d '{
"token": "USDC",
"recipients": [{ "recipientAddress": "0xAbC123...", "amount": "500000" }]
}'
5. Send your first disbursement
curl -X POST http://127.0.0.1:8787/api/disburse \
-H "Authorization: Bearer lgk_live_..." \
-H "Idempotency-Key: first-run-1" \
-H "Content-Type: application/json" \
-d '{
"token": "USDC",
"recipients": [{ "recipientAddress": "0xAbC123...", "amount": "500000" }]
}'
Poll the returned statusUrl until the job reaches a terminal state:
curl http://127.0.0.1:8787/api/disburse/first-run-1 \
-H "Authorization: Bearer lgk_live_..."
Full request/response shapes, batching behavior, and error handling are in Disbursement.
Next steps
- Authentication & API Access: every credential in this flow, what each one gates, and how to rotate them.
- Eligibility: whitelist and blacklist mechanics in detail.
- Disbursement: the full
/api/disbursereference and agent configuration. - Account Operations: enroll your own wallet as a pool subject, read its balance, transfer within the pool, or withdraw out to a chain address.
- DFNS Fast Track: one command per operation —
disburse,balance,transfer,send— over a single config file the binary reads itself, once your account is enrolled. - Delegated Custody with DFNS: the funder, the holder, and recipients who never sign in — all with no private key on the agent host.
- Glossary: quick lookups for terms like SIWX, note commitment, and dual signature.