Trading API
For programs that trade your OGuard account
The trading API gives your own software the same trading your OGuard terminal has, on three products: perpetuals, tokenized stocks and CFDs. Place, amend and cancel orders, manage positions, read your account, and stream prices and fills. Requests are signed with an API key you create on your account. The key can never move funds. A ready-made Python client handles the signing, the clock and safe retries for you.
Connect
| REST API | https://api.oguard.io |
|---|---|
| Market stream (no key) | wss://api.oguard.io/ws/market |
| Account stream (perpetuals, stocks) | wss://api.oguard.io/ws/history?ticket=… |
| CFD stream | wss://api.oguard.io/ws/cfd?ticket=… |
| Python client (one file) | https://oguard.io/developers/oguard_client.py · Python 3.9 or newer |
| OpenAPI contract | https://api.oguard.io/api/v1/public/openapi.json |
Download the Python client or the OpenAPI contract to generate a client in another language, or import it into Postman or Insomnia to try every endpoint. Everything is served over HTTPS only.
Three products
Each product has its own address and its own methods in the Python client. A symbol from another product is refused (400 wrong_product), and another product’s order id answers 404 order_not_found, so an order can never land on the wrong market.
| Product | Address | Symbols | Python |
|---|---|---|---|
| Perpetuals | /api/v1/perps | USDT perpetuals, ending in .P: BTCUSDT.P | og.perps |
| Tokenized stocks | /api/v1/stocks | Stock tokens against USDT: NVDAXUSDT | og.stocks |
| CFDs | /api/v1/cfd | FX, metals, indices, energy: EURUSD | og.cfd |
| Account | /api/v1/account | Balances, transactions, wallet reads | og |
- Perpetuals and tokenized stocks trade from the same balance,
GET /api/v1/account/balances. - CFDs trade from their own wallet,
GET /api/v1/cfd/account. Move money into it on the website: a key never moves funds.
Prices and quantities are decimal strings such as "0.01" or "65000.5": never ".01", "1e-2" or a JSON number. They are never rounded for you; one that breaks the instrument’s tick or lot size is refused.
clientOrderId makes placement safe to retry, on every product
Give each order your own id: 1 to 30 of A-Z a-z 0-9 _ -. A resend with an id you already used returns the order placed under it, with deduplicated: true, and nothing is placed twice. If a placement times out, look it up by your id before deciding anything. The id also works in place of the order id to read, amend, cancel or close. Use a new id for every order: reusing a perpetual’s id on tokenized stocks, or the other way round, answers 409 client_order_id_in_use, and so does reusing an id for a different order (another symbol, side or order type).
Quick start in Python
- Create a key on Account → Developer → API keys. Tick Allow trading if the program will place orders. Copy the secret: it is shown once.
- Download oguard_client.py next to your program. REST needs nothing else. For streams, run
pip install websockets. - Check the key and your clock from a terminal. On Windows (PowerShell):
$env:OGUARD_API_KEY = "ogk_..."
$env:OGUARD_API_SECRET = "ogs_..."
python oguard_client.py doctorOn macOS or Linux:
export OGUARD_API_KEY="ogk_..."
export OGUARD_API_SECRET="ogs_..."
python3 oguard_client.py doctorIt prints how far your computer’s clock is from ours, the number of listed instruments, and whether the key is accepted. Then trade:
import os
from decimal import Decimal
from oguard_client import OGuardClient, OGuardAPIError
og = OGuardClient(os.environ["OGUARD_API_KEY"], os.environ["OGUARD_API_SECRET"])
print(og.balances())
print(og.perps.tickers(["BTCUSDT.P"]))
# Prices and quantities are str, int or Decimal, never float.
# Each order gets a clientOrderId; pass client_order_id="..." to use your own
# (unique per account: resending one returns the order already placed under it).
ack = og.perps.place_order("BTCUSDT.P", "buy", "limit", Decimal("0.001"), price="50000")
print(ack) # {'ok': True, 'orderId': 'OG-...', 'clientOrderId': 'py-...'}
order = og.perps.order(ack["orderId"])["order"] # waits briefly while the new order is recorded
og.perps.amend_order(ack["orderId"], price="49900")
og.perps.cancel_order(ack["orderId"])
try:
og.perps.cancel_order(ack["orderId"]) # already cancelled above
except OGuardAPIError as err:
print(err.status, err.code) # every refusal carries an HTTP status and an error codeWhat the client does for you
- Signing. Every request is signed with your key, fresh each time.
- Clock. It signs with the server’s time, so a PC clock that has drifted does not get requests refused.
- Safe retries. Reads and order placements are retried on network errors, 5xx and 429 (after
Retry-After). Placements carry aclientOrderId, so a retry can never place a second order. Cancels, amends and leverage changes are not retried. - Exact numbers. Prices and quantities are
str,intorDecimal. Afloatis refused before anything is sent, because most prices have no exact float. Responses parse decimals asDecimal. - Reads after writes. An order can be read, amended or cancelled as soon as its placement returns. Fills reach positions within about a second:
og.perps.position(symbol, side, wait=5)waits for one, and the account stream shows it first. - Connections. One client is safe to share across threads and keeps its connections open between calls.
API keys
- Created only on your account page, never through the API, and only after your authenticator code (or an emailed code if you have no authenticator).
- Read keys can only read. Trading keys can also place and manage orders, positions and leverage on every product. No key can withdraw, transfer or deposit.
- IP allowlist (optional): up to 50 addresses or ranges. Recommended for any key that trades.
- Expiry (optional): 1 to 3650 days.
- Up to 10 active keys. Revoking one stops it at once and closes any stream it opened.
Two kinds of signing. HMAC: we generate a secret, shown once. Ed25519: you generate a key pair, keep the private key, and paste only the public key on the key page, so the secret never leaves your machine. The Python client generates the pair:
pip install cryptography
python oguard_client.py keygen # prints a private key (keep it) and a public key (paste it on the API keys page)og = OGuardClient("ogk_...", private_key=open("oguard_ed25519.pem").read())Signing requests yourself
Skip this section if you use the Python client. Every signed request carries five headers:
| Header | Value |
|---|---|
| X-OG-API-KEY | Your key id, ogk_… |
| X-OG-TIMESTAMP | Current time in Unix milliseconds |
| X-OG-RECV-WINDOW | How long the signature stays valid, in ms: default 5000, at most 60000 |
| X-OG-SIGN-TYPE | hmac or ed25519 |
| X-OG-SIGN | The signature: lowercase hex for HMAC-SHA256, base64 for Ed25519 |
The signed string is timestamp + apiKey + recvWindow + payload with no separators. The payload is the query string exactly as sent (without ?) for GET and DELETE, and the exact JSON body bytes for POST and PATCH. Serialize the body once and send the string you signed.
- A timestamp more than 1 s ahead of the server, or older than your recvWindow, is refused. Read the server clock from
GET /api/v1/timeif your clock may drift. - A signed write is accepted once. Resending the same signed bytes returns
request_replayed, so sign every attempt again. Two identical bodies signed in the same millisecond have the same signature, so give every order its ownclientOrderId.
import hashlib, hmac, json, time, urllib.request
API = "https://api.oguard.io"
KEY, SECRET = "ogk_...", "ogs_..."
def signed(method, path, body=None, query=""):
payload = json.dumps(body, separators=(",", ":")) if body is not None else query
ts, window = str(int(time.time() * 1000)), "5000"
sig = hmac.new(SECRET.encode(), (ts + KEY + window + payload).encode(), hashlib.sha256).hexdigest()
req = urllib.request.Request(API + path + ("?" + query if query else ""), method=method,
data=payload.encode() if body is not None else None)
for name, value in {"X-OG-API-KEY": KEY, "X-OG-TIMESTAMP": ts, "X-OG-RECV-WINDOW": window,
"X-OG-SIGN-TYPE": "hmac", "X-OG-SIGN": sig,
"Content-Type": "application/json"}.items():
req.add_header(name, value)
with urllib.request.urlopen(req, timeout=10) as r:
return json.loads(r.read())
print(signed("GET", "/api/v1/account/balances"))
print(signed("POST", "/api/v1/perps/orders", {"symbol": "BTCUSDT.P", "side": "buy", "type": "limit",
"qty": "0.001", "price": "50000",
"clientOrderId": f"hand-{int(time.time() * 1000)}"}))TS=$(($(date +%s%N)/1000000)); BODY='{"symbol":"BTCUSDT.P","side":"buy","type":"limit","qty":"0.001","price":"50000"}'
SIG=$(printf '%s' "${TS}${KEY}5000${BODY}" | openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.* //')
curl -sS -X POST "https://api.oguard.io/api/v1/perps/orders" -H "Content-Type: application/json" \
-H "X-OG-API-KEY: $KEY" -H "X-OG-TIMESTAMP: $TS" -H "X-OG-RECV-WINDOW: 5000" \
-H "X-OG-SIGN-TYPE: hmac" -H "X-OG-SIGN: $SIG" --data-raw "$BODY"Perpetuals
List instruments with GET /api/v1/perps/markets. Accounts trade in hedge mode: each symbol has a long side (positionIdx 1) and a short side (2).
| Field | Meaning |
|---|---|
| symbol, side, type, qty | Required. side buy or sell; type market or limit |
| price | Required for limit orders |
| tif | gtc, ioc, fok or postOnly (limit orders) |
| positionIdx | 1 long side, 2 short side |
| reduceOnly | Only closes: a reduce-only sell on side 1 closes a long, a buy on side 2 a short |
| triggerPrice, triggerBy, triggerDirection | Makes the order conditional. triggerBy last, mark or index; triggerDirection 1 = rises to, 2 = falls to |
| takeProfit, stopLoss | Attached to the position the order opens |
| leverage | A JSON number, applied to the symbol before the order is sent |
| clientOrderId | Your id for the order |
An order is recorded the moment the market accepts it, so you can read, amend or cancel it with the id (or your clientOrderId) as soon as you have the response. Fills, positions and balances follow the market within about a second; the account stream shows them first.
# Hedge mode: position_idx 1 is the long side, 2 the short side.
og.perps.set_leverage("BTCUSDT.P", 5)
og.perps.place_order("BTCUSDT.P", "buy", "market", "0.01", position_idx=1)
pos = og.perps.position("BTCUSDT.P", "buy", wait=5) # the fill takes a moment to be recorded
og.perps.set_tpsl(pos["id"], "entire", take_profit_pct="2.5", stop_loss_pct="1")
# Close the long: a reduce-only sell on the long side.
og.perps.place_order("BTCUSDT.P", "sell", "market", "0.01", reduce_only=True, position_idx=1)import time
from oguard_client import OGuardNetworkError
cid = f"grid-{int(time.time() * 1000)}" # your id: 1-30 of A-Z a-z 0-9 _ -, unique per account
try:
ack = og.perps.place_order("BTCUSDT.P", "buy", "limit", "0.001", price="50000", client_order_id=cid)
except OGuardNetworkError as err:
# No answer came back. The order may still have been placed: ask by your id.
# This waits up to 2 s for it to be recorded; OGuardAPIError 404 then means it was not placed.
order = og.perps.order_by_client_id(err.client_order_id)Tokenized stocks
Stock tokens are bought and sold outright from the trading balance, without leverage; what you own is in GET /api/v1/stocks/holdings. List instruments with GET /api/v1/stocks/markets.
| Field | Meaning |
|---|---|
| symbol, side, orderType, qty | Required. side buy or sell; orderType market or limit |
| marketUnit | Required. baseCoin: qty in the stock token; quoteCoin: qty in USDT (market buys only). A limit order and a market sell use baseCoin |
| price | Required for limit orders |
| conditional | { "triggerPrice": "…", "mode": "market" | "limit" }: the order is placed when the last price reaches the trigger |
| clientOrderId | Your id for the order |
A sell is limited to what you hold.
print([s["symbol"] for s in og.stocks.markets()["symbols"]]) # NVDAXUSDT, AAPLXUSDT, ...
# Buy 25 USDT of Nvidia at market: market_unit="quoteCoin" sizes a market buy in USDT.
og.stocks.place_order("NVDAXUSDT", "buy", "market", "25", market_unit="quoteCoin")
# A limit order's qty is always in the stock token.
ack = og.stocks.place_order("NVDAXUSDT", "buy", "limit", "0.5", price="100")
og.stocks.cancel_order(ack["orderId"])
print(og.stocks.holdings()) # what you own
print(og.stocks.order_history(days=7))CFDs
FX, metals, indices and energy, traded on margin from the CFD wallet. List instruments, with their digits, lot sizes and margin, with GET /api/v1/cfd/instruments. Sizes are in lots; order ids are numbers. A CFD read comes straight from the book the order was written to, so there is no delay between placing an order and reading it back.
| Field | Meaning |
|---|---|
| symbol, side | Required. side buy or sell |
| orderType | market (default) opens a position now; limit, stop and stop_limit rest until entryPrice is reached |
| volume or value | Size in lots, or as a position value in account currency; one of the two |
| entryPrice | Required for a pending order |
| stopLimitPrice | The limit a stop_limit rests at once its stop is reached |
| takeProfit, stopLoss | Protective levels |
| trailingStopPoints, trailingStepPoints | A trailing stop, in whole points |
| expiresAt | Unix ms at which a pending order is cancelled; omit for good-till-cancelled |
| clientOrderId | Your id for the order |
POST /api/v1/cfd/orders/{id}/closecloses a position, orvolumelots of it;…/orders/by-client-id/{clientOrderId}/closedoes the same by your id. It is not retried for you: a repeated partial close would close more.PATCH /api/v1/cfd/orders/{id}/protectionsets levels: a field you omit is kept, andnullclears it.- Orders are refused while the market is shut (
market_closed) or when there is no current price (no_fresh_price); fills are at the live bid or ask.
import time
from decimal import Decimal
symbols = [i["symbol"] for i in og.cfd.instruments()["instruments"]]
eurusd = next(s for s in symbols if s.startswith("EURUSD"))
print(og.cfd.account()) # the CFD wallet: balance, equity, free margin, margin level
print(og.cfd.preview(eurusd, "buy", "0.10")) # margin the order would take
quote = og.cfd.quotes([eurusd])["quotes"][0] # prices are strings at the instrument's digits
bid, ask = Decimal(quote["bid"]), Decimal(quote["ask"])
# Open 0.10 lots at market with a stop 200 pips away. Idempotent, like every placement.
ack = og.cfd.place_order(eurusd, "buy", "market", "0.10", stop_loss=bid - Decimal("0.02"))
order_id = ack["order"]["id"]
og.cfd.set_protection(order_id, take_profit=ask + Decimal("0.02")) # the stop-loss is kept
og.cfd.close(order_id, volume="0.05") # close half
og.cfd.close(order_id) # and the rest
# A pending order: a buy limit below the market, good for a day.
pending = og.cfd.place_order(eurusd, "buy", "limit", "0.10", entry_price=bid - Decimal("0.03"),
expires_at=int(time.time() * 1000) + 86_400_000)
og.cfd.modify_order(pending["order"]["id"], entry_price=bid - Decimal("0.025"))
og.cfd.cancel_order(pending["order"]["id"])If your program stops: cancel-all-after
Arm the dead-man’s switch with a timeout and re-arm it as a heartbeat. If the heartbeat stops for longer than the timeout (your program crashed, hung or lost its network), every open order on every product is cancelled within a second. Positions are left as they are.
POST /api/v1/account/cancel-all-afterwith{"timeoutMs": 15000}arms or re-arms it;0disarms. The timeout is 5 to 600 seconds.- Send the heartbeat well inside the timeout, for example every 5 s for a 15 s timeout.
- Every product also has its own cancel-all:
/perps/orders/cancel-all,/stocks/orders/cancel-alland/cfd/orders/cancel-all.
from oguard_client import CancelAllAfterHeartbeat
# While the block runs, a background thread re-arms the switch every 5 s. If the
# process dies or hangs, every open order is cancelled within 15 s. A clean exit
# disarms it.
with CancelAllAfterHeartbeat(og, timeout_ms=15_000, every_s=5):
book = og.perps.orderbook("BTCUSDT.P", limit=5)
best_bid = book["bids"][0]["price"]
og.perps.place_order("BTCUSDT.P", "buy", "limit", "0.001", price=best_bid, client_order_id="mm-bid-1")
og.perps.cancel_order_by_client_id("mm-bid-1")
print(og.cancel_all_after_state()) # {'enabled': False, ...} after the blockRate limits
Limits apply per account, summed across all of the account’s keys. The API keys page shows your current usage.
| Budget | Default |
|---|---|
| Request weight | 1,200 per rolling minute |
| Order actions | 50 per 10 seconds |
| Order actions | 300 per minute |
A request weighs 1 unless listed below. Order actions, on every product, are placements, amends, cancels, cancel-all, reverses, closes and take-profit / stop-loss changes.
| Route | Weight |
|---|---|
| POST / | 5 |
| POST / | 3 |
| GET / | 3 |
| GET / | 3 |
| GET / | 2 |
| GET / | 2 |
| GET / | 2 |
| GET / | 2 |
| GET / | 2 |
| GET / | 2 |
| GET / | 2 |
| GET / | 2 |
| GET / | 2 |
| GET / | 2 |
| POST / | 5 |
| POST / | 5 |
Every response reports usage in X-OG-Used-Weight-1m, X-OG-Order-Count-10s and X-OG-Order-Count-1m, next to the matching X-OG-Limit-… header. The Python client keeps the latest values in client.last_rate_limit.
Two kinds of 429
rate_limited: your budget is spent. Wait theRetry-Afterseconds; that is the exact time until your next request fits. Refused requests count too, so retrying early only makes the wait longer.upstream_busy: the shared connection to the market is saturated. Nothing was sent and nothing was charged; retry after a second. Perpetuals and tokenized stocks only: CFDs never answer it.
Streams
Market data, wss://api.oguard.io/ws/market, needs no key. Send {"op":"sub","symbol":"BTCUSDT.P"} for tickers, or add "ch" with kline (and an interval), depth or trade. Send {"op":"ping"} every 20 seconds.
Your perpetuals and tokenized stocks, wss://api.oguard.io/ws/history?ticket=…, streams your orders, fills, positions, balances and funding. Your CFDs, wss://api.oguard.io/ws/cfd?ticket=…, streams live CFD quotes once you send {"op":"subscribe","symbols":["EURUSD"]}, and what the book does on its own: a pending order filling, a take-profit or stop firing, a stop-out, each followed by your account. Both take a ticket from GET /api/v1/ws/ticket (signed), which lasts 60 seconds; each connection needs a new one.
Account frames are numbered
On /ws/history each frame carries seq and epoch, and the first frame of every connection is a hello with the account’s current position. A seq that skips ahead, a new epoch, or a hello ahead of the last frame you saw means frames were missed: re-read positions, open orders and balances over REST. The Python client does this bookkeeping and yields {"kind": "resync"} when it happens. CFD frames are not numbered: after a reconnect, re-read CFD positions and pending orders.
import asyncio
from oguard_client import OGuardClient, MarketStream, HistoryStream, CfdStream
og = OGuardClient("ogk_...", "ogs_...")
async def account():
async with HistoryStream(og) as stream: # perpetuals and tokenized stocks
async for event in stream:
if event["kind"] == "resync":
# Frames were missed: re-read positions, open orders and balances.
print("resync:", event["reason"], og.perps.positions())
elif event["kind"] in ("order", "trade", "position", "balance"):
print(event["kind"], event["seq"], event["data"])
async def cfds():
async with CfdStream(og) as stream:
await stream.subscribe_quotes(["EURUSD"])
async for event in stream:
print("cfd", event["kind"], event.get("data"))
async def market():
async with MarketStream() as m:
await m.subscribe("BTCUSDT.P") # ticker
await m.subscribe("BTCUSDT.P", "kline", "1") # 1-minute candles
async for msg in m:
print(msg["ch"], msg.get("data"))
async def main():
await asyncio.gather(account(), cfds(), market())
asyncio.run(main())Errors
Every refusal is JSON {"error": "<code>", "message": "…"} with a meaningful HTTP status. error is a stable snake_case code: branch on it. message, when present, is a readable explanation for your logs and may change. Some refusals add fields, such as detail or available. The Python client raises OGuardAPIError with .status, .code, .message and .retry_after.
| Status | Code | Meaning |
|---|---|---|
| 401 | missing_headers | A signing header is missing, or the sign type is not hmac or ed25519 |
| 401 | invalid_api_key | No such key |
| 401 | key_revoked, key_expired | The key no longer works |
| 401 | timestamp_invalid | More than 1 s ahead of the server, or older than your recvWindow |
| 401 | signature_invalid | The signature does not match the bytes the server received |
| 401 | request_replayed | This exact signed write was already accepted |
| 403 | ip_not_allowed | Your IP is not on the key's allowlist |
| 403 | account_inactive | The account is suspended or not a trading account |
| 403 | route_not_public | API keys cannot use this route |
| 403 | insufficient_scope | A read-only key called a trading route |
| 429 | rate_limited | Your budget is spent; wait Retry-After seconds |
| 429 | upstream_busy | The market connection is saturated; nothing was sent or charged; retry after a second |
| Status | Code | Meaning |
|---|---|---|
| 400 | wrong_product | The symbol belongs to another product |
| 400 | invalid_quantity, invalid_price, invalid_trigger_price, invalid_side, invalid_order_type | A field is malformed or out of range |
| 400 | no_open_position, close_exceeds_position | A reduce-only close has nothing (or not enough) to close |
| 400 | insufficient_holding | A stock sell exceeds what you hold |
| 400 | insufficient_balance, insufficient_margin | Not enough funds for the order |
| 400 | below_min_order_value, volume_below_minimum | The order is smaller than the instrument allows |
| 404 | order_not_found, unknown_symbol | No such order (or not in this product), or no such instrument |
| 409 | order_not_active, order_not_open | The order already filled, cancelled or closed |
| 409 | client_order_id_in_use | The id was used on another product |
| 409 | market_closed, no_fresh_price | CFD: the market is shut, or there is no current price |
| 502 | market_rejected, market_unavailable | The market refused the order or could not be reached |
A code not listed here still follows its status: 400 a rule of the order was broken, 404 not found, 409 a state conflict, 502 the market refused or could not be reached. A network error or timeout on a placement is not a refusal: look the order up by its clientOrderId.
Endpoint reference
Every route an API key can call, by product. Anything else answers 403 route_not_public. The same list, with request and response schemas and each route’s weight, is the OpenAPI contract.
Perpetuals /api/v1/perps
| Route | Key needed | What it does | Parameters |
|---|---|---|---|
| GET / | No key | Perpetual instruments | |
| GET / | No key | Live quotes | symbols, comma-separated (optional) |
| GET / | No key | Tick size, lot size, leverage limits | |
| GET / | No key | Candles | interval (1 3 5 15 30 60 120 240 360 720 D W M); start, end in Unix ms; limit up to 1000 |
| GET / | No key | Funding rate history | limit |
| GET / | No key | Order book snapshot | limit levels per side, 1 to 200 (default 25) |
| GET / | No key | Recent public trades | limit 1 to 1000 (default 50) |
| GET / | No key | Open interest history | interval (5min 15min 30min 1h 4h 1d), limit up to 200 |
| POST / | Trading | Place an order | See Perpetuals |
| POST / | Trading | Amend an open order | Any of qty, price, triggerPrice, takeProfit, stopLoss ("0" clears a leg) |
| POST / | Trading | Cancel an order | |
| POST / | Trading | Amend an open order by your clientOrderId | As amend |
| POST / | Trading | Cancel an order by your clientOrderId | |
| POST / | Trading | Cancel every open perpetual order | |
| GET / | Read | Open orders | |
| GET / | Read | Order history | days (1, 7, 15, 30, 45, 60) or window=24h; optional symbol, limit |
| GET / | Read | One order by id | |
| GET / | Read | One order by your clientOrderId | |
| GET / | Read | Open positions | |
| POST / | Trading | Reverse a position | |
| POST / | Trading | Set take-profit / stop-loss | mode (entire or partial), takeProfitPct, stopLossPct (numbers, % of last price), partialQty |
| DELETE / | Trading | Clear take-profit / stop-loss | |
| POST / | Trading | Set leverage for a symbol | symbol, leverage (a JSON number) |
| GET / | Read | Leverage and margin tier for a symbol | symbol; optional notional, side |
| GET / | Read | Effect of a leverage change on open positions | symbol, leverage |
| GET / | Read | Fills | days (1, 7, 15, 30, 45, 60) or window=24h; optional symbol, limit |
| GET / | Read | Closed-position profit and loss | days (1, 7, 15, 30, 45, 60) or window=24h; optional symbol, limit |
Tokenized stocks /api/v1/stocks
| Route | Key needed | What it does | Parameters |
|---|---|---|---|
| GET / | No key | Tokenized stock instruments | |
| GET / | No key | Live quotes | symbols, comma-separated (optional) |
| GET / | No key | Candles | interval (1 3 5 15 30 60 120 240 360 720 D W M); start, end in Unix ms; limit up to 1000 |
| GET / | No key | Order book snapshot | limit levels per side, 1 to 200 (default 25) |
| GET / | No key | Recent public trades | limit 1 to 60 (default 50) |
| POST / | Trading | Place an order | See Tokenized stocks |
| POST / | Trading | Cancel an order | |
| POST / | Trading | Cancel an order by your clientOrderId | |
| POST / | Trading | Cancel every open tokenized stock order | |
| GET / | Read | Open orders | |
| GET / | Read | Order history | days (1, 7, 15, 30, 45, 60) or window=24h; optional symbol, limit |
| GET / | Read | One order by id | |
| GET / | Read | One order by your clientOrderId | |
| GET / | Read | Tokenized stocks you hold | |
| GET / | Read | Fills | days (1, 7, 15, 30, 45, 60) or window=24h; optional symbol, limit |
CFDs /api/v1/cfd
| Route | Key needed | What it does | Parameters |
|---|---|---|---|
| GET / | Read | Instruments and their specifications | |
| GET / | Read | Live quotes | symbols, comma-separated (optional) |
| GET / | Read | Candle timeframes | |
| GET / | Read | Candles | symbol, timeframe (1m 5m 15m 30m 1h 4h 6h 12h 1d 1w 1M) |
| GET / | Read | CFD wallet: balance, equity, margin, margin level | |
| GET / | Read | Open positions, priced | |
| GET / | Read | Pending or closed orders | status (pending or closed); for closed, days (1 to 366) and limit (default 100, at most 1000) |
| GET / | Read | One order by your clientOrderId | |
| GET / | Read | Margin and value of an order, without placing it | symbol, side, volume; orderType and entryPrice for a pending order |
| POST / | Trading | Open a position or place a pending order | See CFDs |
| POST / | Trading | Close a position, fully or partly | volume in lots (omit to close all) |
| DELETE / | Trading | Cancel a pending order | |
| POST / | Trading | Close a position by your clientOrderId | volume in lots (omit to close all) |
| DELETE / | Trading | Cancel a pending order by your clientOrderId | |
| POST / | Trading | Cancel every pending CFD order | |
| PATCH / | Trading | Move a pending order | entryPrice; stopLimitPrice for a stop-limit |
| PATCH / | Trading | Set take-profit, stop-loss and trailing stop | takeProfit, stopLoss (null clears, omitted keeps); trailingStopPoints, trailingStepPoints (whole points) |
Account /api/v1/account
| Route | Key needed | What it does | Parameters |
|---|---|---|---|
| GET / | No key | Server time in Unix milliseconds | |
| GET / | Read | Balance, equity and margin of the trading account (perpetuals and tokenized stocks) | |
| GET / | Read | Account transactions | days |
| GET / | Read | Deposit details (read only) | |
| GET / | Read | Withdrawal limits (read only) | |
| GET / | Read | Saved withdrawal addresses (read only) | |
| POST / | Trading | Arm, re-arm or disarm the dead-man's switch | timeoutMs: 0 (off) or 5000 to 600000 |
| GET / | Read | Dead-man's switch state | |
| GET / | Read | A 60-second ticket for the account streams |
What the API never does
- Move funds: there is no withdrawal, transfer or deposit route for keys, and money moves into or out of the CFD wallet only on the website.
- Create, change or revoke keys: that is only done on your account page.
- Change margin mode: accounts trade in cross margin.
Help
Questions about the API, a key that stopped working, or higher limits for a production strategy: write to support@oguard.io with your key id (never the secret).

