Every row on explorer.leash.market and every counter in /v1/metrics/events comes from a single source: the events table. Two writers populate it:
  1. The API. Every prepare → submit → confirm hop appends a row with the right kind and bumps its phase. Anything you drive through the public HTTP surface lands here automatically.
  2. The chain indexer. A dual-network worker that walks signatures for every (network, address) row on its watchlist and emits events for the on-chain activity it sees, including activity the API never initiated (deposits to a treasury, an SDK-driven Execute, a third-party top-up).
If you want a row to land on the explorer, you have one of two jobs:
  • API-driven kinds — call the matching prepare endpoint, sign locally, then POST /v1/submit. The event is created in phase=prepared before the network call returns and gets stamped to submitted then confirmed automatically. See Prepare → Submit lifecycle.
  • Indexer-only kinds — perform the on-chain action through any channel (SDK, wallet, faucet, another protocol), and make sure the agent + ATA are on the indexer watchlist (covered below).
This page is the cookbook that maps every event kind to the specific endpoint or trigger that surfaces it.

Auto-registration: how the indexer learns about your agent

The indexer never crawls the cluster from scratch. It iterates a strict, per-network watchlist seeded by the API. Four watch kinds exist: asset, treasury, treasury_ata, and leash_fee_ata. Once an agent is on the watchlist, every transaction touching its asset, treasury PDA, or any of its ATAs is decoded and emitted as the right event kind. leash_fee_ata rows are seeded automatically at indexer startup for every supported stablecoin, so on-chain protocol fee inflows surface even when no receipt is pushed (see Protocol fee).
EndpointWatchlist side-effect
Any prepare* endpoint targeting /v1/agents/{mint}/...Adds asset + treasury rows for the agent.
POST /v1/agents/{mint}/treasury/provision/prepareAdds the provisioned ATAs as treasury_ata.
POST /v1/agents/{mint}/treasury/withdraw/prepareAdds the source ATA as treasury_ata.
POST /v1/agents/{mint}/treasury/withdraw-all/prepareAdds the source ATA as treasury_ata.
GET /v1/agents/{mint}/treasury/balancesAdds the PDA + every SPL ATA it surfaces.
POST /v1/receipts/{agent} (first push for the agent)Adds an asset row.
POST /v1/agents/{mint}/pull-target (registers a feed URL)Adds an asset row.
The indexer also self-bootstraps on startup: for every treasury row it has never paged, it issues one getTokenAccountsByOwner per token program and registers each result as a treasury_ata. The single most useful “make it light up” call is GET /v1/agents/{mint}/treasury/balances. It is read-only, free, and side-effects all three watch kinds at once. If you have an agent that was funded out-of-band before any API call, hit /treasury/balances once and the indexer will surface its history on the next tick.

Per-event-kind matrix

