The Halyrd API is a local HTTP service exposed by the Python FastAPI backend that ships with your Halyrd installation. Because the agent runs entirely on your machine, the API is bound to localhost — you talk to it directly from your own code, scripts, or dashboard without routing through any external server. All agent resources live under /agents/{id}/…, and the single running agent is always reachable via the canonical alias /agents/current.
Base URL
The FastAPI backend starts automatically when you launch Halyrd. Every path in this reference is relative to that base URL.
Authentication
The API runs on localhost for a single user. No API token, header, or session cookie is required in the current version. If you ever expose the API beyond localhost — for example, through a reverse proxy — you should add your own authentication layer at that boundary.
Content-Type
All requests and responses use application/json. Set the Content-Type: application/json header on any request that includes a body.
Agent path conventions
Every agent resource follows the pattern /agents/{id}/…. For the single running agent you can use either its literal ID (seed) or the stable alias current — both resolve identically.
/agents/current ← always the running agent
/agents/seed ← same agent, by explicit ID
/agents/current/equity ← equity history for the running agent
/agents/current/trades ← trade history
The path is plural (/agents/) even though only one agent runs today. The schema is designed for a future multi-agent fleet — using /current keeps your integration forward-compatible.
Transport model
Halyrd splits its data across two transports so you always have the right tool for the job.
| Transport | Use for |
|---|
| REST | Initial page load, historical data, config reads, and mutations (promote / demote / kill-switch) |
| WebSocket | Live streaming data — equity snapshots, trades, journal entries, risk vetoes, and status changes |
Use REST to seed your UI with history and to issue commands. Subscribe to the WebSocket to receive every new event as soon as it happens. See WebSocket for full event payloads.
Health check
Verify the backend is running before making any other requests.
Response
Quick example
Fetch the current agent’s status and phase:
curl http://localhost:8000/agents/current
{
"id": "seed",
"name": "Halyrd",
"status": "PAPER",
"phase": "paper",
"market_mode": "spot"
}
TypeScript types
TypeScript types are available in the @repo/api-types package. Import them directly in your frontend or integration code:
import type { AgentStatus, EquitySnapshot } from '@repo/api-types';
// AgentStatus: "PAPER" | "ELIGIBLE" | "LIVE" | "DEMOTED"
// EquitySnapshot: { ts: string; equity: number; phase: "paper" | "live"; dd_from_peak: number; }
Error codes
When an operation fails, the API returns a JSON body containing a code field alongside the HTTP status. Use the code field — not just the HTTP status — to drive your error handling logic, since multiple distinct situations share the same 422 status.
| Code | HTTP Status | When | What to do |
|---|
RISK_VETO | 422 | Policy layer blocked a trade | Check risk objectives; the journal shows which rule fired |
QUOTE_DIVERGENCE | 422 | Quote too far from CMC mid price | Retry; the skip is journaled automatically |
QUOTE_STALE | 422 | Quote or block is too old | Retry after a short delay |
INSUFFICIENT_LIQUIDITY | 422 | Insufficient liquidity for the trade size | Reduce position size or choose a more liquid token |
MARKET_MODE_MISMATCH | 422 | StrategySpec mode ≠ executor mode | Check the market_mode field in your spec |
TWAK_BLOCK | 422 | Signing layer refused the transaction | Check allowlist, per-trade cap, and daily cap |
NOT_ELIGIBLE | 409 | Promote attempted without ELIGIBLE status | Use force-promote with explicit confirmation |
DEMOTED | 409 | Agent is currently demoted | Wait for reset or re-approve in Settings |
TWAK_BLOCK only occurs in live mode. In paper mode, the signing layer is not involved, so all 422 errors come from the policy or quote layers.
REST endpoint reference
The table below lists every REST endpoint. Build-now endpoints are available in the current release; roadmap endpoints are designed and reserved but not yet implemented.
| Method | Path | Response | Notes |
|---|
GET | /health | { status: "ok" } | Backend health check |
GET | /agents | Agent[] | Returns an array of length 1 today |
GET | /agents/{id} | Agent | id, name, status, phase, market_mode, last_heartbeat |
GET | /agents/current | Agent | Alias — always resolves to the running agent |
GET | /agents/{id}/equity | EquitySnapshot[] | Query params: from, to, interval |
GET | /agents/{id}/trades | Trade[] | Query param: phase=paper|live|all |
GET | /agents/{id}/journal | { entries[], next_cursor } | Cursor-paginated; query params: cursor, limit |
GET | /agents/{id}/evaluation | Evaluation state + objectives | state, objectives[], window_days_elapsed, trade_floor, eligible |
GET | /agents/{id}/risk/objectives | Objective[] | rule, current, limit, headroom_pct per objective |
GET | /agents/{id}/risk/blocks | Block[] | ts, layer, rule, reason, order_ref per block |
GET | /agents/{id}/watchlist/candidates | Ranked candidates + recommended presets | — |
PUT | /agents/{id}/watchlist | New ConfigVersion | Commits watchlist selection |
GET | /agents/{id}/config/versions | ConfigVersion[] | id, parent_id, params_summary, run_period, outcome |
POST | /agents/{id}/promote | { status: "LIVE", first_tx_hash } | Pass { force: true } to bypass NOT_ELIGIBLE guard |
POST | /agents/{id}/demote | { status: "PAPER" } | — |
POST | /agents/{id}/kill-switch | { twak_session: "revoked" } | Requires { confirm: true } in body |
PATCH | /agents/{id}/config | New ConfigVersion | Body: evaluation_config_patch |
GET | /agents/{id}/identity | ERC-8004 identity fields | Roadmap |
POST | /agents | New Agent | Roadmap — multi-agent spawn |
POST | /backtests | { backtest_id } | Roadmap |
GET | /backtests/{id} | Backtest result + gate status | Roadmap |
GET | /indicators | Indicator[] | Roadmap |