Hosted app
The hosted app at app.tradefloor.dev keeps simulated markets, called sessions, on a server. An agent or a bot trades them through an MCP server at /mcp, an HTTP API at /v1, or an API shaped like Alpaca's trading API at /broker/{session_id}. Hosted tools place orders, and time in a session moves only when the caller advances it. To run a model on your own machine instead, use the LLM adapters, or the local MCP server, which places no orders.
The hosted app is coming soon, and opens in beta. This page shows how agents and bots will connect to it.
Setup
Sign in to app.tradefloor.dev with your email address (there is no password) and make an API key on the Keys page, /keys. Send it as Authorization: Bearer tfk_... on every request. MCP clients that support OAuth, such as claude.ai, Claude Desktop, Claude Code and Cursor, can sign in instead of using a key, and the Connect page, /connect, shows the configuration for each client.
A key has one of three scopes, chosen when it is made:
| Scope | What it can do |
|---|---|
full | The whole API, including forks, analysis, scoring and choosing a session's seed. Keys themselves are made and revoked only in the browser. |
trader | The trading loop only: observe, orders, advance, close, fills, bars, series, events, its own sessions, describe and usage, and the whole Alpaca-shaped API. It opens a session only from a named template, on a seed it never sees. Use it for an agent under test. |
read-only | Reads everything and changes nothing, for dashboards and reports. |
A call outside the key's scope answers 403 forbidden and names the scope.
The loop
Every interface runs the same loop on a session:
- Open a session once and keep the
session_idit returns. - Observe the session: the clock, quotes, the account and open orders.
- Decide what to trade from that observation.
- Place orders, or cancel orders that are still waiting.
- Advance time by a step or more, and go back to the second step.
- Close the session. Its report carries caveats that say what the result does and does not show.
A market order fills at the start of the next step, when you advance, at the price the order book gives for its size. Right after it is placed its status is accepted. GET /v1/sessions/{id}/manifest returns the session's manifest, from which the tradefloor package rebuilds its market.
MCP
The MCP endpoint is https://app.tradefloor.dev/mcp, over streamable HTTP. With Claude Code and a key in TF_KEY:
claude mcp add --transport http tradefloor https://app.tradefloor.dev/mcp \ --header "Authorization: Bearer $TF_KEY"
Other clients, such as claude.ai, Codex and Cursor, take the same URL, and the Connect page shows the configuration for each.
A first session is four tool calls: open_session, observe, place_order with a ticker, a side and a whole-share quantity, and advance. To try it, ask the model to open a session, buy 10 shares of the first ticker, advance one step and observe again. close_session ends the session and returns the report. Every write takes an idempotency_key argument, and the tools besides the loop include fork_session, list_fills, get_bars, get_events, list_presets, describe and get_usage. list_presets names the recommended preset, and every session records its own.
HTTP API
The same loop over /v1, with curl. A write carries an Idempotency-Key header with a new value for each action:
export TF_KEY=tfk_...
API=https://app.tradefloor.dev/v1
curl -H "Authorization: Bearer $TF_KEY" $API/describe
curl -X POST $API/sessions -H "Authorization: Bearer $TF_KEY" \
-H "Idempotency-Key: $(uuidgen)" -H 'content-type: application/json' \
-d '{"universe_size": 5, "seed": 7}'
# the answer carries "session_id"; set S to it
curl "$API/sessions/$S/observation?view=compact" -H "Authorization: Bearer $TF_KEY"
curl -X POST $API/sessions/$S/orders -H "Authorization: Bearer $TF_KEY" \
-H "Idempotency-Key: $(uuidgen)" -H 'content-type: application/json' \
-d '{"ticker": "AAA", "side": "buy", "quantity": 10}'
curl -X POST $API/sessions/$S/advance -H "Authorization: Bearer $TF_KEY" \
-H "Idempotency-Key: $(uuidgen)" -H 'content-type: application/json' \
-d '{"steps": 1}'
curl $API/sessions/$S/fills -H "Authorization: Bearer $TF_KEY"POST /v1/sessions/{id}/close ends the session and returns its report. Sending a write again with the same Idempotency-Key returns the first result and does nothing twice, so a request that timed out can be resent safely.
Alpaca-shaped API
A bot written for alpaca-py trades a hosted session when its base URL is https://app.tradefloor.dev/broker/{session_id} and the API key goes where alpaca-py takes the secret. Sessions are opened on the HTTP API, and the facade trades them. Simulated time moves only when the bot calls /tradefloor/advance, which is where a live bot would sleep.
import os
from alpaca.trading.client import TradingClient
from alpaca.trading.enums import OrderSide, TimeInForce
from alpaca.trading.requests import MarketOrderRequest
KEY = os.environ["TF_KEY"]
url = f"https://app.tradefloor.dev/broker/{os.environ['SESSION_ID']}"
trading = TradingClient("tradefloor", KEY, url_override=url)
trading.submit_order(MarketOrderRequest(symbol="AAA", qty=10, side=OrderSide.BUY,
time_in_force=TimeInForce.DAY))
trading.post("/tradefloor/advance", {"until": "next_open"})
print(trading.get_all_positions())alpaca-py puts /v2 in front of every path, so the advance call goes to /broker/{session_id}/v2/tradefloor/advance, and the app serves the route there as well as without the /v2.
The facade serves the account, clock, calendar, assets, positions, market, limit, stop and stop-limit orders with day or gtc, fill activities, latest trades and quotes, snapshots, bars, and news built from the session's event log. It refuses, with a message naming what it does not serve, streaming, trailing-stop, bracket, OCO and OTO orders, notional and fractional orders, ioc, fok, opg and cls, extended hours, and replacing an order. A bot that depends on any of those needs changes beyond the base URL.
Hosted sessions and the library
| Hosted session | Library on your machine | |
|---|---|---|
| Order quantity | Whole shares only. A fraction is refused, never rounded. | Any finite number of shares, fractions included. |
| When a market order fills | At the start of the next step, when you advance. | When act returns, against the book at that step. |
| Ticks in a step | 30 by default, set when the session opens | 65 by default |
| Warm-up history | history_days up to 260 | history_days up to 2,520 |
| Analysis tools | check_envelope, evaluate_strategies, rank_strategies and explain_price_move, with tighter caps a call (20 days, 6 seeds and 4 strategies) | The local MCP server's limits, or none through the library |
Troubleshooting
Every error has the same body over HTTP and MCP, {"code", "message", "hint"}, with retry_after in seconds when waiting and resending the same request will work.
| Code | Status | What to do |
|---|---|---|
unauthorized | 401 | Send a valid API key, or sign in. |
forbidden | 403 | The key's scope does not cover the call. Use a key with a wider scope. |
invalid_order | 400 | Fix the ticker, the quantity or the price. A fractional quantity lands here. |
insufficient_buying_power | 403 | Send a smaller order or reduce a position first. The hint says how many shares would fit, and an order that lowers exposure is always accepted. |
rate_limited | 429 | Wait retry_after seconds and resend. |
quota_exceeded | 429 | A daily or capacity limit. The message says when it resets. |
session_closed | 409 | The session is read-only now. Open a new one. |
conflict | 409 | A race, or a reused idempotency key or client_order_id with a different request. |
not_found | 404 | No such session or order for this account. |
The free plan allows 20 names a session, 5 open sessions, and 2,000 simulated days and 300 compute seconds a day. GET /v1/usage, or the get_usage tool, gives the current limits and what is left today, and does not count as a call. A bot with only a trader key that cannot open a session needs open_from_template, or POST /v1/sessions/from-template.