REST Endpoints

The Trading API provides REST endpoints for order management, position tracking, trade history, account information, and market data. All endpoints return JSON responses.

Base URL

The Gateway exposes REST and WebSocket on the same host and port (default http://localhost:8080). Every REST endpoint is versioned under the /v1/ prefix — for example GET http://localhost:8080/v1/orders.

The only unversioned paths are the Kubernetes-style health probes served at the host root:

  • GET /health — overall health
  • GET /livez — liveness probe
  • GET /readyz — readiness probe

The same Base URL applies whether the Gateway is running in backtest, paper, or live mode — mode selection is a server-side GATEWAY_PROVIDER / GATEWAY_MODE concern, not a client URL change.

Authentication

Include your API credentials in the request headers as configured during connection setup. See the Connection Guide for details.

Response Format

All successful responses return JSON. Error responses use a standard ApiError body:

{
  "code": "ORDER_NOT_FOUND",
  "message": "Order not found: abc123",
  "details": { "resource": "order", "id": "abc123" }
}

code is the machine-readable discriminator (see the Error Codes and Retry Strategy section below), message is a human-readable description, and details carries per-code structured context (may be absent).

Common Status Codes

StatusDescription
200Success
201Created (for POST /orders)
400Invalid request parameters
401Missing or invalid API key
404Resource not found
409Conflict (e.g., order not modifiable, reset cooldown)
422Order rejected by the broker
429Rate limit exceeded
500Internal error
501Operation not supported by the configured provider
502Upstream provider returned an error
503Provider unavailable or Gateway shutting down

Error Codes and Retry Strategy

Every error response carries a machine-readable code field. Client libraries should branch on code, not on message.

Canonical ErrorCode enumeration

codeHTTP statusMeaningRetryable?
INVALID_REQUEST400Malformed request body or missing fieldNo
INVALID_VALUE400A specific field has a disallowed value (details.field, details.provided)No
VALIDATION_ERROR400Multi-field validation failure (details carries the validator report)No
UNAUTHORIZED401API key missing or invalidNo
ORDER_NOT_FOUND404No order with the given idNo
POSITION_NOT_FOUND404No position with the given idNo
SYMBOL_NOT_FOUND404Symbol is not supported by the active providerNo
OCO_GROUP_NOT_FOUND404No OCO group with the given idNo
ORDER_NOT_MODIFIABLE409Order already filled, cancelled, or otherwise terminalNo
RESET_COOLDOWN409Circuit-breaker reset called within the 60s cooldownNo for this instance — the cooldown is server-enforced; wait out the 60 seconds before the next reset attempt
ORDER_REJECTED422Broker rejected the order — see details.reject_codeUsually no; only retry when the rejection is explicitly transient (see below)
RATE_LIMITED429Gateway or provider rate limit hitYes — honour details.retry_after_seconds
INTERNAL_ERROR500Unexpected server-side failureNo
UNSUPPORTED_OPERATION501The active provider does not implement this featureNo
PROVIDER_ERROR502Upstream broker returned an errorYes — exponential backoff
SHUTTING_DOWN503Gateway process is terminating, new orders are refusedNo for this instance
PROVIDER_UNAVAILABLE503Upstream broker is unreachable or the adapter circuit breaker is openYes — exponential backoff

Rate Limiting (429)

The Gateway returns RATE_LIMITED when either its own ingress limits or the upstream provider's limits are hit. The details object carries retry hints:

{
  "code": "RATE_LIMITED",
  "message": "Rate limit exceeded",
  "details": {
    "retry_after_seconds": 30,
    "reset_at": "2025-01-15T10:31:00Z"
  }
}
  • retry_after_seconds — how long to wait before the next request (integer seconds, may be absent if the provider didn't supply a hint).
  • reset_at — absolute UTC timestamp when the window resets (ISO-8601, may be absent).

Prefer retry_after_seconds when both are present; fall back to computing the delay from reset_at otherwise. Where neither is present, fall back to the exponential backoff schedule below. Add jitter to whichever value you pick to avoid thundering herds.

Note: the Gateway does not currently set an HTTP Retry-After header — this is a deliberate deviation from RFC 6585 / 7231. Clients that rely on library-level header-based retry logic should be configured to read the JSON body instead.

Retryable codes are RATE_LIMITED, PROVIDER_UNAVAILABLE, and PROVIDER_ERROR. ORDER_REJECTED is only retryable when the rejection is explicitly transient — case-insensitive self-trade or wash in the message field (or in a passthrough details.reject_code) are the two the Gateway classifies as retryable. Everything else is terminal — surface the error to the caller, do not retry blindly.

Retry Schedule

For retryable errors (including 429 with no explicit hint and 503 PROVIDER_UNAVAILABLE), use exponential backoff with full jitter:

SettingRecommended value
Initial delay500 ms
Factor2× per failed attempt
Maximum delay30 seconds
Jitterfull (delay = random_between(0, current_delay))
Max attempts5 (or 60 seconds total elapsed, whichever is shorter)
Per-request timeout10 seconds

After the max-attempts budget is exhausted, surface the error to the strategy — do not keep retrying indefinitely.

Idempotent retries for POST endpoints

POST /v1/orders is not idempotent by default. If the original request reached the Gateway and an upstream timeout or connection drop happened after the order was placed but before the response landed, a naive retry creates a duplicate order.

Always supply a stable client_order_id when retrying a POST /v1/orders call:

  1. Generate the client_order_id before the first attempt (e.g. a UUID).
  2. Reuse the same client_order_id on every retry of the same logical order.
  3. On an ambiguous failure, query GET /v1/orders?client_order_id=<id> before retrying — if the order already exists, reconcile rather than re-submit.

DELETE and PATCH endpoints are naturally idempotent by target id; the same retry schedule applies to them without extra safeguards.

ORDER_REJECTED details

422 responses carry the broker's rejection reason in details.reject_code. The Gateway normalises a small set of canonical values; broker-specific strings (for example OANDA's INSUFFICIENT_MARGIN, MARKET_HALTED) pass through unchanged for codes that aren't in the canonical list.

Canonical reject_code values:

reject_codeMeaning
INSUFFICIENT_FUNDSAccount lacks buying power / margin for this order
MARKET_CLOSEDVenue is closed for the instrument
INVALID_QUANTITYQuantity is zero, negative, or below the venue's minimum lot size
INVALID_SYMBOLSymbol rejected by the broker (not available on this account / tier)
{
  "code": "ORDER_REJECTED",
  "message": "Order rejected: insufficient funds for order",
  "details": {
    "reject_code": "INSUFFICIENT_FUNDS"
  }
}

Orders

POST /v1/orders

Submit a new order.

Request Body (OrderRequest)

FieldTypeRequiredDescription
symbolstringYesTrading symbol (e.g., "AAPL", "EUR/USD")
sidestringYesBUY or SELL
quantitystringYesOrder quantity (decimal string)
order_typestringYesMARKET, LIMIT, STOP, STOP_LIMIT, TRAILING_STOP
limit_pricestringNoRequired for LIMIT and STOP_LIMIT orders
stop_pricestringNoRequired for STOP and STOP_LIMIT orders
time_in_forcestringNoGTC (default), DAY, IOC, FOK
client_order_idstringNoClient-provided ID for idempotency
stop_lossstringNoStop loss price for bracket order
take_profitstringNoTake profit price for bracket order
reduce_onlybooleanNoIf true, can only reduce position
post_onlybooleanNoIf true, reject if would immediately fill
oco_group_idstringNoLink orders for one-cancels-other
hiddenbooleanNoIf true, order not visible in order book
display_quantitystringNoVisible quantity for iceberg orders
leveragestringNoLeverage multiplier
margin_modeMarginModeNoCROSS or ISOLATED margin mode
position_idstringNoTarget position ID (for hedging accounts)
trailing_distancestringNoDistance for TRAILING_STOP orders
trailing_typeTrailingTypeNoABSOLUTE or PERCENT trailing type

Example Request

{
  "symbol": "AAPL",
  "side": "BUY",
  "quantity": "100",
  "order_type": "LIMIT",
  "limit_price": "150.00",
  "time_in_force": "GTC"
}

Response (201 Created)

Returns an OrderHandle with the assigned order ID:

{
  "id": "ord_abc123",
  "status": "PENDING",
  "client_order_id": null
}

Error Responses

StatuscodeWhen
400INVALID_REQUEST / INVALID_VALUE / VALIDATION_ERRORMissing field, bad value (e.g. negative quantity), multi-field validator failure
401UNAUTHORIZEDAPI key missing or invalid
422ORDER_REJECTEDBroker rejected — see details.reject_code (e.g. INSUFFICIENT_FUNDS, MARKET_CLOSED)
429RATE_LIMITEDIngress or upstream rate limit — honour details.retry_after_seconds
501UNSUPPORTED_OPERATIONActive provider does not support this order type
502PROVIDER_ERRORUpstream broker returned an error — retry with exponential backoff
503PROVIDER_UNAVAILABLE / SHUTTING_DOWNBroker unreachable or Gateway is shutting down

GET /v1/orders

List open orders.

Query Parameters

ParameterTypeDescription
symbolstringFilter by symbol
statusstring[]Filter by status (multiple allowed)
sidestringFilter by side (BUY or SELL)
client_order_idstringFilter by client order ID
oco_group_idstringFilter by OCO group ID
sincedatetimeFilter orders created after this time
untildatetimeFilter orders created before this time
limitintegerMaximum number of orders to return

Response (200 OK)

[
  {
    "id": "ord_abc123",
    "symbol": "AAPL",
    "side": "BUY",
    "order_type": "LIMIT",
    "quantity": "100",
    "filled_quantity": "0",
    "remaining_quantity": "100",
    "limit_price": "150.00",
    "status": "OPEN",
    "time_in_force": "GTC",
    "created_at": "2025-01-15T10:30:00Z",
    "updated_at": "2025-01-15T10:30:00Z"
  }
]

GET /v1/orders/{order_id}

Get a specific order by ID.

Path Parameters

ParameterTypeDescription
order_idstringThe order ID

Response (200 OK)

Returns the full Order object (same schema as list response items).

Error Responses

StatuscodeWhen
401UNAUTHORIZEDAPI key missing or invalid
404ORDER_NOT_FOUNDNo order with the given order_id
429RATE_LIMITEDRate limit hit — retry after details.retry_after_seconds
502PROVIDER_ERRORUpstream broker returned an error
503PROVIDER_UNAVAILABLEBroker unreachable

PATCH /v1/orders/{order_id}

Modify an existing order. Only non-null fields will be updated.

Path Parameters

ParameterTypeDescription
order_idstringThe order ID

Request Body (ModifyOrderRequest)

FieldTypeDescription
quantitystringNew total quantity (must be >= filled_quantity)
limit_pricestringNew limit price
stop_pricestringNew stop price
stop_lossstringNew stop loss price
take_profitstringNew take profit price
trailing_distancestringNew trailing distance

Example Request

{
  "limit_price": "151.00"
}

Response (200 OK)ModifyOrderResult

FieldTypeRequiredDescription
orderOrderYesThe modified (or replacement) order.
previous_order_idstringNoPrevious order ID if the provider used cancel-replace (e.g. Alpaca). null when modified in-place.

Example

{
  "order": {
    "id": "ord_abc123",
    "symbol": "AAPL",
    "side": "BUY",
    "order_type": "LIMIT",
    "quantity": "100",
    "filled_quantity": "0",
    "remaining_quantity": "100",
    "limit_price": "151.00",
    "status": "OPEN",
    "time_in_force": "GTC",
    "created_at": "2025-01-15T10:30:00Z",
    "updated_at": "2025-01-15T10:31:00Z"
  },
  "previous_order_id": null
}

Error Responses

StatuscodeWhen
400INVALID_REQUEST / INVALID_VALUEMissing or invalid field in the modify payload
401UNAUTHORIZEDAPI key missing or invalid
404ORDER_NOT_FOUNDNo order with the given order_id
409ORDER_NOT_MODIFIABLEOrder already filled, cancelled, or otherwise terminal
429RATE_LIMITEDRate limit hit — honour details.retry_after_seconds
501UNSUPPORTED_OPERATIONProvider does not support order modification
502PROVIDER_ERRORUpstream broker returned an error
503PROVIDER_UNAVAILABLEBroker unreachable

DELETE /v1/orders/{order_id}

Cancel a specific order.

Path Parameters

ParameterTypeDescription
order_idstringThe order ID

Response (200 OK)

{
  "success": true,
  "order": {
    "id": "ord_abc123",
    "status": "CANCELLED",
    ...
  }
}

Error Responses

StatuscodeWhen
401UNAUTHORIZEDAPI key missing or invalid
404ORDER_NOT_FOUNDNo order with the given order_id
409ORDER_NOT_MODIFIABLEOrder already filled or in a terminal state
429RATE_LIMITEDRate limit hit
502PROVIDER_ERRORUpstream broker returned an error
503PROVIDER_UNAVAILABLEBroker unreachable

DELETE /v1/orders

Cancel all open orders.

Query Parameters

ParameterTypeDescription
symbolstringFilter by symbol (optional, omit to cancel all)

Response (200 OK)

{
  "cancelled_count": 5,
  "failed_count": 0,
  "failed_order_ids": null
}

GET /v1/orders/history

Get historical orders (filled, cancelled, rejected, expired).

Query Parameters

Same as GET /v1/orders.

Response (200 OK)

Returns an array of Order objects.


Positions

GET /v1/positions

Get all open positions.

Query Parameters

ParameterTypeDescription
symbolstringFilter by symbol (optional)

Response (200 OK)

[
  {
    "id": "pos_xyz789",
    "symbol": "AAPL",
    "side": "LONG",
    "quantity": "100",
    "average_entry_price": "150.25",
    "current_price": "151.50",
    "unrealized_pnl": "125.00",
    "realized_pnl": "0",
    "opened_at": "2025-01-15T10:30:01Z",
    "updated_at": "2025-01-15T11:00:00Z"
  }
]

GET /v1/positions/{position_id}

Get a specific position by ID.

Path Parameters

ParameterTypeDescription
position_idstringThe position ID

Response (200 OK)

Returns the full Position object.

Error Responses

  • 404 - Position not found
  • 503 - Provider unavailable

DELETE /v1/positions/{position_id}

Close a specific position.

Path Parameters

ParameterTypeDescription
position_idstringThe position ID

Request Body (optional)

FieldTypeDescription
quantitystringQuantity to close (omit for full close)
order_typestringMARKET (default) or LIMIT
limit_pricestringRequired if order_type is LIMIT
cancel_associated_ordersbooleanCancel SL/TP orders (default: true)

Response (200 OK)

Returns an OrderHandle for the close order.

Error Responses

  • 404 - Position not found
  • 503 - Provider unavailable

DELETE /v1/positions

Close all open positions.

Query Parameters

ParameterTypeDescription
symbolstringFilter by symbol (optional, omit to close all)

Response (200 OK)

Returns an array of OrderHandle objects for each close order created.


Trades

GET /v1/trades

Get trade/fill history.

Query Parameters

ParameterTypeDescription
symbolstringFilter by symbol
order_idstringFilter by order ID
sincedatetimeReturn trades after this time (inclusive)
untildatetimeReturn trades before this time (exclusive)
limitintegerMaximum number of trades to return

Response (200 OK)

[
  {
    "id": "trd_def456",
    "order_id": "ord_abc123",
    "symbol": "AAPL",
    "side": "BUY",
    "quantity": "100",
    "price": "150.25",
    "commission": "0.50",
    "commission_currency": "USD",
    "is_maker": false,
    "timestamp": "2025-01-15T10:30:01Z"
  }
]

Account

GET /v1/account

Get account information including balances and margin.

Response (200 OK)

{
  "balance": "10000.00",
  "equity": "10500.00",
  "margin_used": "2000.00",
  "margin_available": "8500.00",
  "unrealized_pnl": "500.00",
  "currency": "USD"
}

Error Responses

  • 400 - Invalid request
  • 503 - Provider unavailable

Market Data

GET /v1/quotes/{symbol}

Get the current quote for a symbol.

Path Parameters

ParameterTypeDescription
symbolstringSymbol (e.g., "AAPL", "BTC/USD")

Response (200 OK)

{
  "symbol": "AAPL",
  "provider": "alpaca",
  "bid": "150.20",
  "bid_size": "100",
  "ask": "150.25",
  "ask_size": "200",
  "last": "150.22",
  "timestamp": "2025-01-15T10:30:00.123Z"
}

Error Responses

  • 404 - Symbol not found
  • 503 - Provider unavailable

GET /v1/bars/{symbol}

Get historical OHLCV bars for a symbol.

Path Parameters

ParameterTypeDescription
symbolstringSymbol (e.g., "S:AAPL", "C:BTCUSD")

Query Parameters

ParameterTypeRequiredDescription
timeframeTimeframeYesBar interval: 1m, 2m, 5m, 10m, 15m, 30m, 1h, 2h, 4h, 12h, 1d, 1w
startdatetimeNoStart of date range (inclusive)
enddatetimeNoEnd of date range (inclusive)
limitintegerNoMaximum number of bars to return

Response (200 OK)

[
  {
    "symbol": "AAPL",
    "provider": "polygon",
    "timeframe": "1h",
    "timestamp": "2025-01-15T10:00:00Z",
    "open": "150.00",
    "high": "151.50",
    "low": "149.75",
    "close": "151.25",
    "volume": "1234567"
  }
]

Error Responses

  • 400 - Invalid request (bad timeframe or symbol)
  • 404 - Symbol not found
  • 503 - Provider unavailable

System

GET /v1/capabilities

Get provider capabilities describing supported features.

Response (200 OK)

{
  "supported_asset_classes": ["STOCK", "CRYPTO"],
  "supported_order_types": ["MARKET", "LIMIT", "STOP", "STOP_LIMIT"],
  "position_mode": "NETTING",
  "features": ["trailing_stop", "oco", "bracket_orders"],
  "max_leverage": "20"
}

GET /v1/status

Get connection status to the trading provider.

Response (200 OK)

{
  "connected": true,
  "latency_ms": 15,
  "last_heartbeat": "2025-01-15T10:30:00Z"
}

GET /v1/circuit-breakers

Inspect the state of the Gateway's two internal circuit breakers. Useful for diagnosing a Gateway that is refusing traffic because an upstream breaker has tripped.

The Gateway runs two breakers:

  • adapter — trips when the upstream broker adapter (Alpaca, Binance, etc.) produces repeated failures. Prevents hammering a provider that is already failing.
  • exit_order — trips when stop-loss / take-profit placement repeatedly fails. Prevents opening unprotected positions when SL/TP submission is unreliable.

Response (200 OK)CircuitBreakerStatusResponse

FieldTypeDescription
adapterCircuitBreakerSnapshotAdapter circuit breaker (provider outage detection).
exit_orderCircuitBreakerSnapshotExit-order circuit breaker (SL/TP placement failure detection).

CircuitBreakerSnapshot schema

FieldTypeDescription
statestring"open" (tripped, requests blocked) or "closed" (normal).
failure_countintegerRecent failures within the breaker's time window. >= 0.

Example

{
  "adapter": { "state": "closed", "failure_count": 0 },
  "exit_order": { "state": "open", "failure_count": 5 }
}

POST /v1/circuit-breakers/reset

Atomically reset both circuit breakers to the closed state. Intended for operator use after the upstream condition has been resolved.

Request Body — none. The endpoint resets both breakers in a single call; it does not accept a per-breaker selector.

Response (200 OK)CircuitBreakerStatusResponse

Returns the post-reset snapshot, using the same schema as GET /v1/circuit-breakers. Both state fields will be "closed" and both failure_count fields will be 0 on a successful reset.

Response (409 Conflict)ApiError with code: RESET_COOLDOWN

Returned when either breaker re-trips within 60 seconds of the last successful reset. The 60-second cooldown prevents reset loops while the upstream condition is still unstable. Clients should back off and retry once the underlying provider or SL/TP placement issue is resolved.


Health probes

The Gateway exposes three unversioned probes at the host root. These are intended for Kubernetes-style liveness and readiness checks and do not carry a /v1/ prefix.

GET /health

Overall health check. Returns 200 OK if the service is running.

Response (200 OK)

{
  "status": "ok"
}

GET /livez

Liveness probe. Returns 200 OK whenever the Gateway process is responsive, independent of provider state. Use this for Kubernetes livenessProbe.

GET /readyz

Readiness probe. Returns 200 OK only when at least one provider is configured and ready to serve traffic. Use this for Kubernetes readinessProbe.

Response (200 OK)

{
  "ready": true,
  "providers": ["alpaca-paper"],
  "provider_count": 1
}

Response (503 Service Unavailable)

Returned when no providers are configured:

{
  "ready": false,
  "providers": [],
  "provider_count": 0,
  "message": "No providers configured"
}

Related Documentation