Broker API HubMenu

A practice trading API you can call right now

19 addresses that answer a real HTTP request with JSON in the shape a trading API uses: an account, positions, a trade history in pages, orders, prices and the errors you will meet. No account, no key, nothing to install.

Independent and unofficialPractice dataNot any broker's API

Risk warning: CFDs and spread bets are complex instruments and come with a high risk of losing money rapidly due to leverage. You can lose money on these products, and each provider publishes its own loss figure under FCA rules. You should consider whether you understand how they work and whether you can afford to take the high risk of losing your money. Automated and API trading can amplify losses through bugs, connectivity failures and runaway orders.

Everything here is practice data. The account, the instruments and every price are invented, and the paths are this site's own. It is not any broker's API, it is not connected to any market, and it cannot place a trade. No address here asks for a key, a username or a password, and you should never send one.

Your first request in two minutes

  1. Open a terminal. On Windows that is PowerShell or Command Prompt, on a Mac it is Terminal.
  2. Type this line and press Enter. On Windows PowerShell, type curl.exe in place of curl.
    curl https://openbrokerapi.com/mock/v1/account.json
  3. Read what comes back. It is JSON: names and values. Find balance. Find practice, which is true in every reply from this API, so you can always tell practice data from the real thing.
  4. That was an HTTP request. Your terminal sent the word GET and an address, and a server sent back a status of 200, which means it worked, and a body, which is the JSON you are reading.
  5. Now try another address from the list below, or do the same from Python in the lesson on your first HTTP request.

No terminal to hand? Press a Try it button below and your browser makes the same request.

Every address

The base address is https://openbrokerapi.com/mock/v1. Every address answers GET and nothing else. Each file opens in a browser tab too: follow the path.

Account

Account summary

GET /mock/v1/account.json

One invented account: its balance, running profit or loss, equity, margin in use and what is available.

curl

curl https://openbrokerapi.com/mock/v1/account.json
Python
import json
import urllib.request

with urllib.request.urlopen("https://openbrokerapi.com/mock/v1/account.json", timeout=10) as reply:
    print(reply.status)
    data = json.load(reply)

print(json.dumps(data, indent=2))

Open positions

GET /mock/v1/positions.json

Two invented open positions, one BUY and one SELL, each with its size, opening level, current bid and offer, and running profit or loss.

curl

curl https://openbrokerapi.com/mock/v1/positions.json
Python
import json
import urllib.request

with urllib.request.urlopen("https://openbrokerapi.com/mock/v1/positions.json", timeout=10) as reply:
    print(reply.status)
    data = json.load(reply)

print(json.dumps(data, indent=2))

Trade history

A history too long for one reply arrives in pages. Here each page is a file of its own, and its paging.next field holds the path of the page after it, or null on the last one. Start at page 1 and follow next until it is null.

Closed trades, page 1

GET /mock/v1/trades/page-1.json

The five most recent of twelve invented closed trades, newest first, with a paging note. Its next field holds the path of page 2.

curl

curl https://openbrokerapi.com/mock/v1/trades/page-1.json
Python
import json
import urllib.request

with urllib.request.urlopen("https://openbrokerapi.com/mock/v1/trades/page-1.json", timeout=10) as reply:
    print(reply.status)
    data = json.load(reply)

print(json.dumps(data, indent=2))

Closed trades, page 2

GET /mock/v1/trades/page-2.json

The next five closed trades. Its next field holds the path of page 3.

curl

curl https://openbrokerapi.com/mock/v1/trades/page-2.json
Python
import json
import urllib.request

with urllib.request.urlopen("https://openbrokerapi.com/mock/v1/trades/page-2.json", timeout=10) as reply:
    print(reply.status)
    data = json.load(reply)

print(json.dumps(data, indent=2))

Closed trades, page 3

GET /mock/v1/trades/page-3.json

The last two closed trades. Its next field is null, which is how a program knows to stop asking.

curl

curl https://openbrokerapi.com/mock/v1/trades/page-3.json
Python
import json
import urllib.request

with urllib.request.urlopen("https://openbrokerapi.com/mock/v1/trades/page-3.json", timeout=10) as reply:
    print(reply.status)
    data = json.load(reply)

print(json.dumps(data, indent=2))

Orders

Open orders

GET /mock/v1/orders.json

Two invented working orders that have not been filled: a limit order and a stop order.

curl

curl https://openbrokerapi.com/mock/v1/orders.json
Python
import json
import urllib.request

with urllib.request.urlopen("https://openbrokerapi.com/mock/v1/orders.json", timeout=10) as reply:
    print(reply.status)
    data = json.load(reply)