Event kindSourceEndpoint / trigger that surfaces it
agent.createAPI onlyPOST /v1/agents/prepare (then /v1/submit). HTTP twin of the SDK’s prepareAgentMint.
agent.identity.registerAPI + indexerPOST /v1/agents/{mint}/identity/prepare (then /v1/submit); or any mpl-agent-identity::CreateIdentity for a watched asset (e.g. an SDK-driven prepareAgentMint).
agent.executive.registerAPI + indexerPOST /v1/agents/{mint}/executive/register/prepare; or mpl-agent-tools::CreateExecutive.
agent.executive.delegateAPI + indexerPOST /v1/agents/{mint}/executive/delegate/prepare; or mpl-agent-tools::Delegate.
agent.delegation.setAPI + indexerPOST /v1/agents/{mint}/delegation/prepare; or an Execute-wrapped SPL Approve for the asset’s signer PDA.
agent.delegation.revokeAPI + indexerPOST /v1/agents/{mint}/delegation/revoke/prepare; or an Execute-wrapped SPL Revoke.
agent.token.setAPI + indexerPOST /v1/agents/{mint}/token/set/prepare; or mpl-agent-identity::SetAgentToken.
agent.treasury.provisionAPI + indexerPOST /v1/agents/{mint}/treasury/provision/prepare (skipped if every ATA already exists); or any CreateAssociatedTokenAccount for the PDA.
agent.treasury.withdrawAPI + indexerPOST /v1/agents/{mint}/treasury/withdraw[-all]/prepare; or any Execute-wrapped TransferChecked debiting the PDA’s ATA.
agent.treasury.withdraw_solAPI + indexerPOST /v1/agents/{mint}/treasury/withdraw-sol[-all]/prepare; or any Execute reducing the PDA’s lamport balance.
agent.treasury.fundIndexer onlyA plain SPL TransferChecked into a treasury ATA. Make sure the ATA is registered (see auto-registration table above).
agent.treasury.fund_solIndexer onlyA plain SystemProgram::Transfer ≥ 5 000 lamports into the treasury PDA. Sub-fee wobbles are filtered out as noise.
submit.rawAPI onlyPOST /v1/submit (any signed tx broadcast through the API).
payment_link.createdAPI onlyPOST /v1/payment-links.
payment_link.updatedAPI onlyPATCH /v1/payment-links/{id}.
payment_link.deletedAPI onlyDELETE /v1/payment-links/{id}.
payment_link.servedAPI onlyAn anonymous client hits /x/{id} with no X-PAYMENT header (paywall serves a 402).
payment_link.settledAPI onlyAn anonymous client hits /x/{id} with a valid X-PAYMENT header and the facilitator settles the payment.
buyer.payment.prepareAPI onlyPOST /v1/buyer/payment/prepare.
receipt.publishedAPI onlyPOST /v1/receipts/{agent} (also auto-emitted by POST /v1/buyer/payment/execute and the paywall on settle).
receipt.pulledIndexer onlyThe pull worker fetches a receipt from a registered pull-target URL.
protocol.fee.collectedAPI + indexerEvery settled earn receipt with price.fee (push, paywall, or pull); plus on-chain fallback when the indexer sees a positive token-balance delta to a Leash fee ATA. Idempotent on (network, receipt_hash) so the receipt-side and on-chain paths never double-count. See Protocol fee.
Brand-new agent creation is exposed as POST /v1/agents/prepare — the HTTP twin of the SDK’s prepareAgentMint. You can either mint via the SDK and start hitting /v1/agents/{mint}/... from the second action onwards, or do the entire lifecycle (mint → identity → executive → delegation → treasury) over plain HTTPS.

Standard integration sequences

These are the four flows almost every integration ends up running. Each one lists the calls in order, what each call does, and — where relevant — the explicit follow-up call you should run so the indexer registers everything the explorer needs.

Sequence 1 — Stand up a brand-new agent end-to-end

Run these in order. After this sequence, the agent + treasury PDA + every stable ATA are on the indexer watchlist, so every future on-chain event for this agent will surface on the explorer automatically.
# 0. Mint the agent. Either over HTTP (recommended for polyglot
#    integrations)…
PREP=$(curl -sX POST https://api.leash.market/v1/agents/prepare \
  -H "Authorization: Bearer $LSH_TEST_KEY" \
  -d '{ "wallet": "'$OWNER_PUBKEY'",
        "name": "Quote Agent",
        "uri": "https://leash.market/agents/quote.json",
        "description": "Returns SOL/USD quotes." }')
MINT=$(echo "$PREP" | jq -r '.echo.asset_address')
# → sign locally → POST /v1/submit
#
#    …or with the SDK:
#       const { assetAddress } = await createAgent(umi, { ... })
#       export MINT=$assetAddress
#
#    Either path drops an `agent.create` event AND auto-registers
#    asset + treasury on the indexer watchlist.

# 1. Bind an executive wallet to the agent (auto-watches asset + treasury).
curl -sX POST https://api.leash.market/v1/agents/$MINT/executive/register/prepare \
  -H "Authorization: Bearer $LSH_TEST_KEY" \
  -d '{ "executive_authority": "'$EXEC_PUBKEY'" }'
# → sign locally → POST /v1/submit

# 2. Authorize the executive to drive Execute for this asset.
curl -sX POST https://api.leash.market/v1/agents/$MINT/executive/delegate/prepare \
  -H "Authorization: Bearer $LSH_TEST_KEY" \
  -d '{ "executive": "'$EXEC_PUBKEY'" }'
# → sign → submit

# 3. Set the SPL spend delegation cap (per stablecoin).
curl -sX POST https://api.leash.market/v1/agents/$MINT/delegation/prepare \
  -H "Authorization: Bearer $LSH_TEST_KEY" \
  -d '{ "executive": "'$EXEC_PUBKEY'", "stable_symbol": "USDC", "allowance": "100" }'
