The order placement endpoint follows the general rules for http request/response as outlined here. This article details the particulars related to order requests when using the trade/v2/orders endpoint.
Response Structure
The response to an order placement request is (of course) a standard Http response code and a flexible response structure with only the relevant fields returned.
Fully populated response structure
The structure below shows a hypothetical situation where all fields in the return structure are filled in.
{ "ErrorCode": "errorcode", "Message": "human readable message", "ModelState": { "SomeFieldName": [ "error related to this field" ] }, "Orders": [ { "ErrorInfo": { "ErrorCode": "errorcode related to first related order order", "Message": "human readable message related to error on first related order" } }, { "ErrorInfo": { "ErrorCode": "errorcode related to second related order order", "Message": "Order not placed as other order in request was rejected" } } ] }
In practice only a sub section of the entire structure is filled in.
Sample successful request, response
To explain this, please also consider the following order placement request:
{ "Uic": 899, "AssetType": "Stock", "AccountKey": "KDP9uS3Re3uwk1aBA2PdCw==", "Amount":1000, "BuySell": "Buy", "OrderType": "Market", "OrderDuration": { "DurationType": "DayOrder" }, "ManualOrder": true, "Orders": [ { "Amount":1000, "BuySell":"Sell", "OrderDuration": { "DurationType": "DayOrder" }, "OrderType":"Limit", "OrderPrice":165, "ManualOrder": true }, { "Amount":1000, "BuySell":"Sell", "OrderDuration": { "DurationType": "DayOrder" }, "OrderType":"StopIfTraded", "OrderPrice":150, "ManualOrder": true } ] }
At the time of placing this order, the current price for Vodafone was 154.22, so the order request was valid.
The return was:
200- OK: { "OrderId": "74993782", "Orders": [ { "OrderId": "74993783" }, { "OrderId": "74993784" } ] }
Types of Error Responses
Syntax or Simple Semantic errors
Simple syntactic errors will return Http response 400 and a InvalidModelState error. The InvalidModelState will include:
- "ErrorCode":"InvalidModelState"
- "Message": "The request is invalid"
- "ModelState": A structure containing one or more fields each containing an array of error messages.
For example:
In the below, we have changed the AssetType in the main part of the request object to "WrongAssetType", and none of the orders will have been placed.
400 - Bad Request: { "Message": "The request is invalid.", "ModelState": { "AssetType": [ ""WrongAssetType" is not a valid value for AssetType" ] }, "ErrorCode": "InvalidModelState" }
If the semantic order affects a related order, it will still be captured, and none of the orders will have been placed. In the below we have kept AssetType="Stock", but changed "BuySell" of the first related order to "Sold".
400 - Bad Request: { "Message": "The request is invalid.", "ModelState": { "Orders[0].BuySell": [ "\"Sold\" is not a valid value for Orders[0].BuySell" ] }, "ErrorCode": "InvalidModelState" }
A similar error is returned if the request body is not well formed json. In the below example we have removed the leading {.
400 - Bad Request { "Message": "The request is invalid.", "ModelState": { ".": [ "Error converting value \"Uic\" . Path ''" ] }, "ErrorCode": "InvalidModelState" }
A final example is if the order definition fails simple up front validations of the combined order body as a whole. In the below example we have set the amount of the entry order to 10000 while the amount of the remaining orders are set to 1000.
{ "Message": "One or more properties of the request are invalid!", "ModelState": { "Amount": [ "Amount must be same for all orders" ] }, "ErrorCode": "InvalidModelState" }
Business Rules Violations
Business rules violations are marked with an ErrorCode and Message only. There will be one ErrorCode and Message for each order not placed.
For example, setting "Amount":10 for both the entry and the related orders will return:
400- Bad Request { "ErrorInfo": { "ErrorCode": "OrderValueToSmall", "Message": "Order value must be above the minimum order value for this exchange" }, "Orders": [ { "ErrorInfo": { "ErrorCode": "OrderNotPlaced", "Message": "Order not placed as other order in request was rejected" } }, { "ErrorInfo": { "ErrorCode": "OrderNotPlaced", "Message": "Order not placed as other order in request was rejected" } } ] }
Setting the OrderPrice of the first related order (the Limit order) to 200 returns:
400 - Bad Request { "OrderId": "74994579", "Orders": [ { "ErrorInfo": { "ErrorCode": "TooFarFromEntryOrder", "Message": "Order price is too far from the entry order" } }, { "ErrorInfo": { "ErrorCode": "OrderNotPlaced", "Message": "Order not placed as other order in request was rejected" } } ] }
In this case:
- Http Response 400 indicates that some parts of the request could not be accepted.
- The OrderId reteturns the OrderId of the order which was actually placed.
- The first entry in the "Orders" array returns an ErrorInfo structure explaining why this order could not be placed.
- The second entry in the "Orders" array returns an ErrorInfo structure explaining that this order was not placed as the first related order was not placed.
Partially accepted order requests
When placing a multi leg order, each order placement is handled individually in sequence. In other words:
- First the master order is validated and placed.
- If successful, then the first related order is validated and placed.
- If successful, then the second related order is validated and placed.
To illustrate this, setting "OrderPrice":1 in the second related order returns:
400 - Created { "OrderId": "74994594", "Orders": [ { "OrderId": "74994595" }, { "ErrorInfo": { "ErrorCode": "TooFarFromEntryOrder", "Message": "Order price is too far from the entry order" } } ] }
In this case the master order and the first related order (the limit order) has been placed, but the stop order was rejected.
Trade Not Completed
The /orders endpoint attempts to wait until the order request has been successfully placed with and accepted by the broker. Normally this happens within less than 1 second. If a response has not been received from the broker within 60 seconds the /orders endpoint will return with a 202 (Accepted), an OrderId and an ErrorCode of "TradeNotCompleted".
202 - Accepted { "ErrorInfo": { "ErrorCode": "TradeNotCompleted", "Message": "Your trade request could not be completed at this time" }, "OrderId": "55024416" }
This essentially means that at this point the status of this order is unknown. Very likely the order will eventually be routed to market.
If the request body had included related orders, these would have been cancelled. Note that in this case you could eventually end up with an entry order, with NO related stop or limit orders, potentially leaving you with an exposure which was not intended. It is recommended to monitor the state of the /port/v1/orders endpoint to make sure you have the precise status of your open orders.
Guidelines for handling order placement errors
Consider to simply provide information to the user
The OpenAPI endpoints are primarily built to support easy development of user interfaces. For example all error messages are localized to the language specified by the user. Further the user may be informed about the status of an order placement both by observing updates delivered through the /messages endpoint and by looking at the activity log. Finally the user will quickly notice an order and/or a position appearing in the user interface, sourced from the /port/v1/orders or /port/v1/positions (or port/v1/netpositions) endpoints.
Reformatting and Re-rendering the error messages
Some developers may decide to reformat the error messages returned by /trade/v2/orders. Alternatively they may decide to keep the existing error messages, but enhance some of them. Our own SaxoTraderGO/Pro uses this approach. For example:
In the above example:
- The text "Order value must be above the minimum order value for this exchange", is fetched directly from the error message in the error response of the /trade/v2/orders endpoint.
- The text "Minimum order value for this exchange is 50 GBP", is an enhancement made by the trading platform:
- It has recognized the specific error code:"OrderValueTooSmall",
- It has then looked up what the minimum order value is for the instrument, and used this information to add an additional line.
A third party application may do the same. Although not recommended, It may consider to completely ignore the Message part of the error response and create its own error message based simply on looking at the value of the ErrorCode. To write this code, the application developer may inspect the list of possible response codes shown for the API, and use a select statement to manage the translation. We currently list around 80 error codes, which can be used to branch on The list provided in the reference documentation shows our current list of possible error codes returned. We may extend this at any point in time without prior warning. The application must be robust enough handle this, for example by introducing a default action for all error codes for which it does not have prior knowledge. Probably the best option in this case is simply to use the already translated error message returned by Saxo Bank.
Out of the roughly 80 error codes, two have a special meaning, and both should occur very seldom:
Error Code | Meaning |
---|---|
OtherError | This represents a long collection of errors which all occur very seldom. They are thus all returned as "OtherError". The Message part of the ErrorInfo will however contain a translated meaningful message, which can be shown to the user. |
CouldNotCompleteRequest | The operation could not be completed and we will not disclose further details as to why. The Message part of the ErrorInfo will contain a standard message in English only along with an error code. For example: "Could not complete request (90)". |
Reacting to ModelState errors
It is not recommended that a third party application try to intercept and override ModelState errors. These are really errors which could (have been) prevented if the app had been written properly. I.e. the app should ensure that the submitted JSON is properly formatted, that the values for an enum is within the list of allowed values, that numbers are formatted correctly etc.
Reacting to Business rules violations
As mentioned above the app may decide to override a list of error messages for known business rules violations. This could be done using a simple switch statement or a lookup, such as:
public string Translate(ErrorInfo er) { switch (er) { case "TradeNotCompleted": return "Order placement has not been confirmed with broker. Please check the list of trade messages and the activity log."; break; case "AmountNotInLotSize": return "Order size is not valid for this exchange."; break; case "OrderSizeTooSmall": return "Order value must be above the minimum value for the exchange."; break; case ...... ..... default: return "Order could not be completed:" +er.Message; } }
Deciding which messages to show
The above is simple when placing a single standalone order. It gets slightly more complicated, when placing an entry order with related orders. SaxoTrader GO/PRO does the following:
- If an order placement is successful, SaxoTraderGO does not show any direct feedback to the user. Instead it simply shows information from the trade/v1/messages endpoint.
- If an order placement has failed, or may not be completed it:
- Shows the error message in the order placement dialog, and
- It uses knowledge about where in the response message the error information is located, to better inform the user about which order placement failed.
For example:
The above is how SaxoTraderGO reacts to a user placing an entry limit order with a valid limit order, but an incorrect stop order, just as discussed about under "Partially accepted order requests".
Note that:
- SaxoTraderGO knows that the problem is with the Stop order, since this is the second of the two orders in the Orders array.
- SaxoTraderGO does not show the actual order Ids as part of the order dialog. Instead it simply shows the progress of the order placement through the /trade/messages endpoint.
In case of a business rules violation for the first of the related orders, both related orders will be rejected. SaxoTraderGO simply displays the error messages for both of the two orders, which were rejected.
Ignoring or using trade messages?
If you want complete control over the user interface it may be tempting to simply create a user interface based on the response returned when an order is placed. If the order placement was successful you can display the ids of the order or orders placed. If the order placement incurred errors, you can display information fetched from the error structure. You may even decide to modify/override those messages as explained above.
However, it is quite complicated to accurately inform the user about the progress of an order placement without also displaying messages returned from the trade/v1/messages endpoint. This is true in particular in the very rare case when an order placement cannot be confirmed immediately. If you really want to avoid showing the trade messages to the user, your best option is probably to:
- Show order ids and messages from the order response as discussed here, but
- Also allow the client to easily see the status of open orders and new positions.
- Perhaps provide easy access to the contents from audit/v1/orderactivities.
Finally note, that if you are an introducing broker and the application you are building, is to be provided to Saxo Banks end clients (introduced by you), it is actually a requirement that your application fetches messages from the /trades/v1/messages endpoint, shows these to the client and also calls /trade/v1/messages/seen to register that the client has seen these messages. Therefore, even if you may find it compelling to do your own interpretation of error messages returned from the /trade/v2/orders endpoint, your clients will still end up also having to see and acknowledge the texts provided by us through the /trade/v1/messages endpoint.