print(json.dumps(data, indent=2))

An order confirmation: accepted

GET /mock/v1/confirms/my-order-0001.json

What a program reads back after sending an order, here with a status of ACCEPTED and the order's id. No order was sent and reading it changes nothing.

curl

curl https://openbrokerapi.com/mock/v1/confirms/my-order-0001.json
Python
import json
import urllib.request

with urllib.request.urlopen("https://openbrokerapi.com/mock/v1/confirms/my-order-0001.json", timeout=10) as reply:
    print(reply.status)
    data = json.load(reply)

print(json.dumps(data, indent=2))

An order confirmation: rejected

GET /mock/v1/confirms/my-order-0002.json

A confirmation with a status of REJECTED and the reason in its body. No order was sent and reading it changes nothing.

curl

curl https://openbrokerapi.com/mock/v1/confirms/my-order-0002.json
Python
import json
import urllib.request

with urllib.request.urlopen("https://openbrokerapi.com/mock/v1/confirms/my-order-0002.json", timeout=10) as reply:
    print(reply.status)
    data = json.load(reply)

print(json.dumps(data, indent=2))

Market data

Instrument search

GET /mock/v1/instruments.json

The four invented instruments. A real search filters by what you ask for. This address cannot, so it always returns all four.

curl

curl https://openbrokerapi.com/mock/v1/instruments.json
Python
import json
import urllib.request

with urllib.request.urlopen("https://openbrokerapi.com/mock/v1/instruments.json", timeout=10) as reply:
    print(reply.status)
    data = json.load(reply)

print(json.dumps(data, indent=2))

Price snapshot

GET /mock/v1/prices/HBR100.json

One bid and offer for the invented index HBR100, with the day's high, low and change. It never moves.

curl

curl https://openbrokerapi.com/mock/v1/prices/HBR100.json
Python
import json
import urllib.request

with urllib.request.urlopen("https://openbrokerapi.com/mock/v1/prices/HBR100.json", timeout=10) as reply:
    print(reply.status)
    data = json.load(reply)

print(json.dumps(data, indent=2))

Price history

GET /mock/v1/history/HBR100.json

Ten invented daily candles for HBR100, oldest first: open, high, low and close.

curl

curl https://openbrokerapi.com/mock/v1/history/HBR100.json
Python
import json
import urllib.request

with urllib.request.urlopen("https://openbrokerapi.com/mock/v1/history/HBR100.json", timeout=10) as reply:
    print(reply.status)
    data = json.load(reply)

print(json.dumps(data, indent=2))

Price snapshot, second instrument

GET /mock/v1/prices/LNTN.json

One bid and offer for the invented share LNTN, with the day's high, low and change. It never moves.

curl

curl https://openbrokerapi.com/mock/v1/prices/LNTN.json
Python
import json
import urllib.request

with urllib.request.urlopen("https://openbrokerapi.com/mock/v1/prices/LNTN.json", timeout=10) as reply:
    print(reply.status)
    data = json.load(reply)

print(json.dumps(data, indent=2))

Price history, second instrument

GET /mock/v1/history/LNTN.json

Ten invented daily candles for LNTN, oldest first, on the same ten dates as HBR100.

curl

curl https://openbrokerapi.com/mock/v1/history/LNTN.json
Python
import json
import urllib.request

with urllib.request.urlopen("https://openbrokerapi.com/mock/v1/history/LNTN.json", timeout=10) as reply:
    print(reply.status)
    data = json.load(reply)

print(json.dumps(data, indent=2))

Price snapshot, third instrument

GET /mock/v1/prices/TIDE.json

One bid and offer for the invented commodity TIDE, with the day's high, low and change. It never moves.

curl

curl https://openbrokerapi.com/mock/v1/prices/TIDE.json
Python
import json
import urllib.request

with urllib.request.urlopen("https://openbrokerapi.com/mock/v1/prices/TIDE.json", timeout=10) as reply:
    print(reply.status)
    data = json.load(reply)

print(json.dumps(data, indent=2))

Price history, third instrument

GET /mock/v1/history/TIDE.json

Ten invented daily candles for TIDE, oldest first, on the same ten dates as HBR100.

curl

curl https://openbrokerapi.com/mock/v1/history/TIDE.json
Python
import json
import urllib.request

with urllib.request.urlopen("https://openbrokerapi.com/mock/v1/history/TIDE.json", timeout=10) as reply:
    print(reply.status)
    data = json.load(reply)

print(json.dumps(data, indent=2))

Example errors

These 4 are bodies to read and to write code against. Each is served with status 200, because a host of fixed files cannot choose a status for each request. The status a real server would send is in the body, in error.status.

