Quickstart
Create a key, read a price, then open and close a demo position. About five minutes.
Before you start
- A JAFX demo account you can log into at https://terminal.jafx.com (if you have none, open one in the JAFX client area, under Accounts).
curl, Python 3.9 or later (withhttpx), or Node.js 20 or later.
1. Create an API key
- Log into the web terminal with your demo account, choosing Demo Server at sign-in.
- Open Developers in the side menu and choose Create API key.
- Pick the Trading preset, give the key a name and review its limits.
- Under Only allow these IP addresses, enter the address your program calls from (recommended: a locked key can't be used anywhere else, even if it leaks). To try it from a laptop on a changing address, tick Let this key be used from any IP address instead.
- For a bot that runs for days, tick Keep my bots trading around the clock (see Around the clock). Without it, your keys trade for 24 hours after each sign-in.
- Confirm with your account's master password. This fresh login proves the request comes from you; your terminal session stays as it is.
- Copy the key now. It is shown once and looks like
jafx_demo_….
Keep the key out of your source code. An environment variable is the simplest place:
export JAFX_API_KEY='jafx_demo_…'2. Check the key
GET /me returns the key's account and its server, scopes, limits and whether trading is currently allowed.
curl https://terminal.jafx.com/trading-api/v1/me -H "Authorization: Bearer $JAFX_API_KEY"import os
import httpx
api = httpx.Client(
base_url="https://terminal.jafx.com/trading-api/v1",
headers={"Authorization": f"Bearer {os.environ['JAFX_API_KEY']}"},
timeout=20,
)
me = api.get("/me").json()
print(me["account"], me["scopes"], me["constraints"]["max_volume_per_order"])const BASE = 'https://terminal.jafx.com/trading-api/v1'
const headers = { Authorization: `Bearer ${process.env.JAFX_API_KEY}` }
const me = await (await fetch(`${BASE}/me`, { headers })).json()
console.log(me.account, me.scopes, me.constraints.max_volume_per_order){
"key_id": "k7mx2v9qr4tz8w1b6nfa",
"name": "my first bot",
"account": 123456,
"server": "demo",
"mode": "demo",
"scopes": ["market:read", "account:read", "trade"],
"constraints": {
"max_volume_per_order": "1.00",
"max_open_positions": 20,
"orders_per_minute": 30,
"daily_loss_limit": { "type": "percent", "value": "10" }
},
"trading": { "mode": "on", "level": "env", "reason": null }
}Note the account number: the account and order endpoints use it.
3. Read a price
curl "https://terminal.jafx.com/trading-api/v1/quotes?symbols=EURUSD,XAUUSD" -H "Authorization: Bearer $JAFX_API_KEY"quotes = api.get("/quotes", params={"symbols": "EURUSD,XAUUSD"}).json()
for q in quotes["data"]:
print(q["symbol"], q["bid"], q["ask"])const quotes = await (await fetch(`${BASE}/quotes?symbols=EURUSD,XAUUSD`, { headers })).json()
for (const q of quotes.data) console.log(q.symbol, q.bid, q.ask){
"data": [
{ "symbol": "EURUSD", "bid": 1.11554, "ask": 1.11555, "time": "2026-10-13T09:30:01.242Z", "price_basis": "raw" },
{ "symbol": "XAUUSD", "bid": 4170.05, "ask": 4170.72, "time": "2026-10-13T09:30:01.834Z", "price_basis": "raw" }
],
"missing": []
}4. Place a demo order
Buy 0.01 lot of EURUSD at market. Every write needs an Idempotency-Key header: a unique value for this order, such as a UUID. If the connection drops, send the same request with the same key again and you will get the original answer instead of a second trade.
export ACCOUNT=123456 # from GET /me
curl -X POST https://terminal.jafx.com/trading-api/v1/accounts/$ACCOUNT/orders \
-H "Authorization: Bearer $JAFX_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"symbol":"EURUSD","side":"buy","type":"market","volume":"0.01","client_tag":"quickstart"}'import uuid
order = {"symbol": "EURUSD", "side": "buy", "type": "market", "volume": "0.01", "client_tag": "quickstart"}
r = api.post(f"/accounts/{me['account']}/orders", json=order,
headers={"Idempotency-Key": str(uuid.uuid4())})
result = r.json()
print(r.status_code, result["status"], result.get("ticket"))import { randomUUID } from 'node:crypto'
const order = { symbol: 'EURUSD', side: 'buy', type: 'market', volume: '0.01', client_tag: 'quickstart' }
const res = await fetch(`${BASE}/accounts/${me.account}/orders`, {
method: 'POST',
headers: { ...headers, 'Content-Type': 'application/json', 'Idempotency-Key': randomUUID() },
body: JSON.stringify(order),
})
const result = await res.json()
console.log(res.status, result.status, result.ticket){
"request_id": "01JQ8Z6M3S4X7T2N9CKV5W0HRB",
"kind": "market",
"status": "filled",
"ticket": 5001,
"client_tag": "quickstart",
"position": { "ticket": 5001, "symbol": "EURUSD", "side": "buy", "volume": "0.01", "open_price": 1.11555, "profit": "-0.01" }
}The broker is still deciding, for example while it covers the trade with a liquidity provider. The body says "status": "pending". Poll GET /accounts/{account}/requests/{request_id} or watch the orders stream; do not send the order again under a new key. See what each answer means.
No uuidgen? Use python3 -c 'import uuid; print(uuid.uuid4())' or any other unique string of up to 64 characters from A–Z a–z 0–9 . _ : -.
5. See the position
curl "https://terminal.jafx.com/trading-api/v1/accounts/$ACCOUNT/positions?client_tag=quickstart" -H "Authorization: Bearer $JAFX_API_KEY"positions = api.get(f"/accounts/{me['account']}/positions", params={"client_tag": "quickstart"}).json()["data"]
print([(p["ticket"], p["volume"], p["profit"]) for p in positions])const positions = (await (await fetch(`${BASE}/accounts/${me.account}/positions?client_tag=quickstart`, { headers })).json()).data
console.log(positions.map((p: any) => [p.ticket, p.volume, p.profit]))6. Close it
Close the whole position with an empty body, or part of it with {"volume": "0.01"}.
curl -X POST https://terminal.jafx.com/trading-api/v1/accounts/$ACCOUNT/positions/5001/close \
-H "Authorization: Bearer $JAFX_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" -d '{}'ticket = positions[0]["ticket"]
r = api.post(f"/accounts/{me['account']}/positions/{ticket}/close", json={},
headers={"Idempotency-Key": str(uuid.uuid4())})
print(r.status_code, r.json()["status"])const ticket = positions[0].ticket
const closed = await fetch(`${BASE}/accounts/${me.account}/positions/${ticket}/close`, {
method: 'POST',
headers: { ...headers, 'Content-Type': 'application/json', 'Idempotency-Key': randomUUID() },
body: '{}',
})
console.log(closed.status, (await closed.json()).status)7. Watch it live
The WebSocket stream pushes quotes, candles, account changes and order events. A minimal Python listener (pip install websockets):
import asyncio, json, os
import websockets
async def main():
headers = {"Authorization": f"Bearer {os.environ['JAFX_API_KEY']}"}
async with websockets.connect("wss://terminal.jafx.com/trading-api/v1/stream", additional_headers=headers) as ws:
await ws.send(json.dumps({"op": "subscribe", "channels": ["quotes:EURUSD", "orders"]}))
async for raw in ws:
print(json.loads(raw))
asyncio.run(main())Next steps
- Orders & positions: pending orders, stops, partial closes, tags and every possible answer.
- Streaming: channels, reconnecting without missing events, and limits.
- Example bots: complete Python and TypeScript bots to start from.
- AI agents (MCP): let Cursor, Claude or your own agent use your account.