Docs / REST API

[ REFERENCE ]

REST API

The endpoints the deck itself calls. Everything here is session-authenticated and scoped to one operator — for programmatic access from outside a browser, use the MCP server instead.

Conventions

Every endpoint answers with the same envelope, so a client never has to guess the shape of a failure.

response envelopeJSON
// success
{ "success": true, "data": { … } }

// failure — data is always null, error is always human-readable
{ "success": false, "data": null, "error": "Sign in to the Command Deck first." }
  • Authentication — the oddysey_session cookie: HMAC-signed, httpOnly, 12-hour life. Missing or invalid gives 401.
  • Scope — every query is bounded by the session address. There is no cross-operator read anywhere in the API.
  • Validation — bodies are parsed with schemas at the boundary. A failure returns 400 with one readable line per bad field.
  • Errors — server-side detail is logged, never returned. Clients get a sentence they can show a person.

Deck

ENDPOINTPURPOSE
GET /api/deckHydration: watches, proposals, ledger, and freshly priced positions in one round trip. Seeds a first-visit deck.
GET /api/deckJSON
GET /api/deck

{
  "success": true,
  "data": {
    "watches":   [ … ],
    "proposals": [ … ],
    "ledger":    [ … ],
    "positions": [
      { "symbol": "NVDA", "name": "NVIDIA stock token",
        "tokens": 8.75, "priceUsd": 184.52, "change24hPct": -4.1 }
    ],
    "market": {
      "source": "finnhub", "live": true, "stale": false,
      "asOf": 1755689400000
    }
  }
}

Watches

ENDPOINTBODYRETURNS
GET /api/watchesYour watches.
POST /api/watchessymbol, thresholdPct, action, note?The new watch plus the ledger event recording that you filed it.
PATCH /api/watches/[id]Toggles active/paused. 404 if the watch is not on your deck.
POST /api/watches/evaluateRuns the sweep for your watches only, with a per-watch outcome.
POST /api/watches/evaluateJSON
POST /api/watches/evaluate

{
  "success": true,
  "data": {
    "owner": "0x…",
    "evaluated": 3,
    "triggered": 1,
    "outcomes": [
      { "watchId": "…", "symbol": "NVDA", "status": "triggered",
        "detail": "Staged accumulation on the NVDA drawdown" },
      { "watchId": "…", "symbol": "AAPL", "status": "held",
        "detail": "1.20% vs -3%" },
      { "watchId": "…", "symbol": "TSLA", "status": "cooling" }
    ]
  }
}

Proposals

ENDPOINTBODYRETURNS
PATCH /api/proposals/[id]{ "decision": "approve" | "reject" }The updated proposal and its ledger event. 409 when the proposal is no longer in a state you can change.

Deck tokens

ENDPOINTBODYRETURNS
GET /api/tokensToken summaries: id, label, created, last used, revoked. Never the token itself.
POST /api/tokenslabel? (≤60 chars)The new token's plaintext — in this response and nowhere else, ever.
DELETE /api/tokensidRevokes it. 404 if it is not yours or already revoked.

Authentication

ENDPOINTPURPOSE
GET /api/auth/nonceIssues a single-use nonce for the SIWE message.
POST /api/auth/verifyVerifies an EIP-4361 signature against the nonce cookie and the claimed address, then sets the session.
POST /api/auth/demoMints a throwaway demo address and session.
POST /api/auth/logoutClears the session cookie.

Verification is strict on all three counts: the nonce in the signed message must match the nonce cookie, the address line must match the claimed address, and the signature must verify. Any mismatch is 401.

Scheduled evaluation

ENDPOINTAUTHPURPOSE
POST /api/cron/evaluatex-cron-secret headerThe sweep across every operator with active watches. Called by the Worker's scheduled handler.

Guarded by a shared secret rather than a session, because the caller is the Worker itself and not a browser. The comparison is constant-time. With CRON_SECRET unset the endpoint returns 503 and refuses to run at all rather than defaulting open — and the sweep caps at 25 operators per run, reporting how many were deferred instead of hiding them.

Status codes

CODEWHEN
200Fine.
400The body failed validation. The error names the fields.
401No valid session, or a bad cron secret, or an unknown deck token.
404The row exists for somebody, but not for you.
409A legal object in an illegal state — approving something already decided.
500Something broke. Detail is in the server logs, not the response.
503A required secret is not configured.