Errors
Errors are problem documents with a stable code. Branch on the code; the title and detail are for people and may be reworded.
Format
{
"type": "https://terminal.jafx.com/trading-api/problems/invalid_request",
"title": "Invalid request",
"status": 422,
"code": "invalid_request",
"detail": "volume: must be positive",
"request_id": "req_01JQ8ZK1V6T3N8R2XM5QW7HB4C",
"details": [{ "field": "volume", "message": "must be positive" }]
}Responses use Content-Type: application/problem+json. details is present when there is more to say, for example the fields that failed validation, or the level of a trading halt. A Retry-After header (seconds) comes with every error that is worth retrying.
Keys and access
| Code | HTTP | Meaning | What to do |
|---|---|---|---|
invalid_api_key | 401 | The key is missing, malformed or unknown. | Check the Authorization header and the key. |
api_key_expired | 401 | The key is past its expires_at. | Create or roll a key. |
api_key_revoked | 401 | The key was revoked. | Create a new key. |
api_key_suspended | 403 | JAFX has suspended the key. | Contact support. |
invalid_token | 401 | MCP only: an app's sign-in token (from Connect with just the address) is malformed or unknown, has expired, or its app was disconnected. The answer's WWW-Authenticate header says where to sign in. | Refresh the token, or sign in again. A suspended app gets api_key_suspended. |
ip_not_allowed | 403 | The request came from an address outside the key's IP lock (ip_allowlist). | Call from an allowed address, or create a key locked to the new address. A key's IP lock can't be changed after it is created. |
insufficient_scope | 403 | The key lacks the scope this call needs (details.required), or MCP is off for the key. | Use a key with the right scope. |
account_not_accessible | 403 | The account in the URL is not the key's account. | Use the account from GET /me. |
live_trading_disabled | 403 | Live trading through the API is switched off, or this key was created for a demo account and the account is live. | Use a demo account, or create a key for the live account. |
account_read_only | 403 | The broker has made the account read-only. | Contact support. |
Limits and trading state
| Code | HTTP | Meaning | What to do |
|---|---|---|---|
symbol_not_allowed | 403 | The instrument is not on the key's allow-list, or is excluded from API trading. | Trade another instrument, or change the key. |
volume_limit_exceeded | 422 | The volume is above the key's max_volume_per_order. | Send a smaller order. |
position_limit_exceeded | 422 | The order would exceed max_open_positions (positions plus pending orders). | Close or cancel something first. |
daily_loss_limit_reached | 403 | Today's loss reached the key's limit; new positions are refused. details has the figures. | Wait for the next broker day (midnight Europe/Athens). Closing still works. |
sign_in_required | 409 | Your account has no active trading session: 24 hours passed since its last sign-in to JAFX Terminal, or it was stopped. With around-the-clock trading on, JAFX signs in again by itself, so you see this only when the trading account refused the saved password (GET /me → session.ended_reason: password_refused). Nothing was sent. | Sign in to JAFX Terminal, or press Renew on its Developers page, then retry with the same Idempotency-Key. After password_refused, switch around-the-clock trading on again with the current password. |
trading_halted | 423 | JAFX has paused API trading. details.level is global, account or key; details.mode is off or close_only. | Wait until GET /me shows trading on. |
rate_limited | 429 | Too many requests per second for this key, or from your IP address across all keys (see Limits). | Wait for Retry-After. |
order_rate_limited | 429 | Too many orders per minute, for the key or for the account. | Wait for Retry-After. |
modification_rate_limited | 429 | Too many stop, target or price changes per minute. | Wait for Retry-After, and change stops less often. |
Requests
| Code | HTTP | Meaning | What to do |
|---|---|---|---|
invalid_request | 422 (400 for a body that is not JSON, 413 above 64 KB) | A parameter or field is missing or invalid; details lists them. | Fix the request. |
idempotency_key_required | 428 | A write was sent without an Idempotency-Key header. | Add one. |
idempotency_conflict | 409 | The key was already used for a different request. | Use a new key for a new order. |
request_in_progress | 409 | The first request with this key is still running. | Wait for Retry-After, then retry with the same key. |
request_already_failed | 409 | The broker already refused this request. | Send a corrected request with a new key. |
order_not_found | 404 | No open position (or pending order) with that ticket on this account. | Re-read positions or orders. |
request_not_found | 404 | No order request with that request_id for this account. | Check the ID. |
symbol_not_available | 404 | Unknown instrument. | See GET /symbols. |
not_found | 404 | No such endpoint. | Check the path. |
feature_disabled | 404 or 503 | The feature is not enabled on this server. | — |
Broker and service answers
| Code | HTTP | Meaning | What to do |
|---|---|---|---|
invalid_volume | 422 | The volume is not a multiple of the instrument's step, or is outside its minimum and maximum. | Check GET /symbols/{symbol}. |
invalid_stops | 422 | A stop loss or take profit is on the wrong side of the price or too close to it. | Move it further away. |
rejected_by_broker | 422 | The broker's routing refused the request. | See detail; do not retry unchanged. |
rejected | 422 | The broker decided against the order (failure says why). | See failure. |
order_busy | 409 | The position or order is being processed, for example covered at a liquidity provider, so the broker refused the request. | Wait for Retry-After, then send it again with a new Idempotency-Key: the old one keeps this refusal. |
broker_throttled | 429 | The broker asked JAFX to slow down. Nothing was kept of the request. | Wait for Retry-After, then retry with the same key. |
broker_unavailable | 503 | The connection to the broker is down or reconnecting, or account data and trading are not available yet for your account's server (GET /status shows each server under broker.servers). Nothing was sent. | Wait for Retry-After, then retry with the same key. |
broker_busy | 503 | Too many requests are waiting for the broker. Nothing was sent. | Wait for Retry-After, then retry with the same key. |
service_unavailable | 503 | The Trading API itself is restarting (for example for an update, usually a few seconds), cannot be reached, or did not answer in time. After a timeout, a write may have gone through. | Wait for Retry-After, then retry the same call. Retry a write with the same Idempotency-Key: if the first call got through, you get its original answer. |
internal_error | 500 | Something unexpected went wrong. | Retry later. If it persists, contact support with the request_id. |
Over MCP: the next line
An MCP tool that refuses returns the same problem document as the tool's error, with one more field, next: a sentence in plain words for the AI model, saying what to do next and whether that is the account owner's to do, such as signing in again, when the daily loss stop resets, how long to wait, or which tool to call. The MCP endpoint's own refusals (a wrong key, MCP off for the key, too many requests) carry it too. REST answers are unchanged and have no next.
{
"type": "https://terminal.jafx.com/trading-api/problems/daily_loss_limit_reached",
"title": "Daily loss limit reached",
"status": 403,
"code": "daily_loss_limit_reached",
"detail": "The daily loss limit for this key is reached; new positions are refused until the next broker day",
"details": { "day": "2026-11-13", "loss": "104.20", "limit": "100.00" },
"next": "Today's loss reached this key's daily loss stop: new positions are refused until the broker's next day starts at 2026-11-13T22:00:00.000Z (midnight in Athens). Closing positions, cancelling orders and changing stops still work. Tell the client."
}Programs should keep branching on code: next may be reworded.
Four codes come only from MCP tools, which have no REST route:
| Code | HTTP | Meaning | What to do |
|---|---|---|---|
help_centre_unavailable | 503 | search_help or read_help_article: JAFX's help centre didn't answer within a few seconds, or is busy. | Try once more after a short wait, or open jafx.com/help. |
help_article_not_found | 404 | read_help_article: the help centre has no article with that id. | Use an id search_help gave. |
feature_request_limit | 429 | request_feature: the account sent 5 feature requests in the last 24 hours, the most allowed. | Send it after Retry-After; list_feature_requests shows the ones sent. |
tool_not_found | 404 | The MCP server has no tool by the name called. | Use a tool tools/list gives; for something none of them does, request_feature. |
An MCP call whose inputs don't fit the tool (a value it doesn't take, such as an order type other than market, limit or stop) answers 422 invalid_request with the reason in detail, and its next line says to fix the inputs or, for something JAFX doesn't offer, to send a feature request.
Stream errors
On the WebSocket stream, problems arrive as {"op": "error", "code": …, "message": …} frames using the same codes, plus connection_replaced just before a 4409 close. See Streaming for the close codes.