Keeper HTTP API
The keeper serves a small read-only JSON API with live venue rates, rate history, the decision log, target weights and fee sweeps. Every endpoint can be called from this page.
On this page
Basics#
| Base URL | https://keeper-production-199f.up.railway.app |
| Methods | GET only (and OPTIONS for CORS preflight) |
| Auth | None. Everything is public and read-only |
| CORS | Any origin, so browsers can call it directly |
| Format | JSON. Times are ISO 8601 UTC, APYs in percent, amounts in USDC, weights as fractions of the vault |
| Errors | { "error": "message" } with status 400, 404 or 500 |
The examples below are real responses captured on 8 October 2026. Send request fetches the live answer from your browser. The web app reads the same endpoints; you can point it at another keeper under Settings.
GET /health#
Liveness, the time the last cycle finished, and the cluster the keeper runs against. It answers 200 as long as the process runs.
/health{ "ok": true, "lastCycle": "2026-10-08T06:45:58.271Z", "cluster": "devnet" }| Field | Meaning |
|---|---|
ok | Always true while the server is up |
lastCycle | When the last full cycle (both tiers) finished; null before the first one |
cluster | devnet, mainnet-beta, localnet, custom, or paper for simulated vaults |
A recent lastCycle is the real health signal: cycles run every 10 minutes, so anything older than about 25 minutes means the keeper is stuck.
GET /rates#
One snapshot per strategy, Bedrock first, in registry order. Cached by the keeper for 5 minutes.
/rates[
{
"id": "kamino-main-usdc",
"venue": "Kamino",
"product": "Main market USDC",
"chain": "solana",
"asset": "USDC",
"apy": 4.3429,
"apyBase": 4.3429,
"apyReward": 0,
"tvlUsd": 124254435,
"utilization": 0.9024,
"riskScore": 2,
"tier": "bedrock",
"source": "kamino-api",
"fetchedAt": "2026-10-08T06:41:48.368Z"
}
]| Field | Meaning |
|---|---|
id | Registry strategy id; see Tiers and venues |
apy, apyBase, apyReward | Total APY and its base and reward parts, percent |
tvlUsd | Venue TVL as the source reports it (0 when unknown) |
utilization | Borrowed share of the venue, 0 to 1, or null when the venue does not expose it |
source | kamino-api, jupiter-api, defillama, exponent-api, or fallback when every source failed |
fetchedAt | When the quote was fetched upstream |
GET /rates/history#
Thirty days of APY history for one strategy. id is required. Venues with a history API return daily points; the others return what the keeper has recorded itself.
/rates/history?id=kamino-main-usdc{
"id": "kamino-main-usdc",
"points": [
{ "ts": "2026-09-08T00:00:00.000Z", "apy": 3.6213 },
{ "ts": "2026-09-09T00:00:00.000Z", "apy": 3.8013 },
…
{ "ts": "2026-10-07T00:00:00.000Z", "apy": 4.4005 }
]
}| Request | Response |
|---|---|
/rates/history | 400 { "error": "missing ?id=<strategy id>" } |
/rates/history?id=nope | 404 { "error": "unknown strategy nope" } |
GET /decisions#
The latest 200 rebalancer decisions, newest first, two per cycle (one per tier). The response is about 200 KB. Field by field, it is described under The decision log.
/decisions[
{
"id": "af686e85-7b13-43bc-8b21-8130a9cde751",
"ts": "2026-10-08T06:45:52.796Z",
"tier": "bedrock",
"kind": "hold",
"summary": "Held allocation at 3.86% net APY: Jupiter Lend → Aave v3 14,896 USDC: -0.09% spread gains -6.85 USDC over 30d vs 9.48 cost (needs 2x, after a 0.5% margin)",
"expectedApyBefore": 3.8551,
"expectedApyAfter": 3.8551,
"moves": [],
"sentinel": { "model": "rules-v1", "verdict": "ok", "score": 0, "note": "All 5 venues nominal" },
"signatures": [],
"rationale": {
"legs": [],
"held": ["Jupiter Lend → Aave v3 14,896 USDC: -0.09% spread gains -6.85 USDC over 30d vs 9.48 cost (needs 2x, after a 0.5% margin)"],
"frozen": ["Kamino Lend", "Kamino Earn"],
"turnover24hUsd": 0,
"turnoverCap24hUsd": 50013.21,
"smoothedApy": { "kamino-main-usdc": 4.4499, "jup-lend-usdc": 4.1688, "kamino-earn-steakhouse": 4.242, "aave-base-usdc": 4.0799, "morpho-steakhouse-eth": 4.3858 },
"spotApy": { "kamino-main-usdc": 4.3429, "jup-lend-usdc": 4.1, "kamino-earn-steakhouse": 4.1167, "aave-base-usdc": 4.2761, "morpho-steakhouse-eth": 4.3589 }
}
}
]Decisions logged before the rebalancer recorded its reasoning have no rationale. moves uses venue names; smoothedApy and spotApy are keyed by strategy id.
GET /targets#
The optimizer's latest target weights per tier, as fractions of the vault. These are where the vault would sit if moving were free; the actual allocation only moves toward them when a move pays. See Targets.
/targets[
{
"tier": "rapids",
"generatedAt": "2026-10-08T06:45:57.839Z",
"targets": [
{ "strategy": "rapids-jup-buffer", "weight": 0.3 },
{ "strategy": "kamino-rwa-onre", "weight": 0 },
{ "strategy": "perena-usd-star", "weight": 0.1 },
{ "strategy": "solstice-eusx", "weight": 0 },
{ "strategy": "exponent-pt", "weight": 0.1 },
{ "strategy": "maple-syrup-eth", "weight": 0.25 }
]
}
]Weights add up to at most 0.95; the rest is the idle buffer. A frozen venue is held at or below its current weight.
GET /fees#
Status of the hourly fee sweep. Each tier shows the shares minted to the treasury by the last sweep and what the treasury holds in total. A keeper running simulated vaults reports enabled: false.
/fees{
"enabled": true,
"intervalMinutes": 60,
"lastRun": "2026-10-08T05:56:20.783Z",
"nextRun": "2026-10-08T06:56:23.655Z",
"treasury": "Hs4RLeoYBs8YtUirjjE8BYaqG596koUWyYMxuJ2GgfEd",
"tiers": {
"bedrock": {
"tier": "bedrock",
"signature": "3gdJTRespXuRd9QfxMmgB2BYJEbtiQUe2jgy4VNc846UibwD5bLuZg1xSLsVunwLRrFdmvivEH4cZGXLPPUCSZej",
"treasury": "Hs4RLeoYBs8YtUirjjE8BYaqG596koUWyYMxuJ2GgfEd",
"shares": 0.057325,
"usdc": 0.057339,
"treasuryShares": 3.654099,
"treasuryUsdc": 3.654997,
"ts": "2026-10-08T05:56:20.783Z"
}
}
}When a sweep fails after its retries, the tier holds { "error": "...", "ts": "..." } instead.
Changes in the next keeper release#
Every endpoint above keeps its current shape. The next keeper adds:
GET /ready, which returns 503 when the keeper is degraded (for uptime monitors), andGET /statuswith per-tier cycle health, rate freshness, the keeper's SOL balance, RPC health and recent alerts;GET /decisions?tier=&kind=&limit=&before=for filtering and paging (with anX-Next-Cursorheader) andGET /decisions/{id}for a single decision;?days=(1 to 90) on/rates/history;GET /metricsin Prometheus format (optionally behind a bearer token) andGET /openapi.jsonwith an OpenAPI 3.1 spec;- errors with a machine-readable
code, ETags, cache headers and standard security headers.
apps/keeper/src/server.ts, apps/keeper/src/types.ts and apps/keeper/src/fees.ts. If the code and this page disagree, the code wins.