# → sign → submit

# 4. Pre-create stable ATAs on the treasury PDA
#    (auto-registers the ATAs as treasury_ata watch rows).
curl -sX POST https://api.leash.market/v1/agents/$MINT/treasury/provision/prepare \
  -H "Authorization: Bearer $LSH_TEST_KEY" \
  -d '{ "stable_symbols": ["USDC", "USDG"] }'
# → sign → submit  (or accept the no_op envelope if every ATA exists)

# 5. ★ FOLLOW-UP: register every ATA with one read-only call.
#    Free, idempotent, and the canonical "make this agent fully
#    visible on the explorer" step. Run this any time you suspect
#    an ATA was created out-of-band (faucet drop, third-party top-up).
curl -s "https://api.leash.market/v1/agents/$MINT/treasury/balances" \
  -H "Authorization: Bearer $LSH_TEST_KEY"

Sequence 2 — Issue a buyer payment against an x402 endpoint

# 1. Quote the seller (no chain writes; emits no events).
QUOTE=$(curl -sX POST https://api.leash.market/v1/buyer/quote \
  -H "Authorization: Bearer $LSH_TEST_KEY" \
  -d '{ "url": "'$SELLER_URL'", "method": "GET" }')

# 2. Optional: gate via your local RulesV1.
curl -sX POST https://api.leash.market/v1/buyer/policy/evaluate \
  -H "Authorization: Bearer $LSH_TEST_KEY" \
  -d '{ "rules": '"$RULES"', "request": '"$REQ"', "state": '"$STATE"' }'

# 3. Build the unsigned SPL TransferChecked.
PREP=$(curl -sX POST https://api.leash.market/v1/buyer/payment/prepare \
  -H "Authorization: Bearer $LSH_TEST_KEY" \
  -d '{ "payer": "'$BUYER_PUBKEY'", "spl_mint": "'$MINT'",
        "destination": "'$SELLER_PAYTO'", "amount": "1000", "decimals": 6,
        "token_program": "spl" }')
# Drops a `buyer.payment.prepare` event automatically.

# 4. Sign locally → wrap into X-PAYMENT header → execute via the API.
curl -sX POST https://api.leash.market/v1/buyer/payment/execute \
  -H "Authorization: Bearer $LSH_TEST_KEY" \
  -d '{ "url": "'$SELLER_URL'", "method": "GET", "x_payment": "'$X_PAYMENT'",
        "agent": "'$BUYER_AGENT'", "nonce": '$NONCE' }'
# Auto-watches the BUYER agent + treasury, writes the spend receipt,
# and emits `receipt.published` for the buyer.

# 5. ★ FOLLOW-UP (only if you want the SELLER's treasury fund event
#    to also show up on the explorer, and the seller is NOT a Leash
#    payment link). One call, idempotent, free.
curl -s "https://api.leash.market/v1/agents/$SELLER_AGENT/treasury/balances" \
  -H "Authorization: Bearer $LSH_TEST_KEY"
When the seller is a Leash payment link (/x/{id}), step 5 is unnecessary — the paywall already auto-watches the payee on every settle.

Sequence 3 — Onboard an agent that already exists on chain

You inherited an agent that the API has never written to (maybe it was minted with the SDK, or by a different team). One call is enough to make every past and future on-chain event surface.
curl -s "https://api.leash.market/v1/agents/$MINT/treasury/balances" \
  -H "Authorization: Bearer $LSH_TEST_KEY"
This single read:
  • Registers the asset as asset on the watchlist.
  • Registers the treasury PDA as treasury.
  • Registers every SPL ATA owned by the PDA as treasury_ata.
The next indexer tick will backfill the agent’s history (capped at the configured backfill floor) and start surfacing every future event.

Sequence 4 — Receive deposits to an agent treasury

Funding is indexer-only because deposits are unauthenticated SPL transfers anyone can send. To make sure they land in the explorer:
# 1. ★ One-time setup: register the treasury PDA + every ATA.
curl -s "https://api.leash.market/v1/agents/$MINT/treasury/balances" \
  -H "Authorization: Bearer $LSH_TEST_KEY"

