Leash has three auth modes:
  • Public reads for discovery, reputation, public identity, health, version, and docs.
  • Agent-signed requests using X-Leash-Sig for endpoints scoped to one agent identity.
  • API keys for legacy authenticated API surfaces such as receipts and payment-link CRUD.
API-key calls use a single header:
Authorization: Bearer <your-api-key>
Public reads do not need a key. State-changing agent-scoped bootstrap flows such as POST /v1/agents/{mint}/api-keys use X-Leash-Sig instead.

Key prefixes pick the network

The first segment of a key encodes the cluster it can act on. The API loads the right RPC, scopes every database query, and rejects any attempt to reach across networks.
PrefixNetworkUse this when…
lsh_test_*solana-devnetBuilding, integration tests, CI, demos. Free RPC, faucet stables.
lsh_live_*solana-mainnetProduction. Real money, mainnet RPC, mainnet receipts and explorer.
Examples:
lsh_test_3o7N4WvR…wA      # devnet
lsh_live_aQ12dpL8…vK      # mainnet
A lsh_test_* key cannot see a mainnet event, mainnet receipt, or mainnet agent. A lsh_live_* key cannot see anything from devnet. Searching a devnet signature with a lsh_live_* key returns 404 even though the signature exists on devnet — this is the explorer’s isolation guarantee.

Agent-created keys

Agents can create their own API keys without a dashboard or admin secret:
POST /v1/agents/{mint}/api-keys
X-Leash-Agent: <agent mint>
X-Leash-Timestamp: <ISO timestamp>
X-Leash-Sig: <base58 ed25519 signature>
Content-Type: application/json

{ "label": "production worker" }
The API verifies that the signature was produced by the agent executive public key stored on the agent record. The created key is stored with:
  • owner_wallet = the agent executive public key.
  • agent_mint = the signed agent mint.
  • scopes = exactly ["agent"].
The plaintext key is returned once on create. Store it immediately; list and revoke calls only return prefix, last4, metadata, and timestamps.
leash api-key create --label "local MCP"
leash api-key list
leash api-key revoke <id>
SDK and MCP equivalents:
const leash = new LeashClient({
  agentMint: process.env.LEASH_AGENT_MINT!,
  executiveSecretBase58: process.env.LEASH_EXECUTIVE_KEY!,
});

const { key, plaintext } = await leash.createAgentApiKey({ label: 'worker' });
await leash.listAgentApiKeys();
await leash.revokeAgentApiKey(key.id);

UI and admin-issued keys

The web apps can still create user/wallet keys for signed-in users, and platform operators can issue keys through admin routes. Those keys may carry broader surface scopes such as agents, marketplace, or admin. Agent-created keys are deliberately narrower: they carry only the agent scope and are bound to one agent mint. The prefix is purely a routing hint and tells you which cluster the key acts on at a glance. The opaque tail is high-entropy; treat the full string as a secret.

Per-request context

Two optional headers (or body fields, where supported) flow into every event row and receipt for traceability:
FieldWhere it livesPurpose
Idempotency-KeyHeader, on POST onlyReplays the prior response if the same key is reused. See Idempotency.
X-Leash-Client-RefHeader, any methodFree-form caller reference. Stored on the event row, returned on lookup, surfaced in the explorer.
client_referenceBody field on every prepare and submit endpointSame as the header. Body wins when both are set.
Example wiring:
curl -X POST https://api.leash.market/v1/agents/$MINT/delegation/prepare \
  -H "Authorization: Bearer $LEASH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "X-Leash-Client-Ref: order-42" \
  -H "Content-Type: application/json" \
  -d '{
    "executive": "9XQ2…",
    "stable_symbol": "USDC",
    "allowance": "100",
    "client_reference": "order-42"
  }'

Rate limits

Each API key gets a sliding-window quota that resets every minute. The default is generous and split per route family so a noisy POST /v1/receipts flow can’t starve your prepare traffic. Headers on every response:
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 591
X-RateLimit-Reset: 2026-04-23T12:01:00.000Z
Going over returns 429 with the same headers and a retry_after field in the JSON body. The limiter is global across the API fleet — retrying against a different region won’t get you more budget.

Rotating and revoking

  • Rotate — issue a new key, deploy it, then have us revoke the old one. There’s no client-side rotation flow yet.
  • Revoke — keys can be flipped to revoked_at = NOW() at any time. The next call returns 401 invalid_api_key.
  • Audit — every authenticated call writes the (hashed) key id and client_reference onto the event row. Deleting a key does not delete its history.