Conventions

Units, signs, dates, identifiers and pagination — what every number in a response actually means.

These rules hold across every endpoint, without exception. They are worth two minutes now because they are the things that quietly corrupt a report six months later.

Numbers

Percentages
number
Expressed in percent, never as a fraction. 5.96 means 5.96%, not 596% and not 0.0596. Any field whose name ends in _percent follows this rule.
Drawdown
number
Always positive. A 15% drawdown is 15, not -15. It is a magnitude, and the field name already says which direction it goes.
Money
number
A JSON number, never a string. The account's currency is a separate field — never assume USD, and never infer it from the broker.
Costs
number
commission and swap keep their natural sign: a cost is negative, a credit is positive. A swap you were paid is a positive number.
Missing values
null
Always an explicit null, never an omitted key and never 0. You should never have to guess whether the value is zero or unknown.
0 is a real value, so we never use it to mean "not set". A stop loss of 0 would be a price. When there is no stop loss, the field is null.

Which number is "the return"?

There are two, they answer different questions, and both are returned:

twr_percent
number
Time-weighted return. Neutralises deposits and withdrawals, so it measures the strategy. This is the number shown on the public page and the one to use when comparing accounts.
absolute_gain_percent
number
Plain return on the money that went in. Measures the account. Deposit halfway through a good month and this number moves; twr_percent does not.

On the example account below they read 5.96 and 5.89. When they disagree sharply, the account has had deposits or withdrawals — that is the whole difference between them.

Dates and times

Always ISO 8601 in UTC, with a trailing Z:

"opened_at": "2026-08-10T23:00:00Z"

Never a local time, never a broker's server time, never a Unix timestamp. Broker server time is a common source of off-by-a-few-hours bugs; converting to your own timezone is your call, and you have an unambiguous instant to do it from.

Identifiers

Objects are addressed by their slug — the same one in the public URL:

GET /api/v1/accounts/dark-algo
Slugs can be changed by their owner. When that happens the old slug keeps working through a redirect, so existing integrations do not break. If you store a reference long-term, store the slug and be ready to follow a redirect.

The internal composite id (mt4-26613005-…) is deliberately not part of this API. It encodes the broker account number and the owner's user id, and we are not making that a permanent part of a public contract.

Accounts and portfolios

A portfolio behaves exactly like an account: same endpoints, same shape, same statistics. They are distinguished by one field:

"type": "account"    // or "portfolio"

Filter a listing with ?type=account or ?type=portfolio when you only want one kind.

Lists and pagination

Every endpoint that returns a list uses the same envelope:

Response
{
  "data": [ /* … */ ],
  "pagination": {
    "page": 1,
    "limit": 50,
    "total": 16,
    "has_more": false
  }
}
page
integer
1-based. Defaults to 1.
limit
integer
Items per page. Defaults to 50, maximum 200.
total
integer
Total items matching the request, across all pages.
has_more
boolean
Whether another page exists. Loop on this, not on arithmetic over total — it stays correct if items are added while you are paging.

Rate limits

Limits are counted per key, not per IP, so a colleague on the same office network cannot eat your quota. Every response carries your current standing:

X-RateLimit-Limit: 120
X-RateLimit-Remaining: 118
X-RateLimit-Reset: 1755864000

Over the limit you get a 429 with a Retry-After header in seconds. Back off on that header rather than a fixed sleep.

Versioning

The version is in the path (/api/v1/). Within a version we will add fields but never remove or repurpose one, and never change the meaning or the unit of an existing field.

Treat unknown fields as harmless: a parser that rejects them will break the first time we add something.

The rest of the site's endpoints — anything not under /api/v1/ — are internal. They power the website, they change shape whenever the interface changes, and they carry no guarantee whatsoever. They are reachable, but building on them is building on sand.