Deltalytix
API documentation.
Authenticate with OAuth or personal access tokens, then read and write trades, accounts, connections, imports, and metrics through the Deltalytix Public API v1.
Try it
Call real /api/v1 endpoints with a personal access token. Logged-in visitors can generate a short-lived docs token; everyone can paste an existing PAT.
…
Overview
What the Deltalytix Public API offers, the base URL, versioning, and shared conventions.
Overview
The Deltalytix Public API lets you read and write trading data, manage broker connections, import files, and compute performance metrics programmatically. It is designed for personal scripts, third-party integrations, and first-party OAuth apps.
Base URL
All REST endpoints are served from:
https://www.deltalytix.appExamples in this documentation use that host. Relative paths such as /api/v1/trades are always rooted at the same origin.
Versioning
The current stable surface is API v1, mounted under /api/v1/*. Breaking changes ship under a new major version path. Additive fields may appear within v1 without a version bump.
Machine-readable discovery:
- OpenAPI 3.1:
/openapi.json - OAuth metadata:
/.well-known/openid-configuration - Protected resource metadata:
/.well-known/oauth-protected-resource
What you can do
| Area | Capabilities |
|---|---|
| Profile | Read the authenticated user (GET /api/v1/me) |
| Trades | List and create trades |
| Accounts | List accounts, optionally with performance metrics |
| Connections | List connections, create IBKR Flex connections, trigger sync |
| Imports | Upload CSV/XLSX files with AI or platform-specific parsers |
| Metrics | Summary statistics, equity curves, per-account metrics |
Authentication
Every /api/v1/* request requires a Bearer token:
Authorization: Bearer dltx_at_…Tokens are either:
- OAuth access tokens from the authorization code flow (
dltx_at_…) - Personal access tokens created in the dashboard (
dltx_pat_…)
See Authentication for scopes, OAuth, and PATs.
Shared conventions
Pagination
List endpoints accept:
| Parameter | Default | Max | Description |
|---|---|---|---|
limit | 100 | 500 | Page size |
cursor | — | — | Opaque cursor from a previous response |
Responses use:
{
"data": [],
"nextCursor": null
}When nextCursor is a string, pass it as cursor on the next request. When it is null, you have reached the last page.
Dates
Timestamps and date filters are ISO 8601 strings (for example 2026-03-15T14:30:00.000Z).
Errors
Failed requests return:
{
"error": "machine_code",
"message": "Human-readable explanation",
"details": {}
}See Errors for status codes and OAuth-specific error shapes.
Quick start
curl https://www.deltalytix.app/api/v1/me \
-H "Authorization: Bearer dltx_pat_YOUR_TOKEN"const res = await fetch("https://www.deltalytix.app/api/v1/me", {
headers: {
Authorization: "Bearer dltx_pat_YOUR_TOKEN",
},
});
const me = await res.json();Next: Authentication.
Authentication
OAuth 2.0 authorization code with PKCE, personal access tokens, scopes, and token formats.
Authentication
Deltalytix is its own OAuth 2.0 authorization server. Humans still sign in with Supabase; API tokens are minted and validated by Deltalytix.
Token formats
Tokens are opaque strings. Deltalytix stores only SHA-256 hashes.
| Kind | Prefix | Lifetime |
|---|---|---|
| Access token | dltx_at_<48 hex chars> | 3600 seconds |
| Refresh token | dltx_rt_<48 hex chars> | 30 days (rotated on use) |
| Personal access token (PAT) | dltx_pat_<48 hex chars> | No expiry until revoked |
| Client ID | dltx_app_<24 hex chars> | — |
| Client secret | dltx_secret_<48 hex chars> | Shown once at creation |
Scopes
Request only the scopes your integration needs. Space-separate them in OAuth scope parameters.
| Scope | Access |
|---|---|
profile:read | Read the authenticated user profile |
trades:read | List trades |
trades:write | Create trades |
accounts:read | List accounts and related metrics |
connections:read | List broker connections |
connections:write | Create connections and trigger sync |
imports:write | Upload import files |
metrics:read | Read summary, equity, and account metrics |
Personal access tokens
PATs are ideal for scripts and private tools. Create and revoke them from the dashboard developer settings, choosing the scopes you need. The token value is shown once.
curl https://www.deltalytix.app/api/v1/me \
-H "Authorization: Bearer dltx_pat_YOUR_TOKEN"OAuth 2.0 authorization code + PKCE
1. Authorize
Send the user to the consent page (HTML). Unauthenticated users are redirected to /authentication?next=….
GET /oauth/authorize
?client_id=dltx_app_…
&redirect_uri=https%3A%2F%2Fyour-app.example%2Fcallback
&response_type=code
&scope=profile%3Aread%20trades%3Aread
&state=csrf-token
&code_challenge=BASE64URL_SHA256_OF_VERIFIER
&code_challenge_method=S256| Query | Required | Notes |
|---|---|---|
client_id | Yes | Registered OAuth app |
redirect_uri | Yes | Must exactly match a registered URI |
response_type | Yes | Must be code |
scope | Yes | Space-separated scopes |
state | Recommended | CSRF protection; echoed on redirect |
code_challenge | Recommended | PKCE S256 challenge |
code_challenge_method | With challenge | Must be S256 |
On approve, Deltalytix issues a single-use authorization code (10 minute TTL) and redirects:
https://your-app.example/callback?code=…&state=…On deny:
https://your-app.example/callback?error=access_denied&state=…2. Exchange the code for tokens
POST /api/oauth/token accepts form-urlencoded or JSON.
curl -X POST https://www.deltalytix.app/api/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=AUTHORIZATION_CODE" \
-d "redirect_uri=https://your-app.example/callback" \
-d "client_id=dltx_app_…" \
-d "code_verifier=PKCE_VERIFIER"Confidential clients may send client_secret instead of (or in addition to) PKCE, depending on how the app was registered.
Successful response:
{
"access_token": "dltx_at_…",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "dltx_rt_…",
"scope": "profile:read trades:read"
}OAuth errors follow RFC 6749:
{
"error": "invalid_grant",
"error_description": "Authorization code is invalid or expired"
}3. Call the API
curl https://www.deltalytix.app/api/v1/trades?limit=10 \
-H "Authorization: Bearer dltx_at_…"const res = await fetch("https://www.deltalytix.app/api/v1/trades?limit=10", {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});4. Refresh the access token
curl -X POST https://www.deltalytix.app/api/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "refresh_token",
"refresh_token": "dltx_rt_…",
"client_id": "dltx_app_…",
"client_secret": "dltx_secret_…"
}'Refresh tokens rotate on use. Store the new refresh_token from each successful response.
Public clients that obtained tokens via PKCE may refresh without a client secret when that was how the original token was issued.
5. Revoke a token
curl -X POST https://www.deltalytix.app/api/oauth/revoke \
-H "Content-Type: application/json" \
-d '{
"token": "dltx_at_…",
"client_id": "dltx_app_…",
"client_secret": "dltx_secret_…"
}'Revocation always returns 200, whether or not the token was found.
Resource-server failures
Missing or invalid Bearer tokens return 401:
{
"error": "unauthorized",
"message": "…"
}with a WWW-Authenticate header pointing at protected-resource metadata.
Valid tokens without the required scope return 403:
{
"error": "insufficient_scope",
"message": "…"
}Managing apps and tokens
In the dashboard developer settings you can:
- Create OAuth apps (name, redirect URIs, allowed scopes) —
client_idis always visible;client_secretis shown once - Create and revoke personal access tokens with chosen scopes — the PAT value is shown once
Trades
List and create trades with filters, pagination, and the shared import/dedupe pipeline.
Trades
Manage the authenticated user’s trade history.
List trades
GET /api/v1/tradesScope: trades:read
Query parameters
| Parameter | Description |
|---|---|
accountNumber | Filter by account number |
instrument | Filter by instrument symbol |
side | Filter by side |
from | Inclusive lower bound on entryDate (ISO 8601) |
to | Inclusive upper bound on entryDate (ISO 8601) |
cursor | Opaque pagination cursor |
limit | Page size (default 100, max 500) |
Example
curl "https://www.deltalytix.app/api/v1/trades?accountNumber=SIM-001&limit=50" \
-H "Authorization: Bearer dltx_at_…"const params = new URLSearchParams({
accountNumber: "SIM-001",
from: "2026-01-01T00:00:00.000Z",
limit: "50",
});
const res = await fetch(`https://www.deltalytix.app/api/v1/trades?${params}`, {
headers: { Authorization: `Bearer ${accessToken}` },
});
const page = await res.json();Response 200
{
"data": [
{
"id": "trade_01HZX…",
"accountNumber": "SIM-001",
"instrument": "ES",
"side": "long",
"quantity": 2,
"entryPrice": 5120.25,
"closePrice": 5128.5,
"entryDate": "2026-03-15T14:30:00.000Z",
"closeDate": "2026-03-15T15:10:00.000Z",
"pnl": 825,
"commission": 8.64,
"timeInPosition": 2400,
"tags": ["breakout"],
"comment": "Morning continuation",
"createdAt": "2026-03-15T15:12:01.000Z"
}
],
"nextCursor": null
}Create trades
POST /api/v1/tradesScope: trades:write
Creates one or more trades using the same dedupe pipeline as the dashboard (UUID v5 identity + createMany with skipDuplicates). Duplicate payloads are counted, not treated as hard failures.
Request body
{
"trades": [
{
"accountNumber": "SIM-001",
"instrument": "ES",
"quantity": 2,
"entryPrice": 5120.25,
"closePrice": 5128.5,
"entryDate": "2026-03-15T14:30:00.000Z",
"closeDate": "2026-03-15T15:10:00.000Z",
"pnl": 825,
"side": "long",
"commission": 8.64,
"entryId": "optional-broker-entry-id",
"closeId": "optional-broker-close-id",
"timeInPosition": 2400,
"tags": ["breakout"],
"comment": "Morning continuation"
}
]
}| Field | Required | Notes |
|---|---|---|
accountNumber | Yes | Target account |
instrument | Yes | Symbol / contract |
quantity | Yes | Size |
entryPrice | Yes | Entry price |
closePrice | Yes | Exit price |
entryDate | Yes | ISO 8601 |
closeDate | Yes | ISO 8601 |
pnl | Yes | Realized P&L |
side | No | e.g. long / short |
commission | No | Fees |
entryId | No | Broker entry identifier |
closeId | No | Broker exit identifier |
timeInPosition | No | Duration in seconds |
tags | No | String array |
comment | No | Free text |
Example
curl -X POST https://www.deltalytix.app/api/v1/trades \
-H "Authorization: Bearer dltx_at_…" \
-H "Content-Type: application/json" \
-d '{
"trades": [
{
"accountNumber": "SIM-001",
"instrument": "ES",
"quantity": 2,
"entryPrice": 5120.25,
"closePrice": 5128.5,
"entryDate": "2026-03-15T14:30:00.000Z",
"closeDate": "2026-03-15T15:10:00.000Z",
"pnl": 825,
"side": "long",
"commission": 8.64
}
]
}'Response 201
{
"imported": 1,
"duplicates": 0,
"total": 1
}If every row is a duplicate, the response still returns success with counts (for example "imported": 0, "duplicates": 3, "total": 3), not an error status.
Accounts
List trading accounts, payouts, and optional per-account performance metrics.
Accounts
Read the authenticated user’s accounts and related payout information.
List accounts
GET /api/v1/accountsScope: accounts:read
Query parameters
| Parameter | Description |
|---|---|
includeMetrics | When true, attaches per-account metrics from computeMetricsForAccounts |
Example
curl "https://www.deltalytix.app/api/v1/accounts?includeMetrics=true" \
-H "Authorization: Bearer dltx_at_…"const res = await fetch(
"https://www.deltalytix.app/api/v1/accounts?includeMetrics=true",
{
headers: { Authorization: `Bearer ${accessToken}` },
},
);
const payload = await res.json();Response 200
{
"data": [
{
"id": "acc_01HZX…",
"accountNumber": "SIM-001",
"name": "Evaluation A",
"payouts": [
{
"id": "pay_01HZX…",
"amount": 2500,
"date": "2026-02-01T00:00:00.000Z",
"status": "paid"
}
],
"metrics": {
"balance": 52480.12,
"drawdown": 3.4,
"consistency": 0.82,
"progress": 0.61
}
}
],
"nextCursor": null
}metrics is present only when includeMetrics=true. Metric fields include balance, drawdown, consistency, and progress toward account targets.
For portfolio-level analytics across all accounts, prefer the Metrics endpoints.
Connections
List broker connections, create IBKR Flex connections, and trigger server-side sync.
Connections
Broker connections sync trades into Deltalytix. Raw broker tokens are never returned by the API.
List connections
GET /api/v1/connectionsScope: connections:read
Example
curl https://www.deltalytix.app/api/v1/connections \
-H "Authorization: Bearer dltx_at_…"Response 200
{
"data": [
{
"id": "conn_01HZX…",
"service": "ibkr",
"externalId": "flex-query-123",
"lastSyncedAt": "2026-03-15T16:00:00.000Z",
"environment": "live",
"accountNumbers": ["U1234567"]
}
],
"nextCursor": null
}| Field | Description |
|---|---|
id | Connection identifier |
service | Provider key (ibkr, …) |
externalId | Provider-side identifier (never a secret token) |
lastSyncedAt | Last successful sync timestamp, or null |
environment | e.g. live / demo when applicable |
accountNumbers | Linked Deltalytix account numbers |
Create an IBKR Flex connection
POST /api/v1/connectionsScope: connections:write
MVP supported service: IBKR Flex.
Request body
{
"service": "ibkr",
"token": "<flex token>",
"queryId": "<flex query id>"
}Example
curl -X POST https://www.deltalytix.app/api/v1/connections \
-H "Authorization: Bearer dltx_at_…" \
-H "Content-Type: application/json" \
-d '{
"service": "ibkr",
"token": "YOUR_FLEX_TOKEN",
"queryId": "YOUR_FLEX_QUERY_ID"
}'const res = await fetch("https://www.deltalytix.app/api/v1/connections", {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
service: "ibkr",
token: process.env.IBKR_FLEX_TOKEN,
queryId: process.env.IBKR_FLEX_QUERY_ID,
}),
});On success the API creates the connection and linked accounts, runs an initial sync, and returns the connection object (without the Flex token).
Unsupported service → 422
{
"error": "unsupported_service",
"message": "Only IBKR Flex is supported in this API version",
"details": {
"supported": ["ibkr"]
}
}Trigger a sync
POST /api/v1/connections/{id}/syncScope: connections:write
Triggers a sync for services with importable server sync logic. IBKR is supported at minimum; additional services such as tradovate, dxfeed, and rithmic-protocol may be available when their sync actions are reusable.
Example
curl -X POST https://www.deltalytix.app/api/v1/connections/conn_01HZX…/sync \
-H "Authorization: Bearer dltx_at_…"Response 200 (completed)
{
"status": "completed",
"imported": 42,
"duplicates": 3
}Response 202 (async)
{
"status": "started"
}Imports
Upload CSV or XLSX trade files with AI mapping or platform-specific parsers.
Imports
Import trades from files without going through the dashboard UI.
Upload a file
POST /api/v1/importsScope: imports:write
Content-Type: multipart/form-data
Form fields
| Field | Required | Description |
|---|---|---|
file | Yes | .csv or .xlsx file |
type | Yes | "ai" or a platform name |
accountNumber | Yes | Destination account number |
AI import (type=ai)
The server parses the file (Papa Parse for CSV, read-excel-file for XLSX), runs the same AI mapping and formatting pipeline used by the dashboard, then saves trades through the shared trades-save core.
curl -X POST https://www.deltalytix.app/api/v1/imports \
-H "Authorization: Bearer dltx_at_…" \
-F "file=@./trades.csv" \
-F "type=ai" \
-F "accountNumber=SIM-001"const form = new FormData();
form.append("file", fileInput.files[0]);
form.append("type", "ai");
form.append("accountNumber", "SIM-001");
const res = await fetch("https://www.deltalytix.app/api/v1/imports", {
method: "POST",
headers: { Authorization: `Bearer ${accessToken}` },
body: form,
});
const result = await res.json();Platform import (type=<platform>)
When type is a platform key, the server uses a registered parser extracted from the dashboard import flow. Supported platforms depend on which parsers are available as pure functions (candidates include tradezella, tradovate, quantower, topstep, ftmo, atas, and others).
curl -X POST https://www.deltalytix.app/api/v1/imports \
-H "Authorization: Bearer dltx_at_…" \
-F "file=@./export.xlsx" \
-F "type=tradovate" \
-F "accountNumber=SIM-001"Success response
{
"imported": 128,
"duplicates": 4,
"total": 132,
"accountNumber": "SIM-001"
}Unsupported platform → 422
{
"error": "unsupported_service",
"message": "Unknown import type",
"details": {
"supported": ["ai", "tradovate", "tradezella", "quantower"]
}
}The exact details.supported list reflects the live parser registry.
Metrics
Summary statistics, equity curves, and per-account performance metrics.
Metrics
Compute performance analytics from the authenticated user’s trades.
All metrics endpoints require the metrics:read scope.
Summary
GET /api/v1/metrics/summaryUses the same filters as GET /api/v1/trades. Statistics come from calculateStatistics plus the dashboard profit-factor formula (gross wins and losses net of commission).
Query parameters
| Parameter | Description |
|---|---|
accountNumber | Filter by account |
instrument | Filter by instrument |
side | Filter by side |
from / to | Bounds on entryDate |
cursor / limit | Accepted for consistency with trades filters where applicable |
Example
curl "https://www.deltalytix.app/api/v1/metrics/summary?from=2026-01-01T00:00:00.000Z" \
-H "Authorization: Bearer dltx_at_…"Response 200
{
"totalPnl": 12450.5,
"totalCommission": 312.4,
"tradeCount": 186,
"winCount": 102,
"lossCount": 78,
"breakevenCount": 6,
"winRate": 0.5484,
"profitFactor": 1.62,
"averageWin": 215.3,
"averageLoss": -142.1,
"longCount": 110,
"shortCount": 76,
"tradingDays": 48
}Equity curve
GET /api/v1/metrics/equityBuilds daily equity points via computeEquityChartData.
Query parameters
| Parameter | Description |
|---|---|
from | Start date (ISO 8601) |
to | End date (ISO 8601) |
accountNumbers | Comma-separated account numbers; when provided, also returns per-account series |
Example
curl "https://www.deltalytix.app/api/v1/metrics/equity?accountNumbers=SIM-001,SIM-002&from=2026-01-01T00:00:00.000Z" \
-H "Authorization: Bearer dltx_at_…"const params = new URLSearchParams({
accountNumbers: "SIM-001,SIM-002",
from: "2026-01-01T00:00:00.000Z",
});
const res = await fetch(
`https://www.deltalytix.app/api/v1/metrics/equity?${params}`,
{ headers: { Authorization: `Bearer ${accessToken}` } },
);
const equity = await res.json();Response 200
{
"points": [
{
"date": "2026-01-02",
"equity": 50120.5
},
{
"date": "2026-01-03",
"equity": 50385.25
}
],
"accounts": {
"SIM-001": [
{ "date": "2026-01-02", "equity": 25050.0 },
{ "date": "2026-01-03", "equity": 25210.75 }
],
"SIM-002": [
{ "date": "2026-01-02", "equity": 25070.5 },
{ "date": "2026-01-03", "equity": 25174.5 }
]
}
}The accounts object is included when accountNumbers is requested.
Account metrics
GET /api/v1/metrics/accountsRuns computeMetricsForAccounts for all of the user’s accounts.
Example
curl https://www.deltalytix.app/api/v1/metrics/accounts \
-H "Authorization: Bearer dltx_at_…"Response 200
{
"data": [
{
"accountNumber": "SIM-001",
"balance": 52480.12,
"drawdown": 3.4,
"consistency": 0.82,
"progress": 0.61
}
]
}Errors
REST error envelope, HTTP status codes, and OAuth error responses.
Errors
REST error envelope
API v1 errors use a consistent JSON body:
{
"error": "machine_code",
"message": "Human-readable explanation",
"details": {}
}| Field | Description |
|---|---|
error | Stable machine-readable code |
message | Human-readable explanation |
details | Optional structured context (validation issues, supported values, …) |
HTTP status codes
| Status | Typical cause |
|---|---|
400 | Validation error (malformed query/body) |
401 | Missing, expired, revoked, or invalid Bearer token |
403 | Authenticated but missing required scope |
404 | Resource not found |
422 | Semantically invalid request (unsupported service/type) |
500 | Unexpected server error |
Unauthorized (401)
{
"error": "unauthorized",
"message": "Missing or invalid access token"
}Responses include:
WWW-Authenticate: Bearer resource_metadata="https://www.deltalytix.app/.well-known/oauth-protected-resource"Insufficient scope (403)
{
"error": "insufficient_scope",
"message": "Token is missing required scope trades:write"
}Unsupported service (422)
{
"error": "unsupported_service",
"message": "Only IBKR Flex is supported in this API version",
"details": {
"supported": ["ibkr"]
}
}OAuth errors
Token endpoint failures use the RFC 6749 shape (not the REST envelope):
{
"error": "invalid_grant",
"error_description": "Authorization code is invalid or expired"
}Common OAuth error values include invalid_request, invalid_client, invalid_grant, unauthorized_client, unsupported_grant_type, and invalid_scope.
Authorization endpoint denials redirect to the registered redirect_uri with error=access_denied (and the original state when provided).
Non-error counts
Some write endpoints report duplicate or zero-import outcomes with HTTP success and count fields instead of an error body. For example, POST /api/v1/trades returns 201 with "imported": 0 when every row already exists.
Handling tips
- Branch first on HTTP status, then on
error. - Surface
messageto developers; useerrorfor programmatic handling. - Treat
details.supportedas the authoritative allow-list when present. - On
401, refresh the OAuth access token or prompt for a new PAT; do not retry indefinitely.
OpenAPI reference
Paths, methods, parameters, and response codes from the live OpenAPI document.
Download openapi.jsonLoading OpenAPI document…