Developers
The Propology API
Show a trader's accounts in your own app, place and manage orders, and read the evaluation rules with the room left under each one. A versioned REST API, opened with keys the trader creates and controls.
Version 1 · Base URL https://api.propology.trade/v1
Overview
The API is the same account a trader uses in the app, reached from somewhere else — a bot, a dashboard, or another application. Every order placed through it goes through the same margin checks, leverage limits and risk engine as an order placed by hand.
Version 1 is stable. Fields are not removed or renamed within a version; new fields may be added, so ignore ones you do not recognise. A breaking change would ship as /v2 alongside it.
curl https://api.propology.trade/v1/accounts \ -H "Authorization: Bearer plg_your_key_here"
Authentication
A trader creates a key in the Propology app under Platforms → API keys. Send it as a bearer token on every request:
Authorization: Bearer plg_7Fq2Kx9aB3nR5tYwLm0pQ8sVc4ZdE6hJkU1oXgTy
Keys begin with plg_. Each one carries:
- A scope.
read— accounts, positions, orders and history — orreadandtrade, which adds placing, cancelling and closing. - Optionally, a set of accounts. A key can be limited to specific trading accounts; otherwise it reaches all of them, including ones opened later.
- Optionally, allowed IP addresses and an expiry. If your app runs from a fixed address, lock the key to it.
There is no withdrawal scope, and there will not be one. A key can trade an account; it can never move money out of it. Withdrawals always require the trader's password and a two-factor code in the app.
Creating a key requires the trader to enter a two-factor code, and they are emailed every time one is created. The key is shown once, at creation — Propology stores only a fingerprint of it and cannot show it again. Treat it like a password: keep it on your server, never in a browser or a mobile app bundle.
Conventions
Every response is JSON wrapped in the same envelope:
{ "success": true, "data": { ... } }
{ "success": false, "error": { "code": "VALIDATION_ERROR", "message": "price is required for limit orders" } }- Money and quantities are strings —
"10432.18", never10432.18. A JSON number is a floating-point value, and a balance that has been through one is no longer the balance. Parse them with a decimal library. - Times are epoch milliseconds, UTC.
- Lists that can grow are paginated as
{ "items": [...], "nextCursor": "..." }. Passcursorto get the next page;nextCursorisnullon the last one.limitis 1–200, default 50.
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | VALIDATION_ERROR | The request body or query is malformed. The message names the first problem. |
| 401 | TOKEN_INVALID | Missing, unknown, revoked or expired key — or a request from an address the key does not allow. |
| 403 | FORBIDDEN | The key does not have the scope this route needs. |
| 403 | ACCOUNT_NOT_TRADEABLE | The account is breached, passed, suspended, closed or under review, and cannot take new orders. |
| 404 | NOT_FOUND | No such account, order or position — or one outside the key’s account scope. |
| 409 | CONFLICT | An order with this clientOrderId is already being placed. See Safe retries. |
| 422 | ORDER_REJECTED | The risk engine refused the order — not enough available margin, leverage above the instrument’s bracket, or a size outside its limits. The message says which. |
| 429 | RATE_LIMITED | Too many requests. Wait for the Retry-After header. |
A key that is revoked, expired or used from the wrong address gets the same 401 as one that never existed — so the response cannot be used to probe which keys were once valid.
Rate limits
Limits are per trader, and shared with the app: a trader running a bot and trading by hand at the same time has one allowance between them.
| Action | Limit | Scope |
|---|---|---|
| Place, cancel, close | 120 / minute | Per trader, across every key and the app. |
| Cancel all orders | 30 / minute | Per trader. |
Responses carry X-RateLimit-Limit and X-RateLimit-Remaining; a 429 carries Retry-After in seconds.
Accounts
Every trading account the key can reach. The trader's wallet is never included.
{
"id": "5b0e…",
"name": "10K Evaluation",
"type": "evaluation", // evaluation | funded | free
"status": "active", // active | breached | passed | pending_review | suspended | closed
"currency": "USDT",
"balance": "10000",
"equity": "9812.40",
"availableBalance": "9412.40",
"marginUsed": "400",
"unrealizedPnl": "-187.60",
"maxLeverage": 250,
"rules": {
"phase": "phase_1",
"profitTarget": { "targetEquity": "10800", "pct": 8 },
"dailyLoss": { "limitEquity": "9700", "remaining": "112.40", "pct": 3 },
"totalLoss": { "limitEquity": "9000", "remaining": "812.40", "pct": 10, "mode": "trailing" },
"tradingDays": { "completed": 2, "required": 5 },
"minHoldSeconds": 120,
"endsAt": 1790380800000
}
}rules is what a prop-trading integration exists for. Each limitEquity is the equity at which that rule is breached, and remaining is how far the account is above it right now. They are computed by the same functions the risk engine enforces with, so a drawdown bar drawn from them is the bar the engine will honour. rules is null on an account no live evaluation governs — a funded account, or one whose evaluation has ended.
totalLoss.mode is static (measured from the starting balance), trailing (follows the high-water mark), or trailing_to_initial (trails until it reaches the starting balance, then stays there).
Positions
Query status: open, closed or liquidated.
Closes at market. Send { "quantity": "0.005" } to close part of it, or an empty body to close all of it. Returns the closing order.
Orders
Query status: open for the working book or history for everything finished; symbol; limit; cursor.
{
"symbol": "BTCUSDT",
"side": "buy",
"type": "limit",
"quantity": "0.010",
"price": "60000",
"leverage": 10,
"timeInForce": "gtc",
"clientOrderId": "my-bot-000123",
"stopLossPrice": "58500",
"takeProfitPrice": "63000"
}| Field | Type | Notes |
|---|---|---|
| symbol | string | An instrument from GET /v1/markets, e.g. BTCUSDT. |
| side | string | buy or sell. |
| type | string | market, limit, stop, stop_limit, take_profit or take_profit_limit. |
| quantity | decimal string | In the base asset. Greater than zero. |
| price | decimal string | Required for limit, stop_limit and take_profit_limit. Not allowed on market. |
| stopPrice | decimal string | Required for stop, stop_limit, take_profit and take_profit_limit. |
| leverage | integer | Capped by the account’s maxLeverage and by the instrument’s own risk brackets. |
| timeInForce | string | gtc (default), ioc or fok. |
| reduceOnly | boolean | Only ever reduce an open position. Default false. |
| positionSide | string | long or short. Required on a hedge-mode account. |
| clientOrderId | string | Up to 48 characters, unique per account. Makes retries safe — see below. |
| stopLossPrice | decimal string | Optional. Attaches a protective stop to the resulting position. |
| takeProfitPrice | decimal string | Optional. Attaches a take-profit to the resulting position. |
The response is the order, plus fill — the position and trade it produced — when it executed immediately. fill is null for an order that is resting or was rejected.
Cancels every resting order on the account, or on one instrument with ?symbol=BTCUSDT. Returns { "cancelled": 3 }.
Safe retries
Networks fail. If a request to place an order times out, you cannot tell whether it was placed — and sending it again could open a second position.
Send a clientOrderId and retrying is always safe. Retry with the same id and you get back the order the first request placed, instead of a new one. If the first request is still being processed when the retry arrives, the retry gets a 409 — look the order up with GET …/orders before trying again.
const id = `bot-${Date.now()}`; // one id per intended order
async function place(order) {
for (let attempt = 0; attempt < 3; attempt++) {
try {
const res = await fetch(`https://api.propology.trade/v1/accounts/${ACCOUNT}/orders`, {
method: 'POST',
headers: { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ ...order, clientOrderId: id }), // same id on every attempt
});
const body = await res.json();
if (body.success) return body.data;
if (res.status !== 409 && res.status < 500) throw new Error(body.error.message);
} catch (networkError) { /* fall through and retry with the same id */ }
await new Promise((r) => setTimeout(r, 1000 * (attempt + 1)));
}
}A clientOrderId is unique per account for good: once used, it always refers to that order.
Trades
Every fill, newest first. Query symbol, since (epoch milliseconds, inclusive), limit and cursor.
Market data
Public — no key needed.
Every tradeable instrument, with its tick size, step size and maximum leverage.
Last, mark and index price, the best bid and ask, 24-hour range and volume, and the funding rate with the time of the next funding.
Which price means what matters when you model an order:
| Order | Price used | How |
|---|---|---|
| Market order | bestAsk / bestBid | Buys fill at the best ask, sells at the best bid. |
| Limit order | lastPrice | Fills when the last traded price reaches your limit. |
| Stop, take-profit | markPrice | Trigger on the mark price, then execute as market or limit. |
| Liquidation, rules | markPrice | Liquidation and every evaluation rule are measured on the mark. |
The mark is Binance's futures mark price, derived from an index across several exchanges — so a brief spike on one order book does not trigger stops or breach an account on its own.
Rules that still apply
An account traded through the API is the same account, under the same trading rules. Three matter most to automated strategies:
- Positions must be held at least two minutes. Closing sooner is a violation, whether a person or a bot closed it. The account's
rules.minHoldSecondssays the exact figure. - Latency arbitrage is prohibited — systematically taking fills against prices the engine has not yet updated.
- Copy trading across Propology accounts is prohibited — running the same signal on several accounts you control.
Questions, or building something we should know about? Write to support@propx.dev.