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

Where to send requests
REST APIhttps://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 streamwss://api.oguard.io/ws/cfd?ticket=…
Python client (one file)https://oguard.io/developers/oguard_client.py · Python 3.9 or newer
OpenAPI contracthttps://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.

Products
ProductAddressSymbolsPython
Perpetuals/api/v1/perpsUSDT perpetuals, ending in .P: BTCUSDT.Pog.perps
Tokenized stocks/api/v1/stocksStock tokens against USDT: NVDAXUSDTog.stocks
CFDs/api/v1/cfdFX, metals, indices, energy: EURUSDog.cfd
Account/api/v1/accountBalances, transactions, wallet readsog
  • 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

  1. Create a key on Account → Developer → API keys. Tick Allow trading if the program will place orders. Copy the secret: it is shown once.
  2. Download oguard_client.py next to your program. REST needs nothing else. For streams, run pip install websockets.
  3. Check the key and your clock from a terminal. On Windows (PowerShell):
PowerShell
$env:OGUARD_API_KEY = "ogk_..."
$env:OGUARD_API_SECRET = "ogs_..."
python oguard_client.py doctor

On macOS or Linux:

Shell
export OGUARD_API_KEY="ogk_..."
export OGUARD_API_SECRET="ogs_..."
python3 oguard_client.py doctor

It prints how far your computer’s clock is from ours, the number of listed instruments, and whether the key is accepted. Then trade:

Python
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 code

What 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 a clientOrderId, so a retry can never place a second order. Cancels, amends and leverage changes are not retried.
  • Exact numbers. Prices and quantities are str, int or Decimal. A float is refused before anything is sent, because most prices have no exact float. Responses parse decimals as Decimal.
  • 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:

Shell
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)
Python
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:

Signing headers
HeaderValue
X-OG-API-KEYYour key id, ogk_…
X-OG-TIMESTAMPCurrent time in Unix milliseconds
X-OG-RECV-WINDOWHow long the signature stays valid, in ms: default 5000, at most 60000
X-OG-SIGN-TYPEhmac or ed25519
X-OG-SIGNThe 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/time if 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 own clientOrderId.
Python, standard library only
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)}"}))
curl
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).

Perpetual order fields
FieldMeaning
symbol, side, type, qtyRequired. side buy or sell; type market or limit
priceRequired for limit orders
tifgtc, ioc, fok or postOnly (limit orders)
positionIdx1 long side, 2 short side
reduceOnlyOnly closes: a reduce-only sell on side 1 closes a long, a buy on side 2 a short
triggerPrice, triggerBy, triggerDirectionMakes the order conditional. triggerBy last, mark or index; triggerDirection 1 = rises to, 2 = falls to
takeProfit, stopLossAttached to the position the order opens
leverageA JSON number, applied to the symbol before the order is sent
clientOrderIdYour 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.

Python
# 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)
Python: a placement that timed out
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.

Tokenized stock order fields
FieldMeaning
symbol, side, orderType, qtyRequired. side buy or sell; orderType market or limit
marketUnitRequired. baseCoin: qty in the stock token; quoteCoin: qty in USDT (market buys only). A limit order and a market sell use baseCoin
priceRequired for limit orders
conditional{ "triggerPrice": "…", "mode": "market" | "limit" }: the order is placed when the last price reaches the trigger
clientOrderIdYour id for the order

A sell is limited to what you hold.

Python
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.

CFD order fields
FieldMeaning
symbol, sideRequired. side buy or sell
orderTypemarket (default) opens a position now; limit, stop and stop_limit rest until entryPrice is reached
volume or valueSize in lots, or as a position value in account currency; one of the two
entryPriceRequired for a pending order
stopLimitPriceThe limit a stop_limit rests at once its stop is reached
takeProfit, stopLossProtective levels
trailingStopPoints, trailingStepPointsA trailing stop, in whole points
expiresAtUnix ms at which a pending order is cancelled; omit for good-till-cancelled
clientOrderIdYour id for the order
  • POST /api/v1/cfd/orders/{id}/close closes a position, or volume lots of it; …/orders/by-client-id/{clientOrderId}/close does the same by your id. It is not retried for you: a repeated partial close would close more.
  • PATCH /api/v1/cfd/orders/{id}/protection sets levels: a field you omit is kept, and null clears 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.
Python
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-after with {"timeoutMs": 15000} arms or re-arms it; 0 disarms. 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-all and /cfd/orders/cancel-all.
Python
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 block

Rate limits

Limits apply per account, summed across all of the account’s keys. The API keys page shows your current usage.

Default limits
BudgetDefault
Request weight1,200 per rolling minute
Order actions50 per 10 seconds
Order actions300 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.

