Design Log
Every decision in this design, dated, with reasoning — so it can be revisited and confirmed.
This is the record Jason asked for: what was decided, when, and why. The docs on this site are the source of truth for the API update; this page is the source of truth for how they got this way. Append-only — reversals get a new entry, not an edit.
2026-07-28 — Initial design
D1. Docs-first development. These docs are written ahead of implementation and act as the spec. The API is built to match the docs, not the other way around. Rationale: the current 5,240-line hand-written OpenAPI file proved that spec-after-code drifts; spec-before-code also gives Jason a reviewable artifact before any backend work is spent.
D2. Two pillars + auth plane. /context (market data engine) and /runtime (execution), with /account for keys. Rationale: matches the business ("infrastructure AI agents build on") and replaces version-prefixes-as-product-matrix. Framework and venue become request fields — adding either never mints a new API version.
D3. Endpoint budget ~30, down from ~138. Consolidations: status/result embedded in resource GETs; paper trading as mode, not a resource tree; one deployment create for all frameworks. The original draft used one PATCH verb for lifecycle; D34 supersedes that with explicit lifecycle endpoints after review found the loose body too ambiguous.
D4. BYOK-only draft superseded. The original draft removed custody endpoints from the public surface and treated the API as BYOK-only. D33 and D35 supersede it: managed trading wallets are the default account path, BYOK remains the self-custody option, and wallet operations live under /wallet with explicit idempotency and polling contracts.
D5. OTP returns the key in-band. Replaces the magic-link flow that emailed the plaintext key. Rationale: an agent can't read its operator's inbox; a six-digit code is human-relayable and expires in 10 minutes; a key in an inbox is a standing liability. Existing abuse controls (3/6h rate limit, disposable-domain screening) carry over unchanged.
D6. Scoped-key preference became a hard default. The original draft labeled scoped_key: false with a warning rather than rejecting master keys. D33 supersedes it: unrestricted credentials are rejected unless the request body includes explicit unsafe-credential acknowledgements. Rationale: agent-facing APIs need unsafe choices to be machine-visible and deliberate, not hidden behind a 2xx.
D7. Client-side onboarding ceremony (@superior-trade/onboard). Agent-wallet approval and builder-fee signatures move to an open-source CLI run locally, because only the user's master key can sign them and BYOK forbids us receiving it. The API checks readiness; it never signs.
D8. Datasets get an HTTP surface. /context/candles + /context/funding serve the existing GCS releases (Lighter: 1m OHLCV + mark + hourly funding, ~226 markets; Polymarket: outcome history). Rationale: the data engine already exists and is validated — it was simply unexposed. Immutable release IDs make reads reproducible and CDN-cacheable.
D9. Honest-data invariants. Venue-side history holes are served as gaps, never interpolated. Backtest results always include market_change_pct. Paper metrics carry simulated: true. Rationale: these are brand values (see the leaderboard's mandatory disclaimer today) promoted to API contract.
D10. Spec generated from code when built. Zod schemas → OpenAPI via @hono/zod-openapi (already a dependency, currently unused). The hand-written spec file is retired. These MD pages remain the narrative layer; the generated spec becomes the reference layer.
D11. Agent-native docs serving. Every page fetchable as raw markdown at /md/<path>; /llms.txt generated from the nav tree; error responses carry docs_url. Modeled on vercel.com/docs, which serves markdown+frontmatter to machine readers — verified live 2026-07-28.
D12. Docs layout = Vercel, skin = terminal v2. Three-column shell (sidebar / article / on-this-page), ⌘K search, card landing. Styling imports terminal-v2's actual tokens: lime #a3e635 on near-black, DM Sans + Space Mono, liquid-glass panels. Dark-only in this preview; the terminal's GitHub-Light remap is planned, logged here so it isn't forgotten.
D13. Error taxonomy commitments. Stable append-only codes; distinct problems get distinct codes (credentials_missing ≠ account_not_funded — a direct lesson from the current API rewriting "not onboarded" as "not funded"); docs_url in every error.
D14. Old API sunsets slowly. v1/v2/v3 keep working during build-out; deprecation with Sunset headers and dates; 410 + docs_url at the end. Existing st_live_ keys valid on both surfaces throughout.
2026-07-28 — Layout rebuilt against the measured Vercel docs system
D15. The first pass looked nothing like Vercel's docs (Jason's call — correct), because it
was built from memory. Rebuilt after pulling vercel.com/docs HTML + CSS chunks and
extracting the actual system: 64px sticky header (transparent over black, hairline on
scroll); 1400px page grid with 300px sidebar / fluid article / 240px sticky TOC; search
box at the top of the sidebar (not the header) with a ⌘K kbd; sidebar groups with 13px
gray labels and 14px-medium h-8 items at 1px gaps; H1 48/56 semibold at −6% tracking;
16/24 body #a0a0a0 on black with #ededed headings; hairline borders at white/14%;
13/20 code in bordered rounded blocks with a 40px header row; breadcrumb + "Copy page"
utility row above the H1; TOC with "Was this helpful?" pill and scroll-to-top; the docs
landing rendered inside the docs shell (Vercel's index is a docs page with card grids,
not a marketing hero). All glass/glow surfaces dropped except the ⌘K modal — Vercel's
docs are flat; Superior identity is carried by the lime accent (links, active nav, badges),
DM Sans/Space Mono, and the black background, which happens to match Vercel's dark theme
exactly. Measured values are annotated in app/globals.css.
2026-07-28 — Header unified with the terminal (Jason-directed)
D16. The docs header is the terminal's header. Rebuilt to mirror
terminal-v2/components/terminal/header.tsx: 56px bar, border-b white/10 over
bg-black/40, the v3 logo mark + "Superior Trade" wordmark (muted "Docs" suffix), and a
single Open Terminal CTA in the terminal's deposit-pill style (lime, rounded-full,
mono 10px bold uppercase). Removed per Jason: the "Design preview" badge and the separate
Terminal / superior.trade buttons. Rationale: the docs and the product should read as one
surface; the chrome users already know is the chrome the docs should wear.
2026-07-28 — Dual-eval validation round and the honesty pass it forced
Jason reset validation with a sharper rubric: (1) does the writing work for its actual
readers, (2) will the documented features work in practice. Two independent evaluators ran —
a reader-experience eval (four personas walking the docs end to end) and a feasibility eval
(every documented behavior graded EXISTS / BUILDABLE / HARD / FICTION against the real
apps/api backend). Feasibility bottom line: ~60% of the surface is a facade over machinery
that already runs; four load-bearing gaps (Nautilus paper mode, Nautilus per-deployment
metrics, HL/Binance dataset releases, delete guarantees on shared runtimes); two claims that
were flatly false. Fixes below — docs changed to be honest, not promises added.
D17. The quickstart must be literally executable. Rewrote it: paper deployment now
started explicitly (creation never starts — the docs said so and the quickstart forgot);
"flip to live" removed (mode is fixed at creation; live = new deployment); a complete
30-line runnable strategy included; content-type on every request; retitled plain
"Quickstart" (the "for Agents" title repelled the human half of the audience).
D18. The human↔agent seam is documented, not implied. The two deliberate human handoffs
(OTP; scoped-key after the local signing ceremony) are named as such, and the deployment
GET gained a venue_account readiness probe (funded, balance_usd) so an agent checks
funding instead of starting-and-hoping. Feasibility: the underlying balance checks exist.
D19. Deletion honesty. "After a 200 no process holding your key exists" was structurally
untrue for Nautilus shared runtimes (reconciler-based teardown; per-wallet shared
credential). New contract: DELETE → 202 + deleting status; deleted only after
confirmed teardown + credential destruction; shared-wallet credentials destroyed with their
last deployment. Confirmed-not-assumed is the strongest claim the architecture supports.
D20. Network-restriction claim retracted. The docs claimed sandbox egress restricted to the venue with violations failing "at validation" — false on both counts (no egress policy on freqtrade pods; v3 policy deliberately allows public 443 because venue IPs live behind CDNs; validation-time detection of arbitrary network calls is not honestly possible). Replaced with the truth: gVisor isolation, private networks blocked, general outbound HTTPS currently permitted and unsupported.
D21. Paper mode is freqtrade-only at launch. Nautilus sim execution has zero code today;
paper_supported now reads false for Polymarket/Lighter, the canonical loop offers a
bounded-live substitute, and Nautilus paper is planned work, not documented behavior.
D22. Release pinning claimed only where release machinery exists. Lighter/Polymarket:
immutable releases, live today. Hyperliquid/Binance: release: null, mutable backfill
store, reproducibility explicitly not claimed until their pipeline exists. "No gaps array
means not-audited, never no-gaps."
D23. The npx @superior-trade/onboard block removed. A copy-pasteable command for a
package that would 404 was the most checkable false claim in the docs. The two-call SDK
ceremony (approveAgent + approveBuilderFee, shown inline) is the supported path; the CLI is
planned. Building it is S/M — it should exist before public launch.
D24. Security page argues with mechanisms, not adjectives. Named gVisor; named the
secret-store boundary (runtime cluster store, never the application DB); stated the
residual risk plainly — a leaked trade-only key can destroy value up to the account balance
via deliberate bad trades; scoped keys bound a leak, they don't neutralize it. Funding
discipline is part of the security model. Also: scoped_key: true is a venue lookup
(derived address ∈ approved agents), not an assurance.
D25. Smaller corrections from the evals: Hyperliquid market responses originally carried
framework aliases as the primary symbol. D32 supersedes that: Context symbol is venue-native
and framework aliases live under framework_symbols. Intelligence bucket list corrected
(majors excluded by the engine's
design) and the "same data as candles" claim dropped; simulated field added to the metrics
example; limit_exceeded added to the error registry; leaderboard example drawdown made
plausible; Free-tier pricing stated ($0) with Pro priced at launch; freqtrade guide now
states the venue scope (HL/Binance only) and the hyperopt/FreqAI boundary in its opening;
HIP-3 defined at first use; a Reliability section added to the runtime overview
(crash-restart, state persistence, positions never auto-closed by infrastructure).
2026-07-30 — Minimal-text rework, 繁體中文, stage-1 scope (Jason-directed)
D26. Reference density, not prose. Jason: "cut down all the bullshit and text — too hard to work on." Every content page rewritten to tables/JSON/one-line notes under a strict contract: all 32 endpoints, every example, table, error code, and honesty caveat preserved; all aphorisms and philosophy paragraphs deleted. Result: −32% words / −29% lines overall, with the survivors being mandatory content (JSON, tables, code), not prose. Chrome cut to match: header is logo + wordmark + language toggle only (no nav links, no Open Terminal, no "Docs" word); TOC dropped "Was this helpful?" and "Scroll to top"; landing is one line + title-only cards.
2026-07-30 — PR review hardening
D32. Context uses canonical venue-native symbols. /context/markets, candles, funding,
datasets, and URL path params use symbol as the venue-native id. Framework-native names are
aliases in framework_symbols, used only inside framework config. Rationale: one primary id
prevents clients from passing a freqtrade pair to a Context endpoint that expects venue data.
D33. Unsafe BYOK credentials require explicit acknowledgement. PUT /runtime/deployments/:id/credentials rejects unrestricted or withdrawal-capable credentials
by default with credential_scope_unsafe. The override requires
allow_unscoped_credential, acknowledge_withdrawal_risk, and
acknowledge_not_recommended in the request body. Rationale: agents treat 2xx as success;
unsafe custody choices must be visible in input and output.
D34. Deployment lifecycle actions use a status endpoint. Metadata stays on
PATCH /runtime/deployments/:id; lifecycle transitions use
PUT /runtime/deployments/:id/status with { "action": "start" | "stop" }.
Delete force is a request-body acknowledgement, not ?force=true. Rationale:
validation and destructive behavior should be discriminated by endpoint and body shape,
not inferred from loose metadata fields or query params.
D35. Money-moving async calls require idempotency and polling. Managed-wallet withdrawal
now requires Idempotency-Key, returns a withdrawal resource, and documents GET /wallet/withdrawals/:id plus list. Rationale: retry after a lost 202 must never
create a second payout.
D27. Traditional Chinese. EN/繁 toggle in the header (cookie-based). Content lives in
content/en/ and content/zh-Hant/ with per-page English fallback, so a missing
translation never 404s. Nav, chrome strings, search index, and /md/<path>?lang=zh-Hant
all localize; llms.txt stays English.
D28. Stage-1 design scope: Account + Runtime only. For first-stage design focus the
sidebar, landing, search, and llms.txt show only the Account and Runtime groups
(stage1 flags in lib/nav.ts). Getting Started, Market Context, Guides, and Reference
remain authored and render by direct URL, but are hidden until later stages.
2026-07-30 — D26 reversed: full text restored (Jason-directed)
D29. The tersification (D26) is rolled back. Jason's call before sharing with the team:
the docs should read like an established API reference (Stripe/Vercel register — complete
sentences, context around every endpoint), not a bare cheat-sheet. All content pages
restored to their pre-tersification (post-honesty-pass) text at content/en/. What
stays from the rework: the minimal chrome (D26's header/TOC/landing cuts), the 繁體中文
toggle (D27), and the stage-1 Account+Runtime scope (D28). The terse experiment remains in
git history (6cf306a3) if a condensed variant is ever wanted.
2026-07-30 — Developer-persona review applied + the two-execution model
D30. Developer-review fixes. A developer-persona evaluation (senior quant-shop dev
deciding whether to integrate) surfaced seven must-fixes; all applied: the stage-1 visible
scope is now self-sufficient for a first request (base URL + x-api-key + full curls in
account/registration and runtime/deployments); alive_until lapse semantics are
specified, including a new on_expiry: "stop" | "block_entries" field (block-entries keeps
exit logic running until flat — the safe expiry for position-holding bots); code/config
immutability and the live-update path are stated; image dependency contents and create-time
digest pinning documented; a backtest cost-model table (fees and builder fee modeled;
slippage and funding NOT — stated plainly); Idempotency-Key on all creates plus
name_taken; the Polymarket paper-mode contradiction in the Nautilus guide fixed. Also:
per-code HTTP status registry with x-request-id on every response, webhooks promoted from
candidate to committed roadmap with an event list, list-response envelopes shown, credential
verification timing (PUT + start) and stop→PUT→start rotation specified, OpenAPI location
declared, UTC reset boundaries stated, and a pass trimming "honest/honesty" self-references
(the reviewer: "Stripe never tells you it's being honest").
D31. Two kinds of execution (Jason-directed). The runtime now has an explicit split:
one-time execution (/runtime/executions — a Python script that runs once with
injected credentials and exits; place/close/rebalance/sweep; 15-min cap; execution_timeout
and script_error codes) and recurring execution (deployments, as before). This also
closes the reviewer's "how do I close positions via the API" gap — a close script is a
one-time execution. New stage-1-visible Quick Start group (Introduction — supported
platforms + the two execution types; Agent — the integration flow request-by-request)
becomes the on-ramp, fixing the reviewer's blocker that stage-1 hid all authentication
context. Site title fixed to "Superior Trade API"; search moved to the header center with
neutral (non-lime) selection.
D32 (planned, Jason 2026-07-30). The Quick Start Agent page becomes a copy-able
prompt: one block the operator pastes into their AI agent that teaches it the whole
integration (base URL, auth + OTP relay, the two human handoffs, endpoint map, readiness
probes, failure etiquette) — the page's docs prose becomes secondary to that block. Deferred
by Jason ("can come later"); the current request-by-request page stands until then. Natural
implementation: a <CopyPrompt> block at the top of quickstart/agent.md reusing the
existing copy-button plumbing, with the prompt maintained as its own file so /md/ serves
it to agents directly.
2026-07-30 — Credential model pivot: managed wallets primary, BYOK optional
D33. D4 amended (Jason-directed). The public API keeps Superior-managed trading
wallets (Privy) as the primary credential path — deposit and deploy, no key handling,
same custody that runs the Terminal — with BYOK as the option for self-custody users
and existing venue accounts. Rationale: flexibility at deploy time beats purity; the
managed path is the fastest onboarding and reuses the proven allocate-on-deploy machinery,
while BYOK remains fully specced (toxic-material rules unchanged, scoped-key verification
unchanged) for users who won't hand over custody. Spec shape: credentials on create
defaults to { "type": "managed" }; BYOK payloads carry "type": "byok". What D4 removed
from the public surface partially returns: managed-wallet funding/withdrawal semantics
(deposit instructions via GET /account, withdrawals restricted to the verified login
wallet). Supported-platforms table now ranked by typical trading volume with venue logos
(Binance > Hyperliquid > Lighter > Polymarket — simple public-volume eval, not a study).
D34 (2026-07-30). Follow-ons to D33: the managed wallet gets its own account-level page
(/wallet/overview — GET /wallet for address/deposit/held-vs-allocated
balances, POST /wallet/withdraw to the verified login wallet only, no destination
parameter by design). Site gains dark/light theme with system default (pre-paint inline
script, GitHub-Light palette in light mode, cycle toggle system→light→dark in the header).
Landing-section icons via the terminal's Higgsfield crystal-glass pipeline are queued
(blocked on CLI re-auth).
2026-07-30 — Phi's API review applied (D35)
D35. Five findings from Truong Ma Phi's review of the spec, all accepted and applied:
(1) POST /account/wallet/withdraw now requires Idempotency-Key — money movement
must be retry-safe by construction, not convention. (2) PATCH's action/metadata mixing was
ambiguous under partial failure — lifecycle moved to explicit POST …/start /
POST …/stop endpoints; PATCH edits metadata only (this reverses D30's "fields combine"
convenience — Phi's critique was correct). (3) DELETE ?force=true replaced by a two-step
acknowledgement: 409 positions_open returns a single-use, state-bound confirm_token;
the caller repeats DELETE with X-Confirm-Delete. (A DELETE request body was considered
per Phi's suggestion and rejected only because proxies drop DELETE bodies unreliably; the
token achieves the same explicit acknowledgement.) (4) Credentials are now one
discriminated union everywhere — {"type": "managed"} | {"type": "byok", "venue", …};
omission ≡ managed on deployments, ≡ credential-less on executions (stated, not implied);
credentials: null eliminated from all examples. (5) The withdrawal flow is complete:
GET /account/wallet/withdrawals + /:id with the queued → processing → completed | failed lifecycle and tx_hash. New error codes: idempotency_key_required,
invalid_confirm_token.
D36 (2026-07-30, Jason-directed). Per-endpoint subsections in the sidebar: the
active page's headings (endpoint METHOD /path entries in mono, prose sections in text)
render directly under its sidebar item with scroll-spy highlighting and smooth-scroll
anchors. The right-rail "On this page" TOC is removed — navigation lives in one place.
2026-07-30 — Second review round from Phi's PR threads (D37)
D37. Five further findings from the PR review threads, all applied: (a) unscoped
(master/withdrawal-capable) credentials are now rejected by default with 400 unscoped_credential; the override is an explicit allow_unscoped: true body field —
machine-visible, deliberate, never a silent 2xx. This substantially resolves open question
1 at attach time. (b) One canonical market identifier: /context/markets' symbol is
THE id for every /context endpoint; framework spellings moved to an aliases map used
only in strategy config ("context reads → canonical; strategy config → alias") — the
markets-vs-candles contradiction is gone. (c) Executions gained a sandbox contract
table: 1 vCPU/1 GiB, ephemeral 512 MiB /tmp, package allowlist, gVisor + egress posture
(consistent with D20's honesty), 1 MiB output cap, credential redaction. (d) The header
no longer overflows small viewports (fixed rails now lg:-only, ⌘K hint hidden on mobile).
(e) Hidden pages get full page chrome — breadcrumb/title resolve against the full nav;
neighbors paginate within the visitor's context (staged list for staged pages, full list
for hidden ones) — and apps/docs has a working ESLint flat config so lint runs.
2026-07-31 — Endpoint diet (43→38) + Managed Wallet section (D39)
D39 (Jason-directed). Five cuts, Account/Runtime only (Context untouched by choice):
GET …/deployments/:id/credentials dropped (duplicate of the deployment GET's embedded
metadata); GET /account/usage merged into GET /account (one small read for the whole
account picture); PATCH /account/keys/:id dropped (names fix at mint; rotation replaces
renaming); GET …/withdrawals/:id merged into the list via ?id=; DELETE /runtime/backtests/:id dropped (12-month retention; re-addable non-breakingly). A cut
that was proposed and rejected, and the principle behind the rejection: folding /logs
into resource GETs via ?include=logs was retracted because agent readers have context
windows — resource GETs must stay small by construction, and bulk text (logs, big trade
lists) is always a separate, deliberate fetch. All three /logs endpoints stay.
Also per Jason: the managed wallet is promoted from one Account page to its own stage-1
Managed Wallet section — Overview (GET /account/wallet, held vs allocated,
lifecycle), Deposit (the on-chain flow, rules and edges), Withdraw (POST …/withdraw +
the merged withdrawals list). account/trading-wallet is removed and all links retargeted.
Crystal-glass wallet icon added to the landing via the terminal icon pipeline.
2026-08-01 — Reconciliation with the unified-api implementation (D40)
Two parallel lines of work met on this branch: the docs' D35–D39 passes and the
first apps/unified-api implementation. Where they disagreed, the implementation's
choices win, superseding parts of D35/D37 recorded above:
- Lifecycle: one endpoint —
PUT /runtime/deployments/:id/statuswith{ "action": "start" | "stop" }. Supersedes D35's separatePOST …/start/POST …/stop.PATCHstays metadata-only either way. - Delete around open positions: explicit body acknowledgement
(
force+acknowledge_positions_open), not the D35confirm_tokentwo-step. Still never a query flag. - Wallet surface:
GET /wallet,GET /wallet/deposits,POST /wallet/withdraw,GET /wallet/withdrawals[/:id](not/account/wallet/*). Nav group is Managed Wallet (Jason's naming); page slugs arewallet/deposits/wallet/withdrawals. - Unscoped-credential override:
allow_unscoped_credentialplus the two acknowledgement fields, errorcredential_scope_unsafe(supersedesallow_unscoped: true/unscoped_credential). - Markets: framework spellings stay under
framework_symbols. - QA callout dropped (Jason, 2026-08-01): the boxed Q&A blockquote treatment is out; those passages render as plain blockquotes.
Open questions awaiting Jason
- D6 override — hard-require scoped keys for live starts?
- Domain — docs at
docs.superior.tradewithapi.superior.trade/docsredirecting, or keep docs on the api host? - Deposit floor amounts —
min_deposit_usdvalues in/context/venuesare design placeholders; confirm per venue. - Plan limits — Free/Pro numbers on Usage & Limits mirror today's tiers; confirm they're the go-forward tiers for API-first users.
- Copy-trade execution — shipped as a strategy template on deployments (current design) or as a first-class resource?
- Delete vs open positions — today's stop path auto-closes positions; this design deliberately does NOT (409
positions_open+ explicit force). Reversing live behavior is a user-safety call — confirm. - Entitlements — the 3/6 deployment limits are new (current code caps at 10 deployments, 3 trading accounts); a
planconcept doesn't exist in code yet. Confirm the go-forward numbers and where Pro is sold. - Secret-store bar — BYOK keys land in Kubernetes Secrets today; is that the accepted bar, or do we require KMS-envelope/sealed secrets before accepting third-party keys?
- Where missing compliance/eligibility surface lives — "an anonymous email buys a trading runtime": geo/KYC/terms posture needs a page, and the policy behind it is not a docs decision.
How to propose a change
Edit the page in apps/docs/content/, and add a dated entry here saying what changed and why. The diff is the design review.