API Reference
32 sections · 245 endpoints
https://<broker-domain>
Bearer token via Authorization: Bearer <token>. Two types: user JWT (broker staff) and customer JWT (traders).
300 requests / 60s per authenticated user. Registration: 600 req/IP/60s.
Send Idempotency-Key header on mutations for at-most-once processing. 24h TTL. 5xx responses are NOT cached.
API Namespaces
| Namespace | Audience | Example |
|---|---|---|
| /api/customer/* | Trading platform frontend | POST /api/customer/orders |
| /api/crm/* | CRM staff (sales, retention) | GET /api/crm/customers |
| /api/backoffice/* | Operations (payments, compliance) | PATCH /api/backoffice/transactions/{id}/approve |
| /api/platform/* | Platform config (groups, workflows) | POST /api/platform/groups |
| /api/admin/* | Administration (users, roles) | POST /api/admin/users |
| /api/prices/* | Price data | GET /api/prices/EURUSD |
Legacy flat paths (e.g., GET /customers, POST /orders) continue working but log a deprecation warning.
1. Gateway
All client traffic goes through the API Gateway on port 8000 -- the only externally exposed port. **Cross-cutting behaviour:** - **X-Request-ID** -- assigned to every request; echoed in response headers. Pass your own to correlate across services. - **Idempotency-Key** -- send on any mutation (POST/PATCH/PUT/DELETE) to ensure at-most-once processing. Same key + same user = same response. Replays return the cached response with `X-Idempotent-Replay: true` header. Key: UUID or string, max 128 chars. TTL: 24 hours. 5xx responses are NOT cached (allow retry). No header = no protection. - **Rate limiting** -- 300 requests/60s per authenticated user. Exceeding returns `429` with `Retry-After` header. Idempotent replays do not consume rate quota. - **Registration rate limit** -- 600 requests per IP per 60s on `POST /customers/register`. - **CORS** -- configured once at the gateway. - **JWT errors** -- `401 {"detail": "Token expired"}` or `401 {"detail": "Invalid token"}` or `401 {"detail": "Missing or invalid Authorization header"}`.
/healthGateway health check
Returns gateway health status including Redis connectivity and idempotency cache stats. Includes `permission_checks_skipped` counter (> 0 = RBAC cache degraded).
Response
{
"status": "ok",
"service": "api-gateway",
"checks": {
"redis": "ok",
"idempotency": {
"hits": 0,
"misses": 0,
"hit_rate": 0
}
},
"timestamp": "2026-03-24T10:00:00Z"
}/health/allAggregated health check for all services
Polls all internal services and returns combined status. Returns `"status": "degraded"` if any service is down or unhealthy. No authentication required.
Response
{
"status": "ok",
"services": {
"api-gateway": {
"status": "ok",
"details": {
"status": "ok",
"service": "api-gateway"
}
},
"price-feed": {
"status": "ok",
"details": {
"status": "ok",
"service": "price-feed"
}
},
"order-service": {
"status": "ok",
"details": {
"status": "ok",
"service": "order-service"
}
}
},
"timestamp": "2026-03-11T22:00:00Z"
}2. Authentication
Authentication endpoints for broker staff (CRM users) and customers (traders). JWTs use HS256 with 15-minute access tokens and 7-day refresh tokens.
/api/admin/users/loginLegacy: /users/loginBroker staff login
Authenticate as a broker staff user. Returns short-lived access token (15 min) + long-lived refresh token (7 days). If user has 2FA enabled and no `totp_code` is provided, returns `{"requires_2fa": true}` -- re-send with `totp_code` to complete login.
Request
{
"email": "[email protected]",
"password": "secret123",
"totp_code": "123456"
}Response
{
"access_token": "eyJ...",
"refresh_token": "abc123...",
"expires_in": 900,
"token_type": "bearer",
"user": {
"id": "uuid",
"email": "[email protected]",
"first_name": "Jane",
"last_name": "Smith",
"full_name": "Jane Smith",
"team_id": null,
"role_id": 2,
"is_active": true,
"created_at": "2026-03-09T10:00:00Z",
"last_login_at": "2026-03-09T10:00:00Z"
}
}- •`totp_code` is required only if user has 2FA enabled. Omit for users without 2FA.
- •200 OK with `{"requires_2fa": true, "message": "Enter your 2FA code"}` when 2FA is enabled but no code provided.
- •Token TTL: access 15 min, refresh 7 days. Algorithm: HS256.
401 — Wrong credentials or invalid 2FA code403 — Account disabled/api/customer/loginLegacy: /customers/loginCustomer (trader) login
Authenticate as a trader and receive a JWT with `type: "customer"`. JWT payload includes `login` (primary active trading account), `logins` (all active account logins for multi-account switcher), `compliance_status`, and `force_password_change`.
Request
{
"email": "[email protected]",
"password": "secret123"
}Response
{
"access_token": "eyJ...",
"refresh_token": "abc123...",
"expires_in": 900,
"token_type": "bearer",
"customer": {
"id": "uuid",
"email": "[email protected]",
"first_name": "John",
"last_name": "Doe",
"full_name": "John Doe",
"force_password_change": false,
"...": "all CustomerResponse fields"
}
}- •Token TTL: access 15 min, refresh 7 days.
- •`force_password_change` restriction: when `true`, gateway restricts customer to only: `POST /customers/me/password`, `GET /customers/me`, `POST /auth/refresh`. All other routes return 403.
401 — Wrong credentials or password not set403 — Account disabled/api/customer/autologinLegacy: /auth/autologinRedeem one-time autologin token
Redeem a one-time autologin token. Returns the same shape as `POST /customers/login`. Token is burned atomically via Redis Lua GET+DEL.
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| token* | uuid | One-time autologin token | — |
- •TTL: 15 minutes for organic registrations. 72 hours for affiliate registrations (configurable via `AUTOLOGIN_EXPIRY_HOURS` env var).
- •If customer has `force_password_change=true`, the issued JWT carries that claim and the gateway restricts routes.
401 — Token invalid, already used, or expired403 — Customer account disabled/api/customer/password-reset/requestLegacy: /customers/password-reset/requestRequest password reset link (authenticated)
Request a password reset link. Customer must be logged in -- email is derived from JWT.
Request
{}Response
{
"message": "...",
"reset_url": "https://..."
}429 — Rate limited (max 3 active tokens per 15 minutes)/api/customer/password-reset/forgotLegacy: /customers/password-reset/forgotForgot password (public)
Public. Always returns 200 to prevent email enumeration.
Request
{
"email": "[email protected]"
}Response
{
"message": "If an account exists with that email, a reset link has been generated"
}/api/customer/password-reset/verifyLegacy: /customers/password-reset/verifyVerify password reset token
Public. Verify token validity without burning it. Returns masked email.
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| token* | uuid | Password reset token | — |
Response
{
"valid": true,
"email": "tr***@example.com"
}401 — Token invalid/expired/used/api/customer/password-reset/confirmLegacy: /customers/password-reset/confirmConfirm password reset
Public. Burns token atomically and updates password.
Request
{
"token": "<uuid>",
"new_password": "NewPass123"
}Response
{
"message": "Password updated successfully"
}401 — Token invalid/expired/used422 — Password <8 or >72 chars/api/customer/me/passwordLegacy: /customers/me/passwordSet new password
Set a new password. Clears `force_password_change` flag and issues a fresh JWT. Used after affiliate/import registration where password was auto-generated. This endpoint is allowed even when `force_password_change` is `true` (gateway exempts it). Password must be 8-72 characters.
Request
{
"new_password": "MySecurePass99"
}Response
{
"access_token": "eyJ...",
"message": "Password updated successfully"
}- •The new token has `force_password_change: false`, removing the gateway restriction.
403 — Not a customer JWT422 — Password too short or too long/api/admin/auth/2fa/setupLegacy: /auth/2fa/setupGenerate TOTP secret for 2FA setup
Generate a TOTP secret for the authenticated user. Returns a QR code URI for authenticator apps (Google Authenticator, Authy, etc.).
Response
{
"secret": "JBSWY3DPEHPK3PXP",
"qr_uri": "otpauth://totp/JumpTech:[email protected]?secret=...",
"message": "Scan QR code, then verify"
}/api/admin/auth/2fa/verifyLegacy: /auth/2fa/verifyVerify TOTP code to enable 2FA
Verify a TOTP code to enable 2FA. Generates 10 one-time backup codes (shown once -- user must save them).
Request
{
"code": "123456"
}Response
{
"enabled": true,
"backup_codes": [
"a1b2c3d4",
"e5f6g7h8",
"..."
],
"message": "2FA enabled. Save backup codes."
}/api/admin/auth/2fa/disableLegacy: /auth/2fa/disableDisable 2FA
Disable 2FA. Requires a valid current TOTP code to confirm.
Request
{
"code": "123456"
}Response
{
"enabled": false,
"message": "2FA has been disabled"
}/api/admin/auth/sessionsLegacy: /auth/sessionsList active sessions
List all active sessions for the authenticated user.
Response
[
{
"session_id": "uuid",
"device": "Mozilla/5.0...",
"ip": "1.2.3.4",
"created_at": "2026-03-20T10:00:00Z"
}
]/api/admin/auth/sessions/{session_id}Revoke a specific session
Revoke a specific session. The session's JWT becomes invalid immediately.
/api/admin/auth/sessionsLegacy: /auth/sessionsRevoke all other sessions
Revoke all sessions except the current one (identified by the JWT's `session_id` claim).
Response
{
"revoked": 3
}/api/admin/auth/refreshLegacy: /auth/refreshRefresh access token
Exchange a valid refresh token for a new short-lived access token. No JWT required -- auth via the refresh token itself. Works for both CRM users and customers.
Request
{
"refresh_token": "abc123..."
}Response
{
"access_token": "eyJ...",
"expires_in": 900,
"token_type": "bearer"
}401 — Invalid, expired, or revoked refresh token3. Customers
Customer management endpoints including registration, CRM lookup, deduplication, and online presence.
/api/customer/registerLegacy: /customers/registerCustomer registration
Public customer registration. Creates a Customer record and a linked TradingAccount in one transaction. Runs fraud checks: disposable email block -> affiliate_lead_id duplicate -> email duplicate -> phone duplicate -> jurisdiction gate. Supports four registration paths: Organic (password required, country->group), Affiliate (auto-generated password, 72h autologin link, force_password_change=true), Import (auto-generated password, CSV group or default), Promo (password required, promo code's group).
Request
{
"email": "[email protected]",
"password": "TestPass1234",
"first_name": "John",
"last_name": "Doe",
"phone": "+447700900000",
"country": "GB",
"gender": "male",
"source": "organic",
"promo_code": null,
"affiliate_id": "aff_123",
"campaign_id": "camp_spring2026",
"funnel": "lp-gold-v3",
"affiliate_lead_id": "portal_lead_99871"
}Response
{
"id": "uuid",
"email": "[email protected]",
"phone": "+447700900000",
"country": "GB",
"gender": "male",
"owner_id": null,
"ftd_sales_agent_id": null,
"sales_status_id": null,
"kyc_status_id": null,
"affiliate_id": "aff_123",
"campaign_id": "camp_spring2026",
"funnel": "lp-gold-v3",
"affiliate_lead_id": "portal_lead_99871",
"manager_counter": 0,
"reg_time": "2026-03-09T10:00:00Z",
"is_deposited": false,
"is_active": true,
"retention_status": null,
"risk_classification": "normal",
"registration_source": "organic",
"force_password_change": false,
"trading_accounts": [
{
"id": "uuid",
"login": "10007",
"currency": "USD",
"account_type": "real",
"balance": 0,
"credit": 0,
"is_active": true,
"created_at": "2026-03-09T10:00:00Z"
}
],
"autologin_token": null,
"autologin_url": null,
"source": "organic"
}- •`source`: `organic` (default), `affiliate`, or `import`. Set automatically to `promo` when `promo_code` is provided.
- •`promo_code`: optional -- validated against `promo_codes` table. Overrides source to `promo` and assigns customer to the promo code's group.
- •`password`: required for `organic` and `promo`. Auto-generated for `affiliate` and `import`.
- •`autologin_token` / `autologin_url` are only populated for `affiliate` registrations. URL valid for 72 hours (configurable via `AUTOLOGIN_EXPIRY_HOURS`).
- •Duplicate email (Organic/Promo): `200 {"duplicate": true, "customer_id": "uuid", "message": "Email already registered."}` (anti-enumeration).
- •Duplicate email (Affiliate): `200 {"duplicate": true, "customer_id": null, "message": "Lead already registered"}` (no customer_id exposed).
- •Duplicate email (Import): Rejection CSV row (not HTTP error).
400 — Invalid, expired, or exhausted promo code403 — Restricted jurisdiction409 — Duplicate affiliate_lead_id OR duplicate phone422 — Disposable email domain blocked, or password missing for organic/promo429 — Gateway registration rate limit exceeded (600 req/IP/60s)/api/crm/customersLegacy: /customersList customers with CRM filters
List customers with CRM filters. Results are scoped by the caller's `customer_view_scope` (own/team/all). Ordered by `reg_time DESC`. Customer responses are PII-masked based on the caller's role -- `email` and `phone` fields may be masked or hidden depending on the role's `pii_email` and `pii_phone` settings.
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| owner_id | UUID | Customers assigned to this owner | — |
| country | string | ISO 3166-1 alpha-2 | — |
| kyc_status_id | int | KYC status filter | — |
| sales_status_id | int | Sales status filter | — |
| affiliate_id | string | Affiliate portal ID | — |
| campaign_id | string | Campaign ID | — |
| has_ftd | bool | `true` = deposited, `false` = never deposited | — |
| is_active | bool | Active/inactive filter | — |
| retention_status | string | active / churning / dormant / null | — |
| reg_from | date | Registration date from (inclusive) | — |
| reg_to | date | Registration date to (inclusive) | — |
| page | int | Page number | 1 |
| page_size | int | Page size (max 500) | 50 |
/api/crm/customers/{id}Legacy: /customers/{id}Get single customer profile
Single customer profile with embedded agent names and trading accounts.
/api/crm/customers/{id}Legacy: /customers/{id}Update customer details
CRM agent update. Scoped by caller's `customer_edit_scope`.
Request
{
"phone": "+44...",
"country": "DE",
"owner_id": "uuid",
"sales_status_id": 3,
"kyc_status_id": 1
}- •All fields are optional.
/api/crm/customers/dedupe-checkLegacy: /customers/dedupe-checkCheck for duplicate customers
Check for potential duplicate customers using scoring-based matching. Scoring: `affiliate_lead_id` = 10, `email` = 4, `phone` = 3, `country` = 1.
Request
{
"email": "[email protected]",
"phone": "+447700900000",
"country": "GB",
"affiliate_lead_id": "portal_lead_99871"
}Response
{
"duplicates": [
{
"customer_id": "uuid",
"email": "[email protected]",
"phone": "+447700900000",
"country": "GB",
"score": 8,
"matched_fields": [
"email",
"phone",
"country"
]
}
]
}/api/crm/customers/{id}/pingLegacy: /customers/{id}/pingCustomer heartbeat
Heartbeat -- refresh Redis online presence TTL (300s). Returns 204 No Content.
/api/crm/customers/{id}/onlineLegacy: /customers/{id}/onlineCheck customer online status
Check Redis online status.
Response
{
"customer_id": "uuid",
"online": true
}4. Promo Codes
Manage promo codes that assign registering customers to a specific trading group.
/api/platform/promo-codesLegacy: /promo-codesCreate a promo code
Create a promo code that assigns registering customers to a specific group.
Request
{
"code": "SPRING2026",
"group_id": "uuid",
"expires_at": "2026-06-30T23:59:59Z",
"max_uses": 500
}- •`code` is normalized to uppercase.
- •`expires_at` -- optional. `null` = no expiry.
- •`max_uses` -- optional. `null` = unlimited.
404 — Group not found409 — Promo code already exists/api/platform/promo-codesLegacy: /promo-codesList all promo codes
List all promo codes, ordered by `created_at DESC`.
/api/platform/promo-codes/{id}Legacy: /promo-codes/{id}Get single promo code
Single promo code by ID.
/api/platform/promo-codes/{id}Legacy: /promo-codes/{id}Update promo code
Update promo code fields: `is_active`, `expires_at`, `max_uses`.
/api/platform/promo-codes/{id}Legacy: /promo-codes/{id}Deactivate promo code
Soft-deactivate (sets `is_active=false`). Returns 204 No Content.
5. Customer Import
Bulk customer import via CSV upload. Processes rows asynchronously at a drip rate of ~10/sec.
/api/backoffice/customers/import/templateLegacy: /customers/import/templateDownload CSV import template
Download CSV template for customer import. Returns a CSV file with headers: `email,phone,first_name,last_name,country,group_name,assigned_to_email,comment`.
/api/backoffice/customers/importLegacy: /customers/importUpload CSV to import customers
Upload a CSV to import customers. Creates an async job and processes rows in the background at a drip rate of ~10/sec. Each row is registered with `source=import`, auto-generated password, and `force_password_change=true`. Duplicate emails produce a rejection row (not an HTTP error).
Request
multipart form with `file` field (UTF-8 CSV, max 50,000 rows)
Response
{
"job_id": "uuid",
"status": "pending",
"total_rows": 1500,
"message": "Import job created. Processing 1500 rows."
}- •Required CSV columns: `email`, `phone`, `first_name`, `last_name`, `country`.
- •Optional CSV columns: `group_name`, `assigned_to_email`, `comment`.
400 — No file, bad encoding, missing required columns, unknown columns, empty CSV, or >50,000 rows/api/backoffice/customers/import/{job_id}/statusLegacy: /customers/import/{job_id}/statusGet import job progress
Job progress for a customer import.
Response
{
"id": "uuid",
"status": "processing",
"filename": "leads_march.csv",
"total_rows": 1500,
"processed_rows": 750,
"success_count": 740,
"rejection_count": 10,
"created_at": "2026-03-24T10:00:00Z"
}/api/backoffice/customers/import/{job_id}/rejectionsLegacy: /customers/import/{job_id}/rejectionsDownload rejection report
Download rejection report as CSV. Each row includes the original data and the rejection reason.
6. Accounts
Trading account management -- live state, balances, transactions, group reassignment, and account closure.
/api/crm/accounts/{login}/stateLegacy: /accounts/{login}/stateLive account equity, PnL, and margin
Live equity, PnL, and margin data from Redis. Includes staleness indicators for frontend display. Falls back to Postgres balance/credit when Redis state is missing (e.g., after Redis restart).
Response
{
"login": "10007",
"equity": 10250.5,
"pnl": 250.5,
"free_margin": 9750.5,
"margin_level": 1025.05,
"margin_used": 500,
"max_withdrawable": 9750.5,
"gross_exposure": 10854.2,
"net_exposure": 5427.1,
"currency": "USD",
"stale": false,
"age_s": 0.2
}- •`stale` (boolean): `true` if Redis state is older than 5 seconds -- frontend should show "data delayed" warning.
- •`age_s` (float): seconds since the PnL engine last updated this account's state in Redis.
/api/crm/accounts/{account_id}/balanceLegacy: /accounts/{account_id}/balanceGet derived account balance
Derived balance: `SUM(amount) WHERE status='APPROVED'`.
/api/crm/accounts/{account_id}/transactionsLegacy: /accounts/{account_id}/transactionsList account transactions
All transactions for a trading account.
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| status | string | Filter by status (e.g., APPROVED) | — |
| limit | int | Max results | 100 |
| offset | int | Pagination offset | 0 |
/api/platform/accounts/{login}/groupLegacy: /accounts/{login}/groupReassign account to a different group
Reassign a trading account to a different group. Open positions retain their snapshotted terms from the original group.
Request
{
"group_id": "uuid"
}/api/backoffice/accounts/{login}/closeLegacy: /accounts/{login}/closeClose a trading account
Close a trading account. Pre-checks: no open positions, no pending transactions. Pending orders are auto-cancelled on closure. `force=true` allows closure with remaining balance (broker acknowledges offline handling). Sets `closure_status='closed'`, `is_active=false`. Deactivates customer if no other active accounts.
Request
{
"reason": "Customer requested closure",
"force": false
}Response
{
"login": 10007,
"status": "closed",
"reason": "Customer requested closure",
"balance_at_closure": 0
}7. Financial Ledger
The financial ledger is an append-only audit trail of every monetary event on a trading account, stored in a TimescaleDB hypertable (`ledger_events`). Each event records atomic deltas for balance, credit, and margin, with DB-computed running totals.
/api/backoffice/accounts/{login}/ledgerLegacy: /accounts/{login}/ledgerQuery financial ledger events
Query the financial ledger for a trading account. Returns atomic financial events in reverse chronological order.
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| event_type | string | Filter: `deposit`, `withdrawal`, `chargeback`, `credit_in`, `credit_out`, `order_open`, `margin_release`, `trade_pnl`, `commission`, `swap`, `nbp`, `correction_in`, `correction_out` | — |
| from_date | string | ISO 8601 start time | — |
| to_date | string | ISO 8601 end time | — |
| limit | int | Max 500 | 100 |
| offset | int | Pagination offset | 0 |
Response
[
{
"id": "uuid",
"account_id": 10007,
"event_type": "deposit",
"reference_type": "transaction",
"reference_id": "uuid",
"balance_delta": 500,
"credit_delta": 0,
"margin_delta": 0,
"balance_after": 10500,
"credit_after": 0,
"margin_after": 500,
"sequence_num": 42,
"description": "Deposit approved",
"idempotency_key": "deposit:tx-uuid",
"created_at": "2026-03-24T10:00:00Z"
}
]/api/backoffice/accounts/{login}/ledger/summaryLegacy: /accounts/{login}/ledger/summaryAggregated ledger summary
Aggregated ledger summary -- total deposits, withdrawals, P&L, commissions, swaps.
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| from_date | string | ISO 8601 start time (optional) | — |
| to_date | string | ISO 8601 end time (optional) | — |
Response
{
"account_id": 10007,
"total_deposits": 5000,
"total_withdrawals": -1000,
"net_trading_pnl": 250.5,
"total_commissions": -45,
"total_swaps": -12.3,
"total_chargebacks": 0,
"total_nbp": 0,
"net_credit": 0,
"net_balance_change": 4193.2,
"total_events": 87
}8. Transactions
All monetary movements -- deposits, withdrawals, bonuses, credits, corrections. **Type values:** DEPOSIT, WITHDRAWAL, BONUS, BONUS_CREDIT_IN, BONUS_CREDIT_OUT, CREDIT_IN, CREDIT_OUT, TRANSFER_IN, TRANSFER_OUT, CORRECTION_IN, CORRECTION_OUT, IB_PAYMENT, REWARD, PROFIT_SHARING_IN, PROFIT_SHARING_OUT **Status values:** PENDING, IN_PROGRESS, PROCESSING, APPROVED, DECLINE, REJECT, DELETED **Source values:** cashier (Praxis), manual (CRM operator), system (platform-generated)
/api/backoffice/transactions/manualLegacy: /transactions/manualCreate a manual transaction
Create a manual transaction.
Request
{
"type": "DEPOSIT",
"account_id": "uuid",
"amount": "500.00",
"currency": "USD",
"account_amount": "500.00",
"usd_amount": "500.00",
"exchange_rate_usd": "1.0",
"gate_name": "Bank Wire",
"proof_reference": "WIRE-REF-20260309",
"comment": "Bank wire confirmed",
"status": "PENDING"
}- •`status` can be `PENDING` or `APPROVED` -- approve in one step.
/api/backoffice/transactions/{id}/approveLegacy: /transactions/{id}/approveApprove a pending transaction
Approve a pending manual transaction.
- •Free margin guard: when account has open positions (`margin_used > 0`), withdrawals limited to `min(balance, free_margin)`, credit removals require `equity - removal >= margin_used`.
- •Maker-checker: when `broker_settings.maker_checker_enabled = 'true'`, the user who created the transaction cannot approve it.
403 — Same user attempted to create and approve (maker-checker violation)422 — Withdrawal/credit removal exceeds free margin/api/backoffice/transactions/{id}/declineLegacy: /transactions/{id}/declineDecline a pending transaction
Decline a pending manual transaction.
/api/backoffice/transactions/{id}/deleteLegacy: /transactions/{id}/deleteSoft-delete a transaction
Soft-delete -- excluded from balance computation, preserved for audit.
/api/backoffice/transactions/cashierLegacy: /transactions/cashierCreate a cashier (Praxis) transaction
Create a cashier (Praxis) transaction. Idempotent on `cashier_id`.
/webhooks/praxisPraxis webhook
Legacy path -- routes to integration-service for HMAC verification and forwarding.
/api/backoffice/transactions/{tx_id}/chargebackLegacy: /transactions/{tx_id}/chargebackProcess a chargeback
Process a chargeback against an approved deposit. Creates a negative CHARGEBACK transaction.
Request
{
"reason": "Card dispute by cardholder"
}Response
{
"chargeback_id": "uuid",
"original_transaction_id": "uuid",
"amount": -500,
"balance_after": -200,
"account_frozen": true
}- •Only approved `DEPOSIT` transactions can be charged back.
- •Idempotent: cannot chargeback the same deposit twice.
- •If balance goes negative, customer is auto-frozen (`compliance_status='frozen'`).
/api/backoffice/transactions/pending-approvalLegacy: /transactions/pending-approvalList transactions awaiting approval
List transactions awaiting approval (status `PENDING` or `PENDING_APPROVAL`). Max 200.
/api/backoffice/transactionsLegacy: /transactionsList transactions
List transactions with filters.
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| account_id | UUID | Filter by account | — |
| customer_id | UUID | Filter by customer | — |
| type | string | Transaction type filter | — |
| status | string | Status filter | — |
| source | string | Source filter (cashier, manual, system) | — |
| date_from | string | Date range start | — |
| date_to | string | Date range end | — |
| limit | int | Max results | — |
| offset | int | Pagination offset | — |
/api/backoffice/transactions/{id}Legacy: /transactions/{id}Get single transaction
Single transaction by UUID.
/transactions/payment-callbackProvider payment callback (internal)
Provider-agnostic internal endpoint. Called by integration-service after webhook verification.
Request
{
"cashier_id": "...",
"status": "APPROVED",
"gateway_payload": {}
}9. Payment Routing & BridgerPay
Per-country payment gateway routing. Brokers configure which cashier handles deposits/withdrawals for each country. Unconfigured countries fall back to `DEFAULT_PAYMENT_GATEWAY` env var (default: `praxis`).
/api/platform/payments/gateways/availableLegacy: /payments/gateways/availableList available payment gateways
List all known payment gateways with their enabled status. A gateway is "enabled" only if activated in integration-service with credentials configured.
Response
[
{
"id": "praxis",
"name": "Praxis",
"type": "redirect",
"enabled": true
},
{
"id": "bridgerpay",
"name": "BridgerPay",
"type": "embed",
"enabled": false
}
]/api/platform/payments/routingLegacy: /payments/routingList payment routing rules
List all per-country payment gateway routing rules.
Response
[
{
"country": "BR",
"deposit_gateway": "bridgerpay",
"withdrawal_gateway": "praxis",
"is_active": true,
"note": "Brazil uses BridgerPay",
"updated_at": "2026-03-19T10:00:00"
}
]/api/platform/payments/routing/{country}Legacy: /payments/routing/{country}Create or update country routing
Create or update payment routing for a country (ISO 3166-1 alpha-2). Rejects if gateway is not enabled.
Request
{
"deposit_gateway": "bridgerpay",
"withdrawal_gateway": "praxis",
"is_active": true,
"note": "Brazil uses BridgerPay for deposits"
}/api/platform/payments/routing/{country}Legacy: /payments/routing/{country}Delete country routing
Delete routing -- country reverts to default gateway. Returns 204.
/payments/gatewayResolve payment gateway for country
Resolve which payment gateway to use for a customer's country. Public (no auth) -- called by the frontend before initializing the cashier.
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| country* | string | ISO 3166-1 alpha-2 country code | — |
| direction | string | `deposit` or `withdrawal` | deposit |
Response
{
"gateway": "bridgerpay",
"country": "BR",
"direction": "deposit",
"type": "embed",
"script_url": "https://embed.bridgerpay.com/connector.min.js",
"cashier_key": "..."
}/payments/bridgerpay/sessionCreate BridgerPay cashier session
Create a BridgerPay cashier session. Requires customer JWT. Creates a PENDING transaction and returns the cashier token for frontend iframe initialization.
Request
{
"amount": 500,
"currency": "USD"
}Response
{
"transaction_id": "uuid",
"cashier_token": "abc123...",
"cashier_session_id": "...",
"cashier_key": "...",
"script_url": "https://embed.bridgerpay.com/connector.min.js"
}/integrations/webhooks/bridgerpayBridgerPay payment webhook
BridgerPay payment webhook. Public (no auth). Handled by integration-service -- connector verifies HMAC, maps status, and forwards to `POST /transactions/payment-callback` on account-service.
- •Status mapping: `approved` -> APPROVED, `declined`/`error`/`cancelled` -> DECLINE, `in_process` -> PROCESSING.
10. Withdrawal Approval Rules
Threshold-based role requirements for withdrawal approval. Default rules: agent < $1K, manager < $10K, compliance $10K+.
/api/platform/withdrawal-approval-rulesLegacy: /withdrawal-approval-rulesList withdrawal approval rules
List all active rules, ordered by `min_amount_usd`.
/api/platform/withdrawal-approval-rulesLegacy: /withdrawal-approval-rulesCreate a withdrawal approval rule
Create a new rule. Returns 201 Created.
Request
{
"min_amount_usd": 0,
"max_amount_usd": 1000,
"required_role": "agent",
"is_active": true
}/api/platform/withdrawal-approval-rules/{rule_id}Legacy: /withdrawal-approval-rules/{rule_id}Update a withdrawal approval rule
Update an existing rule (same fields as POST).
/api/platform/withdrawal-approval-rules/{rule_id}Legacy: /withdrawal-approval-rules/{rule_id}Delete a withdrawal approval rule
Soft-delete (sets `is_active=false`). Returns 204 No Content.
11. Orders (Trading)
Market order placement, position management, and stop-loss/take-profit modification.
/api/customer/ordersLegacy: /ordersPlace a market order (async)
Place a market order -- async execution. Poll `GET /orders/request/{request_id}` for execution result.
Request
{
"login": 10007,
"symbol": "EURUSD",
"cmd": 0,
"volume": 0.1
}Response
{
"request_id": "uuid",
"status": "queued"
}- •`cmd`: `0` = buy, `1` = sell.
/api/customer/orders/request/{request_id}Legacy: /orders/request/{request_id}Poll async order execution status
Poll async execution status. Key expires after 5 minutes.
Response
{
"request_id": "uuid",
"status": "executed",
"order": 100001
}- •Possible statuses: `executed`, `rejected` (with `reason` field, e.g., "Insufficient margin", "EXPOSURE_LIMIT_EXCEEDED").
/api/crm/ordersLegacy: /ordersList open positions
List open positions.
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| login* | int | Trading account login | — |
/api/crm/orders/{ticket}Legacy: /orders/{ticket}Get single open position
Single open position with live profit.
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| login* | int | Trading account login | — |
/api/crm/orders/{ticket}Legacy: /orders/{ticket}Close a position
Close a position. Supports partial close.
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| login* | int | Trading account login | — |
| volume | float | Partial close volume (omit for full close) | — |
- •Partial close creates a new `order_history` record linked via `parent_order`.
- •`volume` must be a multiple of 0.001; remaining must be >= 0.001 or zero.
- •Margin, commission, and storage split proportionally.
/api/customer/orders/{ticket}/sltpLegacy: /orders/{ticket}/sltpModify stop-loss / take-profit
Modify stop-loss / take-profit on an open position.
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| login* | int | Trading account login | — |
Request
{
"sl": 1.08,
"tp": 1.12
}- •At least one of `sl` or `tp` required; set to `0` to remove.
12. Pending Orders
Pending orders (Limit / Stop / Stop-Limit). Pending orders do not reserve margin -- margin is checked at execution time only.
/api/customer/orders/pendingLegacy: /orders/pendingPlace a pending order
Place a pending order.
Request
{
"login": 10007,
"symbol": "EURUSD",
"cmd": 2,
"volume": 0.1,
"trigger_price": 1.085,
"limit_price": null,
"sl": 1.08,
"tp": 1.09,
"expiry_at": "2026-03-15T00:00:00Z",
"comment": "Buy limit on EUR"
}- •`cmd`: `2` = BUY_LIMIT, `3` = SELL_LIMIT, `4` = BUY_STOP, `5` = SELL_STOP, `6` = BUY_STOP_LIMIT, `7` = SELL_STOP_LIMIT.
- •`limit_price`: required for cmd 6/7, rejected for cmd 2-5. The limit price used after the stop triggers.
- •`expiry_at`: optional -- `null` = GTC (good till cancelled).
- •Trigger price validation: BUY_LIMIT < ask, SELL_LIMIT > bid, BUY_STOP > ask, SELL_STOP < bid, BUY_STOP_LIMIT > ask, SELL_STOP_LIMIT < bid.
- •Stop-limit execution: when the stop triggers, if price is within the limit -> fill immediately. If price gapped past the limit -> converts to a regular limit order and waits.
/api/customer/orders/pending/{order_id}Legacy: /orders/pending/{order_id}Modify a pending order
Modify an active pending order.
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| login* | int | Trading account login | — |
Request
{
"sl": 1.075,
"tp": 1.095,
"trigger_price": 1.086,
"limit_price": 1.087
}- •At least one field required.
- •`limit_price` can only be set on stop-limit orders (cmd 6/7).
- •`trigger_price` validated against current market price.
- •Uses `FOR UPDATE` lock to prevent race conditions.
/api/customer/orders/pendingLegacy: /orders/pendingList pending orders
List pending orders.
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| login* | int | Trading account login | — |
| status | string | Filter by status (e.g., `active`) | — |
/api/customer/orders/pending/{order_id}Legacy: /orders/pending/{order_id}Cancel a pending order
Cancel a pending order. Only active orders.
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| login* | int | Trading account login | — |
13. Trade History
Closed trade history with currency conversion rates.
/api/crm/historyLegacy: /historyList closed trades
List closed trades. Response includes `open_fx_rate` and `close_fx_rate` for margin/profit currency conversion.
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| login* | int | Trading account login | — |
/api/crm/history/{ticket}Legacy: /history/{ticket}Get single closed trade
Single closed trade.
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| login* | int | Trading account login | — |
14. Prices
Real-time and historical price data, FX rates, price health monitoring, and WebSocket streaming.
/api/pricesLegacy: /pricesGet all latest prices
Returns latest cached price for every active symbol.
Response
{
"prices": [
{
"symbol": "BTCUSDT",
"bid": 67152.53,
"ask": 67152.54,
"mid": 67152.535,
"timestamp_ms": 1773033231279,
"provider": "platform"
}
],
"count": 3
}/api/prices/{symbol}Legacy: /prices/{symbol}Get single symbol price
Single symbol lookup. URL-encode slashes for forex: `EUR%2FUSD`.
404 — Symbol not active or cache expired (TTL 60s)/api/prices/historyLegacy: /prices/historyOHLC bars for charting
OHLC bars for charting (TradingView Charting Library datafeed).
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| symbol* | string | e.g. `EURUSD` | — |
| timeframe | string | `M1`, `M5`, `M15`, `H1`, `H4`, `D1` | M1 |
| from | string | ISO 8601 start time | — |
| to | string | ISO 8601 end time | — |
| limit | int | Max 5000 | 500 |
Response
{
"symbol": "EURUSD",
"timeframe": "H1",
"bars": [
{
"time": 1711234567,
"open": 1.085,
"high": 1.086,
"low": 1.084,
"close": 1.0855,
"volume": 147
}
],
"count": 500
}/api/prices/fx-ratesLegacy: /fx-ratesGet all USD-based FX rates
Returns all USD-based exchange rates from the in-memory cache (refreshed hourly).
Response
{
"base": "USD",
"rates": {
"EUR": 0.92,
"GBP": 0.79,
"JPY": 149.5
},
"last_updated": 1710000000,
"count": 161
}/api/prices/fx-rates/{currency}Legacy: /fx-rates/{currency}Get single currency FX rate
Single currency rate lookup.
Response
{
"currency": "GBP",
"rate": 0.79,
"base": "USD"
}/api/prices/healthLegacy: /health/pricesPer-symbol price freshness check
Per-symbol price freshness check. Each symbol reports its last tick age against its configured `stale_threshold_ms` (default 10000ms). Status is `"ok"` when no symbols are stale, `"degraded"` when at least one is stale.
Response
{
"status": "ok",
"symbols": {
"EURUSD": {
"last_tick_ms": 1773033231279,
"age_ms": 312,
"stale": false,
"threshold_ms": 10000
},
"BTCUSDT": {
"last_tick_ms": 1773033230100,
"age_ms": 1491,
"stale": false,
"threshold_ms": 5000
}
},
"summary": {
"total": 2,
"stale": 0,
"healthy": 2
},
"timestamp": "2026-03-12T10:00:31Z"
}/api/prices/outagesLegacy: /price-outagesHistorical price outage log
Historical price outage log. Each row represents a detected staleness window for a symbol. `ended_at` is `null` for ongoing outages.
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| symbol | string | Filter by symbol (exact match) | — |
| limit | int | Max rows returned (max 500) | 50 |
Response
{
"outages": [
{
"id": "uuid",
"symbol": "XAUUSD",
"started_at": "2026-03-12T09:55:00Z",
"ended_at": "2026-03-12T09:57:12Z",
"duration_ms": 132000,
"threshold_ms": 10000,
"last_tick_at": "2026-03-12T09:54:50Z"
}
],
"count": 1
}/api/customer/ws/pricesWebSocket price streaming
WebSocket endpoint for real-time price streaming.
/api/prices/providersList price providers
List configured price data providers.
/api/prices/symbolsList all price symbols
List all symbols available in the price feed.
15. Instruments
Instrument catalog and broker-level instrument configuration.
/api/customer/instrumentsLegacy: /instrumentsList all active instruments
List all active instruments.
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| group_by | string | `asset_class` -- returns grouped response | — |
Response
[
{
"id": "uuid",
"symbol": "EURUSD",
"display_name": "Euro / US Dollar",
"category": "forex",
"asset_class": "forex_majors",
"base_currency": "EUR",
"quote_currency": "USD",
"digits": 5,
"pip_size": 0.0001,
"contract_size": 100000,
"min_lot": 0.01,
"max_lot": 100,
"lot_step": 0.01,
"margin_rate": 0.01,
"spread": 0.2,
"stale_threshold_ms": 10000,
"is_active": true,
"provider": "none",
"last_bid": 1.08542,
"last_ask": 1.08556,
"last_tick_at": "2026-03-12T10:30:00Z"
}
]/api/customer/instruments/asset-classesLegacy: /instruments/asset-classesList active asset classes
Active asset classes for frontend tabs.
/api/customer/instruments/{symbol}Legacy: /instruments/{symbol}Get single instrument
Single instrument by symbol.
/api/platform/instrumentsLegacy: /instrumentsAdd a new instrument
Add a new instrument.
409 — Symbol already exists/api/platform/instrumentsLegacy: /broker/instrumentsList broker-level instrument config
List broker-level configuration for all instruments.
/api/platform/instruments/availableLegacy: /broker/instruments/availableList broker-enabled instruments
Instruments enabled at broker level and active in master catalog. Use for trading UI.
/api/platform/instruments/{symbol}Legacy: /broker/instruments/{symbol}Update broker-level instrument config
Update broker-level config: `is_enabled`, `tradeability` (`close_only`, `null` to clear).
16. Groups (Trading Configuration)
Trading group management including per-group instrument overrides and swap configuration.
/api/platform/groupsLegacy: /groupsList all trading groups
List all active trading groups (public).
/api/platform/groupsLegacy: /groupsCreate a trading group
Create a trading group.
Request
{
"name": "vip",
"description": "VIP clients",
"currency": "USD",
"default_leverage": 200,
"margin_call_level": 50,
"stopout_level": 20,
"commission_type": "per_lot",
"commission_value": 5,
"swap_free": false,
"swap_enabled": true,
"swap_multiplier": 1,
"swap_free_days": 0,
"is_islamic": false,
"is_default": false,
"max_exposure": 500000,
"net_max_exposure": false
}- •`stopout_level` must be < `margin_call_level`.
- •`is_islamic=true` forces `swap_free=true`.
- •`swap_enabled` -- default `true`; set `false` to disable swap charges for all accounts in this group.
- •`swap_multiplier` -- default `1.0`; multiplier applied to all swap rates.
- •`swap_free_days` -- default `0`; days after account creation during which swaps are not charged.
- •`max_exposure` -- per-account exposure limit in USD; `null` = unlimited. When `net_max_exposure=false` (default), this is gross exposure. When `true`, hedged positions on the same symbol reduce net exposure.
/api/platform/groups/{id}Legacy: /groups/{id}Get single group
Single group by ID.
/api/platform/groups/{id}Legacy: /groups/{id}Update group fields
Update group fields. All fields optional.
/api/platform/groups/{id}Legacy: /groups/{id}Soft-delete a group
Soft-delete (sets `is_active=false`).
409 — Active accounts assigned; reassign them first/api/platform/groups/{id}/instruments/{symbol}Legacy: /groups/{id}/instruments/{symbol}Create or update per-group instrument config
Create or update per-group instrument config. All fields optional and nullable -- `null` = inherit from master catalog.
Request
{
"spread": 2,
"margin_rate": 0.015,
"tradeability": "close_only",
"commission_type": "per_lot",
"commission_value": 5,
"min_lot": 0.01,
"max_lot": 50
}- •`spread` in pips -- converted to price delta at fill time.
- •`tradeability`: `tradable` | `close_only` | `non_tradable`.
/api/platform/groups/{id}/instrumentsLegacy: /groups/{id}/instrumentsList group instrument configs
List all instrument configs for a group.
/api/platform/groups/{id}/swap-configLegacy: /broker/groups/{id}/swap-configBulk upsert swap rates
Bulk upsert swap rates for multiple instruments on a group.
Request
{
"instruments": {
"BTCUSDT": {
"swap_type": "percent",
"swap_long": -0.05,
"swap_short": -0.05
},
"EURUSD": {
"swap_type": "points",
"swap_long": -5.5,
"swap_short": -3.2
}
}
}Response
{
"group_id": "uuid",
"instruments_updated": 1,
"instruments_created": 1
}/api/platform/groups/{id}/swap-ratesLegacy: /broker/groups/{id}/swap-ratesGet resolved swap rates
Resolved swap rates for all active instruments in a group. Shows source and effective rates after applying `swap_multiplier`.
17. Users (Broker Staff)
/api/admin/usersLegacy: /usersCreate a broker staff account
Request
{
"email": "[email protected]",
"first_name": "Jane",
"last_name": "Smith",
"password": "secret123",
"team_id": "uuid-or-null",
"role_id": 2
}Response
UserResponse
- •201 Created -- returns UserResponse.
409 — Email already registered./api/admin/users/meLegacy: /users/meCurrent user profile from JWT
Returns the profile of the currently authenticated user. No special permission required -- any authenticated user can call this.
/api/admin/usersLegacy: /usersList broker staff with filters
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| team_id | UUID | Filter by team | — |
| role_id | int | Filter by role | — |
| is_active | bool | Filter by active status | — |
| search | string | ilike match on first_name, last_name, email | — |
| page | int | Page number (default 1) | 1 |
| page_size | int | Page size (default 50, max 500) | 50 |
/api/admin/users/{id}Legacy: /users/{id}Single user profile
/api/admin/users/{id}Legacy: /users/{id}Update user fields
Request
{
"first_name": "Jane",
"last_name": "Smith",
"team_id": "uuid",
"role_id": 3,
"is_active": false
}- •All fields are optional.
/api/admin/users/{id}/pingLegacy: /users/{id}/pingHeartbeat -- refresh online presence
Refresh Redis online presence TTL (300s) and stamp `last_login_at`. Any authenticated user can call this.
- •204 No Content.
/api/admin/users/{id}/onlineLegacy: /users/{id}/onlineCheck Redis online status
Response
{
"user_id": "uuid",
"online": true
}18. Teams
Organisational hierarchy for broker staff. Teams support nested parent-child relationships with materialized path lookups.
/api/admin/teamsLegacy: /teamsCreate a team
`path` and `depth` are computed automatically.
Request
{
"name": "Europe",
"parent_team_id": "root-uuid-or-null"
}- •201 Created -- returns TeamResponse.
/api/admin/teamsLegacy: /teamsFlat list of teams
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| is_active | bool | Filter by active status | — |
| depth | int | Filter by depth level | — |
| parent_team_id | UUID | Filter by parent team | — |
/api/admin/teams/treeLegacy: /teams/treeFull hierarchy as nested JSON
Returns the complete team hierarchy as nested JSON for UI rendering.
/api/admin/teams/{id}Legacy: /teams/{id}Single team by ID
/api/admin/teams/{id}/childrenLegacy: /teams/{id}/childrenDirect children only (one level down)
/api/admin/teams/{id}/subtreeLegacy: /teams/{id}/subtreeAll descendants at any depth
Uses `path LIKE` -- one index scan.
/api/admin/teams/{id}/usersLegacy: /teams/{id}/usersAll active users directly in this team
/api/admin/teams/{id}/subtree/usersLegacy: /teams/{id}/subtree/usersAll active users in this team and all descendants
/api/admin/teams/{id}Legacy: /teams/{id}Update a team
If `parent_team_id` changes, all descendant paths are recomputed atomically.
400 — Team cannot be its own parent, or re-parenting under own descendant./api/admin/teams/{id}/permissionsLegacy: /teams/{id}/permissionsList permission overrides for a team
/api/admin/teams/{id}/permissions/{permission_id}Legacy: /teams/{id}/permissions/{permission_id}Set a permission override for a team
/api/admin/teams/{id}/permissions/{permission_id}Legacy: /teams/{id}/permissions/{permission_id}Remove a permission override (reverts to role default)
19. Roles & Permissions
RBAC system for broker staff. Roles define customer visibility scopes, PII masking rules, and permission grants.
/api/admin/rolesLegacy: /rolesList all roles
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| include_inactive | bool | Include inactive roles | false |
/api/admin/rolesLegacy: /rolesCreate a role
Request
{
"name": "retention_agent",
"display_name": "Retention Agent",
"description": "Handles churning customers",
"customer_view_scope": "own",
"customer_edit_scope": "none",
"pii_email": "masked",
"pii_phone": "masked",
"can_export_pii": false,
"level": 0
}- •Role RBAC columns:
- •`customer_view_scope` (own / team / all) -- whose customers this role can see.
- •`customer_edit_scope` (none / own / team / all) -- whose customers this role can edit.
- •`pii_email` (full / masked / hidden) -- how customer email is displayed.
- •`pii_phone` (full / masked / hidden) -- how customer phone is displayed.
- •`can_export_pii` (true / false) -- whether PII can be exported (CSV, etc.).
/api/admin/roles/{role_id}Legacy: /roles/{role_id}Single role by ID
/api/admin/roles/{role_id}Legacy: /roles/{role_id}Update role fields
Updatable fields: `display_name`, `description`, `customer_view_scope`, `customer_edit_scope`, `pii_email`, `pii_phone`, `can_export_pii`, `level`, `is_active`.
- •Privilege escalation guard: a user cannot set a scope value higher than their own role's scope. For example, a user with `customer_edit_scope=own` cannot create a role with `customer_edit_scope=all`.
/api/admin/roles/{role_id}/permissionsLegacy: /roles/{role_id}/permissionsList all permissions with granted status for a role
/api/admin/roles/{role_id}/permissions/{permission_id}Legacy: /roles/{role_id}/permissions/{permission_id}Grant or revoke a permission
Request
{
"granted": true
}- •Privilege escalation guard: a user cannot grant a permission they do not hold themselves.
/api/admin/roles/{role_id}/permissions/{permission_id}Legacy: /roles/{role_id}/permissions/{permission_id}Remove a permission grant
/api/admin/permissionsLegacy: /permissionsList all defined permissions
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| category | string | Filter by permission category | — |
/api/admin/permissionsLegacy: /permissionsCreate a new permission definition
20. CRM — Statuses, Assignments, Activities, Tasks
CRM building blocks: status management, customer-to-agent assignments, activity feeds, and task tracking.
/api/crm/statusesLegacy: /statusesCreate a status
`type` is normalised to lowercase.
Request
{
"type": "finance",
"name": "Deposited"
}/api/crm/statusesLegacy: /statusesList statuses
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| type | string | Filter by status type | — |
| active_only | bool | Only active statuses | — |
/api/crm/statuses/groupedLegacy: /statuses/groupedAll active statuses grouped by type
Call once on page load.
/api/crm/statuses/{id}Legacy: /statuses/{id}Single status
/api/crm/statuses/{id}Legacy: /statuses/{id}Update name or soft-delete
Request
{
"name": "Funded",
"is_active": false
}/api/crm/statuses/{id}Legacy: /statuses/{id}Hard delete a status
- •Rejected if any customer holds this status.
/api/crm/customers/assignLegacy: /customers/assignAssign customers to an agent
Request
{
"customer_ids": [
"uuid1",
"uuid2"
],
"role": "sales",
"new_agent_id": "agent-uuid",
"source": "rotation",
"note": "Round-robin batch"
}- •`new_agent_id: null` = unassign.
/api/crm/customers/{id}/assignmentsLegacy: /customers/{id}/assignmentsFull assignment history
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| role | string | Filter by assignment role | — |
| limit | int | Max results | — |
/api/crm/customers/{id}/assignments/currentLegacy: /customers/{id}/assignments/currentCurrent agent per role
/api/admin/users/{id}/teamLegacy: /users/{id}/teamMove a user to a new team
Request
{
"new_team_id": "uuid-or-null",
"note": "Q2 restructure"
}/api/admin/users/{id}/team-historyLegacy: /users/{id}/team-historyFull team membership history
/api/crm/customers/{id}/activitiesLegacy: /customers/{id}/activitiesActivity feed with keyset pagination
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| type | string | Comma-separated activity types | — |
| limit | int | 1-200 | — |
| before_id | int | Cursor for keyset pagination | — |
| include_deleted | bool | Include soft-deleted notes | — |
Response
{
"items": [
"..."
],
"next_cursor": "1187"
}/api/crm/customers/{id}/activities/notesLegacy: /customers/{id}/activities/notesCreate a note
Request
{
"body": "Called customer, interested in gold trading"
}/api/crm/customers/{id}/activities/{act_id}Legacy: /customers/{id}/activities/{act_id}Edit a note body
Only the author may edit. Previous body saved to `note_edits`.
/api/crm/customers/{id}/activities/{act_id}Legacy: /customers/{id}/activities/{act_id}Soft-delete a note
Only the author may delete.
/api/crm/customers/{id}/activities/{act_id}/editsLegacy: /customers/{id}/activities/{act_id}/editsFull edit history for a note
/api/crm/customers/{id}/tasksLegacy: /customers/{id}/tasksCreate a task
Request
{
"title": "Follow up call Friday 3pm",
"due_at": "2026-03-13T13:00:00Z",
"assigned_to": "user-uuid"
}/api/crm/customers/{id}/tasksLegacy: /customers/{id}/tasksList tasks for a customer
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| status | string | Filter by task status | — |
/api/crm/customers/{id}/tasks/{task_id}Legacy: /customers/{id}/tasks/{task_id}Update a task
Status transitions: `-> done` (sets `completed_at`), `-> cancelled` (sets `cancelled_at`).
/api/crm/tasks/mineLegacy: /tasks/mineTasks assigned to current user
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| status | string | Filter by task status | — |
| overdue | bool | Only overdue tasks | — |
/api/crm/tasks/teamLegacy: /tasks/teamTasks for all users in caller's team
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| status | string | Filter by task status | — |
| assigned_to | UUID | Filter by assignee | — |
21. Broker Settings
Key-value configuration store for broker-wide settings.
/api/platform/broker-settingsLegacy: /broker-settingsList all broker settings
Response
[
{
"key": "maker_checker_enabled",
"value": "true",
"description": "Require different user for tx approval",
"updated_at": "2026-03-20T10:00:00Z"
},
{
"key": "platform_name",
"value": "MyBroker",
"description": "Broker display name",
"updated_at": "2026-03-19T08:00:00Z"
}
]/api/platform/broker-settings/{key}Legacy: /broker-settings/{key}Create or update a broker setting
Upserts on key.
Request
{
"value": "true",
"description": "Require different user for transaction approval"
}Response
{
"key": "maker_checker_enabled",
"value": "true"
}- •Notable settings:
- •`maker_checker_enabled` -- when `"true"`, the user who created a transaction cannot approve it.
- •`platform_name` -- broker display name.
- •`platform_domain` -- used for autologin URLs, password reset links.
22. Email & SMS Templates
Manage email and SMS templates with multi-locale translations and placeholder support.
/api/platform/email-templates/schema/placeholdersLegacy: /email-templates/schema/placeholdersList all available placeholders for email templates
Returns available placeholders (e.g., `{{customer.first_name}}`, `{{transaction.amount}}`).
/api/platform/email-templatesLegacy: /email-templatesCreate a new email template
Creates the parent record with no translations yet.
Request
{
"name": "Welcome Email",
"event_type": "customer.registered",
"description": "Sent on signup",
"default_locale": "en"
}- •201 Created -- returns template with ID.
409 — event_type already has a template./api/platform/email-templatesLegacy: /email-templatesList all email templates with their translation locales
/api/platform/email-templates/{template_id}Legacy: /email-templates/{template_id}Single template with all translations
/api/platform/email-templates/{template_id}Legacy: /email-templates/{template_id}Update template metadata
Update name, description, default_locale.
/api/platform/email-templates/{template_id}Legacy: /email-templates/{template_id}Soft-delete an email template
- •204 No Content.
/api/platform/email-templates/{template_id}/translations/{locale}Legacy: /email-templates/{template_id}/translations/{locale}Create or update a locale-specific translation
Request
{
"subject": "Welcome, {{customer.first_name}}!",
"body_html": "<p>Hello {{customer.first_name}}</p>",
"body_text": "Hello {{customer.first_name}}"
}/api/platform/email-templates/{template_id}/translations/{locale}Legacy: /email-templates/{template_id}/translations/{locale}Remove a translation
- •204 No Content.
/api/platform/email-templates/{template_id}/previewLegacy: /email-templates/{template_id}/previewPreview a rendered template with sample data
Returns resolved HTML with placeholders filled.
/api/platform/sms-templates/analyzeLegacy: /sms-templates/analyzeAnalyze SMS content
Returns encoding (GSM-7 or UCS-2), character count, and segment count.
Request
{
"body": "Your code is {{code}}"
}/api/platform/sms-templatesLegacy: /sms-templatesCreate a new SMS template
Request
{
"name": "Verification Code",
"event_type": "verification",
"description": "OTP SMS",
"default_locale": "en"
}- •201 Created.
/api/platform/sms-templatesLegacy: /sms-templatesList all SMS templates with translation locales
/api/platform/sms-templates/{template_id}Legacy: /sms-templates/{template_id}Single SMS template with all translations
/api/platform/sms-templates/{template_id}Legacy: /sms-templates/{template_id}Update SMS template metadata
/api/platform/sms-templates/{template_id}Legacy: /sms-templates/{template_id}Soft-delete an SMS template
- •204 No Content.
/api/platform/sms-templates/{template_id}/translations/{locale}Legacy: /sms-templates/{template_id}/translations/{locale}Create or update a locale-specific SMS translation
Request
{
"body": "Your verification code is {{code}}"
}/api/platform/sms-templates/{template_id}/translations/{locale}Legacy: /sms-templates/{template_id}/translations/{locale}Remove an SMS translation
- •204 No Content.
/api/platform/sms-templates/{template_id}/previewLegacy: /sms-templates/{template_id}/previewPreview a rendered SMS with sample data
23. Workflow Builder
Trigger -> conditions -> actions automation engine. Data-driven rules that execute on Kafka events.
/api/platform/workflowsLegacy: /workflowsCreate a workflow rule
Request
{
"name": "Auto-assign new deposits to retention",
"description": "When a customer deposits, assign to retention team",
"trigger_event": "customer.deposited",
"trigger_config": {},
"conditions": {
"match": "all",
"rules": [
{
"field": "event.amount",
"operator": "gte",
"value": 1000
},
{
"field": "customer.retention_status",
"operator": "eq",
"value": "churning"
}
]
},
"actions": [
{
"type": "handover_to_retention",
"params": {}
},
{
"type": "add_note",
"params": {
"body": "Auto-assigned: high-value deposit from churning customer"
}
},
{
"type": "send_email",
"params": {
"subject": "Welcome back!",
"body_html": "<p>We noticed your deposit...</p>"
}
}
],
"priority": 10,
"stop_after": false
}- •201 Created.
- •`conditions`: Expression tree with `match` (`all`/`any`/`none`) and `rules` array -- supports unlimited nesting.
- •`priority`: Lower number = higher priority. All matching workflows execute in order; `stop_after=true` skips lower-priority workflows.
/api/platform/workflowsLegacy: /workflowsList all workflows
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| trigger | string | Filter by trigger event type | — |
| is_active | bool | Filter by active status | — |
/api/platform/workflows/{id}Legacy: /workflows/{id}Single workflow with execution stats
/api/platform/workflows/{id}Legacy: /workflows/{id}Full replace of workflow definition
/api/platform/workflows/{id}/toggleLegacy: /workflows/{id}/toggleToggle is_active on/off
Response
{
"id": "uuid",
"is_active": true
}/api/platform/workflows/{id}Legacy: /workflows/{id}Soft-delete a workflow
Sets `is_active=false`.
/api/platform/workflows/{id}/cloneLegacy: /workflows/{id}/cloneDuplicate workflow
Clones the workflow with "(copy)" appended to name.
- •201 Created.
/api/platform/workflows/{id}/historyLegacy: /workflows/{id}/historyExecution log for a specific workflow
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| limit | int | Max results | 100 |
| offset | int | Offset for pagination | 0 |
Response
[
{
"id": "uuid",
"workflow_id": "uuid",
"workflow_name": "Auto-assign new deposits to retention",
"trigger_event": "customer.deposited",
"customer_id": "uuid",
"customer_email": "[email protected]",
"status": "completed",
"actions_executed": 3,
"error_message": null,
"started_at": "2026-03-19T10:00:00Z",
"completed_at": "2026-03-19T10:00:01Z"
}
]- •Status: `completed`, `failed`, or `delayed` (waiting for a `delay` action).
/api/platform/workflows/schema/triggersLegacy: /workflows/schema/triggersAvailable trigger event types
Returns trigger event types with descriptions and field categories. Trigger events: `customer.registered`, `customer.deposited`, `customer.ftd`, `customer.withdrew`, `customer.traded`, `customer.status_changed`, `customer.retention_changed`, `call.no_answer`, `call.completed`, `task.overdue`, `customer.inactive_days`.
Response
[
{
"event": "customer.deposited",
"description": "Customer deposit approved",
"available_categories": [
"customer",
"event",
"transaction"
]
},
{
"event": "customer.traded",
"description": "Customer opened a trade",
"available_categories": [
"customer",
"event",
"order"
]
}
]/api/platform/workflows/schema/fieldsLegacy: /workflows/schema/fieldsAll fields available in workflow conditions
Returns fields with value source metadata for UI rendering. Field categories: `customer.*`, `event.*`, `transaction.*`, `order.*`. Custom status types auto-discovered from the `statuses` table -- broker-created statuses appear without code changes.
Response
[
{
"path": "customer.country",
"type": "string",
"description": "Customer country (ISO 3166-1)",
"category": "customer",
"value_source": null
},
{
"path": "customer.sales_status_id",
"type": "integer",
"description": "Sales status",
"category": "customer",
"value_source": {
"type": "status",
"status_type": "sales"
}
}
]/api/platform/workflows/schema/actionsLegacy: /workflows/schema/actionsRegistered action types with parameter schemas
Available actions (14): `assign_to_team`, `assign_to_agent`, `reassign_round_robin`, `handover_to_retention`, `set_status`, `send_email`, `send_sms`, `create_task`, `add_note`, `increment_manager_counter`, `set_customer_active`, `freeze_account`, `delay`, `webhook`.
Response
[
{
"type": "send_email",
"description": "Send email via integration service",
"params": {
"subject": {
"type": "string",
"required": true
},
"body_html": {
"type": "string",
"required": true
}
}
}
]- •Team hierarchy in assignment actions: `assign_to_team`, `reassign_round_robin`, and `handover_to_retention` support hierarchical teams. When a parent team (desk) is selected, agents from ALL descendant sub-teams are included in the round-robin pool. Uses `GET /teams/{id}/subtree/users` (materialized path query). Round-robin counter persisted in Redis for even distribution across service restarts.
/api/platform/workflows/schema/operatorsLegacy: /workflows/schema/operatorsAll condition operators
16 operators: `eq`, `ne`, `gt`, `lt`, `gte`, `lte`, `in`, `not_in`, `is_null`, `is_not_null`, `contains`, `not_contains`, `starts_with`, `ends_with`, `between`, `regex`.
24. Risk, Retention, FTD & Trader Days
Risk classification, retention lifecycle, first-time deposit tracking, and trader engagement metrics.
/api/admin/risk/rulesLegacy: /risk/rulesReturn current risk rule config
Returns the current risk rule configuration, or null.
/api/admin/risk/rulesLegacy: /risk/rulesUpdate risk classification thresholds
Triggers full batch recompute.
/api/crm/risk/profilesLegacy: /risk/profilesList risk profiles
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| classification | string | Filter by risk classification | — |
| min_trades | int | Minimum trade count | — |
| sort_by | string | Sort field | — |
| limit | int | Max results | — |
| offset | int | Offset for pagination | — |
/api/crm/risk/profiles/{customer_id}Legacy: /risk/profiles/{customer_id}Single customer risk profile
404 — No profile yet./api/platform/retention/rulesLegacy: /retention/rulesCreate or replace the retention classification rule
Triggers full recompute.
Request
{
"signals": [
"last_deposit_at",
"last_trade_at"
],
"operator": "OR",
"thresholds": {
"active": {
"max_days": 35
},
"churning": {
"min_days": 35,
"max_days": 60
},
"dormant": {
"min_days": 60
}
}
}- •Thresholds must be contiguous. Boundary: max_days is exclusive, min_days is inclusive.
/api/platform/retention/rulesLegacy: /retention/rulesReturn the current retention rule
Returns the current rule, or null if not yet configured.
/api/platform/ftd/minimumsLegacy: /ftd/minimumsSet a new per-country minimum deposit threshold
Append-only.
Request
{
"country": "BR",
"minimum_usd": 300,
"effective_from": "2026-03-11T00:00:00Z",
"note": "Q2 Brazil campaign"
}/api/platform/ftd/minimumsLegacy: /ftd/minimumsFull FTD minimum history
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| country | string | Filter by country code | — |
/api/platform/ftd/minimums/currentLegacy: /ftd/minimums/currentCurrently active minimum per country
/api/crm/ftdLegacy: /ftdConfirmed FTDs
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| country | string | Filter by country | — |
| affiliate_id | string | Filter by affiliate | — |
| manager_team_id | UUID | Filter by manager team | — |
| from_date | string | Start date | — |
| to_date | string | End date | — |
| page | int | Page number | — |
| page_size | int | Page size | — |
/api/crm/ftd/incompleteLegacy: /ftd/incompleteIn-progress FTDs (deposited but not reached minimum)
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| country | string | Filter by country | — |
| affiliate_id | string | Filter by affiliate | — |
| page | int | Page number | — |
| page_size | int | Page size | — |
/api/crm/ftd/customers/{customer_id}Legacy: /ftd/customers/{customer_id}FTD status for a specific customer
/api/crm/customers/{id}/trader-daysLegacy: /customers/{id}/trader-daysCustomer trading activity calendar
Returns dates when the customer opened at least one order.
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| from | date | Start date (default: first of current month) | — |
| to | date | End date (default: last of current month). Max range: 90 days. | — |
Response
{
"customer_id": "uuid",
"from": "2026-03-01",
"to": "2026-03-31",
"total_days": 12,
"days": [
{
"date": "2026-03-01",
"first_order_at": "2026-03-01T09:14:22Z",
"symbol": "BTCUSDT",
"login": "10007"
}
]
}/api/crm/trader-days/summaryLegacy: /trader-days/summaryAggregated trader counts per agent and team
For a date range. Respects `customer_view_scope`.
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| from | date | Start date (default: current month) | — |
| to | date | End date (default: current month). Max: 90 days. | — |
Response
{
"from": "2026-03-01",
"to": "2026-03-31",
"total_trader_days": 4520,
"unique_traders": 890,
"by_owner": [
{
"owner_id": "uuid",
"owner_name": "John Smith",
"trader_days": 320,
"unique_traders": 45
}
],
"by_team": [
{
"team_id": "uuid",
"team_name": "Sales Team A",
"trader_days": 1200,
"unique_traders": 150
}
]
}/api/crm/trader-days/dailyLegacy: /trader-days/dailyDay-by-day breakdown for charting engagement trends
Respects `customer_view_scope`.
Response
{
"from": "2026-03-01",
"to": "2026-03-31",
"days": [
{
"date": "2026-03-01",
"trader_days": 145,
"unique_traders": 145
}
]
}25. Compliance & Jurisdiction
KYC webhook processing, jurisdiction restrictions, compliance events, account freezing, and fraud prevention. **Compliance Gates:** | Gate | Tier 1 | Tier 2 | Tier 3 | |------|--------|--------|--------| | Registration | Block | Set `restricted_jurisdiction` | Log | | Login | Freeze at 3rd login | Record | Record | | Deposit | Reject (frozen) | Hold for review | Allow | | Trade | Reject (frozen) | Allow | Allow | | KYC | Retroactive freeze | Set restricted | Log | **Frozen accounts:** can login, can withdraw, cannot trade, cannot deposit.
/webhooks/kycProvider-agnostic KYC webhook
Accepts results from SumSub, Shufti, Jumio, or manual verification. Public endpoint, HMAC-verified.
Request
{
"customer_id": "uuid",
"provider": "sumsub",
"provider_id": "ext-ref-123",
"status": "approved",
"doc_type": "passport",
"doc_country": "US",
"raw_result": {}
}/api/backoffice/compliance/jurisdictionsLegacy: /compliance/jurisdictionsList restricted jurisdictions
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| tier | int | Filter by tier (e.g., 1) | — |
/api/backoffice/compliance/jurisdictionsLegacy: /compliance/jurisdictionsAdd a restricted jurisdiction
/api/backoffice/compliance/jurisdictions/{country_code}Legacy: /compliance/jurisdictions/{country_code}Update tier, reason, or is_active
/api/backoffice/compliance/eventsLegacy: /compliance/eventsCompliance events
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| customer_id | UUID | Filter by customer | — |
| event_type | string | Filter by event type | — |
| gate | string | Filter by compliance gate | — |
| limit | int | Max results | — |
/api/backoffice/compliance/logins/{customer_id}Legacy: /compliance/logins/{customer_id}Login audit trail
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| limit | int | Max results | — |
/api/backoffice/compliance/freeze/{customer_id}Legacy: /compliance/freeze/{customer_id}Manually freeze a customer account
Request
{
"reason": "Suspicious activity"
}/api/backoffice/compliance/unfreeze/{customer_id}Legacy: /compliance/unfreeze/{customer_id}Manually unfreeze a customer account
Request
{
"reason": "KYC verified",
"new_status": null
}/api/backoffice/admin/disposable-domainsLegacy: /admin/disposable-domainsList all blocked disposable email domains
/api/backoffice/admin/disposable-domainsLegacy: /admin/disposable-domainsAdd a domain to the blocklist
Request
{
"domain": "tempmail.com"
}/api/backoffice/admin/disposable-domains/{domain}Legacy: /admin/disposable-domains/{domain}Remove a domain from the blocklist
/api/backoffice/admin/fraud-logLegacy: /admin/fraud-logQuery the fraud audit log
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| date_from | string | Start date filter | — |
| date_to | string | End date filter | — |
| fraud_type | string | Filter by fraud type | — |
| affiliate_id | string | Filter by affiliate | — |
| limit | int | Max results (default 100) | 100 |
26. Consents & Communications
Track customer consent records for legal documents and view communication history.
/api/crm/customers/{customer_id}/consentsLegacy: /customers/{customer_id}/consentsRecord customer acceptance of a legal document
Captures IP address and User-Agent automatically. Upserts on `(customer_id, document_type, document_version)`.
Request
{
"document_type": "terms_and_conditions",
"document_version": "2.3",
"accepted": true
}- •201 Created.
/api/crm/customers/{customer_id}/consentsLegacy: /customers/{customer_id}/consentsList all consent records for a customer
Ordered by `accepted_at DESC`. For compliance review.
/api/crm/customers/{customer_id}/communicationsLegacy: /customers/{customer_id}/communicationsCommunication log -- all emails and SMS sent to this customer
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| channel | string | Filter: `email` or `sms` | — |
| limit | int | Max 500, default 50 | 50 |
27. Country Default Groups
Per-country default trading group assigned at registration. Customers from a mapped country get that group; unmapped countries fall back to the platform default group.
/api/platform/country-default-groupsLegacy: /country-default-groupsList all per-country group assignments
Response
[
{
"country": "DE",
"group_id": "uuid",
"group_name": "EU Standard",
"note": "EU regulation",
"updated_at": "..."
}
]/api/platform/country-default-groups/{country}Legacy: /country-default-groups/{country}Set or update the default group for a country
ISO 3166-1 alpha-2 (2 chars). Validates group exists. Upserts on country code.
Request
{
"group_id": "uuid",
"note": "EU regulation"
}/api/platform/country-default-groups/{country}Legacy: /country-default-groups/{country}Remove mapping -- country reverts to platform default group
- •204 No Content.
28. Integration Service
Connector marketplace for VoIP, email, SMS, payments, and KYC integrations. One active provider per category per broker (V1). Credentials encrypted at rest (AES-256-GCM). V1 catalog: `zadarma` (VoIP), `voiso` (VoIP), `sendgrid` (email), `gmail` (email), `praxis` (payment), `bridgerpay` (payment), `twilio_voice` (VoIP), `twilio_sms` (SMS), `sumsub` (KYC).
/api/platform/integrations/catalogLegacy: /integrations/catalogList all available integrations in the platform catalog
Response
[
{
"slug": "sendgrid",
"category": "email",
"display_name": "SendGrid",
"description": "Transactional email via SendGrid API",
"credential_schema": {
"api_key": {
"type": "string",
"required": true
}
},
"config_schema": {},
"is_available": true
}
]/api/platform/integrations/catalog/{slug}Legacy: /integrations/catalog/{slug}Single catalog entry by slug
/api/platform/integrations/activeLegacy: /integrations/activeList all activated integrations for this broker
Credentials are masked in the response.
/api/platform/integrations/active/{slug}Legacy: /integrations/active/{slug}Single active integration with masked credentials and health status
Response
{
"id": "uuid",
"slug": "sendgrid",
"category": "email",
"display_name": "SendGrid",
"is_active": true,
"health_status": "healthy",
"health_message": "OK",
"last_health_check": "2026-03-19T10:00:00Z",
"config": {},
"credentials_summary": {
"api_key": "SG.***redacted***"
},
"activated_at": "2026-03-10T12:00:00Z"
}/api/platform/integrations/{slug}/activateLegacy: /integrations/{slug}/activateActivate an integration with credentials
One provider per category -- activating a second email provider returns 409.
Request
{
"credentials": {
"api_key": "SG.xxx"
},
"config": {}
}Response
{
"slug": "sendgrid",
"category": "email",
"is_active": true,
"activated_at": "..."
}409 — Category already has an active integration.422 — Credential/config schema validation failed./api/platform/integrations/{slug}/deactivateLegacy: /integrations/{slug}/deactivateDeactivate an integration
400 — Already inactive./api/platform/integrations/{slug}/credentialsLegacy: /integrations/{slug}/credentialsUpdate credentials for an active integration
Re-encrypts at rest, resets health status, invalidates connector pool cache.
Request
{
"credentials": {}
}/api/platform/integrations/{slug}/configLegacy: /integrations/{slug}/configUpdate configuration for an active integration
Non-credential settings.
Request
{
"config": {}
}/api/platform/integrations/{slug}/testLegacy: /integrations/{slug}/testTest connector health
If credentials provided in body, tests with those temporarily (no storage). Otherwise tests with stored credentials.
Request
{
"credentials": {}
}Response
{
"status": "success",
"message": "..."
}- •Request body is optional. Status: `success` or `failure`.
/api/platform/integrations/execute/{category}/{action}Legacy: /integrations/execute/{category}/{action}Execute an action through the active provider for a category
404 — No integration configured for category.501 — Action not implemented by provider.502 — Provider timeout or error./api/platform/integrations/email/sendLegacy: /integrations/email/sendSend an email via the active email provider
Provider-agnostic send endpoint. All fields except `to`, `subject` are optional. `from_email`/`from_name` fall back to `EMAIL_FROM_ADDRESS`/`EMAIL_FROM_NAME` env vars.
Request
{
"to": "[email protected]",
"subject": "Welcome to trading",
"body_html": "<p>Hello John</p>",
"body_text": "Hello John",
"from_email": "[email protected]",
"from_name": "Broker Support",
"reply_to": "[email protected]",
"cc": [],
"bcc": [],
"tags": {
"campaign": "onboarding"
},
"customer_id": "uuid",
"event_type": "welcome"
}Response
{
"message_id": "...",
"status": "sent",
"provider": "sendgrid"
}502 — Provider error./api/platform/integrations/sms/sendLegacy: /integrations/sms/sendSend an SMS via the active SMS provider
`from_number` falls back to `SMS_FROM_NUMBER` env var.
Request
{
"to": "+447700900000",
"body": "Your verification code is 123456",
"from_number": "+15551234567",
"tags": {
"type": "verification"
},
"customer_id": "uuid",
"event_type": "verification"
}Response
{
"message_id": "...",
"status": "sent",
"provider": "twilio_sms"
}/api/platform/integrations/call/initiateLegacy: /integrations/call/initiateInitiate a phone call via the active VoIP provider
`from_number` falls back to `CALL_FROM_NUMBER` env var.
Request
{
"to": "+447700900000",
"from_number": "+15551234567",
"from_agent_id": "uuid",
"customer_id": "uuid",
"metadata": {
"reason": "follow_up"
}
}Response
{
"call_id": "...",
"status": "initiated",
"provider": "zadarma"
}/api/platform/integrations/configLegacy: /integrations/configProvider names and sender defaults
No credentials exposed. Used by frontend to show current configuration.
Response
{
"email": {
"provider": "sendgrid",
"from_address": "[email protected]",
"from_name": "Broker"
},
"sms": {
"provider": "twilio_sms",
"from_number": "+15551234567"
},
"call": {
"provider": "zadarma",
"from_number": "+15559876543"
}
}/integrations/email/webhook/{provider}Inbound delivery status from email provider
Provider delivery status callback (e.g., `sendgrid`). Verified via provider-specific HMAC signatures.
/integrations/sms/webhook/{provider}Inbound delivery status from SMS provider
Provider delivery status callback (e.g., `twilio_sms`). Verified via provider-specific HMAC signatures.
/integrations/call/webhook/{provider}Inbound call completion status from VoIP provider
Provider callback (e.g., `zadarma`, `voiso`). Verified via provider-specific HMAC signatures.
/integrations/webhooks/{slug}Generic webhook handler for payment and KYC providers
Connector verifies HMAC, processes payload, forwards to account-service. - **Payment flow**: Verifies -> forwards to `POST /transactions/payment-callback` with `{cashier_id, status, gateway_payload}` - **KYC flow (sumsub)**: Verifies `x-payload-digest` -> maps status -> fetches doc details -> signs and forwards to `POST /webhooks/kyc`
/api/platform/integrations/bridgerpay/create-sessionLegacy: /integrations/bridgerpay/create-sessionCreate a BridgerPay cashier session
Called internally by account-service during `POST /payments/bridgerpay/session`. Requires active BridgerPay integration.
Request
{
"order_id": "bp_abc123",
"amount": 500,
"currency": "USD",
"email": "[email protected]",
"first_name": "Omar",
"last_name": "Al-Rashid",
"country": "AE",
"phone": "+971501234567",
"customer_id": "uuid"
}Response
{
"cashier_token": "abc123...",
"cashier_session_id": "...",
"cashier_key": "..."
}/api/platform/integrations/auditLegacy: /integrations/auditIntegration activity log
Actions: `activated`, `deactivated`, `credentials_updated`, `config_updated`, `health_check_passed`, `health_check_failed`, `execute_success`, `execute_failure`, `webhook_received`.
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| slug | string | Filter by integration slug | — |
| action | string | Filter by action type | — |
| limit | int | Max results | 50 |
| offset | int | Offset for pagination | 0 |
29. Affiliate Webhooks
Outbound webhook management for affiliate portals. Affiliate portals receive HTTP POST with HMAC-SHA256 signature in `X-Webhook-Signature`. Batched every 30 seconds. **Outbound payload examples:** `customer.status_updated` -- status change notification. `lead.ftd` -- final webhook for this lead (all subsequent events permanently suppressed).
/api/platform/affiliates/webhooksLegacy: /webhooksRegister a new affiliate webhook endpoint
Request
{
"affiliate_id": "partner_acme",
"endpoint_url": "https://acme-affiliates.com/api/webhook",
"secret": "hmac-secret-key",
"batch_size_max": 500
}/api/platform/affiliates/webhooksLegacy: /webhooksList all registered webhooks
/api/platform/affiliates/webhooks/{webhook_id}Legacy: /webhooks/{webhook_id}Single webhook
/api/platform/affiliates/webhooks/{webhook_id}Legacy: /webhooks/{webhook_id}Update a webhook
Updatable fields: `endpoint_url`, `secret`, `is_active`, `batch_size_max`.
/api/platform/affiliates/webhooks/{webhook_id}Legacy: /webhooks/{webhook_id}Delete a webhook
/api/platform/affiliates/webhooks/{webhook_id}/deliveriesLegacy: /webhooks/{webhook_id}/deliveriesDelivery attempts (most recent first)
- •Limit 100.
/api/platform/affiliates/webhooks/{webhook_id}/deliveries/statsLegacy: /webhooks/{webhook_id}/deliveries/statsAggregate delivery statistics
30. Calendar
Upcoming economic events calendar.
/calendarUpcoming economic events
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| from | string | Start date `YYYY-MM-DD` | today |
| to | string | End date (inclusive) | +7 days |
| currency | string | Comma-separated: `USD,EUR,GBP` | — |
| impact | string | `high` or `medium` | — |
Response
[
{
"id": 1,
"event_name": "Business NZ PMI",
"country": "New Zealand",
"currency": "NZD",
"category": "Manufacturing PMI",
"impact": "medium",
"event_date": "2026-03-13T03:30:00Z",
"actual": null,
"forecast": null,
"previous": 55.2
}
]31. Stopout Events & Audit Log
Stopout force-close event monitoring and immutable audit trail of financial state changes.
/api/backoffice/stopout-eventsLegacy: /stopout-eventsList recent stopout force-close events
For dealing room / risk monitoring.
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| login | string | Filter by account login | — |
| limit | int | Max results (default 50, max 500) | 50 |
| offset | int | Offset for pagination | — |
Response
{
"id": 1,
"login": 10001,
"order_ticket": 100042,
"symbol": "EURUSD",
"margin_level_at_trigger": 18.5,
"margin_level_after_close": 45.2,
"equity_at_trigger": 450.2,
"profit_realized": -125.3,
"close_price": 1.08542,
"stopout_level": 20,
"positions_remaining": 2,
"created_at": "2026-03-11T14:30:00Z"
}/api/backoffice/audit-logLegacy: /audit-logImmutable audit trail of financial state changes
Query Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| entity_type | string | Filter by entity type | — |
| entity_id | string | Filter by entity ID | — |
| login | string | Filter by account login | — |
| action | string | Filter by action type | — |
| limit | int | Max results (default 100, max 1000) | 100 |
| offset | int | Offset for pagination | — |
Response
{
"id": 1,
"entity_type": "order",
"entity_id": "100042",
"action": "open",
"login": "10001",
"details": {
"symbol": "EURUSD",
"volume": 0.1,
"cmd": 0
},
"balance_before": 10000,
"balance_after": 10000,
"credit_before": 0,
"credit_after": 0,
"margin_used_before": 0,
"margin_used_after": 108.54,
"source_service": "order-service",
"created_at": "2026-03-11T14:30:00Z"
}32. Account SSE Stream
Server-Sent Events stream of live account state. Browser-compatible via `EventSource`. - Initial snapshot from Redis `HGETALL account:{login}:state` - Subsequent updates via Redis Pub/Sub, throttled to max 500ms between events - 25s keepalive ping - Headers: `Cache-Control: no-cache`, `X-Accel-Buffering: no`
/api/crm/accounts/{login}/streamLegacy: /accounts/{login}/streamLive account state via Server-Sent Events
Opens an SSE stream for real-time account state updates. Connect using `EventSource` in the browser. **Event format:** ``` event: state data: {"balance": 10000.0, "credit": 0.0, "equity": 10250.5, "pnl": 250.5, "margin_used": 500.0, "free_margin": 9750.5, "margin_level": 2050.1, "gross_exposure": 10854.2, "net_exposure": 5427.1, "currency": "USD"} ```
Response
{
"balance": 10000,
"credit": 0,
"equity": 10250.5,
"pnl": 250.5,
"margin_used": 500,
"free_margin": 9750.5,
"margin_level": 2050.1,
"gross_exposure": 10854.2,
"net_exposure": 5427.1,
"currency": "USD"
}- •Content-Type: text/event-stream.
- •Initial snapshot sent immediately on connection.
- •Subsequent updates pushed via Redis Pub/Sub, throttled to max 500ms between events.
- •25s keepalive ping to prevent proxy timeouts.