Heavier requests
RouteWeight
POST /api/v1/perps/orders/cancel-all5
POST /api/v1/perps/leverage3
GET /api/v1/perps/leverage-info3
GET /api/v1/perps/leverage-impact3
GET /api/v1/perps/markets/{symbol}/kline2
GET /api/v1/perps/markets/{symbol}/contract-details2
GET /api/v1/perps/markets/{symbol}/funding-history2
GET /api/v1/stocks/markets/{symbol}/kline2
GET /api/v1/cfd/bars2
GET /api/v1/perps/markets/{symbol}/orderbook2
GET /api/v1/perps/markets/{symbol}/trades2
GET /api/v1/perps/markets/{symbol}/open-interest2
GET /api/v1/stocks/markets/{symbol}/orderbook2
GET /api/v1/stocks/markets/{symbol}/trades2
POST /api/v1/stocks/orders/cancel-all5
POST /api/v1/cfd/orders/cancel-all5

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 the Retry-After seconds; 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.

Python (pip install websockets)
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.

Authentication and limit errors
StatusCodeMeaning
401missing_headersA signing header is missing, or the sign type is not hmac or ed25519
401invalid_api_keyNo such key
401key_revoked, key_expiredThe key no longer works
401timestamp_invalidMore than 1 s ahead of the server, or older than your recvWindow
401signature_invalidThe signature does not match the bytes the server received
401request_replayedThis exact signed write was already accepted
403ip_not_allowedYour IP is not on the key's allowlist
403account_inactiveThe account is suspended or not a trading account
403route_not_publicAPI keys cannot use this route
403insufficient_scopeA read-only key called a trading route
429rate_limitedYour budget is spent; wait Retry-After seconds
429upstream_busyThe market connection is saturated; nothing was sent or charged; retry after a second
Trading errors
StatusCodeMeaning
400wrong_productThe symbol belongs to another product
400invalid_quantity, invalid_price, invalid_trigger_price, invalid_side, invalid_order_typeA field is malformed or out of range
400no_open_position, close_exceeds_positionA reduce-only close has nothing (or not enough) to close
400insufficient_holdingA stock sell exceeds what you hold
400insufficient_balance, insufficient_marginNot enough funds for the order
400below_min_order_value, volume_below_minimumThe order is smaller than the instrument allows
404order_not_found, unknown_symbolNo such order (or not in this product), or no such instrument
409order_not_active, order_not_openThe order already filled, cancelled or closed
409client_order_id_in_useThe id was used on another product
409market_closed, no_fresh_priceCFD: the market is shut, or there is no current price
502market_rejected, market_unavailableThe 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

Perpetuals endpoints
RouteKey neededWhat it doesParameters
GET /api/v1/perps/marketsNo keyPerpetual instruments
GET /api/v1/perps/tickersNo keyLive quotessymbols, comma-separated (optional)
GET /api/v1/perps/markets/{symbol}/contract-detailsNo keyTick size, lot size, leverage limits
GET /api/v1/perps/markets/{symbol}/klineNo keyCandlesinterval (1 3 5 15 30 60 120 240 360 720 D W M); start, end in Unix ms; limit up to 1000
GET /api/v1/perps/markets/{symbol}/funding-historyNo keyFunding rate historylimit
GET /api/v1/perps/markets/{symbol}/orderbookNo keyOrder book snapshotlimit levels per side, 1 to 200 (default 25)
GET /api/v1/perps/markets/{symbol}/tradesNo keyRecent public tradeslimit 1 to 1000 (default 50)
GET /api/v1/perps/markets/{symbol}/open-interestNo keyOpen interest historyinterval (5min 15min 30min 1h 4h 1d), limit up to 200
POST /api/v1/perps/ordersTradingPlace an orderSee Perpetuals
POST /api/v1/perps/orders/{id}/amendTradingAmend an open orderAny of qty, price, triggerPrice, takeProfit, stopLoss ("0" clears a leg)
POST /api/v1/perps/orders/{id}/cancelTradingCancel an order
POST /api/v1/perps/orders/by-client-id/{clientOrderId}/amendTradingAmend an open order by your clientOrderIdAs amend
POST /api/v1/perps/orders/by-client-id/{clientOrderId}/cancelTradingCancel an order by your clientOrderId
POST /api/v1/perps/orders/cancel-allTradingCancel every open perpetual order
GET /api/v1/perps/ordersReadOpen orders
GET /api/v1/perps/orders/historyReadOrder historydays (1, 7, 15, 30, 45, 60) or window=24h; optional symbol, limit
GET /api/v1/perps/orders/{id}ReadOne order by id
GET /api/v1/perps/orders/by-client-id/{clientOrderId}ReadOne order by your clientOrderId
GET /api/v1/perps/positionsReadOpen positions
POST /api/v1/perps/positions/{id}/reverseTradingReverse a position
POST /api/v1/perps/positions/{id}/tpslTradingSet take-profit / stop-lossmode (entire or partial), takeProfitPct, stopLossPct (numbers, % of last price), partialQty
DELETE /api/v1/perps/positions/{id}/tpslTradingClear take-profit / stop-loss
POST /api/v1/perps/leverageTradingSet leverage for a symbolsymbol, leverage (a JSON number)
GET /api/v1/perps/leverage-infoReadLeverage and margin tier for a symbolsymbol; optional notional, side
GET /api/v1/perps/leverage-impactReadEffect of a leverage change on open positionssymbol, leverage
GET /api/v1/perps/tradesReadFillsdays (1, 7, 15, 30, 45, 60) or window=24h; optional symbol, limit
GET /api/v1/perps/closed-pnlReadClosed-position profit and lossdays (1, 7, 15, 30, 45, 60) or window=24h; optional symbol, limit

