REST API
Eight endpoints, JSON in and JSON out, over the same base URL the agent itself calls.
Base URL and versioning
All requests go to https://api.almeru.app/v1. The version lives in the path and is only incremented for breaking changes; additive fields can appear in any response, so parse defensively and ignore what you do not recognise. Every response carries an X-Request-Id header — keep it, it is the only thing worth quoting when something looks wrong.
Authentication
Reads are open and unauthenticated but rate-limited by IP. Writes require a bearer key with write scope:
Authorization: Bearer alm_live_7f2c9a4be1d84605
Content-Type: application/json
Keys are prefixed alm_live_ or alm_test_. A test key resolves against the same schemas but never reaches the chain: launches created with one always stop after simulation. Send keys in the header only — never in a query string, and never from a browser you do not control.
Endpoints
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | /protocols | Open | List protocols, newest first. |
| GET | /protocols/{slug} | Open | One protocol in full. |
| GET | /miners | Open | List miners with their current round. |
| GET | /miners/{id} | Open | One miner by id or slug. |
| POST | /launches | Bearer | Create a launch. |
| GET | /launches/{id} | Bearer | Step-by-step launch state. |
| GET | /wallets/{xUserId} | Open | Derive and inspect an agent wallet. |
| GET | /treasury/{address} | Open | Treasury, reserve and staking snapshot. |
GET /protocols
Query parameters: kind (treasury | miner), status (live | settling | retired), limit (1–100, default 25) and cursor.
curl -s "https://api.almeru.app/v1/protocols?kind=treasury&limit=2"{
"data": [
{
"slug": "harbor-reserve",
"name": "Harbor Reserve",
"ticker": "HRBR",
"kind": "treasury",
"status": "live",
"chain_id": 4663,
"address": "0x7b41c0a2e5d93f6a8c1b0e47d2f95a3c6b8e1d40",
"created_at": "2026-05-02T09:14:31Z",
"treasury_value": "184320.55",
"backing_per_token": "1.0742"
},
{
"slug": "tin-line",
"name": "Tin Line",
"ticker": "TINL",
"kind": "treasury",
"status": "live",
"chain_id": 4663,
"address": "0x91a7f60c8e34d2b5107aec9d3f8b64205cd1e7a9",
"created_at": "2026-06-18T14:47:02Z",
"treasury_value": "42780.10",
"backing_per_token": "1.0113"
}
],
"has_more": true,
"next_cursor": "c3Rhcl9oYXJib3ItcmVzZXJ2ZQ"
}
Pagination is forward-only with opaque cursors: pass next_cursor back as cursor and stop when has_more is false. Cursors are tied to the filter that produced them — changing kind mid-walk invalidates them.
GET /protocols/{slug}
curl -s https://api.almeru.app/v1/protocols/harbor-reserve{
"slug": "harbor-reserve",
"name": "Harbor Reserve",
"ticker": "HRBR",
"kind": "treasury",
"status": "live",
"chain_id": 4663,
"address": "0x7b41c0a2e5d93f6a8c1b0e47d2f95a3c6b8e1d40",
"deployer": "0x2ea9c4b7f18d3056a9c7e21b40fd7c85ba36d0f1",
"description": "A slow reserve for the north dock.",
"image_url": "/media/harbor-reserve.png",
"bootstrap_split": { "emissions_bps": 8000, "staking_bps": 1000, "reserve_bps": 1000 },
"routing": { "operations_bps": 100, "losing_side_recycled_bps": 1000, "deployment_fee_bps": 100 },
"created_at": "2026-05-02T09:14:31Z"
}
GET /miners and GET /miners/{id}
/miners accepts phase (open | lock | draw | settle), limit and cursor, and returns the same envelope as /protocols. The single-miner route accepts either the id or the slug.
curl -s https://api.almeru.app/v1/miners/copper-field{
"id": "mnr_8fq2k1x",
"slug": "copper-field",
"name": "Copper Field",
"ticker": "CPFD",
"chain_id": 4663,
"tiles": 25,
"round": {
"index": 41207,
"phase": "open",
"epoch": 27714055,
"opens_at": "2026-07-24T11:03:00Z",
"locks_at": "2026-07-24T11:04:00Z",
"winner_reward": "12.480000",
"token_pot": "1.248000",
"entries": 63,
"entry_value": "418.75"
},
"odds": { "tile": "1/25", "token_pot_release": "1/333" },
"schedule": { "funded_rounds_total": 525600, "funded_rounds_remaining": 484393 },
"routing": { "operations_bps": 100, "losing_side_recycled_bps": 1000, "deployment_fee_bps": 100 }
}
POST /launches
The only endpoint that spends. Send Idempotency-Key: a retry with the same key returns the original launch rather than creating a second one, and keys are remembered for 24 hours.
curl -s -X POST https://api.almeru.app/v1/launches \
-H "Authorization: Bearer $ALMERU_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: copper-field-2026-07-24-01" \
-d '{
"kind": "miner",
"name": "Copper Field",
"ticker": "CPFD",
"bootstrap": { "asset": "USDC", "amount": "1200.00" },
"x_user_id": "1596180932847104000"
}'{
"id": "lnc_3v7pq0m",
"state": "queued",
"kind": "miner",
"quote": {
"bootstrap": "1200.00",
"deployment_fee": "12.00",
"total": "1212.00",
"cap_remaining_24h": "12300.00"
}
}
The same call from JavaScript:
const res = await fetch('https://api.almeru.app/v1/launches', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + process.env.ALMERU_API_KEY,
'Content-Type': 'application/json',
'Idempotency-Key': 'copper-field-2026-07-24-01',
},
body: JSON.stringify({
kind: 'miner',
name: 'Copper Field',
ticker: 'CPFD',
bootstrap: { asset: 'USDC', amount: '1200.00' },
x_user_id: '1596180932847104000',
simulate_only: false,
}),
});
if (!res.ok) {
const { error } = await res.json();
throw new Error(error.code + ' — ' + error.message);
}
const launch = await res.json();
console.log(launch.id, launch.state, launch.quote.total);
GET /launches/{id}
{
"id": "lnc_3v7pq0m",
"state": "settled",
"kind": "miner",
"created_at": "2026-07-24T11:02:41Z",
"steps": [
{ "name": "parse", "status": "ok", "at": "2026-07-24T11:02:41Z" },
{ "name": "validate", "status": "ok", "at": "2026-07-24T11:02:41Z" },
{ "name": "quote", "status": "ok", "at": "2026-07-24T11:02:42Z" },
{ "name": "simulate", "status": "ok", "block": 19442806 },
{ "name": "execute", "status": "ok", "block": 19442807,
"tx": "0x4c1d9a70be25f3c8d1a6470b9e83f215cc47ab30" }
],
"protocol": { "slug": "copper-field", "address": "0x5d38b71ea940c26f8b03d571ce4a92f0d7e19c02" }
}
States move queued → simulating → executing → settled, or terminate at failed. Poll no faster than once per second; a settled launch is immutable, so cache it and stop polling.
GET /wallets/{xUserId} and GET /treasury/{address}
curl -s https://api.almeru.app/v1/wallets/1596180932847104000{
"x_user_id": "1596180932847104000",
"address": "0x2ea9c4b7f18d3056a9c7e21b40fd7c85ba36d0f1",
"chain_id": 4663,
"deployed": true,
"root_owner": "0x9c3f18ba7e0d54216fbc9037ad81e5c4207fb6d8",
"agent_signer": { "granted": true, "method": "executeLaunch" },
"caps": { "per_launch": "5000.00", "rolling_24h": "15000.00", "asset": "USDC" },
"balance": "3184.90"
}
The wallet route answers for any account id, deployed or not — derivation is a pure function of the immutable id, so an undeployed wallet returns the same address with "deployed": false. /treasury/{address} returns treasury_value, supply, backing_per_token, a reserve object with the timelock, and a staking object carrying vesting_days: 7 and return_threshold_bps: 1000.
Status codes and errors
Errors are always a single JSON object under error, with a stable code, a human message, an optional details map and the request id.
{
"error": {
"code": "cap_exceeded",
"message": "Bootstrap plus fee exceeds the rolling 24-hour cap.",
"details": { "requested": "1212.00", "cap_remaining_24h": "840.00" },
"request_id": "req_0b93f7c1a2"
}
}| Status | Codes | Notes |
|---|---|---|
| 200 | — | Success. Reads and completed writes. |
| 201 | — | Launch created. |
| 400 | invalid_request | Malformed body or query parameter. |
| 401 | unauthorized | Missing, malformed or revoked key. |
| 403 | forbidden_scope | Read-only key, or no live authorization grant on the wallet. |
| 404 | not_found | Unknown slug, id or address. |
| 409 | ticker_taken, idempotency_conflict | Reused key with a different body. |
| 422 | cap_exceeded, insufficient_funds, simulation_failed | Well-formed but not executable. Nothing spent. |
| 429 | rate_limited | See Retry-After. |
| 503 | upstream_unavailable | Chain or indexer degraded. Retry reads with backoff. |
Rate limits
| Surface | Limit | Window |
|---|---|---|
| Unauthenticated reads | 60 requests | per minute per IP |
| Authenticated reads | 600 requests | per minute per key |
POST /launches | 10 requests | per minute per key |
POST /launches | Rolling 24-hour spend cap | per wallet, enforced on chain |
Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. On a 429, honour Retry-After and back off exponentially with jitter. The per-minute limit is a service courtesy; the rolling 24-hour cap is a contract rule and no amount of backoff moves it.