Multiuser And Workspaces
Use this guide when you want to inspect the current session, create a workspace, or call workspace-scoped paper trading and backtest endpoints.
The app has an early multiuser/workspace API layer. It is still local-first: authentication defaults to a local user, and app-created state is stored in a local SQLite app-state database while the PostgreSQL-backed version is being built.
Quick Start
- Start the backend and frontend.
- Check the current session with
GET /api/v1/auth/session. - Create or select a workspace.
- Use workspace-scoped endpoints for backtests and paper trade plans.
- Keep real trading disabled unless an execution layer is intentionally added.
Current Behavior
Default local mode:
AUTH_MODE=none- one local user
- one default local workspace
- shared DuckDB market data from
file.db - workspace-scoped durable app state in
app_state.db - persisted workspaces, paper trade plans, paper fills, order placeholders, and backtest metadata/results
Password mode:
- set
AUTH_MODE=password - set
DEFAULT_USER_PASSWORDbefore first startup to bootstrap the local owner - login uses an HTTP-only session cookie
- workspace owners can add members and assign
owner,editor,trader, orviewerroles
Market-data endpoints remain global because candle data is shared:
GET /api/v1/symbols
GET /api/v1/candles
GET /api/v1/ratios
GET /api/v1/scanner/uptrends
GET /api/v1/scanner/ratio-uptrends
User-created research and paper-trading objects can be accessed through workspace-scoped endpoints.
Start The App
Start the backend:
uv run uvicorn main:app --host 127.0.0.1 --port 8000
Start the frontend:
npm run dev
Open:
http://127.0.0.1:5173/
The frontend top bar shows the active workspace, lets you create another workspace, and lets you switch between loaded workspaces. Live paper trade plans and category backtests use the selected workspace.
App-State Database
The backend stores multiuser app state in:
app_state.db
Override the path with:
APP_STATE_DB_PATH=/path/to/app_state.db uv run uvicorn main:app --host 127.0.0.1 --port 8000
This SQLite database is separate from the market-data DuckDB file file.db.
Deleting app_state.db removes saved workspaces, paper trade plans, fills,
order placeholders, and persisted backtest metadata/results. It does not delete
market candles.
Inspect The Current Session
Check the active local user and workspace:
curl http://127.0.0.1:8000/api/v1/auth/session
Expected response shape:
{
"auth_mode": "none",
"user": {
"id": "local-user",
"email": "local@example.invalid",
"display_name": "Local User",
"status": "active"
},
"active_workspace_id": "local-workspace",
"workspaces": [
{
"id": "local-workspace",
"name": "Local Workspace",
"default_quote_asset": "USDT",
"role": "owner"
}
]
}
Equivalent user/session endpoint:
curl http://127.0.0.1:8000/api/v1/me
Enable Password Login
Start the backend with password auth:
AUTH_MODE=password DEFAULT_USER_PASSWORD='change-this-password' \
uv run uvicorn main:app --host 127.0.0.1 --port 8000
Then open the frontend. The app shows a login screen before the chart workspace. The bootstrap user is:
local@example.invalid
Use the value of DEFAULT_USER_PASSWORD as the password. The session is stored
in an HTTP-only cookie.
Login through the API:
curl -i -X POST http://127.0.0.1:8000/api/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"local@example.invalid","password":"change-this-password"}'
Logout:
curl -X POST http://127.0.0.1:8000/api/v1/auth/logout
List And Create Workspaces
List workspaces:
curl http://127.0.0.1:8000/api/v1/workspaces
Create a workspace:
curl -X POST http://127.0.0.1:8000/api/v1/workspaces \
-H 'Content-Type: application/json' \
-d '{"name":"Research Team","default_quote_asset":"USDT"}'
The response includes the new id. Use that id as {workspace_id} in the
workspace endpoints below.
Get one workspace:
curl http://127.0.0.1:8000/api/v1/workspaces/{workspace_id}
Manage Workspace Members
Workspace owners can add or update members from the frontend top bar. The API endpoint is:
POST /api/v1/workspaces/{workspace_id}/members
Example:
curl -X POST http://127.0.0.1:8000/api/v1/workspaces/{workspace_id}/members \
-H 'Content-Type: application/json' \
-d '{
"email": "member@example.invalid",
"display_name": "Member",
"role": "trader",
"password": "member-password"
}'
List members:
GET /api/v1/workspaces/{workspace_id}/members
Legacy Local Routes
The original routes still work and map to the default local workspace:
POST /api/v1/backtests
POST /api/v1/backtests/category-relative-momentum
GET /api/v1/backtests/{backtest_id}
GET /api/v1/trade-plans
POST /api/v1/trade-plans
GET /api/v1/accounts
GET /api/v1/orders
POST /api/v1/orders
Objects created through a non-default workspace route are not visible through these legacy routes.
Workspace Backtests
Create a simple backtest in a workspace:
curl -X POST http://127.0.0.1:8000/api/v1/workspaces/{workspace_id}/backtests \
-H 'Content-Type: application/json' \
-d '{
"strategy_id": "ma_cross",
"symbol": "BTCUSDT",
"interval": "1d",
"from": 1704067200,
"to": 1735689600,
"initial_capital": 10000
}'
Create a category relative momentum backtest:
curl -X POST http://127.0.0.1:8000/api/v1/workspaces/{workspace_id}/backtests/category-relative-momentum \
-H 'Content-Type: application/json' \
-d '{
"category": "Layer 1",
"strategy": "relative_momentum",
"interval": "1d",
"quote_asset": "USDT",
"initial_capital": 10000,
"long_count": 5,
"short_count": 5
}'
Read backtest results:
GET /api/v1/workspaces/{workspace_id}/backtests/{backtest_id}
GET /api/v1/workspaces/{workspace_id}/backtests/{backtest_id}/equity
GET /api/v1/workspaces/{workspace_id}/backtests/{backtest_id}/trades
GET /api/v1/workspaces/{workspace_id}/backtests/{backtest_id}/rebalances
Backtests are still computed synchronously in the API process. Durable queued jobs are planned for a later phase.
Workspace Paper Trade Plans
List trade plans:
curl http://127.0.0.1:8000/api/v1/workspaces/{workspace_id}/trade-plans
Create a single-leg paper trade plan:
curl -X POST http://127.0.0.1:8000/api/v1/workspaces/{workspace_id}/trade-plans \
-H 'Content-Type: application/json' \
-d '{
"account_id": "paper-main",
"trade_type": "single",
"strategy_tag": "trend_following",
"thesis": "Trend continuation",
"invalidation": "Daily close below support",
"max_risk_amount": 100,
"legs": [
{
"symbol": "BTCUSDT",
"side": "long",
"planned_quantity": 0.01,
"planned_entry_price": 60000
}
]
}'
Read, cancel, open, close, and inspect a trade plan:
GET /api/v1/workspaces/{workspace_id}/trade-plans/{trade_plan_id}
DELETE /api/v1/workspaces/{workspace_id}/trade-plans/{trade_plan_id}
POST /api/v1/workspaces/{workspace_id}/trade-plans/{trade_plan_id}/paper-open
POST /api/v1/workspaces/{workspace_id}/trade-plans/{trade_plan_id}/paper-close
GET /api/v1/workspaces/{workspace_id}/trade-plans/{trade_plan_id}/pnl
GET /api/v1/workspaces/{workspace_id}/trade-plans/{trade_plan_id}/fills
Paper open example with explicit mark price:
curl -X POST http://127.0.0.1:8000/api/v1/workspaces/{workspace_id}/trade-plans/{trade_plan_id}/paper-open \
-H 'Content-Type: application/json' \
-d '{"mark_prices":{"BTCUSDT":60000},"fee_bps":5}'
Workspace Accounts And Orders
List paper accounts:
curl http://127.0.0.1:8000/api/v1/workspaces/{workspace_id}/accounts
Read paper positions:
curl http://127.0.0.1:8000/api/v1/workspaces/{workspace_id}/accounts/paper-main/positions
Order endpoints are present as placeholders. They remain disabled when
TRADING_MODE=readonly, which is the default.
GET /api/v1/workspaces/{workspace_id}/orders
POST /api/v1/workspaces/{workspace_id}/orders
DELETE /api/v1/workspaces/{workspace_id}/orders/{order_id}
If trading mode is enabled later, POST /orders requires an Idempotency-Key
header.
Troubleshooting
Workspace changes disappear
Check APP_STATE_DB_PATH. Workspace, paper plan, fill, order placeholder, and
backtest state is stored in the SQLite app-state database, not in the DuckDB
market data file.
Login does not show the expected user
Confirm AUTH_MODE and DEFAULT_USER_PASSWORD were set before startup. In
default local mode, the app uses the local bootstrap user and default
workspace.
Paper trade endpoints return workspace errors
Use a workspace id returned by GET /api/v1/workspaces, and make sure the
current user has access to that workspace.
Important Limitations
- Workspace, backtest, trade plan, fill, and order state is durable in local SQLite, but it is not yet PostgreSQL-backed or multi-process safe.
- Password authentication is local and SQLite-backed. It is useful for self-hosted/local teams, but it is not an OIDC/SAML enterprise identity integration.
- The frontend exposes workspace selection and creation, but not member management, invitations, or role editing.
- Category mappings still come from
configs/symbol-categories.yaml; workspace category sets are planned but not implemented. - Real exchange order execution is not implemented.
The private application repository tracks the deeper multiuser architecture work behind these limitations.