Skip to main content

Authentication & API Access

This integration crosses three separate identity boundaries: you (the Funding Party), your disbursement agent, and the pool-gate eligibility service. Each boundary has its own credential. None of them are interchangeable, and the pool-gate in particular never sees your identity at all, only a machine credential. This page is the single reference for every credential you'll encounter across Getting Started, Eligibility, and Disbursement.

Credentials at a glance​

CredentialFormatGatesSent as
Funding Party API keylgk_live_..., capability-scopedYour disbursement agent's /api/disburse, /api/validate, or (for holder.* capabilities) the transfer/send roles in Account OperationsAuthorization: Bearer
SIWX sessionSession cookie/api/employers/register, /api/employers/eligibility/resync, /api/employers/holder/onboard, /api/employers/keysCookie: sessionId=...
Pool-gate OAuth (read)OAuth 2.0 client-credentials, scope pool:readPool-gate whitelist/blacklist readsAuthorization: Bearer
Pool-gate OAuth (write)OAuth 2.0 client-credentials, scope admin:whitelist:writePool-gate whitelist writes (platform-internal)Authorization: Bearer
Pool-gate admin keyStatic keyFallback for pool-gate whitelist writes when OAuth isn't configuredX-API-Key
Agent loopback tokenStatic bearer you setFences the agent's local API from other processes on its hostAuthorization: Bearer
Agent-to-platform edge tokenPer-tenant bearerThe agent's outbound connection to the platformAuthorization: Bearer
Which host each path is on