Tokenized stocks /api/v1/stocks

Tokenized stocks endpoints
RouteKey neededWhat it doesParameters
GET /api/v1/stocks/marketsNo keyTokenized stock instruments
GET /api/v1/stocks/tickersNo keyLive quotessymbols, comma-separated (optional)
GET /api/v1/stocks/markets/{symbol}/klineNo keyCandlesinterval (1 3 5 15 30 60 120 240 360 720 D W M); start, end in Unix ms; limit up to 1000
GET /api/v1/stocks/markets/{symbol}/orderbookNo keyOrder book snapshotlimit levels per side, 1 to 200 (default 25)
GET /api/v1/stocks/markets/{symbol}/tradesNo keyRecent public tradeslimit 1 to 60 (default 50)
POST /api/v1/stocks/ordersTradingPlace an orderSee Tokenized stocks
POST /api/v1/stocks/orders/{id}/cancelTradingCancel an order
POST /api/v1/stocks/orders/by-client-id/{clientOrderId}/cancelTradingCancel an order by your clientOrderId
POST /api/v1/stocks/orders/cancel-allTradingCancel every open tokenized stock order
GET /api/v1/stocks/ordersReadOpen orders
GET /api/v1/stocks/orders/historyReadOrder historydays (1, 7, 15, 30, 45, 60) or window=24h; optional symbol, limit
GET /api/v1/stocks/orders/{id}ReadOne order by id
GET /api/v1/stocks/orders/by-client-id/{clientOrderId}ReadOne order by your clientOrderId
GET /api/v1/stocks/holdingsReadTokenized stocks you hold
GET /api/v1/stocks/tradesReadFillsdays (1, 7, 15, 30, 45, 60) or window=24h; optional symbol, limit

CFDs /api/v1/cfd

CFDs endpoints
RouteKey neededWhat it doesParameters
GET /api/v1/cfd/instrumentsReadInstruments and their specifications
GET /api/v1/cfd/quotesReadLive quotessymbols, comma-separated (optional)
GET /api/v1/cfd/timeframesReadCandle timeframes
GET /api/v1/cfd/barsReadCandlessymbol, timeframe (1m 5m 15m 30m 1h 4h 6h 12h 1d 1w 1M)
GET /api/v1/cfd/accountReadCFD wallet: balance, equity, margin, margin level
GET /api/v1/cfd/positionsReadOpen positions, priced
GET /api/v1/cfd/ordersReadPending or closed ordersstatus (pending or closed); for closed, days (1 to 366) and limit (default 100, at most 1000)
GET /api/v1/cfd/orders/by-client-id/{clientOrderId}ReadOne order by your clientOrderId
GET /api/v1/cfd/order-previewReadMargin and value of an order, without placing itsymbol, side, volume; orderType and entryPrice for a pending order
POST /api/v1/cfd/ordersTradingOpen a position or place a pending orderSee CFDs
POST /api/v1/cfd/orders/{id}/closeTradingClose a position, fully or partlyvolume in lots (omit to close all)
DELETE /api/v1/cfd/orders/{id}TradingCancel a pending order
POST /api/v1/cfd/orders/by-client-id/{clientOrderId}/closeTradingClose a position by your clientOrderIdvolume in lots (omit to close all)
DELETE /api/v1/cfd/orders/by-client-id/{clientOrderId}TradingCancel a pending order by your clientOrderId
POST /api/v1/cfd/orders/cancel-allTradingCancel every pending CFD order
PATCH /api/v1/cfd/orders/{id}/pendingTradingMove a pending orderentryPrice; stopLimitPrice for a stop-limit
PATCH /api/v1/cfd/orders/{id}/protectionTradingSet take-profit, stop-loss and trailing stoptakeProfit, stopLoss (null clears, omitted keeps); trailingStopPoints, trailingStepPoints (whole points)

Account /api/v1/account

Account endpoints
RouteKey neededWhat it doesParameters
GET /api/v1/timeNo keyServer time in Unix milliseconds
GET /api/v1/account/balancesReadBalance, equity and margin of the trading account (perpetuals and tokenized stocks)
GET /api/v1/account/transactionsReadAccount transactionsdays
GET /api/v1/account/wallet/deposit-infoReadDeposit details (read only)
GET /api/v1/account/wallet/withdraw-infoReadWithdrawal limits (read only)
GET /api/v1/account/wallet/withdrawal-addressesReadSaved withdrawal addresses (read only)
POST /api/v1/account/cancel-all-afterTradingArm, re-arm or disarm the dead-man's switchtimeoutMs: 0 (off) or 5000 to 600000
GET /api/v1/account/cancel-all-afterReadDead-man's switch state
GET /api/v1/ws/ticketReadA 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).