# 2. Tell your funder to deposit normally — any TransferChecked into
#    the treasury's ATA, no x402, no special header. After the next
#    indexer tick (~5 s), check:
curl -sG https://api.leash.market/v1/events \
  -H "Authorization: Bearer $LSH_TEST_KEY" \
  --data-urlencode "kind=agent.treasury.fund" \
  --data-urlencode "agent=$MINT" \
  --data-urlencode "limit=5"
For an end-to-end devnet demo, run the reference script:
LEASH_FUND_AMOUNT=1 LEASH_FUND_SYMBOL=USDG \
  pnpm --filter @leash/api fund

Recipes

”Show every state-changing action my agent takes”

Use the prepare → submit flow for every action you initiate. The event lifecycle (prepared → submitted → confirmed) renders directly on the explorer’s per-agent timeline, plus the matching /tx/<signature> page once the chain confirms.
# 1. Prepare
PREP=$(curl -sX POST https://api.leash.market/v1/agents/$MINT/delegation/prepare \
  -H "Authorization: Bearer $LEASH_API_KEY" \
  -d '{ "executive": "'$EXEC'", "stable_symbol": "USDC", "allowance": "100" }')

EVENT_ID=$(echo "$PREP" | jq -r '.event_id')
TX=$(echo "$PREP" | jq -r '.transaction.base64')

# 2. Sign locally with your wallet/HSM
SIGNED=$(node ./sign.mjs "$TX")

# 3. Submit
curl -sX POST https://api.leash.market/v1/submit \
  -H "Authorization: Bearer $LEASH_API_KEY" \
  -d "{ \"event_id\": \"$EVENT_ID\", \"transaction_base64\": \"$SIGNED\" }"

# 4. Track (or subscribe via /v1/webhooks instead)
curl -s https://api.leash.market/v1/events/$EVENT_ID \
  -H "Authorization: Bearer $LEASH_API_KEY"

”Make sure deposits to my agent’s treasury show up”

Funds are indexer-only because anyone can deposit and there is no authority to satisfy. The one-time setup:
# Forces the indexer to register the treasury PDA + every ATA.
curl -s "https://api.leash.market/v1/agents/$MINT/treasury/balances" \
  -H "Authorization: Bearer $LEASH_API_KEY"
After the next indexer tick, every existing and future SPL deposit to any of the treasury’s ATAs will land as agent.treasury.fund, and every SOL deposit ≥ 5 000 lamports will land as agent.treasury.fund_sol. To generate a real deposit on devnet for testing, see the fund.ts reference script:
LEASH_FUND_AMOUNT=1 LEASH_FUND_SYMBOL=USDG \
  pnpm --filter @leash/api fund

“Surface withdraws an SDK driver did out-of-band”

The chain indexer treats SDK-driven Execute instructions identically to API-driven ones. As long as the agent is on the watchlist (any prior prepare* call or one /treasury/balances ping is enough), agent.treasury.withdraw and agent.treasury.withdraw_sol will surface within one indexer tick of confirmation.

”Get push notifications for explorer-relevant events”

Subscribe via Webhooks. You can subscribe to the empty event list (= every kind) or a curated subset:
curl -X POST https://api.leash.market/v1/webhooks \
  -H "Authorization: Bearer $LEASH_API_KEY" \
  -d '{
    "url": "https://your-app.example.com/leash-hook",
    "events": [
      "agent.treasury.withdraw",
      "agent.treasury.fund",
      "receipt.published",
      "payment_link.settled"
    ]
  }'

“Aggregate event volume for a dashboard”

GET /v1/metrics/events returns event counts grouped by phase and kind for the active network. Combined with GET /v1/metrics/usage that gives you a full “API traffic in → on-chain volume out” picture.

Quick checklist

When something should be on the explorer but isn’t:
  1. Is the indexer worker running? GET /v1/indexer/statuscursors.last_run_at should be < 30 seconds old.
  2. Is the agent on the watchlist? Hit GET /v1/agents/{mint}/treasury/balances once. Read-only, side-effects three watch kinds.
  3. Is the action one the API initiates? Did you complete the full prepare → submit round-trip (not just prepare)? Calls that stop at prepared show up with phase=prepared and never advance — that’s almost always a stuck client, not a stuck API.
  4. Is the action a deposit? Confirm the treasury ATA is on the watchlist (the /treasury/balances call above guarantees it), then wait one indexer tick.
  5. Is your API key on the right network? lsh_test_* keys see devnet only; lsh_live_* keys see mainnet only. Cross-network reads return 404 by design.