The /api/employers/* and /api/validate paths above are on the platform root — https://<platform-host>/api/employers/register. They are not under /agent-edge, which is only the agent's own outbound channel to the platform and the value of DISBURSE_PLATFORM_URL. Because the agent's configuration is the place that suffix appears, it is easy to reuse that base for a curl and get a 404 with nothing in it saying the path was fine and the prefix was not. disburse-agent onboard --platform likewise takes the site root and appends what it needs itself.


Funding Party API key​

The credential your disbursement agent uses to authorize requests. Format: the literal prefix lgk_live_ followed by 40 base64url characters. Every key carries exactly one capability, and can only reach the routes that capability covers:

CapabilityGrants
disburse/api/disburse, /api/validate on your own agent (the Disbursement flow).
holder.readReading your own pool subject's notes: /api/balance and the balance role. No ability to move funds.
holder.transferThe transfer role.
holder.sendThe send/withdraw role.
roster.attestAttaching recipients' W and registering their P — the attest role. Decides who may be paid, not who is paid.

A key that can disburse cannot also transfer or send, and vice versa: this is enforced platform-side, not by convention, specifically because disburse's worst case (paying a whitelisted recipient) and transfer/send's worst case (moving or paying out the holder's whole balance) are different enough that a shared credential would silently widen whichever role holds it.

roster.attest is separated for the same reason in the other direction: fused with disburse, anything holding your payroll credential could add a wallet to the eligibility set. And holder.read is separated in a third direction — a holder.transfer key can already read your notes, so permitting it as a read credential would work, and would mean no deployment can be read-only. A reporting job should hold something that genuinely cannot move value.

Obtaining one. Minted at POST /api/employers/keys, SIWX-session-gated, body { "capability": "...", "name": "..." }. The plaintext is shown exactly once, in that response; only a hash is persisted server-side. Unlike account registration, minting is additive: it doesn't revoke any other live key on your wallet, so you hold one key per capability you actually use (up to 50 live keys per wallet). See Account Operations → Minting a scoped credential for a full example.

Rotation. There's no dedicated rotate call: mint a new key with POST /api/employers/keys, switch your agent over to it, then revoke the old one with DELETE /api/employers/keys/:id (its id comes back from the mint response, or from GET /api/employers/keys, which lists every key you've ever minted). Revocation takes effect on the agent's next request, no redeploy needed.

Sending it.

Authorization: Bearer lgk_live_...

against your own disbursement agent's /api/disburse or /api/validate. The agent validates it by forwarding to the platform's POST /api/employers/verify-key; if that verify call can't be reached, the agent fails closed (401) rather than accepting an unverifiable key.

Treat it like a production secret

Anyone holding this key can trigger disbursements against your funder wallet, gated only by whatever allowance and balance that wallet holds. Store it in a secrets manager, not in source control or agent config files checked into version control.

SIWX session​

"Sign-in with X": a wallet-signature login, distinct from every credential above. It authenticates a human driving admin actions, never a disbursement call. It's required for:

  • POST /api/employers/register
  • POST /api/employers/eligibility/resync
  • POST /api/employees/update-encryption-key

The session is a cookie (sessionId), backed by a server-side row with a default 24-hour TTL. Onboarding and eligibility-resync are further gated by an allowlist of wallet addresses configured for your deployment. An unconfigured allowlist closes these routes entirely; only wallets on it can call them.

Getting a session​

Three calls: fetch a nonce, sign a SIWE message with your wallet, post it back. Signing in through the web app produces the same cookie, but every example in these docs is scriptable, so here is the flow directly.

BASE=https://<platform-host>
NONCE=$(curl -fsS "$BASE/api/auth/nonce?namespace=eip155" | jq -r .nonce)

Build the message in exactly this shape. Line 2 is the address alone, and Chain ID carries the eip155: prefix:

<platform-host> wants you to sign in with your Ethereum account:
0xYourAddress

Sign in to Ligero.

URI: https://<platform-host>
Version: 1
Chain ID: eip155:84532
Nonce: <the nonce you just fetched>
Issued At: 2026-09-01T12:00:00.000Z

Sign it as a personal message (EIP-191), then post it back and keep the cookie:

curl -fsS -D headers.txt -X POST "$BASE/api/auth/verify" \
-H "Content-Type: application/json" \
-d "{\"message\":$MSG_JSON,\"signature\":\"$SIG\",\"namespace\":\"eip155\",\"walletPub\":\"$WALLET_PUB\"}"

grep -o 'sessionId=[^;]*' headers.txt # what every SIWX call above needs
Send walletPub, or eligibility silently never happens

walletPub is your wallet public key W: 128 hex characters, x‖y — the uncompressed secp256k1 key with its 0x04 prefix removed. Recover it from the signature you just produced (recoverPublicKey({ hash: hashMessage(message), signature }) in viem) rather than prompting the wallet for it; that's what lets the browser do this with no second popup.

/verify persists W, and first login is where recipient eligibility is written. Omit it and the login still succeeds — you get a valid cookie and nothing complains — but W is never recorded, so the recipient stays ineligible and a later disburse fails its pre-proof eligibility check instead. W is not your address: the address is what goes in a roster, W is what the pool binds a note commitment to. See Eligibility.

Pool-gate OAuth​

The pool-gate never receives your SIWX identity or your Funding Party API key; it only ever authenticates a machine principal, via OAuth 2.0 client-credentials (Cognito). There are two scopes:

ScopePurpose
pool:readReading whitelist entries, blacklist non-membership intervals, and the pool-gate's public signing key.
admin:whitelist:writeWriting new whitelist entries. Platform-internal: issued to the platform's own service credential, not to callers directly.

A token is minted by POSTing client id/secret/scope to the configured token endpoint; scopes are namespaced (resourceserver/scope; a bare pool:read is rejected). If OAuth isn't configured for a deployment, whitelist writes fall back to a static X-API-Key admin credential instead.

How your agent gets a read token. Two options:

  1. Mint it yourself: configure the agent with the OAuth client-credentials directly, and it requests its own pool:read token.
  2. Have the platform vend one: call the platform's /agent-edge/bootstrap, which returns a short-lived pool:read token scoped to your deployment. Preferred: your agent never needs to hold the raw OAuth client secret at all.

Agent loopback token​

A bearer secret that fences your disbursement agent's local HTTP API off from other processes on the same host, unrelated to any Funding Party identity or to the pool-gate. Use this instead of (or alongside) Funding Party API key auth if the agent's /api/disburse needs to accept calls from a co-located scheduler that shouldn't need to hold your production lgk_live_... key.

If neither this nor Funding Party key verification is configured, the agent auto-generates an ephemeral token at boot and logs it. Fine for local testing, not for anything handling real funds.

Agent-to-platform edge token​

A per-tenant bearer credential for the agent's own outbound, agent-initiated connection to the platform's /agent-edge. This is what lets the platform recognize which deployment's agent is submitting proved batches for settlement; it's provisioned alongside your deployment, separately from your Funding Party API key.


Trust boundary

SIWX identity is never forwarded to the pool-gate. The pool-gate only ever sees a service credential: either the platform's own (for the SIWX-driven onboarding/resync paths) or your agent's own pool-gate credential (for the direct read path the agent uses before proving). In the direct-agent model there is no SIWX involved on the pool-gate side at all; the agent is a service principal end-to-end, and the read credential lives in your infrastructure, not the platform's.