Conventions
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
5.96 means 5.96%, not 596% and not 0.0596.
Any field whose name ends in _percent follows this rule.15, not -15. It is a magnitude, and the field
name already says which direction it goes.currency is a separate field — never assume
USD, and never infer it from the broker.commission and swap keep their natural sign: a cost is negative, a credit is
positive. A swap you were paid is a positive number.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 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
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:
{
"data": [ /* … */ ],
"pagination": {
"page": 1,
"limit": 50,
"total": 16,
"has_more": false
}
}
1.50, maximum 200.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.
/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.