Skip to main content
Developers

Conventions

The rules every endpoint follows. Read this once and the rest of the API behaves the way you expect.

Base URL and versions

  • REST: https://terminal.jafx.com/trading-api/v1. Streaming: wss://terminal.jafx.com/trading-api/v1/stream. MCP: https://terminal.jafx.com/trading-api/mcp.
  • HTTPS only. Requests and responses are JSON (UTF-8); send Content-Type: application/json with a body. Bodies are limited to 64 KB.
  • The version is in the path. Within v1, changes are additive: new endpoints, new optional fields, new values in lists. Write clients that ignore fields they do not know. Anything breaking comes as a new version, announced in the changelog beforehand.
  • The machine-readable description is the OpenAPI 3.1 spec.

Authentication

HTTP
Authorization: Bearer jafx_demo_k7mx2v9qr4tz8w1b6nfa_…

Every endpoint needs a key except GET /status. The API is meant for server-side programs; it does not answer cross-origin requests from browsers. See API keys & security.

Numbers

  • Money and volume are decimal strings: "0.01" lot, "10985.05" USD. They are exact; parse them with a decimal type, not a float.
  • Prices are JSON numbers (1.11554), with as many decimals as the instrument's digits.
  • Volumes you send must be a multiple of the instrument's volume_step and between volume_min and volume_max (GET /symbols/{symbol}).

Time

  • Times are ISO-8601 in UTC: 2026-10-13T09:30:01.242Z. Query parameters such as from and to also accept epoch milliseconds.
  • Stream quote frames carry time_ms (epoch milliseconds) instead, to keep high-frequency frames small.
  • The broker day starts at midnight Europe/Athens. Daily candles and the daily loss limit follow it.

Prices

Instruments, quotes and candles come from one central JAFX reference quote board, the same for every key, and are marked "price_basis": "raw". The board is not your account's own server: your account trades at its own server's prices, with the markup of its account type, so they can differ a little from the board. A raw quote is a reference, not a guaranteed fill price. The broker fills and validates every order at your account's own prices, and refuses an instrument your account cannot trade.

Quotes update at most four times a second per instrument. They are snapshots of the board, not every tick, and candles are built from the same snapshots.

Idempotency

Every write (POST, PATCH, DELETE) needs an Idempotency-Key header: 1–64 characters from A–Z a–z 0–9 . _ : -, unique per intended action. A UUID is ideal.

  • Retrying with the same key and the same body returns the original answer, with the header Idempotent-Replayed: true. It never trades twice. This is what makes it safe to retry after a timeout or a dropped connection.
  • The same key with a different body answers 409 idempotency_conflict.
  • The same key while the first call is still running answers 409 request_in_progress with Retry-After.
  • Refused before it reached the broker (validation, limits, a trading halt, the broker connection being unavailable): the key is released, so you can retry with the same key once the cause is fixed.
  • Answered by the broker (filled, placed, rejected): the key stays bound to that answer for 72 hours. After that, the same key places a new order.
  • If JAFX lost its own record of the first call, for example in a server crash, a retry with the same key still reaches the broker as the same request, and the broker answers it with the original order. That answer has a new request_id and no Idempotent-Replayed header, but the original ticket.
One key per intent

Generate the key when you decide to place the order, store it with the order in your program, and reuse it for every retry of that order. A new key means a new order.

Held decisions and wait

Most orders are decided in a fraction of a second. Sometimes the broker holds an order while it covers it with a liquidity provider. Write calls wait up to wait seconds for the decision (query parameter, 0–10, default 5), then answer 202 with "status": "pending". The decision then arrives as a request.decided event on the orders stream, and GET /accounts/{account}/requests/{request_id} shows it.

If the connection to the broker fails while an order is in flight, the answer is 202 with "status": "outcome_unknown". Poll the request until it resolves. Do not send the order again under a new key: it may already have executed.

Pagination and windows

  • Lists that can be long (/trades, /transactions) take limit (1–1000, default 100) and return next_cursor. Pass it as cursor to get the next page; null means there is no more.
  • History is queried by window: from defaults to 7 days before to, to defaults to now, and a window may span up to 90 days. Closed trades are ordered by close time, newest first.

Rate limits

Per key: reads 20 per second with bursts of 40, writes 5 per second with bursts of 10. Order and modification limits per minute come on top (see Limits). Every response carries:

HTTP
RateLimit-Limit: 40
RateLimit-Remaining: 37
RateLimit-Reset: 1

Over the limit, the answer is 429 with a Retry-After header in seconds.

All REST and MCP requests from one IP address also share a limit of 50 per second with bursts of 100, whatever the key. It only matters when several keys or programs call from the same address. Over it, the answer is 429 rate_limited with Retry-After, without the RateLimit-* headers.

Errors

Errors are RFC 9457 problem documents (application/problem+json) with a stable code to branch on:

JSON
{
  "type": "https://terminal.jafx.com/trading-api/problems/volume_limit_exceeded",
  "title": "Volume limit exceeded",
  "status": 422,
  "code": "volume_limit_exceeded",
  "detail": "This key allows at most 1.00 lots per order",
  "request_id": "req_01JQ8ZJ4X2M7R9T5KN3VW6QH8B"
}

Branch on code, not on title or detail, which may be reworded. Validation errors list the fields in details. The full list is on Errors.

Request IDs

Every response has an X-Request-Id header (req_…, an opaque string whose length can vary), repeated as request_id in problem documents. Quote it when you contact support. It identifies the HTTP call; an order's own request_id (26 characters, in order results) identifies the order request for GET /accounts/{account}/requests/{request_id}.

Retries

What happenedWhat to do
Network error or timeout on a readRetry with backoff.
Network error or timeout on a writeRetry with the same Idempotency-Key. You get the original answer if the first call got through.
429, 503, 409 request_in_progress, 409 order_busyWait for Retry-After, then retry the same call.
503 service_unavailable on a writeWait for Retry-After, then retry with the same Idempotency-Key. You get the original answer if the first call got through.
202 pending or 202 outcome_unknownDo not resend. Follow the request on the stream or poll it.
423 trading_haltedWait until GET /me shows trading back on.
Other 4xxDo not retry unchanged; fix the request first.

Give write calls a client timeout of at least 15 seconds: the server may hold them for up to wait seconds.

Automated and AI-driven trading can lose money quickly; nothing in these docs is investment advice.

JAFX (PTY) LTD · FSCA FSP No. 52058 · Trading leveraged derivatives with JAFX carries a high risk of losing money rapidly. It is not suitable for everyone.