Realtime API
Server-Sent Events for agent execution updates, WebSocket for price ticks and OHLCV bars, and the trading-key (permit) flow that authorises agentic actions.
Two channels, two purposes
| Channel | Transport | Purpose |
|---|---|---|
| Agent Execution Stream | SSE | Per-wallet stream of agent lifecycle events: arming, executing, errors, completion. |
| Price Engine Stream | WebSocket | Per-asset stream of unified price ticks and OHLCV bar updates. |
Agent Execution Stream (SSE)
GET /api/agents/stream Per-wallet SSE stream. Auth is by walletAddress (query param) or by JWT via the requireWalletAddress middleware. The server emits an SSE comment heartbeat (: heartbeat\n\n) every 25 seconds; standard browser EventSource clients handle the heartbeat automatically.
Event types
| Event | Payload |
|---|---|
executed | { agentId, executionId, rail, txSignature, filledAmount, filledPriceUsd, feeUsd } |
errored | { agentId, executionId, reason } |
next_run_changed | { agentId, nextRunAt } |
trigger_armed | { agentId, condition } |
completed | { agentId, terminalReason } |
const es = new EventSource(
`/api/agents/stream?walletAddress=${wallet}`
);
es.addEventListener('executed', (e) => {
const d = JSON.parse(e.data);
console.log('Filled', d.rail, d.filledAmount, '@', d.filledPriceUsd);
});
es.addEventListener('errored', (e) => {
console.warn(JSON.parse(e.data));
});
Price Engine Stream (WebSocket)
GET wss://quotes.truefinance.ai/v1/stream Single multiplexed socket for all price subscriptions on the connection. Auth via Bearer JWT or API key (the Authenticator middleware accepts either).
Connection lifecycle
Inbound frames
{ "type": "subscribe", "assets": ["SOL", "BTC"] }
{ "type": "unsubscribe", "assets": ["BTC"] }
{ "type": "pong" }
Outbound frames
{ "type": "hello", "tier": "pro", "limits": { "maxAssets": 100, "maxConnections": 5 } }
{ "type": "price", "data": { "symbol": "SOL", "priceUsd": 178.41, "ts": 1746144000000, "source": "pyth", "stale": false } }
{ "type": "bar_update", "data": { "symbol": "SOL", "interval": "1m", "open": 178.20, "high": 178.51, "low": 178.05, "close": 178.41, "volume": 12340.5, "ts": 1746144000000 } }
{ "type": "ping", "ts": 1746144030000 }
{ "type": "error", "code": "symbol_limit", "message": "Tier cap reached" }
Caps and tiers
| Limit | Free | Pro | Quant |
|---|---|---|---|
| Max concurrent connections | 1 | 5 | 25 |
| Max symbols per connection | 10 | 100 | 1,000 |
| Global cap | 15,000 connections | — | — |
| Per-IP cap | 50 connections | — | — |
Errors the server may emit before close: bad_json, symbol_limit, unknown_type.
Trading-key (Permit) flow
The “trading key” is a permit — an Ed25519 signature over a canonical JSON message that scopes an ephemeral session wallet to specific agentic actions. There is no long-lived API key with trade authority. Every authorised execution traces back to a permit the user signed.
Permit JSON
{
"agentId": "uuid",
"sessionPubkey": "Sess1...PubKeyBase58",
"assetWhitelist": ["SOL", "USDC", "JTO"],
"maxAmountPerExecutionUsd": 250,
"expiresAt": "2026-08-01T00:00:00Z",
"issuedAt": "2026-05-02T12:00:00Z",
"nonce": "a1b2c3..."
}
The permit is signed with the user’s primary wallet. The server stores the bounds (without the raw signature) on the agent row so the executor can re-validate every fire.
No environment variable, dashboard toggle, or admin override grants trade authority. If a permit is not on the agent, the executor will not sign. Permits cannot be edited after issuance — to change scope, the user must stop the agent and sign a new permit.
Caps that bound everything
- 20 active agents per user.
- 500 executions / 24h per user.
- Per-trade USD cap from the permit (
maxAmountPerExecutionUsd), enforced at signing time. - Daily SOL auto-fund cap per user (
MAX_AUTOFUND_LAMPORTS_PER_USER_PER_DAY) for session-wallet top-ups.
SSE and EventSource gotchas
EventSource reconnects on its own. Treat the SSE stream as best-effort delivery: every event carries an executionId so dedup is straightforward, and the REST endpoints (GET /api/agents/{id}, GET /api/agents/{id}/executions) are the source of truth for state reconciliation.
WS reconnect
If the server closes after a missed pong, reconnect with the existing token and re-subscribe to the asset list. Subscriptions are not persisted across connections.
Stable identifiers
executionId is unique per execution attempt. txSignature is unique per landed Solana transaction. Use txSignature for on-chain reconciliation; use executionId for cross-replica dedup.
Safety, limits, failure modes
- Token expiry mid-stream. A JWT that expires while the WS is connected is not refreshed automatically; the next heartbeat will close the connection. Reconnect with a fresh token.
- Stale-flagged ticks. Ticks may carry
stale: truewhen the upstream oracle is degraded; do not trade on stale ticks. See Price Engine. - Permit expiry. An expired permit produces a
PERMIT_EXPIREDerror on the next execution and the agent is paused; the user must sign a new permit to resume. - Connection caps. Hitting the per-tier connection or symbol cap returns a
symbol_limiterror frame and closes the connection. Upgrade tier or distribute symbols across connections.
See also
- True Agents — the consumer of permits.
- Birdeye Feeds — the upstream WebSocket for memecoin and long-tail data.
- Price Engine — the source of unified price ticks.
- Authentication — JWT vs API-key auth model.