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.

Attribution

Every account object carries an attribution field, and so does every list of trades or positions, next to pagination:

"attribution": {
  "required": true,
  "text": "Data from ShowMyTrades",
  "url": "https://showmytrades.com/account/dark-algo",
  "account_url": "https://showmytrades.com/account/dark-algo"
}

It is the credit to show next to the data, already worded and already linked. On the free plan it is required, on Pro and Ultra it is recommended. The rules are in Attribution.

Rate limits

Limits are counted per key: 30 requests per minute on the free plan, 120 on Pro and Ultra. Requests without a key (public accounts only) are counted per IP address, 20 per minute. 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.

A second, much higher limit protects the whole site and is counted per IP address. A normal integration never meets it; a burst of more than 10 requests in one second does.

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.