REST and WebSocket API Reference¶
Status: Normative. This appendix is the single source of truth for the
pm-api-gwyREST contract as exposed under/api/v1. For an operational, tutorial-style guide see API Gateway (REST/WebSocket). The key words MUST, MUST NOT, SHOULD, and MAY are used per RFC 2119.
Scope & conventions¶
This appendix documents the client-visible REST surface of the API gateway.
It covers request shapes, replies, query parameters, auth rules, and the
stable error codes returned by the gateway. WebSocket streams live under
/api/v1/ws/ (private, market-data, admin/monitor, admin/sim) and are
specified normatively in the WebSocket API Reference; they
are not repeated here.
All paths are rooted at /api/v1. Admin paths name their target as a path
segment — /admin/symbols/{symbol}/..., /admin/gateways/{gateway_id}/...,
/admin/market/... — and a body, where one is accepted, carries only
optional parameters.
Endpoint index¶
Bootstrap¶
| Endpoint | Access | Purpose |
|---|---|---|
| GET /api/v1/bootstrap/trader | Any valid key | One-request startup payload for TRADER / MARKET_MAKER / ADMIN |
| GET /api/v1/bootstrap/mm | MARKET_MAKER key | One-request startup payload for MARKET_MAKER (adds quote state) |
| GET /api/v1/bootstrap/admin | Admin role | One-request startup payload for ADMIN |
Trading REST¶
| Endpoint | Access | Purpose |
|---|---|---|
| POST /api/v1/orders | Trading key | Submit one order for the caller's gateway |
| DELETE /api/v1/orders/{order_id} | Trading key | Cancel one live order |
| PATCH /api/v1/orders/{order_id} | Trading key | Amend price and/or quantity on one live order |
| POST /api/v1/orders/{order_id}/replace | Trading key | Cancel then replace one live order |
| GET /api/v1/orders | Trading key | Return the caller gateway's live order cache |
| GET /api/v1/orders/{order_id} | Trading key | Return one cached order |
| POST /api/v1/oco | Trading key | Submit an OCO pair |
| DELETE /api/v1/oco/{oco_id} | Trading key | Cancel an OCO pair |
| POST /api/v1/combos | Trading key | Submit a combo order |
| DELETE /api/v1/combos/{combo_id} | Trading key | Cancel a combo order |
| POST /api/v1/quotes | Trading key | Submit a two-sided market-maker quote |
| DELETE /api/v1/quotes/{symbol} | Trading key | Cancel the active quote for one symbol |
| POST /api/v1/mass-cancel | Trading key | Cancel all resting exposure for the caller or one symbol |
| GET /api/v1/symbols | Trading key | Return instrument metadata |
| GET /api/v1/news | Authenticated key | Return the latest market-sim headlines and sector map |
| GET /api/v1/companies/{symbol} | Authenticated key | Return one company profile from the company database |
| GET /api/v1/session | Authenticated key | Return the session phase, exchange timezone and trading date |
| GET /api/v1/halts | Authenticated key | Return currently-halted symbols |
| GET /api/v1/status | Trading key | Return the caller's gateway id and role |
| GET /api/v1/healthz | Public | Liveness probe |
| GET /api/v1/quotes | Trading key | Return the caller's active market-maker quotes |
| GET /api/v1/quotes/legs | Trading key | Return current quote-leg state |
| GET /api/v1/positions | Trading key | Return current net positions |
Reference data¶
| Endpoint | Access | Purpose |
|---|---|---|
| GET /api/v1/reference | Authenticated key | Return the full reference bundle |
| GET /api/v1/reference/config-version | Authenticated key | Return the reference bundle version |
| GET /api/v1/reference/symbols | Authenticated key | Return per-symbol tick and risk metadata |
| GET /api/v1/reference/risk | Authenticated key | Return risk-band definitions |
| GET /api/v1/reference/indexes | Authenticated key | Return configured exchange index definitions |
| GET /api/v1/reference/schedule | Authenticated key | Return session schedule metadata |
Market data¶
| Endpoint | Access | Purpose |
|---|---|---|
| GET /api/v1/book | Authenticated key | Return best bid, best ask and last price for every symbol |
| GET /api/v1/book/{symbol} | Authenticated key | Return one symbol's book levels, last trade and recent prints |
History¶
| Endpoint | Access | Purpose |
|---|---|---|
| GET /api/v1/history/orders | Trading key | Return the caller gateway's order lifecycle events |
| GET /api/v1/history/orders/{order_id} | Trading key | Return the full lifecycle of one order |
| GET /api/v1/history/fills | Trading key | Return fill events |
| GET /api/v1/history/trades | Authenticated key | Return public trade tape rows |
| GET /api/v1/history/daily | Authenticated key | Return daily OHLCV rows |
| GET /api/v1/history/price-snapshots | Authenticated key | Return intraday price snapshots |
| GET /api/v1/history/index-daily | Authenticated key | Return daily index OHLC rows |
| GET /api/v1/history/index-snapshots | Authenticated key | Return intraday index snapshots |
| GET /api/v1/history/indexes | Authenticated key | List index ids with recorded data |
| GET /api/v1/history/index-events | Authenticated key | Return index structural and audit events |
Admin REST¶
| Endpoint | Access | Purpose |
|---|---|---|
| POST /api/v1/admin/session/transition | Admin role | Request a session-phase transition |
| GET /api/v1/admin/sim | Admin role | Market simulator state and its true fair values |
| GET /api/v1/admin/gateways | Admin role | List configured gateways and live connection state |
| POST /api/v1/admin/gateways/{gateway_id}/disconnect | Admin role | Forcibly disconnect one gateway |
| POST /api/v1/admin/symbols/{symbol}/halt | Admin role | Halt one symbol through the circuit breaker |
| POST /api/v1/admin/symbols/{symbol}/resume | Admin role | Resume one halted symbol |
| GET /api/v1/admin/risk/state | Admin role | Return live per-symbol risk state |
| GET /api/v1/admin/orders | Admin role | Return the cross-gateway active-order table |
| GET /api/v1/admin/orders/{order_id}/audit | Admin role | Return the full cross-gateway lifecycle of one order |
| GET /api/v1/admin/gateways/{gateway_id}/positions | Admin role | Return net position and average cost for any one gateway |
| POST /api/v1/admin/symbols/{symbol}/cancel-orders | Admin role | Cancel all resting exposure on one symbol |
| POST /api/v1/admin/gateways/{gateway_id}/kill-switch | Admin role | Cancel all resting exposure for one gateway |
| POST /api/v1/admin/market/halt | Admin role | Halt every symbol and cancel every quote |
| POST /api/v1/admin/market/resume | Admin role | Resume every symbol halted by a market halt |
| GET /api/v1/admin/market/volume | Admin role | Today's traded volume per symbol and in total |
| POST /api/v1/admin/symbols/{symbol}/reopen | Admin role | Uncross and reopen a symbol, optionally at an operator price |
| GET /api/v1/admin/symbols/{symbol}/orders | Admin role | Every resting order on a symbol, optionally one price level |
| GET /api/v1/admin/gateways/{gateway_id}/orders | Admin role | One gateway's orders as the engine holds them |
| DELETE /api/v1/admin/gateways/{gateway_id}/quotes/{symbol} | Admin role | Withdraw one gateway's quote on one symbol |
| POST /api/v1/admin/market/kill-switch | Admin role | Cancel all resting exposure across every gateway and symbol |
| POST /api/v1/admin/indexes/{index_id}/rebalance | Admin role | Rebalance one configured index |
| POST /api/v1/admin/reference/reload | Admin role | Request a reference-data reload (currently refused by the engine) |
Auth and roles¶
- Trading and admin REST requests use
Authorization: Bearer <api_key>. gateway_id: nullcredentials are read-only and may access public data — reference data, public history,GET /session,GET /halts, news and company profiles — but no gateway-scoped or admin endpoint.- ADMIN endpoints require a credential whose resolved engine gateway role is
ADMIN.
Key types and provisioning¶
This appendix uses two access labels in endpoint tables:
Authenticated key(also calledAuthentication key): any configured API key accepted bypm-api-gwy. This includes both read-only and trading keys.Trading key: an authenticated key whose configuredgateway_idis notnull. This key is bound to one engine gateway identity and can submit and manage that gateway's orders.
How keys are specified:
- Keys are configured by operators in
engine_config.yamlunderapi_gateways.<INSTANCE>.credentials. - Each credential entry has
api_keyandgateway_id. gateway_id: nullcreates a read-only authenticated key.gateway_id: <GW_ID>creates a trading-capable key for that gateway.
Example:
api_gateways:
default:
credentials:
- api_key: key-trader-demo
gateway_id: TRADER01
description: Trading client for TRADER01
- api_key: key-dashboard-demo
gateway_id: null
description: Read-only dashboard client
How to get a key:
- API keys are provisioned out-of-band by the venue/operator team.
- There is no REST endpoint in
pm-api-gwyto self-issue or rotate keys. - After updating credentials, restart/redeploy the API gateway instance so the new key set is loaded.
Common error codes¶
Every non-2xx reply carries one of these error.code values. reject_code
is the engine vocabulary shared with ALF; it is UNKNOWN where no engine
reject applies.
Every endpoint can answer AUTH, ENGINE_AUTH, ENGINE_UNAVAILABLE (the
engine could not be reached to confirm a trading key) and INTERNAL_ERROR.
Each endpoint's Errors table lists exactly the other codes it can return;
the OpenAPI document lists all of them per operation.
| Code | HTTP status | reject_code |
Meaning |
|---|---|---|---|
VALIDATION |
400 |
MISSING_FIELD, UNSUPPORTED_FIELD, INVALID_VALUE, TICK_VIOLATION |
A body field, query parameter or history filter (date, cursor, event_type) failed validation; field names it. A price off the instrument's tick grid carries TICK_VIOLATION |
AUTH |
401 |
AUTH_REQUIRED |
Missing, malformed or unknown API key. The reply carries WWW-Authenticate: Bearer |
ENGINE_AUTH |
403 |
AUTH_FAILED |
The engine refused the key's gateway identity |
READ_ONLY |
403 |
ROLE_DENIED |
Read-only key used on a gateway-scoped endpoint |
ROLE_DENIED |
403 |
ROLE_DENIED |
The key's gateway lacks the role the endpoint needs (ADMIN, or MARKET_MAKER for /bootstrap/mm) |
NOT_FOUND |
404 |
UNKNOWN |
No such path |
UNKNOWN_ORDER |
404 |
UNKNOWN |
Order id unknown to the gateway (GET /orders/{id}) or absent from the audit trail (GET /admin/orders/{id}) |
UNKNOWN_COMPANY |
404 |
UNKNOWN |
No company profile for the symbol |
UNKNOWN_SYMBOL |
404 |
UNKNOWN |
The symbol is not in the venue's reference data (GET /book/{symbol}) |
METHOD_NOT_ALLOWED |
405 |
UNKNOWN |
The path exists but not for this method; the reply carries Allow |
TRANSITION_REJECTED |
409 |
UNKNOWN |
The engine refused the session transition |
RELOAD_REJECTED |
409 |
UNKNOWN |
The engine refused the reference reload |
HALT_REJECTED |
409 |
UNKNOWN |
The engine refused the halt (for example Unknown symbol: XYZ) |
RESUME_REJECTED |
409 |
UNKNOWN |
The engine refused the resume (for example TSLA is not halted) |
KILL_SWITCH_REJECTED |
409 |
UNKNOWN |
The engine refused a symbol, gateway or global kill switch |
REOPEN_REJECTED |
409 |
UNKNOWN |
The engine refused a symbol reopen (force-uncross), for example the symbol is not halted |
QUERY_REJECTED |
409 |
UNKNOWN |
The engine refused an admin query, for example Unknown symbol: XYZ |
REBALANCE_REJECTED |
409 |
UNKNOWN |
pm-index refused the rebalance |
REPLACE_REJECTED |
409 |
engine's | The engine refused replace's cancel (for example the order already filled); nothing was sent |
IDEMPOTENCY_KEY_REUSED |
409 |
UNKNOWN |
The Idempotency-Key was already used for a different request |
RATE_LIMIT |
429 |
RATE_LIMITED |
Per-key write limit exceeded |
INTERNAL_ERROR |
500 |
INTERNAL_ERROR |
A gateway bug; the message carries no internals |
INDEX_ERROR |
502 |
UNKNOWN |
pm-index answered with an error |
ENGINE_TIMEOUT |
503 |
INTERNAL_ERROR |
No engine reply in time to a read, or to replace's cancel. Transient — retry. A write whose ack is late is not an error: 202 with ack_timeout: true |
ENGINE_UNAVAILABLE |
503 |
INTERNAL_ERROR |
The command could not be handed to the engine (down or backpressured). Transient — retry |
SYMBOLS_NOT_READY |
503 |
SYMBOL_NOT_READY |
Order entry before the symbol's tick precision reached the gateway. Transient — retry |
INDEX_TIMEOUT |
503 |
UNKNOWN |
No pm-index reply in time |
STATS_DB |
503 |
UNKNOWN |
The pm-stats database is not present |
AUDIT_INDEX_UNAVAILABLE |
503 |
UNKNOWN |
The pm-audit index is not built or not running |
COMPANY_DB_UNAVAILABLE |
503 |
UNKNOWN |
The company database is missing or unreadable |
For an engine refusal (409), message is the engine's own reason. New codes
may be added within v1; clients must treat an unknown code by its HTTP status.
Swagger/OpenAPI documentation¶
The API gateway supports live API documentation through FastAPI:
- OpenAPI schema:
/openapi.json - Swagger UI:
/docs
The same document is committed in the repository as
spec/rest/openapi.json,
so a client can be generated or reviewed without a running gateway. It does not
depend on deployment configuration: every v1 gateway publishes exactly that
document, and CI fails if the code and the committed copy disagree.
These endpoints are available when swagger_enabled: true in the
api_gateways configuration. When disabled, /docs and /openapi.json are
not exposed.
Common reply shapes¶
200 OKmeans the response body carries the outcome: every read, and every command the gateway waited for (admin commands,POST /mass-cancel, and any order, cancel, amend, replace, OCO, combo or quote write with?wait=ackwhose verdict arrived in time).202 Acceptedmeans the request was handed on and the outcome follows on a WebSocket: those writes without?wait=ack(or with it, when the verdict was late:ack_timeout: true), andPOST /admin/gateways/{gateway_id}/disconnect.- Pagination endpoints return
count,has_more, andnext_cursorwhere applicable;afteris the opaque cursor input.
Response contract¶
Every reply has a declared schema in the OpenAPI document (/openapi.json),
and the gateway validates what it sends against it. The schemas of replies the
gateway forwards from the engine are generated from the same message specs
as the engine's own bindings, so the REST shape and the bus payload cannot
drift apart (see Messages).
- Omitted versus null. A field the schema marks optional is omitted when
it does not apply (an ordinary order has no
oco_group_id); a field it marks required is always present, and isnullonly where the schema allows it. - Collections are
{ "<plural>": [...], "count": n }; a paginated one addshas_moreandnext_cursor. Envelopes do not nest: a bootstrap'sordersis the list itself, not{ "orders": [...] }. - Status fields are closed enums declared in the schema.
- Time.
*_nsfields are epoch nanoseconds;tsfields and every historyfrom/toare ISO-8601 (or aYYYY-MM-DDdate);dateis a trading day in the exchange timezone (session_timezoneinGET /session). - Prices are JSON numbers in display units (
187.25), always on the symbol's tick grid. A field named*_tickscarries the same price as an integer tick count; it supplements a display field and never replaces one. - Extension. New optional fields, new endpoints and new enum members may be added within v1. A client must ignore fields and enum values it does not recognise.
Canonical error envelope¶
All non-2xx replies MUST use this JSON envelope shape — including an unknown
path (404 NOT_FOUND), a wrong method (405 METHOD_NOT_ALLOWED) and an
unhandled gateway error (500 INTERNAL_ERROR).
| Field | Type | Required | Description |
|---|---|---|---|
error |
Object |
yes | Top-level error container |
error.code |
String |
yes | Stable machine-readable code (for example VALIDATION) |
error.message |
String |
yes | Human-readable summary |
error.field |
String |
no | Field name when validation pinpoints one input field |
error.reject_code |
String |
no | Canonical order-rejection class for ALF/REST comparison, when applicable |
error.client_tag |
String |
no | Echo of the submitted order correlation tag, when available |
error.request_tag |
String |
no | Echo of the amend/cancel request tag, when available |
Canonical example:
{
"error": {
"code": "VALIDATION",
"reject_code": "INVALID_VALUE",
"message": "Input should be greater than 0",
"field": "quantity",
"client_tag": "desk1-aapl-00042"
}
}
Pagination contract¶
Endpoints that support pagination use keyset cursoring with these rules:
- Clients MAY pass
after(opaque cursor from a previous response). - Replies include
count,has_moreandnext_cursor. next_cursoris a string when another page exists andnullon the last.- Clients fetch the next page by passing
after=<next_cursor>unchanged. - Ordering is deterministic and backend-defined per endpoint; cursors are valid only for the same endpoint and compatible filter set.
- A malformed, stale, or cross-endpoint cursor returns
400 VALIDATIONwithfield: "after".
Write outcomes and safe retries¶
Every write that creates or changes orders (POST /orders, DELETE and
PATCH /orders/{order_id}, POST /orders/{order_id}/replace, POST and
DELETE on /oco, /combos and /quotes) answers in one of three ways, so a
client can always tell whether something reached the engine:
| Reply | Sent to the engine? | What to do |
|---|---|---|
200 OK, status: "ACKED" |
Yes, and the verdict is in the body (accepted, reject_code, event) |
Done |
202 Accepted, ack_timeout: false |
Yes; no ?wait=ack, the verdict follows on the private WebSocket |
Watch the WebSocket |
202 Accepted, ack_timeout: true |
Yes; ?wait=ack gave up waiting (wait_ack_sec) |
Watch the WebSocket or GET /orders; do not resubmit |
4xx or 5xx |
No (503 ENGINE_UNAVAILABLE, 400, 429, ...) |
Fix and retry |
The reply always carries the id to cancel or track (order_id, oco_id,
combo_id, symbol), including after an ack timeout. Two exceptions, both on
replace, which only sends the replacement once the original's cancel is
confirmed: a refused cancel is 409 REPLACE_REJECTED (nothing sent), and a
cancel whose outcome did not arrive is 503 ENGINE_TIMEOUT (no replacement
sent; check the original with GET /orders/{order_id}).
Idempotency-Key. A client whose connection dropped cannot know which of
those replies it missed. The submit writes (POST /orders, /oco, /combos,
/quotes and /orders/{order_id}/replace) accept an optional
Idempotency-Key header (1–255 characters; a UUID per logical request):
- The first request with a key runs. A later one with the same key, from the
same gateway, is not sent again: it gets the first reply (same status, same
body) with the header
Idempotent-Replayed: true. A duplicate that arrives while the first is still waiting for its ack waits for it too. - The same key on a different request (another path, query or body) is
409 IDEMPOTENCY_KEY_REUSED. - A request that failed before the engine was reached is not remembered, so it may be retried with the same key.
replaceis two writes — cancel, then submit the replacement — and the key records which of them succeeded. If the cancel is confirmed but the replacement fails, a retry with the same key resumes at the replacement: it does not cancel again, and it does not leave you with no order. A retry after both steps succeeded replays the first reply as usual.- Keys are kept for
order_retention_secand are lost when the gateway restarts.
client_tag is unrelated: it is an opaque, non-unique label (below).
Order correlation with client_tag¶
Order submit supports client_tag as an optional client-supplied correlation
tag.
- Scope: the tag is client-scoped and opaque.
- Behavior: the gateway and engine do not enforce uniqueness.
- Lifetime: when supplied, the tag is echoed on order lifecycle events and on
cached order reads so clients can map exchange-assigned
order_idvalues back to their own submissions. That includes events the client did not ask for — an unsolicited cancel, an expiry at the close — which are precisely the ones it cannot correlate any other way.
Unsolicited cancels and cancel_reason¶
An order.cancelled event does not always answer a DELETE. When the exchange
cancels an order itself, request_tag is absent and cancel_reason says why:
cancel_reason |
Meaning |
|---|---|
SELF_MATCH_PREVENTED |
The order would have traded against another order from the same gateway |
INSUFFICIENT_LIQUIDITY |
A MARKET or IOC order exhausted the book; this cancels the remainder, which never rests |
KILL_SWITCH |
The gateway's kill switch (KILL / KILL_ACK) cancelled this resting order |
CIRCUIT_BREAKER_HALT |
A circuit-breaker halt cancelled this resting order |
GATEWAY_DISCONNECT |
The owning gateway disconnected and its disconnect_behaviour cancelled this order |
ADMIN_CANCEL_SYMBOL |
An operator-issued admin cancel for the symbol cancelled this order |
QUOTE_REPLACED |
A market-maker's new quote replaced this quote leg |
QUOTE_LEG_FILLED |
The sibling leg of a two-sided quote filled, cancelling this leg |
Both fields are omitted rather than null when they do not apply, so
request_tag absent with cancel_reason also absent means the exchange
cancelled the order for a cause not yet classified under this vocabulary.
cancel_reason is a separate vocabulary from reject_code: a cancel is not
a rejection. New members may be added; existing members are never removed or
renamed. Clients must ignore values they do not recognise. See
Messages — order.cancelled.{gateway_id}
for the same enum at the engine-message level.
Category examples¶
The examples below are normative shape examples for each category. Values are illustrative.
Trading example (POST /api/v1/orders)¶
Minimal request:
Full request:
{
"symbol": "AAPL",
"side": "BUY",
"order_type": "LIMIT",
"quantity": 100,
"price": 187.25,
"tif": "DAY",
"smp_action": "CANCEL_AGGRESSOR",
"client_tag": "desk1-aapl-00042"
}
Minimal response:
Full response:
{
"order_id": "ORD-20260806-00042",
"client_tag": "desk1-aapl-00042",
"status": "ACKED",
"accepted": true,
"event": {
"order_id": "ORD-20260806-00042",
"accepted": true,
"gateway_id": "G1"
}
}
Reference data example (GET /api/v1/reference)¶
Minimal response:
Full response:
{
"symbols": [
{
"symbol": "AAPL",
"tick_decimals": 2,
"level": "L1"
}
],
"risk": {
"default_level": "L1",
"levels": {
"L1": {
"soft_pct": 5.0,
"hard_pct": 10.0
}
}
},
"indexes": [
{
"id": "TECH10",
"description": "Tech sample index"
}
],
"schedule": {
"sessions_enabled": true,
"country": "US"
},
"config_version": "sha256:6d8d..."
}
History example (GET /api/v1/history/trades)¶
Minimal response:
Full response:
{
"trades": [
{
"symbol": "AAPL",
"price": 187.3,
"quantity": 50,
"ts": "2026-08-06T09:30:00Z"
}
],
"count": 1,
"has_more": true,
"next_cursor": "eyJhZnRlciI6Ii4uLiJ9"
}
Admin example (POST /api/v1/admin/session/transition)¶
Minimal request:
Full request:
Minimal response:
Full response:
Compatibility and deprecation policy¶
- Versioning: this surface is namespaced under
/api/v1. The WebSocket streams under/api/v1/ws/(the WebSocket API Reference) are part of the same v1 contract and follow every rule below. - Backward compatibility: additive changes (new optional fields/endpoints) MAY be introduced within v1 without changing the base path.
- Breaking changes (field removal, required-field additions, semantic redefinition) MUST ship under a new versioned base path.
- Deprecation: deprecated fields/endpoints SHOULD be documented with migration guidance before removal in a later major API version.
- The contract:
spec/rest/openapi.jsonis the v1 contract. Any change to it is a reviewed diff, and each diff is judged against these rules. - Additive in practice:
- A new endpoint is additive.
- A new optional request field is additive.
- A new response field is additive. Clients must ignore unknown response fields.
- A new error
codeis additive. Clients handle an unknown one by its HTTP status. - A new enum value in a response is additive only if the gateway and engine
are deployed together. The gateway validates its responses, so a gateway
older than the engine answers
500on a value it does not know. EduMatcher releases both from one spec. - What clients MUST tolerate, so that the additive changes above stay additive for them:
- Unknown fields, in any REST response body and in any WebSocket frame
or
data: ignore them. - Unknown enum values, in any enum (
status,reject_code,cancel_reason,order_type, ...): treat them as "other", not as an error. New members may be added to any enum within v1. - Unknown WebSocket
typevalues: ignore the frame. New event types may be added within v1. - Unknown error
codevalues: handle them by HTTP status. - What is breaking for error codes: removing a
codea route can return, changing acode's HTTP status or meaning, or returning a differentcodefor a condition that already has one. Adding acode, or returning an existingcodefrom one more route, is additive.
Bootstrap¶
Bootstrap endpoints return a single composed response that a browser client
can fetch immediately after authenticating, replacing the 6–13 sequential
round-trips the current login flow requires. Sub-queries inside each handler
run in parallel using asyncio.gather.
Each response includes an incomplete array. When a field is listed there
its value is null; the rest of the response is still usable. Required
fields (marked below) return 503 ENGINE_TIMEOUT on failure — the response
would be too incomplete to be useful without them.
GET /api/v1/bootstrap/trader¶
Purpose: one-request startup payload for TRADER, MARKET_MAKER, and ADMIN sessions. Returns identity, full reference data, live session state, positions, active orders, today's recent fills, and capability flags.
Access: any valid key (read-only keys receive gateway_role: "READ_ONLY",
empty positions and orders and null recent_fills, but session and
halts like any key).
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
fills_limit |
Int |
no | Maximum number of today's fill events in recent_fills; 1..500, default 50 |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
the object below | Startup payload |
{
"ts": "2026-07-27T09:30:01.123Z",
"incomplete": [], // field names that timed out (values are null)
"gateway_id": "TRADER01", // null for read-only keys
"gateway_role": "TRADER", // TRADER | MARKET_MAKER | ADMIN | READ_ONLY
"session_timezone": "Europe/Stockholm", // IANA name; the zone clients render wall-clock times in
// identical to GET /reference
"reference": {
"symbols": [ { "symbol": "AAPL", "tick_decimals": 2, ... } ],
"risk": { "default_level": "L2", "levels": { ... } },
"schedule": { "sessions_enabled": true, "country": "Sweden", "schedule": { ... } },
"config_version": "7f3a2c1"
},
// identical to GET /session; null if engine timed out (optional)
"session": { "state": "CONTINUOUS", "sessions_enabled": true, "next": { ... },
"session_timezone": "Europe/Stockholm", "session_date": "2026-07-27" },
// identical to GET /positions: an engine round trip (required — 503 on failure)
"positions": [ { "symbol": "AAPL", "net_qty": 200, "avg_cost": 208.40, "last_price": 210.25,
"market_value": 42050.0, "unrealized_pnl": 370.0,
"realized_pnl_session": 0.0, "realized_pnl_mtd": 0.0, "realized_pnl_ytd": 0.0 } ],
// the `orders` list of GET /orders (required — 503 on failure)
"orders": [ /* OrderDisplay[] */ ],
// today's fill events, at most fills_limit; null if stats DB absent (optional)
"recent_fills": [ /* OrderEvent[] */ ],
// the `halted` list of GET /halts; null if engine timed out (optional)
"halts": [ ],
// capability flags — assembled from config, never null
"capabilities": {
"sessions_enabled": true, // from reference.schedule.sessions_enabled
"stats_db_available": true, // false → history unavailable
"audit_db_available": false, // false → order lifecycle drill-down unavailable
"index_available": false // false → index tab unavailable
}
}
Required fields (return 503 ENGINE_TIMEOUT if they fail):
reference, orders and positions (the last two are empty, without an
engine query, for read-only keys).
Optional fields (appear as null + listed in incomplete on failure):
session, recent_fills, halts.
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
fills_limit is not an integer in 1–500 |
READ_ONLY |
403 |
A read-only key used on a gateway-scoped endpoint |
ENGINE_TIMEOUT |
503 |
reference, orders or positions could not be fetched |
GET /api/v1/bootstrap/mm¶
Purpose: one-request startup payload for MARKET_MAKER sessions. Superset of
/bootstrap/trader — adds active quote bootstrap state and quote legs.
Access: MARKET_MAKER key only. TRADER and ADMIN keys receive 403.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
fills_limit |
Int |
no | As for /bootstrap/trader |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
the object below | Startup payload |
All fields from /bootstrap/trader, plus:
{
// ...trader fields...
"gateway_role": "MARKET_MAKER",
// the `quotes` list of GET /quotes; null if engine timed out (optional)
"quotes": [ /* ActiveQuote[] */ ],
// the `legs` list of GET /quotes/legs; null if engine timed out (optional)
"quote_legs": [ /* QuoteLeg[] */ ]
}
Required fields: reference, orders, positions.
Optional fields: session, recent_fills, halts, quotes, quote_legs.
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
fills_limit is not an integer in 1–500 |
READ_ONLY |
403 |
Read-only key (no gateway_id) |
ROLE_DENIED |
403 |
Key resolves to TRADER or ADMIN role |
ENGINE_TIMEOUT |
503 |
reference, orders or positions could not be fetched |
GET /api/v1/bootstrap/admin¶
Purpose: one-request startup payload for ADMIN sessions. Returns reference data, session state, the full gateway roster, active halts, per-gateway active-order counts, per-gateway drop-copy sequence numbers, and capability flags.
Access: ADMIN role required. TRADER and MARKET_MAKER keys receive 403.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
| none | — | — | No query parameters |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
the object below | Startup payload |
{
"ts": "2026-07-27T09:30:01.250Z",
"incomplete": [],
"gateway_id": "INSTRUCTOR",
"gateway_role": "ADMIN",
// identical to GET /reference (required — 503 on failure)
"reference": { /* symbols, risk, schedule, config_version */ },
// identical to GET /session; null if engine timed out (optional)
"session": { "state": "CONTINUOUS", "sessions_enabled": true, "session_timezone": "...", "session_date": "..." },
// the `gateways` list of GET /admin/gateways; null if engine timed out (optional)
"gateways": [
{ "id": "TRADER01", "role": "TRADER", "description": "...", "connected": true }
],
// the `halted` list of GET /halts; null if engine timed out (optional)
"halts": [ { "symbol": "AAPL", "level": "L2", ... } ],
// per-gateway active (non-terminal) order count — pure cache, never null
"active_order_counts": { "TRADER01": 3, "MM01": 12 },
// each active gateway's private-stream stream_seq now — pure cache, never null
"monitor_last_seq": { "TRADER01": 100482, "MM01": 88213 },
"capabilities": {
"sessions_enabled": true,
"stats_db_available": true,
"audit_db_available": false,
"index_available": false
}
}
Required fields: reference.
Optional fields: session, gateways, halts.
The gateways and halts fields are also supplied by the monitor.snapshot
frame that the WS /api/v1/ws/admin/monitor socket sends immediately after auth
— a partial bootstrap response is therefore recoverable without a manual retry.
Errors
| Code | Status | When |
|---|---|---|
READ_ONLY |
403 |
A read-only key used on a gateway-scoped endpoint |
ROLE_DENIED |
403 |
Key is not ADMIN |
ENGINE_TIMEOUT |
503 |
reference could not be fetched |
Trading REST¶
POST /api/v1/orders¶
Purpose: submit one order for the caller's gateway.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
symbol |
Symbol |
yes | Instrument symbol |
side |
Side |
yes | BUY or SELL |
order_type |
OrderType |
yes | MARKET, LIMIT, STOP, STOP_LIMIT, FOK, ICEBERG, IOC, or TRAILING_STOP |
quantity |
Int |
yes | Positive order quantity |
tif |
Tif |
no | Time-in-force; defaults to DAY |
price |
Price |
conditional | Required for LIMIT, FOK, IOC, STOP_LIMIT, ICEBERG |
stop_price |
Price |
conditional | Required for STOP, STOP_LIMIT; optional initial stop for TRAILING_STOP (default: last trade ± trail_offset) |
visible_qty |
Qty |
conditional | Required for ICEBERG; must be less than quantity |
trail_offset |
Price |
conditional | Required for TRAILING_STOP |
smp_action |
SmpAction |
no | Self-match prevention action |
client_tag |
Str |
no | Optional opaque client correlation tag |
wait |
Bool |
no | ?wait=ack waits for the engine's verdict (see Write outcomes and safe retries) |
Idempotency-Key |
header | no | Makes a retry safe; see Write outcomes and safe retries |
Reply
| Status | Shape | Meaning |
|---|---|---|
202 Accepted |
{"order_id": "...", "status": "PENDING", "ack_timeout": false, ...} |
Default immediate reply |
200 OK |
{"order_id": "...", "status": "ACKED", "accepted": false, "reject_code": "COLLAR_BREACH", "event": {..., "reason": "collar breach"}, "ack_timeout": false} |
Returned when ?wait=ack waits for the matching ACK; a rejected ACK carries the stable machine-readable reject_code, and the engine's order.ack in event has the human-readable reason |
202 Accepted |
{"order_id": "...", "status": "PENDING", "ack_timeout": true, ...} |
?wait=ack and the ACK did not arrive in time; the order was sent |
Takes an optional Idempotency-Key header; see
Write outcomes and safe retries.
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
Body does not match the order type, or a price is not an exact multiple of the instrument's tick size (reject_code: TICK_VIOLATION) |
READ_ONLY |
403 |
Read-only credential used |
IDEMPOTENCY_KEY_REUSED |
409 |
The Idempotency-Key was used for a different request |
RATE_LIMIT |
429 |
Write limit exceeded |
SYMBOLS_NOT_READY |
503 |
Symbol metadata has not reached the gateway yet (reject_code: SYMBOL_NOT_READY) |
A conditional field the order_type does not use is rejected
(400 VALIDATION, reject_code: UNSUPPORTED_FIELD, for example
"LIMIT forbids trail_offset"), never silently dropped; a missing one is
MISSING_FIELD. The same per-type rule applies to OCO and combo legs, and ALF,
BALF and the engine apply it too, so an order gets the same reject_code on
every transport.
Prices are refused rather than rounded to the nearest tick: rounding would rest the order at a price the client never sent. See Tick precision.
DELETE /api/v1/orders/{order_id}¶
Purpose: cancel one live order in the caller's gateway.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
order_id |
Str |
yes (path) | Order to cancel |
wait |
Bool |
no | ?wait=ack waits for order.cancelled.* |
request_tag |
Str |
no | Query parameter echoed on order.cancelled.* or rejected ACK; max 64 chars |
Reply
| Status | Shape | Meaning |
|---|---|---|
202 Accepted |
{"order_id": "...", "request_tag": "...", "status": "PENDING_CANCEL"} |
Default: cancel sent; the outcome follows on the private WebSocket |
200 OK |
{"order_id": "...", "status": "ACKED" \| "REJECTED", "accepted": ..., "reject_code": ..., "event": {...}} |
With ?wait=ack: the cancel's outcome |
202 Accepted |
{..., "status": "PENDING_CANCEL", "ack_timeout": true} |
?wait=ack and the outcome did not arrive in time; the cancel was sent |
Without a request_tag, ?wait=ack waits for order.cancelled alone, so a
refused cancel shows as an ack timeout; pass a request_tag to get it as
REJECTED.
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
Bad path or query |
READ_ONLY |
403 |
Read-only credential used |
RATE_LIMIT |
429 |
Per-key write limit exceeded; retry after a short pause |
PATCH /api/v1/orders/{order_id}¶
Purpose: amend price and/or quantity on one live order.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
order_id |
Str |
yes (path) | Order to amend |
price |
Price |
conditional | New order price |
quantity |
Qty |
conditional | New order quantity |
request_tag |
Str |
no | Body field echoed on order.amended.* or rejected ACK; max 64 chars |
wait |
Bool |
no | ?wait=ack waits for order.amended.* |
Reply
| Status | Shape | Meaning |
|---|---|---|
202 Accepted |
{"order_id": "...", "request_tag": "...", "status": "PENDING_AMEND"} |
Default: amend sent; the outcome follows on the private WebSocket |
200 OK |
{"order_id": "...", "status": "ACKED" \| "REJECTED", "accepted": ..., "reject_code": ..., "event": {...}} |
With ?wait=ack: the amend's outcome |
202 Accepted |
{..., "status": "PENDING_AMEND", "ack_timeout": true} |
?wait=ack and the outcome did not arrive in time; the amend was sent |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
Neither or both fields invalid |
READ_ONLY |
403 |
Read-only credential used |
RATE_LIMIT |
429 |
Per-key write limit exceeded; retry after a short pause |
POST /api/v1/orders/{order_id}/replace¶
Purpose: cancel one live order and submit a replacement in one workflow.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
order_id |
Str |
yes (path) | Order to replace |
| body | Order fields |
yes | Same shape as POST /orders, on the same symbol |
wait |
Bool |
no | ?wait=ack also waits for the replacement's order.ack |
Idempotency-Key |
header | no | See Write outcomes and safe retries |
The gateway always waits for the original's cancel, and sends the replacement only once the engine has confirmed it.
Reply
| Status | Shape | Meaning |
|---|---|---|
202 Accepted |
{"cancelled_order_id": "...", "replacement_order_id": "...", "status": "PENDING", "accepted": null, ...} |
Original cancelled; replacement sent |
200 OK |
{..., "status": "ACKED", "accepted": true \| false, "reject_code": ..., "event": {...}} |
With ?wait=ack: the replacement's verdict |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
Replacement body invalid, or names another symbol than the original (field: symbol) |
READ_ONLY |
403 |
Read-only credential used |
IDEMPOTENCY_KEY_REUSED |
409 |
The Idempotency-Key was used for a different request |
REPLACE_REJECTED |
409 |
The engine refused the cancel (the order already filled, say; reject_code says why); nothing was sent |
RATE_LIMIT |
429 |
Per-key write limit exceeded; retry after a short pause |
ENGINE_TIMEOUT |
503 |
The cancel's outcome did not arrive in time; no replacement was sent |
SYMBOLS_NOT_READY |
503 |
The symbol's metadata is not loaded; nothing was cancelled |
GET /api/v1/orders¶
Purpose: return the caller gateway's orders as the engine holds them: resting orders plus recently finished ones.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
| none | — | — | No query parameters |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "orders": [...] } |
The engine's order snapshot for the caller |
Errors
| Code | Status | When |
|---|---|---|
READ_ONLY |
403 |
Read-only credential used |
ENGINE_TIMEOUT |
503 |
The engine did not answer in time. There is no fallback to the gateway's cache; the private WebSocket orders.snapshot is the cached view |
GET /api/v1/orders/{order_id}¶
Purpose: return one cached order from the caller gateway.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
order_id |
Str |
yes (path) | Order to fetch |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
cached order object | Current order state |
404 Not Found |
error envelope | Order id not in cache |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
A body field, query parameter, header or history filter failed validation; field names it. reject_code is one of MISSING_FIELD, UNSUPPORTED_FIELD, INVALID_VALUE, TICK_VIOLATION |
READ_ONLY |
403 |
Read-only credential used |
UNKNOWN_ORDER |
404 |
Order id unknown to the cache |
POST /api/v1/oco¶
Purpose: submit an OCO pair.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
oco_id |
Str |
yes | Your id for the pair, unique among your live pairs: reusing one is refused (accepted: false). Free again once the pair is done |
symbol |
Symbol |
yes | Instrument symbol |
quantity |
Qty |
yes | Total quantity |
leg1 |
OrderLeg |
yes | First leg definition: side, order_type, and the price / stop_price / trail_offset that type needs (same rule as POST /orders; ICEBERG is not supported on a leg) |
leg2 |
OrderLeg |
yes | Second leg definition, same shape |
tif |
Tif |
no | Time-in-force of both legs; defaults to DAY |
client_tag |
Str |
no | Opaque label echoed on both legs' events; max 64 chars |
wait |
Bool |
no | ?wait=ack waits for the engine's verdict (see Write outcomes and safe retries) |
Idempotency-Key |
header | no | Makes a retry safe; see Write outcomes and safe retries |
Reply
| Status | Shape | Meaning |
|---|---|---|
202 Accepted |
{ "oco_id": "...", "status": "PENDING", "accepted": null, "reason": null, "event": null, "ack_timeout": false } |
Sent; the verdict follows on the private WebSocket (oco.ack) |
200 OK |
{ "oco_id": "...", "status": "ACKED", "accepted": true \| false, "reason": ..., "event": {...}, "ack_timeout": false } |
With ?wait=ack: the engine's oco.ack |
202 Accepted |
{..., "status": "PENDING", "ack_timeout": true} |
?wait=ack and the verdict did not arrive in time; it was sent |
Takes ?wait=ack and an optional Idempotency-Key header; see
Write outcomes and safe retries.
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
Body invalid |
READ_ONLY |
403 |
Read-only credential used |
IDEMPOTENCY_KEY_REUSED |
409 |
The Idempotency-Key was used for a different request |
RATE_LIMIT |
429 |
Per-key write limit exceeded; retry after a short pause |
SYMBOLS_NOT_READY |
503 |
Order entry before the symbol's tick precision reached the gateway. Transient; retry |
DELETE /api/v1/oco/{oco_id}¶
Purpose: cancel an OCO pair.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
oco_id |
Str |
yes (path) | One of your own OCO pairs; another gateway's id is ORDER_NOT_FOUND |
request_tag |
Str |
no | Query: echoed on the engine's oco.cancel_ack; max 64 chars |
wait |
Bool |
no | ?wait=ack waits for the oco.cancel_ack |
Reply
| Status | Shape | Meaning |
|---|---|---|
202 Accepted |
{ "oco_id": "...", "request_tag": ..., "status": "PENDING_CANCEL", "accepted": null, ..., "ack_timeout": false } |
Default: cancel sent; the outcome follows on the private WebSocket (oco.cancel_ack) |
200 OK |
{ ..., "status": "ACKED" \| "REJECTED", "accepted": ..., "reject_code": ..., "event": {...} } |
With ?wait=ack: the engine's oco.cancel_ack. A refusal (ORDER_NOT_FOUND, ORDER_ALREADY_TERMINAL) is REJECTED, not an error |
202 Accepted |
{ ..., "status": "PENDING_CANCEL", "ack_timeout": true } |
?wait=ack and the ack did not arrive in time; the cancel was sent |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
A body field, query parameter, header or history filter failed validation; field names it. reject_code is one of MISSING_FIELD, UNSUPPORTED_FIELD, INVALID_VALUE, TICK_VIOLATION |
READ_ONLY |
403 |
Read-only credential used |
RATE_LIMIT |
429 |
Per-key write limit exceeded; retry after a short pause |
POST /api/v1/combos¶
Purpose: submit a combo order.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
combo_id |
Str |
yes | Combo group id |
legs |
List<ComboLeg> |
yes | 2–10 legs: symbol, side, order_type (default LIMIT), quantity, and the price / stop_price that type needs (same rule as POST /orders; ICEBERG and TRAILING_STOP are not supported on a leg) |
combo_type |
ComboType |
no | Fill rule; AON (all or none), the default and only value |
tif |
Tif |
no | Time-in-force of every leg; defaults to DAY |
smp_action |
SmpAction |
no | Self-match prevention action, as on POST /orders |
client_tag |
Str |
no | Opaque label echoed on every leg's events; max 64 chars |
wait |
Bool |
no | ?wait=ack waits for the engine's verdict (see Write outcomes and safe retries) |
Idempotency-Key |
header | no | Makes a retry safe; see Write outcomes and safe retries |
Reply
| Status | Shape | Meaning |
|---|---|---|
202 Accepted |
{ "combo_id": "...", "status": "PENDING", "accepted": null, "reason": null, "event": null, "ack_timeout": false } |
Sent; the verdict follows on the private WebSocket (combo.ack) |
200 OK |
{ "combo_id": "...", "status": "ACKED", "accepted": true \| false, "reason": ..., "event": {...}, "ack_timeout": false } |
With ?wait=ack: the engine's combo.ack |
202 Accepted |
{..., "status": "PENDING", "ack_timeout": true} |
?wait=ack and the verdict did not arrive in time; it was sent |
Takes ?wait=ack and an optional Idempotency-Key header; see
Write outcomes and safe retries.
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
Body invalid |
READ_ONLY |
403 |
Read-only credential used |
IDEMPOTENCY_KEY_REUSED |
409 |
The Idempotency-Key was used for a different request |
RATE_LIMIT |
429 |
Per-key write limit exceeded; retry after a short pause |
SYMBOLS_NOT_READY |
503 |
Order entry before the symbol's tick precision reached the gateway. Transient; retry |
DELETE /api/v1/combos/{combo_id}¶
Purpose: cancel a combo order.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
combo_id |
Str |
yes (path) | Combo group to cancel |
request_tag |
Str |
no | Query: echoed on the engine's combo.cancel_ack; max 64 chars |
wait |
Bool |
no | ?wait=ack waits for the combo.cancel_ack |
Reply
| Status | Shape | Meaning |
|---|---|---|
202 Accepted |
{ "combo_id": "...", "request_tag": ..., "status": "PENDING_CANCEL", "accepted": null, ..., "ack_timeout": false } |
Default: cancel sent; the outcome follows on the private WebSocket (combo.cancel_ack) |
200 OK |
{ ..., "status": "ACKED" \| "REJECTED", "accepted": ..., "reject_code": ..., "event": {...} } |
With ?wait=ack: the engine's combo.cancel_ack. A refusal (ORDER_NOT_FOUND, ORDER_ALREADY_TERMINAL) is REJECTED, not an error |
202 Accepted |
{ ..., "status": "PENDING_CANCEL", "ack_timeout": true } |
?wait=ack and the ack did not arrive in time; the cancel was sent |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
A body field, query parameter, header or history filter failed validation; field names it. reject_code is one of MISSING_FIELD, UNSUPPORTED_FIELD, INVALID_VALUE, TICK_VIOLATION |
READ_ONLY |
403 |
Read-only credential used |
RATE_LIMIT |
429 |
Per-key write limit exceeded; retry after a short pause |
POST /api/v1/quotes¶
Purpose: submit a two-sided market-maker quote.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
symbol |
Symbol |
yes | Quoted instrument |
bid_price |
Price |
yes | Bid price |
bid_qty |
Qty |
yes | Bid quantity |
ask_price |
Price |
yes | Ask price |
ask_qty |
Qty |
yes | Ask quantity |
tif |
Tif |
no | Time-in-force of both sides; defaults to DAY |
quote_id |
Str |
no | Your id for the quote; defaults to the symbol |
wait |
Bool |
no | ?wait=ack waits for the engine's verdict (see Write outcomes and safe retries) |
Idempotency-Key |
header | no | Makes a retry safe; see Write outcomes and safe retries |
Reply
| Status | Shape | Meaning |
|---|---|---|
202 Accepted |
{ "symbol": "...", "quote_id": "..." \| null, "status": "PENDING", "accepted": null, "reason": null, "event": null, "ack_timeout": false } |
Sent; the verdict follows on the private WebSocket (quote.ack) |
200 OK |
{ "symbol": "...", ..., "status": "ACKED", "accepted": true \| false, "reason": ..., "event": {...} } |
With ?wait=ack: the engine's quote.ack |
202 Accepted |
{..., "status": "PENDING", "ack_timeout": true} |
?wait=ack and the verdict did not arrive in time; it was sent |
Takes ?wait=ack and an optional Idempotency-Key header; see
Write outcomes and safe retries.
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
Quote body invalid |
READ_ONLY |
403 |
Read-only credential used |
IDEMPOTENCY_KEY_REUSED |
409 |
The Idempotency-Key was used for a different request |
RATE_LIMIT |
429 |
Per-key write limit exceeded; retry after a short pause |
SYMBOLS_NOT_READY |
503 |
Order entry before the symbol's tick precision reached the gateway. Transient; retry |
DELETE /api/v1/quotes/{symbol}¶
Purpose: cancel the active quote for one symbol.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
symbol |
Symbol |
yes (path) | Quoted instrument |
request_tag |
Str |
no | Query: echoed on the engine's quote.cancel_ack; max 64 chars |
wait |
Bool |
no | ?wait=ack waits for the quote.cancel_ack |
Reply
| Status | Shape | Meaning |
|---|---|---|
202 Accepted |
{ "symbol": "...", "request_tag": ..., "status": "PENDING_CANCEL", "accepted": null, ..., "ack_timeout": false } |
Default: cancel sent; the outcome follows on the private WebSocket (quote.cancel_ack) |
200 OK |
{ ..., "status": "ACKED" \| "REJECTED", "accepted": ..., "reject_code": ..., "event": {...} } |
With ?wait=ack: the engine's quote.cancel_ack. A refusal (ORDER_NOT_FOUND, ORDER_ALREADY_TERMINAL) is REJECTED, not an error |
202 Accepted |
{ ..., "status": "PENDING_CANCEL", "ack_timeout": true } |
?wait=ack and the ack did not arrive in time; the cancel was sent |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
A body field, query parameter, header or history filter failed validation; field names it. reject_code is one of MISSING_FIELD, UNSUPPORTED_FIELD, INVALID_VALUE, TICK_VIOLATION |
READ_ONLY |
403 |
Read-only credential used |
RATE_LIMIT |
429 |
Per-key write limit exceeded; retry after a short pause |
POST /api/v1/mass-cancel¶
Purpose: cancel all resting exposure for the caller or one symbol.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
symbol |
Symbol |
no | Restrict the cancel to one symbol |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
kill-switch ack | What the engine cancelled (the call waits for the ack) |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
A body field, query parameter, header or history filter failed validation; field names it. reject_code is one of MISSING_FIELD, UNSUPPORTED_FIELD, INVALID_VALUE, TICK_VIOLATION |
READ_ONLY |
403 |
Read-only credential used |
RATE_LIMIT |
429 |
Per-key write limit exceeded; retry after a short pause |
ENGINE_TIMEOUT |
503 |
Engine did not ACK in time |
GET /api/v1/symbols¶
Purpose: return instrument metadata for the caller gateway.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
| none | — | — | No query parameters |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "symbols": [...] } |
Current symbol metadata |
Errors
| Code | Status | When |
|---|---|---|
READ_ONLY |
403 |
A read-only key used on a gateway-scoped endpoint |
ENGINE_TIMEOUT |
503 |
Engine did not reply in time |
GET /api/v1/news¶
Purpose: return the latest market-sim headlines, oldest first, plus the sector → symbol mapping. Access: Authenticated key (read-only keys included) — unlike most endpoints in this section, it does not require a trading key, since this is public information, not a trading action.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
limit |
int | No | Maximum headlines to return, 1-200 (default 50) |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "news": [...], "sectors": {...} } |
news is a list of news.event-shaped objects (see Message Reference — news.event), oldest first; sectors maps each sector name to its sorted list of symbols. Empty news list when pm-market-sim is not running. |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
limit outside 1-200 |
GET /api/v1/companies/{symbol}¶
Purpose: return the company profile behind one symbol, from the company
database pm-compdb-cli init builds (companies.db in the data directory).
The gateway opens it read-only. Access: Authenticated key (read-only keys included) — the profiles
are public information.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
symbol |
path | Yes | Ticker, case-insensitive |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "symbol": "AAPL", "name": ..., "pe_ratio": ..., ... } |
Every column of company_view: the stored fields plus the derived years_in_business, ceo_tenure_years, pe_ratio, ps_ratio and net_margin_pct. Money is in millions of USD; a field the database has no value for is null. pm-compdb-cli columns lists them all. |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
A body field, query parameter, header or history filter failed validation; field names it. reject_code is one of MISSING_FIELD, UNSUPPORTED_FIELD, INVALID_VALUE, TICK_VIOLATION |
UNKNOWN_COMPANY |
404 |
The database has no profile for symbol |
COMPANY_DB_UNAVAILABLE |
503 |
No companies.db in the data directory, or it cannot be read |
GET /api/v1/session¶
Purpose: return the venue's session phase and the locale clients render it in. Open to every key, read-only included.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
| none | — | — | No query parameters |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "state", "sessions_enabled", "next", "session_timezone", "session_date" } |
state and next come from the engine; session_timezone (IANA) is the exchange timezone every wall-clock time and history date filter is in, and session_date today's trading date in it |
Errors
| Code | Status | When |
|---|---|---|
ENGINE_TIMEOUT |
503 |
Engine did not reply in time |
GET /api/v1/halts¶
Purpose: return the currently-halted symbols table, for any key, read-only included. Halt state is public: every client needs it at bootstrap and again on every market-data reconnect, or a halt missed while disconnected leaves a stale badge for the rest of the session.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
| none | — | — | No query parameters |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "halted": [...] } |
Currently-halted symbols; each { symbol, resume_at_ns?, level?, halt_source? } |
Errors
| Code | Status | When |
|---|---|---|
ENGINE_TIMEOUT |
503 |
Engine did not reply in time |
GET /api/v1/status¶
Purpose: return who the caller is: its gateway id and engine role.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
| none | — | — | No query parameters |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "gateway_id", "gateway_role", "gateway_count"? } |
gateway_role is TRADER, MARKET_MAKER or ADMIN; ADMIN keys also get gateway_count, the connected gateways |
Errors
| Code | Status | When |
|---|---|---|
READ_ONLY |
403 |
A read-only key used on a gateway-scoped endpoint |
GET /api/v1/healthz¶
Purpose: liveness probe for the API gateway.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
| none | — | — | No auth required |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "ok": true, ... } |
Gateway is enabled and its engine listener is alive |
503 Service Unavailable |
{ "ok": false, ... } |
Gateway is disabled or its engine listener has stopped; probes read the status code |
Errors
| Code | When |
|---|---|
| none | This endpoint does not require auth |
GET /api/v1/quotes¶
Purpose: return the caller's active market-maker quotes: both legs' order ids, sizes, remaining quantities, statuses and prices.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
| none | — | — | No query parameters |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "quotes": [...] } |
The caller's active quotes |
Errors
| Code | Status | When |
|---|---|---|
READ_ONLY |
403 |
A read-only key used on a gateway-scoped endpoint |
ENGINE_TIMEOUT |
503 |
Engine did not reply in time |
GET /api/v1/quotes/legs¶
Purpose: return the current quote-leg state for the caller.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
| none | — | — | No query parameters |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "legs": [...] } |
Current quote legs |
Errors
| Code | Status | When |
|---|---|---|
READ_ONLY |
403 |
A read-only key used on a gateway-scoped endpoint |
ENGINE_TIMEOUT |
503 |
Engine did not reply in time |
GET /api/v1/positions¶
Purpose: return current net positions by symbol.
To query a different gateway's position (e.g. checking on a running
pm-mm-bot), see GET /api/v1/admin/gateways/{gateway_id}/positions,
which requires the ADMIN role.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
| none | — | — | No query parameters |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "positions": [{ "symbol", "net_qty", "avg_cost", "last_price", "market_value", "unrealized_pnl", "realized_pnl_session", "realized_pnl_mtd", "realized_pnl_ytd" }, ...] } |
Net positions for the caller gateway, from the engine; P&L fields are null when unknown |
Errors
| Code | Status | When |
|---|---|---|
READ_ONLY |
403 |
A read-only key used on a gateway-scoped endpoint |
ENGINE_TIMEOUT |
503 |
Engine did not reply in time |
Reference data¶
Base path: /api/v1/reference. These endpoints expose compiled reference data
that changes only when an admin reloads it.
GET /api/v1/reference¶
Purpose: return the full reference bundle in one round-trip.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
| none | — | — | No query parameters |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "symbols", "risk", "indexes", "schedule", "config_version" } |
Full resolved reference bundle |
Errors
| Code | Status | When |
|---|---|---|
ENGINE_TIMEOUT |
503 |
Engine did not reply in time |
GET /api/v1/reference/config-version¶
Purpose: return the content-hash version of the reference bundle.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
| none | — | — | No query parameters |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "config_version": "..." } |
Opaque bundle version hash |
Errors
| Code | Status | When |
|---|---|---|
ENGINE_TIMEOUT |
503 |
Engine did not reply in time |
GET /api/v1/reference/symbols¶
Purpose: return per-symbol tick and risk metadata.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
| none | — | — | No query parameters |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "symbols": [...], "config_version": "..." } |
One object per symbol (each carries its own symbol) |
Each symbol object carries tick_decimals, level, and — when configured —
collar, order_limits (max_order_qty / max_order_value) and
circuit_breaker. collar is the symbol's effective band, its risk
level's defaults already merged under any per-symbol override; order_limits
is configured per symbol only, so it is simply what that symbol declared.
Either is omitted entirely when not configured. Full field law:
ReferenceSymbol in the message reference.
Errors
| Code | Status | When |
|---|---|---|
ENGINE_TIMEOUT |
503 |
Engine did not reply in time |
GET /api/v1/reference/risk¶
Purpose: return risk-band definitions and the default risk level.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
| none | — | — | No query parameters |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "default_level", "levels", "config_version" } |
Risk-band configuration |
Each entry of levels carries its name and, when the level configures one,
collar — as configured, before any per-symbol override. Levels do not carry
order limits; those are per symbol and appear on /reference/symbols. Full
field law: RiskLevel in the message reference.
Errors
| Code | Status | When |
|---|---|---|
ENGINE_TIMEOUT |
503 |
Engine did not reply in time |
GET /api/v1/reference/indexes¶
Purpose: return configured exchange index definitions.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
| none | — | — | No query parameters |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "indexes": [...], "config_version": "..." } |
Static index definitions |
Errors
| Code | Status | When |
|---|---|---|
ENGINE_TIMEOUT |
503 |
Engine did not reply in time |
GET /api/v1/reference/schedule¶
Purpose: return session schedule metadata.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
| none | — | — | No query parameters |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
schedule object | sessions_enabled, country, and session transition times |
Errors
| Code | Status | When |
|---|---|---|
ENGINE_TIMEOUT |
503 |
Engine did not reply in time |
Market data¶
Base path: /api/v1/book. The order book as of now, without a WebSocket.
Both endpoints read the gateway's copy of the book.{symbol} stream, the one
a WebSocket snapshot is answered from, so a REST read and a WebSocket
subscription agree on the same snapshot.
seqis the market-data sequence of the snapshot on topicbook.{symbol}, theseqits WebSocketbookframe carries. A client that reads here and then subscribes ignoresbookframes whoseseqis at or below it (the subscription replays the latest snapshot first).ts_nsis when the engine produced the snapshot. The engine publishes a book when it changes, so a quiet book keeps an oldts_nsand is still current.- Every price is a display price with a
*_tickssibling: the integerprice * 10**tick_decimals(see Prices in Response contract). Use the ticks to compare or sum prices exactly. - Iceberg orders contribute their displayed size only.
GET /api/v1/book/{symbol}¶
Purpose: return one symbol's latest book: bid and ask levels (best first), the last trade, and the last five prints. Access: Authenticated key (read-only keys included) — market data is public.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
symbol |
path | Yes | Ticker, case-insensitive |
levels |
query | No | Price levels per side, 1–100, default 10 |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "symbol", "tick_decimals", "ts_ns", "seq", "bids": [...], "asks": [...], "last_price", "last_price_ticks", "last_qty", "last_buy_price", "last_buy_price_ticks", "last_sell_price", "last_sell_price_ticks", "recent_trades": [...] } |
bids descending and asks ascending, each level { price, price_ticks, qty, count }; last_* are null until the symbol has traded; recent_trades are the last five prints, oldest first, each as in trade.executed plus price_ticks |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
levels outside 1–100, or symbol over-long |
UNKNOWN_SYMBOL |
404 |
symbol is not in the reference data |
ENGINE_TIMEOUT |
503 |
The engine did not publish the book in time (only on the first read of a book the gateway has not seen) |
GET /api/v1/book¶
Purpose: return best bid, best ask and last price for every symbol in the reference data, sorted by symbol, in one call. Access: Authenticated key (read-only keys included).
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
| none | — | — | No query parameters |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "books": [...], "count": n } |
One entry per symbol: { symbol, tick_decimals, ts_ns, seq, best_bid, best_ask, last_price, last_price_ticks }. A side with no orders is null; best_bid and best_ask are levels as above |
Errors
| Code | Status | When |
|---|---|---|
ENGINE_TIMEOUT |
503 |
The engine did not reply in time. The list is all or nothing: one book that cannot be had fails the call |
History¶
Base path: /api/v1/history. Trading endpoints are scoped to the caller's
gateway id; public history accepts any valid key.
GET /api/v1/history/orders¶
Purpose: return the caller gateway's order lifecycle events.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
symbol |
Symbol |
no | Filter by symbol |
event_type |
Str |
no | Filter by event type |
date |
Date |
no | Single trading date |
from |
DateTime |
no | Range start |
to |
DateTime |
no | Range end |
limit |
Int |
no | Page size |
after |
Cursor |
no | Opaque page cursor |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
paginated list envelope | Order lifecycle events for the caller gateway |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
Query invalid |
READ_ONLY |
403 |
Read-only credential used |
STATS_DB |
503 |
pm-stats database missing |
GET /api/v1/history/orders/{order_id}¶
Purpose: return the full lifecycle of one order for the caller gateway.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
order_id |
Str |
yes (path) | Order to fetch |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "order_id", "count", "events" } |
Full lifecycle for one order |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
Path invalid |
READ_ONLY |
403 |
Read-only credential used |
STATS_DB |
503 |
pm-stats database missing |
GET /api/v1/history/fills¶
Purpose: return fill events for the caller gateway.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
symbol |
Symbol |
no | Filter by symbol |
date |
Date |
no | Trading date |
from |
DateTime |
no | Range start |
to |
DateTime |
no | Range end |
limit |
Int |
no | Page size |
after |
Cursor |
no | Opaque page cursor |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
paginated list envelope | Fill history for the caller gateway |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
A body field, query parameter, header or history filter failed validation; field names it. reject_code is one of MISSING_FIELD, UNSUPPORTED_FIELD, INVALID_VALUE, TICK_VIOLATION |
READ_ONLY |
403 |
Read-only credential used |
STATS_DB |
503 |
pm-stats database missing |
GET /api/v1/history/trades¶
Purpose: return public trade tape rows.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
symbol |
Symbol |
no | Filter by symbol |
date |
Date |
no | Trading date |
from |
DateTime |
no | Range start |
to |
DateTime |
no | Range end |
limit |
Int |
no | Page size |
after |
Cursor |
no | Opaque page cursor |
order |
asc | desc |
no | asc (default) is oldest first; desc is newest first, so limit returns the most recent trades. The cursor continues in the chosen direction |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
paginated list envelope | Public trade rows |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
A body field, query parameter, header or history filter failed validation; field names it. reject_code is one of MISSING_FIELD, UNSUPPORTED_FIELD, INVALID_VALUE, TICK_VIOLATION |
STATS_DB |
503 |
pm-stats database missing |
GET /api/v1/history/daily¶
Purpose: return daily OHLCV rows.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
symbol |
Symbol |
no | Filter by symbol |
date |
Date |
no | Single trading day |
from |
Date |
no | Range start |
to |
Date |
no | Range end |
limit |
Int |
no | Page size |
after |
Cursor |
no | Opaque page cursor |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
paginated list envelope | Daily OHLCV rows |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
A body field, query parameter, header or history filter failed validation; field names it. reject_code is one of MISSING_FIELD, UNSUPPORTED_FIELD, INVALID_VALUE, TICK_VIOLATION |
STATS_DB |
503 |
pm-stats database missing |
GET /api/v1/history/price-snapshots¶
Purpose: return intraday price snapshots.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
symbol |
Symbol |
yes | Instrument symbol |
date |
Date |
no | Trading day |
from |
DateTime |
no | Range start |
to |
DateTime |
no | Range end |
limit |
Int |
no | Page size |
after |
Cursor |
no | Opaque page cursor |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
paginated list envelope | Intraday mid/bid/ask series |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
A body field, query parameter, header or history filter failed validation; field names it. reject_code is one of MISSING_FIELD, UNSUPPORTED_FIELD, INVALID_VALUE, TICK_VIOLATION |
STATS_DB |
503 |
pm-stats database missing |
GET /api/v1/history/index-daily¶
Purpose: return daily index OHLC rows.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
index_id |
IndexId |
no | Index id |
date |
Date |
no | Single trading day |
from |
Date |
no | Range start |
to |
Date |
no | Range end |
limit |
Int |
no | Page size |
after |
Cursor |
no | Opaque page cursor |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
paginated list envelope | Daily index rows |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
A body field, query parameter, header or history filter failed validation; field names it. reject_code is one of MISSING_FIELD, UNSUPPORTED_FIELD, INVALID_VALUE, TICK_VIOLATION |
STATS_DB |
503 |
pm-stats database missing |
GET /api/v1/history/index-snapshots¶
Purpose: return intraday index snapshots.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
index_id |
IndexId |
yes | Index id |
date |
Date |
no | Trading day |
from |
DateTime |
no | Range start |
to |
DateTime |
no | Range end |
limit |
Int |
no | Page size |
after |
Cursor |
no | Opaque page cursor |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
paginated list envelope | Intraday index series |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
A body field, query parameter, header or history filter failed validation; field names it. reject_code is one of MISSING_FIELD, UNSUPPORTED_FIELD, INVALID_VALUE, TICK_VIOLATION |
STATS_DB |
503 |
pm-stats database missing |
GET /api/v1/history/indexes¶
Purpose: list index ids with recorded data.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
date |
Date |
no | Filter by trading day |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "index_ids": [...], "count": N } |
Index ids with data |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
A body field, query parameter, header or history filter failed validation; field names it. reject_code is one of MISSING_FIELD, UNSUPPORTED_FIELD, INVALID_VALUE, TICK_VIOLATION |
STATS_DB |
503 |
pm-stats database missing |
GET /api/v1/history/index-events¶
Purpose: return index structural and audit events from pm-index.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
index_id |
IndexId |
yes | Index id |
from |
Timestamp |
no | ISO-8601 or YYYY-MM-DD, like every history range (no offset = exchange time); default 30 days ago |
to |
Timestamp |
no | Same form; default now |
types |
Str |
no | Repeatable event-type filter |
max_records |
Int |
no | Reply cap |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "events": [...], "count": N } |
Structural audit log |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
A body field, query parameter, header or history filter failed validation; field names it. reject_code is one of MISSING_FIELD, UNSUPPORTED_FIELD, INVALID_VALUE, TICK_VIOLATION |
INDEX_ERROR |
502 |
pm-index rejected the request |
INDEX_TIMEOUT |
503 |
pm-index did not reply in time |
Admin REST¶
Base path: /api/v1/admin. ADMIN role required.
POST /api/v1/admin/session/transition¶
Purpose: request a session-phase transition from the engine.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
to_state |
SessionState |
yes | Requested session phase |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{"requested_state": "...", "status": "APPLIED", "command_id": "..."} |
Transition applied |
409 Conflict |
error envelope | The engine refused the transition |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
Invalid to_state |
READ_ONLY |
403 |
A read-only key used on a gateway-scoped endpoint |
ROLE_DENIED |
403 |
Caller is not ADMIN |
TRANSITION_REJECTED |
409 |
Engine rejected the transition |
RATE_LIMIT |
429 |
Per-key write limit exceeded; retry after a short pause |
ENGINE_TIMEOUT |
503 |
Engine did not reply in time |
GET /api/v1/admin/sim¶
Purpose: the market simulator's latest state and the model's true (hidden)
fair value per symbol, from pm-market-sim. For instructors: students see
only prices, never these values.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
| none | — | — | No query parameters |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "available": true, "state": {...}, "values": {...} } |
available is false, and state/values null, when the simulator is not running |
Errors
| Code | Status | When |
|---|---|---|
READ_ONLY |
403 |
A read-only key used on a gateway-scoped endpoint |
ROLE_DENIED |
403 |
The key's gateway is not ADMIN |
GET /api/v1/admin/gateways¶
Purpose: list configured gateways and live connection state.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
| none | — | — | No query parameters |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "gateways": [...] } |
Gateway roster and connection status |
Errors
| Code | Status | When |
|---|---|---|
READ_ONLY |
403 |
A read-only key used on a gateway-scoped endpoint |
ROLE_DENIED |
403 |
Caller is not ADMIN |
ENGINE_TIMEOUT |
503 |
Engine did not reply in time |
POST /api/v1/admin/gateways/{gateway_id}/disconnect¶
Purpose: forcibly disconnect one gateway.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
gateway_id |
GatewayId |
yes (path) | Gateway to disconnect (case-insensitive) |
reason |
Str |
no | Body, optional: note recorded in the engine log (default admin disconnect) |
Reply
| Status | Shape | Meaning |
|---|---|---|
202 Accepted |
{ "gateway_id": "...", "status": "PENDING_DISCONNECT" } |
Disconnect sent; not awaited — the roster in GET /admin/gateways shows the result |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
A body field, query parameter, header or history filter failed validation; field names it. reject_code is one of MISSING_FIELD, UNSUPPORTED_FIELD, INVALID_VALUE, TICK_VIOLATION |
READ_ONLY |
403 |
A read-only key used on a gateway-scoped endpoint |
ROLE_DENIED |
403 |
Caller is not ADMIN |
RATE_LIMIT |
429 |
Per-key write limit exceeded; retry after a short pause |
POST /api/v1/admin/symbols/{symbol}/halt¶
Purpose: halt one symbol through the engine's circuit-breaker path. The body is optional.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
symbol |
Symbol |
yes (path) | Symbol to halt (case-insensitive) |
level |
Str |
no | A rung of the symbol's ladder; omit for an indefinite halt until resumed |
reason |
Str |
no | Optional note |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
halt ack | The symbol is halted |
409 Conflict |
error envelope | The engine refused the halt |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
Body invalid |
READ_ONLY |
403 |
A read-only key used on a gateway-scoped endpoint |
ROLE_DENIED |
403 |
Caller is not ADMIN |
HALT_REJECTED |
409 |
Engine refused (unknown symbol, no circuit breaker, unknown level); message is its reason |
RATE_LIMIT |
429 |
Per-key write limit exceeded; retry after a short pause |
ENGINE_TIMEOUT |
503 |
Engine did not reply in time |
POST /api/v1/admin/symbols/{symbol}/resume¶
Purpose: resume one halted symbol.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
symbol |
Symbol |
yes (path) | Symbol to resume (case-insensitive) |
reason |
Str |
no | Body, optional: note for the audit log |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
resume ack | The symbol is trading again |
409 Conflict |
error envelope | The engine refused the resume |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
A body field, query parameter, header or history filter failed validation; field names it. reject_code is one of MISSING_FIELD, UNSUPPORTED_FIELD, INVALID_VALUE, TICK_VIOLATION |
READ_ONLY |
403 |
A read-only key used on a gateway-scoped endpoint |
ROLE_DENIED |
403 |
Caller is not ADMIN |
RESUME_REJECTED |
409 |
Engine refused (for example the symbol is not halted); message is its reason |
RATE_LIMIT |
429 |
Per-key write limit exceeded; retry after a short pause |
ENGINE_TIMEOUT |
503 |
Engine did not reply in time |
GET /api/v1/admin/risk/state¶
Purpose: return live per-symbol risk state.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
| none | — | — | No query parameters |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "symbols": [...] } |
Current collar and circuit-breaker state (one object per symbol) |
Errors
| Code | Status | When |
|---|---|---|
READ_ONLY |
403 |
A read-only key used on a gateway-scoped endpoint |
ROLE_DENIED |
403 |
Caller is not ADMIN |
ENGINE_TIMEOUT |
503 |
Engine did not reply in time |
GET /api/v1/admin/gateways/{gateway_id}/orders¶
Purpose: one gateway's resting and recently finished orders as the engine holds
them: the authoritative view. GET /admin/orders is the gateway's own read
model across all gateways, faster and filterable but limited to what this
gateway has seen since it started.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
gateway_id |
GatewayId |
yes (path) | Whose orders (case-insensitive) |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "orders": [...], "count": N } |
Same shape as GET /orders |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
A body field, query parameter, header or history filter failed validation; field names it. reject_code is one of MISSING_FIELD, UNSUPPORTED_FIELD, INVALID_VALUE, TICK_VIOLATION |
READ_ONLY |
403 |
A read-only key used on a gateway-scoped endpoint |
ROLE_DENIED |
403 |
Caller is not ADMIN |
ENGINE_TIMEOUT |
503 |
Engine did not reply in time |
GET /api/v1/admin/symbols/{symbol}/orders¶
Purpose: every resting order on one symbol across all gateways, each with its
gateway_id, ordered by price then arrival (time priority). With price, only
that level: the per-order composition behind the book's aggregated levels.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
symbol |
Symbol |
yes (path) | Instrument (case-insensitive) |
price |
Price |
no | Query: only this price level |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "symbol", "price"?, "rejected": false, "orders": [...], "count": N } |
The resting orders |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
A body field, query parameter, header or history filter failed validation; field names it. reject_code is one of MISSING_FIELD, UNSUPPORTED_FIELD, INVALID_VALUE, TICK_VIOLATION |
READ_ONLY |
403 |
A read-only key used on a gateway-scoped endpoint |
ROLE_DENIED |
403 |
Caller is not ADMIN |
QUERY_REJECTED |
409 |
Engine refused (for example Unknown symbol: XYZ) |
ENGINE_TIMEOUT |
503 |
Engine did not reply in time |
GET /api/v1/admin/orders¶
Purpose: return the cross-gateway active-order table.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
symbol |
Symbol |
no | Filter by symbol |
gateway_id |
GatewayId |
no | Filter by gateway |
status |
OrderStatus |
no | Filter by status |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "count": N, "orders": [...], "retention_sec": N } |
Current cross-gateway active orders |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
A body field, query parameter, header or history filter failed validation; field names it. reject_code is one of MISSING_FIELD, UNSUPPORTED_FIELD, INVALID_VALUE, TICK_VIOLATION |
READ_ONLY |
403 |
A read-only key used on a gateway-scoped endpoint |
ROLE_DENIED |
403 |
Caller is not ADMIN |
GET /api/v1/admin/orders/{order_id}/audit¶
Purpose: return the full cross-gateway lifecycle of one order.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
order_id |
Str |
yes (path) | Order to fetch |
limit |
Int |
no | Maximum number of events |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "order_id", "count", "events" } |
Full audited lifecycle |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
A body field, query parameter, header or history filter failed validation; field names it. reject_code is one of MISSING_FIELD, UNSUPPORTED_FIELD, INVALID_VALUE, TICK_VIOLATION |
READ_ONLY |
403 |
A read-only key used on a gateway-scoped endpoint |
ROLE_DENIED |
403 |
Caller is not ADMIN |
UNKNOWN_ORDER |
404 |
No audited events for the order |
AUDIT_INDEX_UNAVAILABLE |
503 |
No audit index available |
GET /api/v1/admin/gateways/{gateway_id}/positions¶
Purpose: return net position and average cost for any one gateway (not
just the caller's own) -- the admin counterpart to
GET /api/v1/positions, which serves the same engine
round trip for the caller enriched with prices and P&L. It is the REST
equivalent of pm-alf-console's and pm-alf-gwy's POS|GW=<gateway_id>
command -- all three read the same system.position_request /
system.position_snapshot.<gateway_id> pair, so they always agree.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
gateway_id |
GatewayId |
yes (path) | Gateway to query (any gateway, case-insensitive) |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "gateway_id", "count", "positions": [{ "symbol", "net_qty", "avg_cost" }, ...] } |
Net position and average cost per symbol for the target gateway |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
A body field, query parameter, header or history filter failed validation; field names it. reject_code is one of MISSING_FIELD, UNSUPPORTED_FIELD, INVALID_VALUE, TICK_VIOLATION |
READ_ONLY |
403 |
A read-only key used on a gateway-scoped endpoint |
ROLE_DENIED |
403 |
Caller is not ADMIN |
ENGINE_TIMEOUT |
503 |
Engine did not reply in time |
POST /api/v1/admin/symbols/{symbol}/cancel-orders¶
Purpose: cancel all resting exposure on one symbol, across every gateway.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
symbol |
Symbol |
yes (path) | Symbol to clear (case-insensitive) |
reason |
Str |
no | Body, optional: note for the audit log |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
kill-switch ack | What the engine cancelled |
409 Conflict |
error envelope | The engine refused the command |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
A body field, query parameter, header or history filter failed validation; field names it. reject_code is one of MISSING_FIELD, UNSUPPORTED_FIELD, INVALID_VALUE, TICK_VIOLATION |
READ_ONLY |
403 |
A read-only key used on a gateway-scoped endpoint |
ROLE_DENIED |
403 |
Caller is not ADMIN |
KILL_SWITCH_REJECTED |
409 |
Engine refused; message is its reason |
RATE_LIMIT |
429 |
Per-key write limit exceeded; retry after a short pause |
ENGINE_TIMEOUT |
503 |
Engine did not reply in time |
POST /api/v1/admin/gateways/{gateway_id}/kill-switch¶
Purpose: cancel all resting exposure for one target gateway, without disconnecting it.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
gateway_id |
GatewayId |
yes (path) | Gateway whose orders and quotes to cancel (case-insensitive) |
symbol |
Symbol |
no | Query: only this instrument; omit for all |
reason |
Str |
no | Body, optional: note for the audit log |
The target need not be connected, and the audit trail names the ADMIN.
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
kill-switch ack | What the engine cancelled; symbol is echoed when the cancel was scoped |
409 Conflict |
error envelope | The engine refused the command |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
A body field, query parameter, header or history filter failed validation; field names it. reject_code is one of MISSING_FIELD, UNSUPPORTED_FIELD, INVALID_VALUE, TICK_VIOLATION |
READ_ONLY |
403 |
A read-only key used on a gateway-scoped endpoint |
ROLE_DENIED |
403 |
Caller is not ADMIN |
KILL_SWITCH_REJECTED |
409 |
Engine refused; message is its reason |
RATE_LIMIT |
429 |
Per-key write limit exceeded; retry after a short pause |
ENGINE_TIMEOUT |
503 |
Engine did not reply in time |
DELETE /api/v1/admin/gateways/{gateway_id}/quotes/{symbol}¶
Purpose: withdraw one participant's two-sided quote on one symbol, leaving its
other orders. Runs through the ADMIN kill switch (quotes_only), so it works
whether or not that gateway is connected and the audit trail names the ADMIN.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
gateway_id |
GatewayId |
yes (path) | Whose quote (case-insensitive) |
symbol |
Symbol |
yes (path) | Instrument (case-insensitive) |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
kill-switch ack, quotes_only: true |
The quote legs cancelled; cancelled_quotes is 0 when there was no quote |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
A body field, query parameter, header or history filter failed validation; field names it. reject_code is one of MISSING_FIELD, UNSUPPORTED_FIELD, INVALID_VALUE, TICK_VIOLATION |
READ_ONLY |
403 |
A read-only key used on a gateway-scoped endpoint |
ROLE_DENIED |
403 |
Caller is not ADMIN |
KILL_SWITCH_REJECTED |
409 |
Engine refused; message is its reason |
RATE_LIMIT |
429 |
Per-key write limit exceeded; retry after a short pause |
ENGINE_TIMEOUT |
503 |
Engine did not reply in time |
POST /api/v1/admin/market/halt¶
Purpose: halt every symbol (halt source ADMIN) and cancel every market-maker
quote. Resting orders stay on the book; POST /admin/market/kill-switch
cancels those.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
| none | — | — | No arguments |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "accepted": true, "halted_symbols": 3, "halted_symbol_ids": [...], "cancelled_quotes": 4, ... } |
What the engine halted |
Errors
| Code | Status | When |
|---|---|---|
READ_ONLY |
403 |
A read-only key used on a gateway-scoped endpoint |
ROLE_DENIED |
403 |
Caller is not ADMIN |
HALT_REJECTED |
409 |
Engine refused; message is its reason |
RATE_LIMIT |
429 |
Per-key write limit exceeded; retry after a short pause |
ENGINE_TIMEOUT |
503 |
Engine did not reply in time |
POST /api/v1/admin/market/resume¶
Purpose: lift the halt of POST /admin/market/halt on every symbol.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
| none | — | — | No arguments |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "accepted": true, "resumed_symbols": 3, "resumed_symbol_ids": [...], ... } |
What the engine resumed |
Errors
| Code | Status | When |
|---|---|---|
READ_ONLY |
403 |
A read-only key used on a gateway-scoped endpoint |
ROLE_DENIED |
403 |
Caller is not ADMIN |
RESUME_REJECTED |
409 |
Engine refused; message is its reason |
RATE_LIMIT |
429 |
Per-key write limit exceeded; retry after a short pause |
ENGINE_TIMEOUT |
503 |
Engine did not reply in time |
GET /api/v1/admin/market/volume¶
Purpose: today's traded quantity, value and trade count per symbol, and the venue totals.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
| none | — | — | No query parameters |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "symbols": [{ "symbol", "qty", "value", "trades" }], "count": 2, "total_qty", "total_value", "total_trades" } |
Today's volume; value is display money |
Errors
| Code | Status | When |
|---|---|---|
READ_ONLY |
403 |
A read-only key used on a gateway-scoped endpoint |
ROLE_DENIED |
403 |
Caller is not ADMIN |
ENGINE_TIMEOUT |
503 |
Engine did not reply in time |
POST /api/v1/admin/symbols/{symbol}/reopen¶
Purpose: force one symbol to uncross now and clear any halt in one step. Without
price it opens at the natural equilibrium; with one, at that operator price,
for when a failed auction left no equilibrium. dry_run reports the indicative
print without changing anything.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
symbol |
Symbol |
yes (path) | Instrument (case-insensitive) |
price |
Price |
no | Body: operator opening price, on the tick grid (refused, not rounded, otherwise) |
dry_run |
Bool |
no | Body: report the indicative print only; default false |
reason |
Str |
no | Body: note for the audit log |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "accepted": true, "symbol", "dry_run", "indicative_price", "indicative_qty", "surplus", "imbalance_side"?, "printed_price", "traded_qty" } |
The uncross result; printed_price is null on a dry run |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
A body field, query parameter, header or history filter failed validation; field names it. reject_code is one of MISSING_FIELD, UNSUPPORTED_FIELD, INVALID_VALUE, TICK_VIOLATION |
READ_ONLY |
403 |
A read-only key used on a gateway-scoped endpoint |
ROLE_DENIED |
403 |
Caller is not ADMIN |
REOPEN_REJECTED |
409 |
Engine refused (for example the symbol is not halted); message is its reason |
RATE_LIMIT |
429 |
Per-key write limit exceeded; retry after a short pause |
ENGINE_TIMEOUT |
503 |
Engine did not reply in time |
SYMBOLS_NOT_READY |
503 |
price given before the symbol's tick precision reached the gateway |
POST /api/v1/admin/market/kill-switch¶
Purpose: cancel all resting exposure across every gateway and symbol.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
reason |
Str |
no | Optional note |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
kill-switch ack | What the engine cancelled |
409 Conflict |
error envelope | The engine refused the command |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
A body field, query parameter, header or history filter failed validation; field names it. reject_code is one of MISSING_FIELD, UNSUPPORTED_FIELD, INVALID_VALUE, TICK_VIOLATION |
READ_ONLY |
403 |
A read-only key used on a gateway-scoped endpoint |
ROLE_DENIED |
403 |
Caller is not ADMIN |
KILL_SWITCH_REJECTED |
409 |
Engine refused; message is its reason |
RATE_LIMIT |
429 |
Per-key write limit exceeded; retry after a short pause |
ENGINE_TIMEOUT |
503 |
Engine did not reply in time |
POST /api/v1/admin/indexes/{index_id}/rebalance¶
Purpose: rebalance one configured index through pm-index.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
index_id |
IndexId |
yes (path) | Index to rebalance |
updates |
List<RebalanceUpdate> |
yes | Shares-outstanding updates |
Reply
| Status | Shape | Meaning |
|---|---|---|
200 OK |
rebalance ack | Update applied |
409 Conflict |
error envelope | Rebalance rejected |
Errors
| Code | Status | When |
|---|---|---|
VALIDATION |
400 |
Batch body invalid |
READ_ONLY |
403 |
A read-only key used on a gateway-scoped endpoint |
ROLE_DENIED |
403 |
Caller is not ADMIN |
REBALANCE_REJECTED |
409 |
pm-index rejected the request |
RATE_LIMIT |
429 |
Per-key write limit exceeded; retry after a short pause |
INDEX_TIMEOUT |
503 |
pm-index did not reply in time |
POST /api/v1/admin/reference/reload¶
Purpose: request a reference-data reload. The current engine rejects this request because the active configuration cannot be both reloaded and reconstructed from the audit trail. Deploy a compiled config artifact and restart the engine to apply configuration changes.
Arguments
| Name | Type | Req | Description |
|---|---|---|---|
| none | — | — | No request body |
Reply (if an engine accepts the request)
| Status | Shape | Meaning |
|---|---|---|
200 OK |
{ "status": "RELOADED", "config_version": "..." } |
Reload accepted by an engine that supports it |
409 Conflict |
error envelope | RELOAD_REJECTED: the current engine rejects reloads that cannot be recorded |
Errors
| Code | Status | When |
|---|---|---|
READ_ONLY |
403 |
A read-only key used on a gateway-scoped endpoint |
ROLE_DENIED |
403 |
Caller is not ADMIN |
RELOAD_REJECTED |
409 |
Reload would change the symbol or index set |
RATE_LIMIT |
429 |
Per-key write limit exceeded; retry after a short pause |
ENGINE_TIMEOUT |
503 |
Engine did not reply in time |
Operational notes¶
- Read-only dashboard keys can call reference and public history endpoints, but not trading or admin write endpoints.
order_retention_secbounds the live order cache, the privateorders.snapshotframe, andGET /api/v1/admin/orders.market_data_cache_secbounds the market-data stream cache that backs the snapshot-on-subscribe,snapshot, andresumecontrols onWS /api/v1/ws/market-data(latestbook/depth/auctionsnapshots are kept regardless of age; only thetradestail is bounded).- WebSocket streams are specified in the WebSocket API Reference; this appendix is REST only.