An expired session

GET /mock/v1/errors/session-expired.jsonServed as 200. The body says 401

The body a server sends when a session has run out and you must sign in again.

curl

curl https://openbrokerapi.com/mock/v1/errors/session-expired.json
Python
import json
import urllib.request

with urllib.request.urlopen("https://openbrokerapi.com/mock/v1/errors/session-expired.json", timeout=10) as reply:
    print(reply.status)
    data = json.load(reply)

print(json.dumps(data, indent=2))

A rate limit refusal

GET /mock/v1/errors/rate-limited.jsonServed as 200. The body says 429

The body a server sends when you have asked too often, with a retryAfterSeconds field and the Retry-After header it would carry.

curl

curl https://openbrokerapi.com/mock/v1/errors/rate-limited.json
Python
import json
import urllib.request

with urllib.request.urlopen("https://openbrokerapi.com/mock/v1/errors/rate-limited.json", timeout=10) as reply:
    print(reply.status)
    data = json.load(reply)

print(json.dumps(data, indent=2))

A rejected order

GET /mock/v1/errors/order-rejected.jsonServed as 200. The body says 422

The body a server sends when it understood an order and refused it, here for lack of funds.

curl

curl https://openbrokerapi.com/mock/v1/errors/order-rejected.json
Python
import json
import urllib.request

with urllib.request.urlopen("https://openbrokerapi.com/mock/v1/errors/order-rejected.json", timeout=10) as reply:
    print(reply.status)
    data = json.load(reply)

print(json.dumps(data, indent=2))

A validation error

GET /mock/v1/errors/validation.jsonServed as 200. The body says 400

The body a server sends when a request is malformed, naming each field to correct.

curl

curl https://openbrokerapi.com/mock/v1/errors/validation.json
Python
import json
import urllib.request

with urllib.request.urlopen("https://openbrokerapi.com/mock/v1/errors/validation.json", timeout=10) as reply:
    print(reply.status)
    data = json.load(reply)

print(json.dumps(data, indent=2))

What it cannot do, and why

The practice API is a folder of files. There is no program behind it. That keeps it free, fast and safe to hammer while you learn, and it sets hard limits that a real API does not have.

Calls that change something: examples to read

None of these can be sent. They are pairs: what a program would send, and what a server would send back. They use the same invented account as the files above.

Place an order

POST /mock/v1/ordersExample only. Not a live address

The clientReference is a name you choose for the order. Send the same one twice and a careful server places one order, not two.

The request body

{
  "instrumentId": "TIDE",
  "direction": "BUY",
  "type": "LIMIT",
  "size": 3,
  "level": 61.5,
  "stopLevel": 59,
  "limitLevel": 66,
  "clientReference": "my-order-0001"
}

The reply a server would send, with status 201

{
  "practice": true,
  "order": {
    "orderId": "O-2001",
    "clientReference": "my-order-0001",
    "instrumentId": "TIDE",
    "direction": "BUY",
    "type": "LIMIT",
    "size": 3,
    "level": 61.5,
    "status": "WORKING",
    "createdAt": "2026-01-02T16:10:00Z"
  }
}

Cancel an open order

DELETE /mock/v1/orders/O-2001Example only. Not a live address

A cancel carries no body. The order is named in the path.

The request body

No body.

The reply a server would send, with status 200

{
  "practice": true,
  "order": {
    "orderId": "O-2001",
    "status": "CANCELLED",
    "cancelledAt": "2026-01-05T09:31:00Z"
  }
}

Close a position

POST /mock/v1/positions/P-1001/closeExample only. Not a live address

Closing a BUY sells at the bid, which is why the close level is the bid from the positions file.

The request body

{
  "size": 2
}

The reply a server would send, with status 200

{
  "practice": true,
  "position": {
    "positionId": "P-1001",
    "status": "CLOSED",
    "size": 2,
    "closeLevel": 5057,
    "profitLoss": 74,
    "closedAt": "2026-01-05T09:32:00Z"
  }
}

Using it from your own code

Call it from a script, a notebook or a web page. The files are served with a header that lets a page on any other site read them, so a request from JavaScript in a browser works. Please pause between calls as you would with a real API: it is a good habit, and the practice costs nothing.

To load every address into an API tool such as Postman, Insomnia, Bruno or Swagger UI, import /mock/v1/openapi.json. It is an OpenAPI 3.1 file made from the same list of files as this page, with each file as the example reply. Like the files, it is invented practice data and describes no broker's API.

Check practice in the reply before you trust a number. Code that has only ever run against this API has not been tested against a broker. Move to a broker's demo account next, never straight to a live one.