Trading signal delivery, strategy validation, billing, and account management for EX7 Capital. All endpoints require a Supabase JWT bearer token unless explicitly marked public.
Schema generated 2026-06-06 from https://api.ex7capital.com/openapi.json
Strategies
get/api/enginesauth
List Engines
List all node types the strategy-builder can render.
Auth required so we don't leak our stack to scrapers; the info is not secret
but there's no reason for anonymous traffic to hit it either.
Responses
200Successful Response
Code samples (cURL · Python · JavaScript)
curl -X GET 'https://api.ex7capital.com/api/engines' \
-H 'Authorization: Bearer YOUR_TOKEN'
get/api/strategiesauth
List Strategies
Parameters
status
string · query
instrument
string · query
tag
string · query
cursor
string · query
updated_at cursor from previous page
limit
integer · query
Responses
200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X GET 'https://api.ex7capital.com/api/strategies?status=STATUS&instrument=INSTRUMENT&tag=TAG&cursor=CURSOR&limit=50' \
-H 'Authorization: Bearer YOUR_TOKEN'
post/api/strategiesauth
Create Strategy
Responses
201Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X POST 'https://api.ex7capital.com/api/strategies' \
-H 'Authorization: Bearer YOUR_TOKEN'
post/api/strategies/{source_id}/cloneauth
Clone Strategy
Clone one of the caller's strategies into a new strategies row at version 1.
Parameters
source_id*
string · path
Responses
201Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X POST 'https://api.ex7capital.com/api/strategies/${source_id}/clone' \
-H 'Authorization: Bearer YOUR_TOKEN'
curl -X GET 'https://api.ex7capital.com/api/strategies/${strategy_id}' \
-H 'Authorization: Bearer YOUR_TOKEN'
put/api/strategies/{strategy_id}auth
Update Strategy
Parameters
strategy_id*
string · path
Responses
200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X PUT 'https://api.ex7capital.com/api/strategies/${strategy_id}' \
-H 'Authorization: Bearer YOUR_TOKEN'
post/api/strategies/{strategy_id}/archiveauth
Archive Strategy
Soft-stop: set status=archived. Reversible via /unarchive.
Archive is a valid target from every non-archived status, so this never
returns 409 for lifecycle reasons — only 404 (not found) or 409 if the
row is already archived (archived→archived is not a legal edge).
Parameters
strategy_id*
string · path
Responses
200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X POST 'https://api.ex7capital.com/api/strategies/${strategy_id}/archive' \
-H 'Authorization: Bearer YOUR_TOKEN'
get/api/strategies/{strategy_id}/exportauth
Export Strategy
Return a portable JSON representation of a strategy's head version.
Payload shape is defined by spec §8: format stamp, exported_at ISO8601,
name/description/instrument/timeframe/tags, and the full graph (nodes,
edges, meta). Internal ids, user_id, and timestamps other than
`exported_at` are stripped before the payload leaves this process.
Parameters
strategy_id*
string · path
Responses
200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X GET 'https://api.ex7capital.com/api/strategies/${strategy_id}/export' \
-H 'Authorization: Bearer YOUR_TOKEN'
post/api/strategies/{strategy_id}/promoteauth
Promote Strategy
Move the strategy one step forward: draft→ready or ready→live.
Any other source status yields 409 with the per-status allowed set so the
client can render a helpful message (e.g. "can't promote live → ready;
archive instead").
Parameters
strategy_id*
string · path
Responses
200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X POST 'https://api.ex7capital.com/api/strategies/${strategy_id}/promote' \
-H 'Authorization: Bearer YOUR_TOKEN'
post/api/strategies/{strategy_id}/rollbackauth
Rollback Strategy
Create a new version whose graph matches `to_version`.
Returns { id, current_version }. Maps:
StrategyNotFound → 404
VersionNotFound → 404
CannotRollbackToCurrent → 409
Parameters
strategy_id*
string · path
Responses
200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X POST 'https://api.ex7capital.com/api/strategies/${strategy_id}/rollback' \
-H 'Authorization: Bearer YOUR_TOKEN'
post/api/strategies/{strategy_id}/unarchiveauth
Unarchive Strategy
Bring an archived strategy back to ``draft``.
The service resets to draft (rather than the pre-archive status) because
the pre-archive status isn't recorded. Callers who want live again must
re-promote explicitly, which re-triggers the live-promotion validation.
Parameters
strategy_id*
string · path
Responses
200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X POST 'https://api.ex7capital.com/api/strategies/${strategy_id}/unarchive' \
-H 'Authorization: Bearer YOUR_TOKEN'
get/api/strategies/{strategy_id}/versionsauth
List Strategy Versions
Return one page of version summaries. Most recent version first.
Response shape:
{
"items": [VersionSummary, ...],
"next_cursor": int | null
}
Parameters
strategy_id*
string · path
cursor
string · query
last version integer from previous page (DESC pagination)
limit
integer · query
Responses
200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X GET 'https://api.ex7capital.com/api/strategies/${strategy_id}/versions?cursor=CURSOR&limit=50' \
-H 'Authorization: Bearer YOUR_TOKEN'
curl -X GET 'https://api.ex7capital.com/api/strategies/${strategy_id}/versions/${version}' \
-H 'Authorization: Bearer YOUR_TOKEN'
post/api/strategies/from-fixture/{slug}auth
Create Strategy From Fixture
Parameters
slug*
string · path
Responses
201Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X POST 'https://api.ex7capital.com/api/strategies/from-fixture/${slug}' \
-H 'Authorization: Bearer YOUR_TOKEN'
post/api/strategies/importauth
Import Strategy
Validate + persist an imported strategy payload.
Body is a JSON document that matches the ExportPayload shape, plus two
import-only keys:
format — must equal "ex7.strategy/v1".
name_on_conflict — "suffix" to auto-disambiguate, else errors out
with 409 on name collision.
We parse the raw body (not the Pydantic model directly) because the
existing `ImportPayload` schema has `extra="ignore"` — the format and
conflict-strategy fields would otherwise be silently dropped before we
ever saw them.
Responses
201Successful Response
Code samples (cURL · Python · JavaScript)
curl -X POST 'https://api.ex7capital.com/api/strategies/import' \
-H 'Authorization: Bearer YOUR_TOKEN'
Signals
get/api/legacy/signalsauth
Get Signals
Get all signals from the last N hours.
Parameters
hours
integer · query
Responses
200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X GET 'https://api.ex7capital.com/api/legacy/signals?hours=24' \
-H 'Authorization: Bearer YOUR_TOKEN'
get/api/legacy/signals/{instrument}auth
Get Signals By Instrument
Get signals for a specific instrument.
Parameters
instrument*
string · path
hours
integer · query
Responses
200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X GET 'https://api.ex7capital.com/api/legacy/signals/${instrument}?hours=24' \
-H 'Authorization: Bearer YOUR_TOKEN'
get/api/signalsauth
List Signals
Cursor-paginated list of the caller's signals.
Filters (all optional): strategy_id, from/to (ISO-8601), side, status,
instrument. Ordering is `(fired_at DESC, id DESC)` — stable even for
signals that fire on the same bar close.
Cursor strategy: we fetch N+1 rows where N = `limit`. If we got back
N+1, the Nth row's (fired_at, id) becomes `next_cursor` and we truncate
to N rows in the response. This is a keyset / seek pagination scheme.
Parameters
strategy_id
string · query
from
string · query
to
string · query
side
string · query
status
string · query
instrument
string · query
cursor
string · query
limit
integer · query
Responses
200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X GET 'https://api.ex7capital.com/api/signals?strategy_id=STRATEGY_ID&from=FROM&to=TO&side=SIDE&status=STATUS&instrument=INSTRUMENT&cursor=CURSOR&limit=50' \
-H 'Authorization: Bearer YOUR_TOKEN'
get/api/signals/{signal_id}auth
Get Signal
Fetch a single signal the caller owns. 404 if missing or not owned.
We intentionally return 404 (not 403) for signals owned by another user:
leaking existence-of-resource to an unauthorized user is an info-disclosure
smell, and a 404 is the standard "nothing here for you" for RLS-style scoping.
Parameters
signal_id*
string · path
Responses
200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X GET 'https://api.ex7capital.com/api/signals/${signal_id}' \
-H 'Authorization: Bearer YOUR_TOKEN'
post/api/signals/{signal_id}/closeauth
Close Signal
Manually close a still-open paper signal.
Workflow (§7.3):
1. SELECT ... FOR UPDATE on the target row scoped by (id, user_id).
Missing → 404 (either not found or not owned — we do not leak
the distinction to avoid a user-enumeration oracle).
2. If status != 'open' → 409 with the current row in the detail so
the UI can reconcile without a refetch.
3. If mode != 'paper' → 403. v1 has no broker path; a manual close
on a live-mode signal would desync our book from the market.
4. Compute P&L via ``compute_pnl(instrument, side, size,
entry_price, close_price)`` — this is the one-and-only P&L
formula (2-tick slippage 2-sided, per-instrument commission,
all Decimal, no float drift).
5. UPDATE with status='cancelled' (per SignalClose.reason Literal
'user'), close_reason='user', pnl fields populated, and
slippage_ticks=2.0 (IRON LAW §7). The WHERE status='open'
guard makes the UPDATE a no-op if a concurrent writer already
closed the row — in which case we re-raise as 409.
6. pg_notify('signal_closed', <json>) inside the same transaction
so the SSE broadcaster sees the close on commit only.
Parameters
signal_id*
string · path
Responses
200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X POST 'https://api.ex7capital.com/api/signals/${signal_id}/close' \
-H 'Authorization: Bearer YOUR_TOKEN'
get/api/signals/auditauth
Get Audit
Return a page of signal_audit rows owned by the caller.
Ordering is newest-first (bar_ts DESC, id DESC). Pagination uses the
keyset cursor returned as next_cursor — pass it back unchanged to
walk the page boundary without OFFSET-scan cost.
The `from` query param arrives as `from_` because `from` is a Python
keyword; the HTTP name is preserved via FastAPI's alias so the wire
spec in §7.1 stays correct.
Parameters
strategy_id
string · query
from
string · query
to
string · query
decision
string · query
cursor
string · query
limit
integer · query
Responses
200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X GET 'https://api.ex7capital.com/api/signals/audit?strategy_id=STRATEGY_ID&from=FROM&to=TO&decision=DECISION&cursor=CURSOR&limit=100' \
-H 'Authorization: Bearer YOUR_TOKEN'
get/api/signals/healthauth
Signals Health
Aggregate pipeline health for status pages + UI ops panels.
Returns:
{
"dispatcher": "ok" | "stale",
"market_data": {
"<instrument>": {"last_bar_ts": iso, "lag_seconds": float,
"status": "fresh"|"stale"|"closed"},
...
},
"runtimes": {"<status>": count, ...},
"now": iso8601-utc
}
Not user-scoped — exposes the pipeline as a whole. No PII, no user_ids.
Safe for a public status page.
Responses
200Successful Response
Code samples (cURL · Python · JavaScript)
curl -X GET 'https://api.ex7capital.com/api/signals/health' \
-H 'Authorization: Bearer YOUR_TOKEN'
get/api/signals/health/meauth
Signals Health Me
Per-user runtime rows — the data behind "my strategies are running".
Returns {"items": [...]} where each item mirrors `strategy_runtimes`
fields the UI needs. Scoped to `user_id` via the strategies join so a
user can only see their own runtimes (defense in depth over RLS).
Returning a wrapped `items` (rather than a bare list) future-proofs for
adding sibling fields (pagination cursor, totals, etc.) without breaking
clients that parsed the top-level type.
Responses
200Successful Response
Code samples (cURL · Python · JavaScript)
curl -X GET 'https://api.ex7capital.com/api/signals/health/me' \
-H 'Authorization: Bearer YOUR_TOKEN'
get/api/signals/runtimesauth
List Runtimes
Return every (strategy, mode) runtime row owned by the caller.
Ordered so the most recently started worker bubbles up; never-started
rows (started_at IS NULL) sink to the bottom with NULLS LAST.
Responses
200Successful Response
Code samples (cURL · Python · JavaScript)
curl -X GET 'https://api.ex7capital.com/api/signals/runtimes' \
-H 'Authorization: Bearer YOUR_TOKEN'
Emergency lever: cancel every 'open' signal for a strategy.
Flow:
1. Verify the strategy belongs to the caller. 404 if it doesn't
exist (or is soft-deleted); 403 if someone else owns it. This
mirrors the ownership gating in `start_runtime` so a hostile
client can't distinguish "your strategy, nothing open" from
"someone else's strategy" via response timing.
2. UPDATE every open signal with close_reason='force_close'.
close_price falls back to entry_price (safe null-free default —
no realized PnL for an emergency cancel).
3. For every closed signal id, emit pg_notify('signal_closed', ...)
so the SSE broadcaster refreshes connected clients and the
alerts pipeline can fire any "position closed" notifications.
Returns the number of signals that transitioned to 'cancelled'.
Parameters
strategy_id*
string · path
Responses
200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X POST 'https://api.ex7capital.com/api/signals/runtimes/${strategy_id}/force-close-all' \
-H 'Authorization: Bearer YOUR_TOKEN'
post/api/signals/runtimes/{strategy_id}/startauth
Start Runtime
Start (or idempotently re-start) a strategy runtime.
Gates (in order):
1. Strategy exists and is not soft-deleted -> 404 otherwise.
2. Strategy belongs to caller -> 403 otherwise (NOT 404, to avoid
timing-based strategy id enumeration).
3. Strategy status is one of ('ready','live','paused') -> 409 with
code ``strategy_not_startable`` otherwise. Draft / archived
strategies cannot fire live signals.
4. body.mode == 'live' -> 403 `live_mode_disabled_v1` (§6). v1 blocks
all live starts regardless of tier until the live gate ships.
5. UPSERT into strategy_runtimes. The ON CONFLICT clause preserves
an already-active row's status. If the returned status is still
'running' / 'draining' we raise 409 so the client sees the
current state instead of a silent no-op.
Idempotent: a caller that POSTs /start while the row is already
'starting' gets a clean 200 `status='starting'`. A caller that POSTs
/start while the row is 'running' gets 409 with the current state so
they can tell the difference between "we brought it up" and "it was
already up."
Parameters
strategy_id*
string · path
Responses
200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X POST 'https://api.ex7capital.com/api/signals/runtimes/${strategy_id}/start' \
-H 'Authorization: Bearer YOUR_TOKEN'
post/api/signals/runtimes/{strategy_id}/stopauth
Stop Runtime
Graceful drain: flip 'running' to 'draining' and let the dispatcher
finish the current bar before it settles on 'stopped'.
Semantics (per §7.2):
- Row in 'running' -> flipped to 'draining', response status='draining'.
- Row already 'stopped' / 'error' / 'draining' / 'starting' -> return
the current status unchanged (200). Stop is idempotent and
side-effect-free for non-running states.
- No such row -> 404.
We do NOT check the strategies table here: ownership is already
enforced by the user_id predicate on the UPDATE. If a caller POSTs
/stop for a strategy_id they don't own, they hit 404 (no row matches),
which is the right shape for an idempotent stop.
Parameters
strategy_id*
string · path
Responses
200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X POST 'https://api.ex7capital.com/api/signals/runtimes/${strategy_id}/stop' \
-H 'Authorization: Bearer YOUR_TOKEN'
get/api/signals/streamauth
Signal Stream
SSE endpoint for live signal delivery.
Lifecycle:
1. Emit retry hint (client reconnect delay).
2. Emit one-time compliance banner.
3. If Last-Event-ID provided, replay any signals fired after that id
via replay_since_event_id(user_id, last_event_id, strategy_id).
4. Subscribe to BROADCASTER with (user.id, strategy_id).
5. Pump the subscriber queue:
- wait up to _HEARTBEAT_INTERVAL_SECONDS for a payload
- on payload: materialize the full SignalOut via fetch_signal,
apply the instrument filter if set, yield a `signal` or
`signal_closed` frame
- on timeout: yield heartbeat, then check client disconnect
6. On disconnect / generator close, unsubscribe cleanly so the
broadcaster's queue dict doesn't leak references.
Instrument filter is post-queue: the broadcaster routes by
(user_id, strategy_id) only. Handful of bytes per skipped event is
cheaper than a broadcaster-side index when v1 has maybe a dozen
connected clients per user.
Parameters
strategy_id
string · query
instrument
string · query
Last-Event-ID
string · header
Responses
200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X GET 'https://api.ex7capital.com/api/signals/stream?strategy_id=STRATEGY_ID&instrument=INSTRUMENT' \
-H 'Authorization: Bearer YOUR_TOKEN'
Billing
post/api/billing/cancelauth
Cancel
Responses
200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X POST 'https://api.ex7capital.com/api/billing/cancel' \
-H 'Authorization: Bearer YOUR_TOKEN'
post/api/billing/checkoutauth
Checkout
Responses
200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X POST 'https://api.ex7capital.com/api/billing/checkout' \
-H 'Authorization: Bearer YOUR_TOKEN'
post/api/billing/compauth
Admin Comp
Responses
200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X POST 'https://api.ex7capital.com/api/billing/comp' \
-H 'Authorization: Bearer YOUR_TOKEN'
get/api/billing/complianceauth
Get Compliance Status
Responses
200Successful Response
Code samples (cURL · Python · JavaScript)
curl -X GET 'https://api.ex7capital.com/api/billing/compliance' \
-H 'Authorization: Bearer YOUR_TOKEN'
post/api/billing/compliance/acceptauth
Accept Compliance
Responses
201Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X POST 'https://api.ex7capital.com/api/billing/compliance/accept' \
-H 'Authorization: Bearer YOUR_TOKEN'
get/api/billing/disclaimerauth
Get Disclaimer
Public. CFTC Rule 4.41 disclaimer + current required doc versions.
Responses
200Successful Response
Code samples (cURL · Python · JavaScript)
curl -X GET 'https://api.ex7capital.com/api/billing/disclaimer' \
-H 'Authorization: Bearer YOUR_TOKEN'
get/api/billing/meauth
Billing Me
Responses
200Successful Response
Code samples (cURL · Python · JavaScript)
curl -X GET 'https://api.ex7capital.com/api/billing/me' \
-H 'Authorization: Bearer YOUR_TOKEN'
post/api/billing/portalauth
Portal
Responses
200Successful Response
Code samples (cURL · Python · JavaScript)
curl -X POST 'https://api.ex7capital.com/api/billing/portal' \
-H 'Authorization: Bearer YOUR_TOKEN'
post/api/billing/resumeauth
Resume
Responses
200Successful Response
Code samples (cURL · Python · JavaScript)
curl -X POST 'https://api.ex7capital.com/api/billing/resume' \
-H 'Authorization: Bearer YOUR_TOKEN'
get/api/billing/tiersauth
List Tiers
Public. Returns every tier with prices + feature caps.
Responses
200Successful Response
Code samples (cURL · Python · JavaScript)
curl -X GET 'https://api.ex7capital.com/api/billing/tiers' \
-H 'Authorization: Bearer YOUR_TOKEN'
post/api/billing/webhookauth
Stripe Webhook
Stripe → us. Signature verified BEFORE parse.
Returns 200 on success or already-processed. 400 on bad signature.
500 on handler error (so Stripe retries).
Responses
200Successful Response
Code samples (cURL · Python · JavaScript)
curl -X POST 'https://api.ex7capital.com/api/billing/webhook' \
-H 'Authorization: Bearer YOUR_TOKEN'
Alerts
get/api/alerts/channelsauth
List Channels
Responses
200Successful Response
Code samples (cURL · Python · JavaScript)
curl -X GET 'https://api.ex7capital.com/api/alerts/channels' \
-H 'Authorization: Bearer YOUR_TOKEN'
post/api/alerts/channelsauth
Create Channel
Responses
201Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X POST 'https://api.ex7capital.com/api/alerts/channels' \
-H 'Authorization: Bearer YOUR_TOKEN'
curl -X POST 'https://api.ex7capital.com/api/alerts/webhooks/resend' \
-H 'Authorization: Bearer YOUR_TOKEN'
post/api/alerts/webhooks/telegramauth
Telegram Webhook
Handle inbound Telegram updates.
Authentication: shared secret in the webhook URL query string. The bot's
setWebhook was called with `?secret=<TELEGRAM_WEBHOOK_SECRET>`. If it
doesn't match, we 401.
Supported update types:
* /start <token> — verification handshake. chat_id becomes the channel
destination, verified=true.
* text messages — logged, no-op (bot is write-only for alerts).
Parameters
secret
string · query
Responses
200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X POST 'https://api.ex7capital.com/api/alerts/webhooks/telegram?secret=SECRET' \
-H 'Authorization: Bearer YOUR_TOKEN'
post/api/alerts/webhooks/twilioauth
Twilio Webhook
Twilio form-encoded delivery receipt + STOP/HELP keyword handler.
v1: signature validation is a no-op (SMS deferred). We still accept the
POST so a webhook-configured Twilio account doesn't 404 during the
v2 rollout pre-flight.
Responses
200Successful Response
Code samples (cURL · Python · JavaScript)
curl -X POST 'https://api.ex7capital.com/api/alerts/webhooks/twilio' \
-H 'Authorization: Bearer YOUR_TOKEN'
Vix
post/api/vix/generate-strategyauth
Generate Strategy
Responses
200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X POST 'https://api.ex7capital.com/api/vix/generate-strategy' \
-H 'Authorization: Bearer YOUR_TOKEN'
get/api/vix/quotaauth
Get Quota
Responses
200Successful Response
Code samples (cURL · Python · JavaScript)
curl -X GET 'https://api.ex7capital.com/api/vix/quota' \
-H 'Authorization: Bearer YOUR_TOKEN'
Account
get/api/instrumentsauth
Get Instruments
List all instruments with current market data.
Responses
200Successful Response
Code samples (cURL · Python · JavaScript)
curl -X GET 'https://api.ex7capital.com/api/instruments' \
-H 'Authorization: Bearer YOUR_TOKEN'
get/api/performanceauth
Get Performance
Per-strategy backtest performance and live stats.
Responses
200Successful Response
Code samples (cURL · Python · JavaScript)
curl -X GET 'https://api.ex7capital.com/api/performance' \
-H 'Authorization: Bearer YOUR_TOKEN'
get/api/portfolioauth
Get Portfolio
Portfolio summary: signals taken today, cumulative stats.
Responses
200Successful Response
Code samples (cURL · Python · JavaScript)
curl -X GET 'https://api.ex7capital.com/api/portfolio' \
-H 'Authorization: Bearer YOUR_TOKEN'
get/api/riskauth
Get Risk
Per-user, per-mode risk state. Mirrors signals/risk_gate.is_blocked.
Response shape: see risk-route-rewrite-2026-04-19.md §4.
Auth: required via `current_user` (Supabase JWT). 401 if missing/invalid.
Mode: validated against {'paper', 'live'}, 422 on anything else.
Pool: acquired from `app.state.supabase` (where `signals` lives).
On DB error: returns `trading_allowed=false` with `blocked_reason.breaker='error'`.
Never returns 500 — fail-closed at the reporting layer too, so the UI
shows "trading blocked" during an outage instead of silently reverting
to "trading_allowed=true" and confusing the user.
Parameters
mode
string · query
Risk pool to report on ('paper' | 'live'). Paper losses do NOT consume live slots and vice versa.
Responses
200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X GET 'https://api.ex7capital.com/api/risk?mode=paper' \
-H 'Authorization: Bearer YOUR_TOKEN'
get/api/risk/blockedauth
Get Blocked
List the current user's recently-blocked signals.
Backed by `signals_blocked` (migration 021), which is written by
the publisher-boundary risk gate (see src/api/signals/risk_gate.py
::record_blocked) every time IRON LAW §4 denies a would-be
publication.
Each row answers "why didn't my signal fire?" with enough context
for a forensic post-mortem:
- triggered_limit: breaker name ('daily_loss_limit', ...)
- reason: machine-readable tag ('daily_loss_limit_breached')
- observed: the tripping value ('-500.00' or '6')
- limit: the threshold ('-500.00' or '6')
- reason_json: full BlockedReason snapshot (limits + checked_at)
Auth: required via `current_user` (Supabase JWT). 401 if missing.
Pool: acquired from `app.state.supabase` (where `signals_blocked`
lives). 503 when the pool isn't wired (dev env).
RLS: `signals_blocked_select_own` restricts SELECT to the
authenticated user. The helper also filters by user_id explicitly.
Response shape:
{
"timestamp": "2026-04-20T...",
"items": [
{
"id": "<uuid>",
"user_id": "<uuid>",
"strategy_id": "<uuid|null>",
"strategy_version": <int|null>,
"instrument": "MNQ",
"timeframe": "2m",
"bar_ts": "2026-04-20T13:30:00+00:00",
"mode": "paper" | "live",
"side": "long" | "short" | null,
"triggered_limit": "daily_loss_limit",
"reason": "daily_loss_limit_breached",
"observed": "-500.00",
"limit": "-500.00",
"reason_json": { ... },
"blocked_at": "2026-04-20T13:30:05+00:00"
},
...
],
"total": <int>,
"limit": <int>,
"offset": <int>,
}
Parameters
limit
integer · query
Max rows to return (newest first). 1-200.
offset
integer · query
Rows to skip (for simple pagination). >= 0.
Responses
200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X GET 'https://api.ex7capital.com/api/risk/blocked?limit=50&offset=0' \
-H 'Authorization: Bearer YOUR_TOKEN'
post/api/waitlistauth
Add To Waitlist
Add email to waitlist.
Responses
200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X POST 'https://api.ex7capital.com/api/waitlist' \
-H 'Authorization: Bearer YOUR_TOKEN'
get/api/waitlist/countauth
Waitlist Count
Public count of waitlist signups.
Responses
200Successful Response
Code samples (cURL · Python · JavaScript)
curl -X GET 'https://api.ex7capital.com/api/waitlist/count' \
-H 'Authorization: Bearer YOUR_TOKEN'
Health
get/api/healthauth
Health
Responses
200Successful Response
Code samples (cURL · Python · JavaScript)
curl -X GET 'https://api.ex7capital.com/api/health' \
-H 'Authorization: Bearer YOUR_TOKEN'