> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tayho.io/llms.txt
> Use this file to discover all available pages before exploring further.

# API and realtime gateway

> Internal architecture of Tayho API admission, composition, live reads, relayed writes, and realtime delivery.

# Public API and realtime gateway

`tayho-api` is the only application-facing backend. Two signerless Elysia replicas compose three
authorities without duplicating their canonical state:

```mermaid theme={null}
flowchart LR
  B[Cloudflare-hosted client] -->|REST + WebSocket| A[Elysia API x2]
  A -->|signed intent, no POST retry| E[Engine x2]
  A -->|canonical history and snapshots| P[Ponder indexer x1]
  A -->|block-pinned quote, simulation, risk| RPC[HyperEVM RPC pool]
  E --> C[(Coordination PostgreSQL)]
  E --> R[(Redis / BullMQ / Streams)]
  P --> I[(Indexer PostgreSQL)]
  P -->|canonical event pump| R
  R -->|replay and replica fan-out| A
  A -. protected /metrics .-> M[Prometheus]
  A -. /readyz .-> H[Railway]
```

Ponder owns on-chain projections. The engine owns automatic intents until settlement. Live RPC
reads provide short-lived quotes, unsigned simulations, and liquidation risk. Redis Streams is a
bounded, rebuildable delivery log and cross-replica backplane; it is never an authority. Responses
identify their source with `x-tayho-source: onchain|engine|live`.

API-key projects, hashed credentials, scopes, restrictions, quotas, and operator audit events live
in the API control tables. The API is the only runtime reader of those credentials; the internal
CLI is the only management interface. Shared admission counters live in Redis and fail closed.

## REST contract

The 47 REST endpoints are grouped by responsibility:

* Protocol: `GET /v1/config` and bounded `POST /v1/batch`.
* Markets: list/detail, funding, stats, price, oracle, liquidity, events, trades, candles, and live
  quote under `/v1/markets`.
* Positions: detail, event history, settlement history, and live risk under `/v1/positions`.
* Oracle: canonical funding attestations at `GET /v1/oracle/attestations`.
* Trader portfolio: positions, sessions, portfolio, balances, exposure, PnL, activity, orders,
  trades, funding, settlements, vault activity, rewards, and transfers under `/v1/traders`.
* Protocol accounting: vault state/events/claims and staking state/events.
* Correlation: indexed order outcome and transaction event bundle.
* Trading: signerless `POST /v1/trades/simulate`.
* Automatic intents: create, batch create, signed account list, capability-scoped status, and signed
  cancellation under `/v1/intents`.
* Immediate actions: capability-scoped session, add-margin, vault, and staking submission/status
  under `/v1/actions`.

The interactive OpenAPI document is at `/openapi`; the machine contract is `/openapi/json`. Every
body, path, and query is validated at the Elysia boundary. JSON is stream-bounded to 512 KiB before
parsing. Decimal identifiers enforce the exact Solidity `uint48`, `uint64`, or `uint256` maximum.
`POST /v1/batch` accepts at most 20 unique, explicitly allowlisted on-chain GET paths; it cannot
reach intent, live-RPC, health, metrics, or arbitrary upstream URLs.

## WebSocket contract

`GET /v1/ws` upgrades to `tayho.realtime.v1` after a short-lived single-use ticket is issued through
`POST /v1/realtime/tickets`. It carries server events only; orders and cancellations always use REST
so acknowledgement and retry semantics remain explicit. The public AsyncAPI contract is in the
[realtime reference](/public/reference/realtime).

Clients send `subscribe`, `unsubscribe`, and `ping`. The server sends `hello`, `snapshot`, `event`,
`subscribed`, `unsubscribed`, `heartbeat`, `pong`, `resync_required`, and `error`. Channels are
strictly allowlisted and canonicalized:

* `market.{state|price|trades|funding|liquidity|oracle}:<marketId>`
* `trader.{positions|portfolio|balances|activity|orders|trades|funding|settlements|sessions|vault|rewards|transfers}:<address>`
* `intent:<orderHash>`, `action:<actionHash>`, and `transaction:<txHash>`
* `protocol.config`, `vault.state`, `staking.state`, and `system.status`

An initial subscription captures a stream boundary, fetches an authoritative snapshot, then tails
from that boundary. This at-least-once handoff can deliberately overlap an event already reflected
in the snapshot; clients deduplicate the stable `eventId` and apply domain sequence/version fields
monotonically. Reconnects send `resumeFrom` per channel. If a replay exceeds the bounded replay
limit, the server sends `resync_required` and a fresh snapshot. Intent and action channels require
the receipt capability returned at intake; capabilities and encrypted intent terms are not realtime
stream payloads.
Each API replica maintains one blocking stream reader and fans out locally, avoiding one Redis
connection per browser. Fan-out uses an inverted channel-to-connection index, so each event visits
only interested sockets rather than scanning every client. Concurrent public subscriptions to the
same channel coalesce their authoritative snapshot request; capability-protected intent snapshots
remain isolated.

## Failure and security boundaries

* Writes are forwarded exactly once. The API never automatically retries POSTs; stable action/order
  hashes and required idempotency keys converge deliberate caller retries.
* Idempotent reads use at most `API_READ_RETRY_COUNT` retries, bounded by
  `API_UPSTREAM_TIMEOUT_MS`. Live RPC reads use a configured fallback pool and never hold a key.
* The private engine requires `ENGINE_INTERNAL_TOKEN`, mounted only into API and engine. Intent
  status snapshots preserve receipt capabilities end to end.
* Redis contains derived indexes, public lifecycle projections, and bounded replay data only. Loss
  degrades realtime delivery; PostgreSQL, Ponder, and chain remain canonical and rebuild it.
* CORS is an exact allowlist. Cloudflare proxies the API domain and enforces coarse WAF/IP limits.
  The API also enforces shared Redis-backed per-client REST, WebSocket-connect, and WebSocket-message
  budgets across both replicas. `API_TRUSTED_PROXY_HOPS` must match the deployed Railway proxy chain;
  the engine separately rate-limits signed identities in shared Redis.
* `x-request-id` and W3C `traceparent` flow through request, upstream, job, transaction, and receipt
  paths. Elysia OpenTelemetry exports standard spans; Prometheus covers REST, upstreams, WebSocket,
  slow consumers, and the indexer event pump.
* `/readyz` fails closed when engine, indexer, live RPC, API credential store, admission Redis,
  realtime Redis, or the shared Ponder-pump freshness heartbeat is unavailable. `/livez` reports
  process health only. API receives its restricted credential-control database role, but no keeper
  coordination/indexer database role, signer, transaction journal, or intent encryption key.

Railway may add API replicas without sticky sessions. WebSocket reconnect/resume makes ordinary
rolling drains safe; the Redis stream is shared, deduplicated by event ID, and the Ponder pump uses a
renewable single-leader lease so two API replicas cannot publish duplicate canonical feeds.

Sources:
[`apps/api/src/app.ts`](https://github.com/Tay-Ho/app/blob/develop/apps/api/src/app.ts),
[`apps/api/src/routes`](https://github.com/Tay-Ho/app/tree/develop/apps/api/src/routes), and
[`apps/api/src/realtime`](https://github.com/Tay-Ho/app/tree/develop/apps/api/src/realtime).
