Skip to main content
API Reference

EX7 Capital API

Trading signal delivery, strategy validation, billing, and account management for EX7 Capital. All endpoints require a Supabase JWT bearer token unless explicitly marked public.

v1.0.066 endpointsRead the introduction →
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

statusstring · query
instrumentstring · query
tagstring · query
cursorstring · queryupdated_at cursor from previous page
limitinteger · 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'
delete/api/strategies/{strategy_id}auth

Delete Strategy

Parameters

strategy_id*string · path

Responses

204Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X DELETE 'https://api.ex7capital.com/api/strategies/${strategy_id}' \
  -H 'Authorization: Bearer YOUR_TOKEN'
get/api/strategies/{strategy_id}auth

Get Strategy

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}' \
  -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
cursorstring · querylast version integer from previous page (DESC pagination)
limitinteger · 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'
get/api/strategies/{strategy_id}/versions/{version}auth

Get Strategy Version

Parameters

strategy_id*string · path
version*integer · path

Responses

200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
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

hoursinteger · 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
hoursinteger · 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_idstring · query
fromstring · query
tostring · query
sidestring · query
statusstring · query
instrumentstring · query
cursorstring · query
limitinteger · 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_idstring · query
fromstring · query
tostring · query
decisionstring · query
cursorstring · query
limitinteger · 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'
post/api/signals/runtimes/{strategy_id}/force-close-allauth

Force Close All

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_idstring · query
instrumentstring · query
Last-Event-IDstring · 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'
delete/api/alerts/channels/{channel_id}auth

Delete Channel

Parameters

channel_id*string · path

Responses

204Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X DELETE 'https://api.ex7capital.com/api/alerts/channels/${channel_id}' \
  -H 'Authorization: Bearer YOUR_TOKEN'
post/api/alerts/channels/{channel_id}/resendauth

Resend Verification

Parameters

channel_id*string · path

Responses

200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X POST 'https://api.ex7capital.com/api/alerts/channels/${channel_id}/resend' \
  -H 'Authorization: Bearer YOUR_TOKEN'
post/api/alerts/channels/{channel_id}/verifyauth

Verify Channel

Parameters

channel_id*string · path

Responses

200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X POST 'https://api.ex7capital.com/api/alerts/channels/${channel_id}/verify' \
  -H 'Authorization: Bearer YOUR_TOKEN'
get/api/alerts/deliveriesauth

List Deliveries

Parameters

strategy_idstring · query
statusstring · query
fromstring · query
tostring · query
cursorstring · query
limitinteger · query

Responses

200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X GET 'https://api.ex7capital.com/api/alerts/deliveries?strategy_id=STRATEGY_ID&status=STATUS&from=FROM&to=TO&cursor=CURSOR&limit=50' \
  -H 'Authorization: Bearer YOUR_TOKEN'
get/api/alerts/deliveries/{delivery_id}auth

Get Delivery

Parameters

delivery_id*string · path

Responses

200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X GET 'https://api.ex7capital.com/api/alerts/deliveries/${delivery_id}' \
  -H 'Authorization: Bearer YOUR_TOKEN'
get/api/alerts/subscriptionsauth

List Subscriptions

Parameters

strategy_idstring · query

Responses

200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X GET 'https://api.ex7capital.com/api/alerts/subscriptions?strategy_id=STRATEGY_ID' \
  -H 'Authorization: Bearer YOUR_TOKEN'
post/api/alerts/subscriptionsauth

Create Subscription

Responses

201Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X POST 'https://api.ex7capital.com/api/alerts/subscriptions' \
  -H 'Authorization: Bearer YOUR_TOKEN'
delete/api/alerts/subscriptions/{sub_id}auth

Delete Subscription

Parameters

sub_id*string · path

Responses

204Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X DELETE 'https://api.ex7capital.com/api/alerts/subscriptions/${sub_id}' \
  -H 'Authorization: Bearer YOUR_TOKEN'
patch/api/alerts/subscriptions/{sub_id}auth

Patch Subscription

Parameters

sub_id*string · path

Responses

200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X PATCH 'https://api.ex7capital.com/api/alerts/subscriptions/${sub_id}' \
  -H 'Authorization: Bearer YOUR_TOKEN'
get/api/alerts/unsubscribeauth

Unsubscribe Landing

Parameters

token*string · query

Responses

200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X GET 'https://api.ex7capital.com/api/alerts/unsubscribe?token=TOKEN' \
  -H 'Authorization: Bearer YOUR_TOKEN'
post/api/alerts/unsubscribeauth

Unsubscribe Confirm

Responses

200Successful Response
422Validation Error
Code samples (cURL · Python · JavaScript)
curl -X POST 'https://api.ex7capital.com/api/alerts/unsubscribe' \
  -H 'Authorization: Bearer YOUR_TOKEN'
post/api/alerts/webhooks/resendauth

Resend Webhook

Handle Resend delivery / bounce / complaint callbacks. Resend event shapes (https://resend.com/docs/dashboard/webhooks/event-types): { "type": "email.delivered" | "email.bounced" | "email.complained" | ..., "created_at": "...", "data": { "email_id": "<uuid>", # matches provider_msg_id on our side "to": ["recipient@..."], "from": "sender@...", "subject": "...", # bounce/complained events add `reason` or `type` in `data.bounce` } }

Responses

200Successful Response
Code samples (cURL · Python · JavaScript)
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

secretstring · 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

modestring · queryRisk 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

limitinteger · queryMax rows to return (newest first). 1-200.
offsetinteger · queryRows 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'