Custom API Documentation
Create Custom Trade API
Overview
This endpoint allows custom strategy systems to create a trade with one or more legs.
Each leg defines an individual trade action (e.g., BUY NIFTY CE, SELL BANKNIFTY PE, etc.).
The system consolidates legs under a single strategyId + token combination and returns the updated open quantity, pending quantity, and position status for each leg.
Endpoint
POST {{baseUrl}}/trades/custom_orders
Authentication Flow
Generate API Key
Navigate to the Subroker Dashboard → Groups tab → Click "Generate API Key"
Login with API Key
Use the generated API key to authenticate and receive an access token
{
"apiKey": "your_generated_api_key"
}
{
"success": true,
"message": "Login successful",
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
Use Access Token for API Calls
Include the access token in the Authorization header for all trade API requests
{
"alertId": "your-alert-id",
"isCustom": true,
"legParams": [...]
}
Authorization header for all subsequent API requests. The token format should be: Authorization:<accessToken>
Request Body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
alertId |
string | ✅ | Unique ID for this signal/trade trigger. Used for idempotency. |
isCustom |
boolean | ✅ | Whether this is a custom trade. |
isExitAll |
boolean | Whether to exit all positions for the strategy. | |
isPaused |
boolean | To exit all positions for the strategy and pause the strategy. | |
positionId |
string | The position id to exit. | |
legParams |
array | ✅ | Array of leg objects defining each trade leg. |
Note: The
alertIdis the id of the group that you have created, you can obtain this id for a strategy by navigating to the Subroker Dashboard → Groups tab → Click on the group you want to use → Click on the Settings button → Copy the Alert Id.
Leg Parameters
| Field | Type | Required | Description |
|---|---|---|---|
direction |
string | "CALL" or "PUT". |
|
type |
string | ✅ | "ENTRY" or "EXIT". Defines whether this leg increase or decreases qty of position. |
instrument |
string | ✅ | Name of the instrument (e.g., "BANKNIFTY", "SBIN"). |
lots |
integer | ✅ | Number of lots to trade. |
transactionType |
string | ✅ | "BUY" or "SELL". |
orderType |
string | ✅ | "MARKET" or "LIMIT". |
anchorValue |
number | Reference value for anchor calculation. | |
anchorType |
string | "LTP", "DELTA", "THETHA", "GAMMA", "MONEYNESS", "FIXED_PRICE". Default = "MONEYNESS". |
|
anchorDirection |
string | "EQUAL", "ABOVE", "BELOW". |
|
isOvernight |
boolean | ✅ | Whether the order is to be INTRADAY or CARRYFORWARD. |
expiry |
string | Expiry type: "W" (Weekly) or "M" (Monthly). |
|
seq |
integer | ✅ | Execution sequence number (used for multi-leg execution order). |
target |
number | Target price value for the position. | |
targetType |
string | Type of target value: "POINTS" or "PERCENT". |
|
stoploss |
number | Stop loss price value for the position. | |
stoplossType |
string | Type of stop loss value: "POINTS" or "PERCENT". |
|
limitValue |
number | Limit order price value. Required when orderType is "LIMIT". |
|
limitType |
string | Type of limit value: "ABSOLUTE", "PERCENT", or "POINTS". Required when orderType is "LIMIT". |
Anchor Type Details
The anchorType field determines how the strike is selected based on the anchorValue:
anchorType |
Description |
|---|---|
"LTP" |
Premium Value - The anchorValue represents the premium price of the contract. Based on the anchorDirection, the system will select the appropriate strike that matches the specified premium value. For example, if you set anchorValue to 50, 60, or 70, the system will select the strike whose premium price matches that value according to the direction specified. |
"MONEYNESS" |
Used to select In the Money (ITM) or Out of the Money (OTM) strikes. The anchorValue determines how far ITM or OTM the strike should be:- For OTM strikes: Use negative values (e.g., -1, -5, -10) to specify how many strikes out of the money you want.- For ITM strikes: Use positive values (e.g., 1, 5, 10) to specify how many strikes in the money you want. |
"FIXED_PRICE" |
The order will be placed at the exact strike price specified in the anchorValue. For example, if you set anchorValue to 25000 or 25100, the order will be placed at that specific strike price. |
"DELTA" |
Delta-based strike selection. |
"THETHA" |
Theta-based strike selection. |
"GAMMA" |
Gamma-based strike selection. |
Limit Type Details
When orderType is "LIMIT", the limitType field determines how limitValue is interpreted:
limitType |
Description |
|---|---|
"ABSOLUTE" |
The limitValue is taken as the absolute price to place the limit order. |
"PERCENT" |
The limitValue represents a percentage of the current price. |
"POINTS" |
The limitValue represents points offset from the current price. |
Example Request
{
"alertId": "03a6639a-2ac1-4baa-ac6a-c233cc8faecb",
"isCustom": true,
"legParams": [
{
"type": "ENTRY",
"instrument": "NIFTY",
"lots": 1,
"transactionType": "BUY",
"orderType": "LIMIT",
"anchorValue": 0,
"direction": "CALL",
"anchorType": "MONEYNESS",
"anchorDirection": "EQUAL",
"expiry": "W",
"isOvernight": false,
"seq": 1,
},
{
"type": "ENTRY",
"instrument": "BANKNIFTY",
"lots": 2,
"transactionType": "SELL",
"direction": "CALL",
"orderType": "MARKET",
"anchorValue": 55000,
"anchorType": "FIXED_PRICE",
"anchorDirection": "EQUAL",
"expiry": "W",
"isOvernight": false,
"seq": 2
}
]
}
Success Response
The response now returns per-leg results keyed by
seq
because multiple legs may open or modify multiple positions.
{
"success": true,
"message": "Trade request accepted",
"data": {
"1": {
"positionId": "pos-5678",
"token": "26009",
"summary": {
"openQty": 10,
"pendingQty": 0
},
"positionStatus": "OPEN"
},
"2": {
"positionId": "pos-5679",
"token": "26037",
"summary": {
"openQty": 0,
"pendingQty": 5
},
"positionStatus": "PENDING"
}
}
}
Response Field Description
| Field | Type | Description |
|---|---|---|
data |
object | Object containing responses per leg seq. |
positionId |
string | Unique ID for the consolidated position. |
token |
string | Identifier used to group entries/exits. |
summary.openQty |
number | Total open quantity after consolidation. |
summary.pendingQty |
number | Quantity yet to execute. |
positionStatus |
string | "OPEN", "CLOSED", "PENDING", or "FAILED". |
Error Response
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid trade parameters",
"details": {
"field": "instrument",
"reason": "Missing or invalid value"
}
}
}
| Code | Meaning |
|---|---|
VALIDATION_ERROR |
Missing or invalid request data. |
DATABASE_ERROR |
Failure during persistence. |
UNAUTHORIZED |
Invalid or missing API key. |
Strategy Types
The system supports three distinct strategy types, each with different behaviors for position management, target/stop loss settings, and order execution.
1. NORMAL
In this strategy type, target and stop loss cannot be set.
Position Management:
- You can add quantity to a particular token by placing orders with
type: "ENTRY". - You can decrease quantity for a particular token by placing orders with
type: "EXIT"and by giving opposite-side transaction type (Example: If you have a position in NIFTY CE, you can decrease the quantity by placing an order withtype: "EXIT"andtransactionType: "SELL"). - Positions are consolidated by token, allowing incremental adjustments to quantity.
2. ENTRY_ONLY
In this strategy type, target and stop loss can be set.
Position Behavior:
- For the same token (e.g., token
123), every time you place an order, a new position will be opened. - Each order creates a separate position entry, even if a position already exists for that token.
- Positions are not consolidated by token; each order maintains its own independent position.
Exit Methods:
- Positions can only be exited by:
- Specifying the
positionIddirectly - Using the "exit all" functionality
- Specifying the
- Exit legs are not available - meaning in
legParams, entries withtype: "EXIT"will not be executed. - You cannot exit positions using exit-type leg parameters in the request.
3. ENTRY_ONCE
In this strategy type, target and stop loss can be set.
Position Behavior:
- For a particular token (e.g., token
123), no new quantity can be added usinglegParamswithtype: "ENTRY". - Once an entry position is created for a token, subsequent entry orders for the same token will be rejected or ignored.
- This ensures that only one entry position exists per token, preventing quantity accumulation through multiple entry orders.
Use Case:
- Ideal for strategies where you want to ensure a single entry point per token, with the ability to set target and stop loss levels.
Test Cases & Examples
1. Entry (NORMAL Type) - Single Entry
Request:
{
"alertId": "b9d4d22a-290a-445b-a44d-0f55f8dfb4cf",
"isExitAll": false,
"isCustom": true,
"isPaused": false,
"positionId": null,
"legParams": [
{
"type": "entry",
"instrument": "NIFTY",
"segment": "FNO",
"lots": 1,
"transactionType": "BUY",
"orderType": "MARKET",
"direction": "CALL",
"anchorValue": 0,
"anchorType": "Moneyness",
"anchorDirection": "EQUAL",
"expiry": "M",
"isOvernight": false,
"seq": 1
}
]
}
Response:
{
"success": true,
"message": "Job completed successfully",
"data": {
"jobId": "custom-trade-undefined-1767866277315",
"result": {
"success": true,
"message": "Positions processed successfully",
"data": {
"1": {
"positionId": "27ee597f-a7e3-479b-9aef-cdb783b9f35a",
"token": 58790,
"summary": {
"openQty": 65,
"pendingQty": 0
},
"positionStatus": "OPEN"
}
}
}
}
}
2. Multi-Sequence Entry (NORMAL Type)
Request:
{
"alertId": "b9d4d22a-290a-445b-a44d-0f55f8dfb4cf",
"isExitAll": false,
"isCustom": true,
"isPaused": false,
"positionId": null,
"legParams": [
{
"type": "entry",
"instrument": "NIFTY",
"segment": "FNO",
"lots": 1,
"transactionType": "BUY",
"orderType": "MARKET",
"direction": "CALL",
"anchorValue": 0,
"anchorType": "Moneyness",
"anchorDirection": "EQUAL",
"expiry": "M",
"isOvernight": false,
"seq": 1
},
{
"type": "entry",
"instrument": "NIFTY",
"segment": "FNO",
"lots": 1,
"transactionType": "BUY",
"orderType": "MARKET",
"direction": "PUT",
"anchorValue": 0,
"anchorType": "Moneyness",
"anchorDirection": "EQUAL",
"expiry": "M",
"isOvernight": false,
"seq": 2
}
]
}
Response:
{
"success": true,
"message": "Job completed successfully",
"data": {
"jobId": "custom-trade-undefined-1767866277315",
"result": {
"success": true,
"message": "Positions processed successfully",
"data": {
"1": {
"positionId": "57f75283-a62a-4b4e-9480-7ba3998669ce",
"token": 58790,
"summary": {
"openQty": 65,
"pendingQty": 0
},
"positionStatus": "OPEN"
},
"2": {
"positionId": "867ccda5-96d2-40f1-a6a6-11ad12238842",
"token": 58791,
"summary": {
"openQty": 65,
"pendingQty": 0
},
"positionStatus": "OPEN"
}
}
}
}
}
3. Exit (NORMAL Type)
3a. Exit by Position ID
Request:
{
"alertId": "b9d4d22a-290a-445b-a44d-0f55f8dfb4cf",
"positionId": "57f75283-a62a-4b4e-9480-7ba3998669ce",
"isCustom": true
}
3b. Exit All Positions for Strategy
Request:
{
"alertId": "b9d4d22a-290a-445b-a44d-0f55f8dfb4cf",
"isExitAll": true,
"isCustom": true
}
3c. Exit and Pause Strategy
Request:
{
"alertId": "b9d4d22a-290a-445b-a44d-0f55f8dfb4cf",
"isPaused": true,
"isCustom": true
}
Response (for all exit methods):
{
"success": true,
"message": "Job completed successfully",
"data": {
"jobId": "custom-trade-undefined-1767867364227",
"result": {
"success": true,
"message": "All positions exited successfully",
"data": {}
}
}
}
4. Increase Quantity (NORMAL Type)
Initial Entry Request:
{
"alertId": "b9d4d22a-290a-445b-a44d-0f55f8dfb4cf",
"isExitAll": false,
"isCustom": true,
"isPaused": false,
"positionId": null,
"legParams": [
{
"type": "entry",
"instrument": "NIFTY",
"segment": "FNO",
"lots": 1,
"transactionType": "BUY",
"orderType": "MARKET",
"direction": "CALL",
"anchorValue": 0,
"anchorType": "Moneyness",
"anchorDirection": "EQUAL",
"expiry": "M",
"isOvernight": false,
"seq": 1
}
]
}
Initial Response:
{
"success": true,
"message": "Job completed successfully",
"data": {
"jobId": "custom-trade-undefined-1767867490984",
"result": {
"success": true,
"message": "Positions processed successfully",
"data": {
"1": {
"positionId": "15bf55b3-53d6-4a54-bcb9-7b521355086d",
"token": 58790,
"summary": {
"openQty": 65,
"pendingQty": 0
},
"positionStatus": "OPEN"
}
}
}
}
}
Add 1 More Lot Request (same as initial, adds to existing position):
{
"alertId": "b9d4d22a-290a-445b-a44d-0f55f8dfb4cf",
"isExitAll": false,
"isCustom": true,
"isPaused": false,
"positionId": null,
"legParams": [
{
"type": "entry",
"instrument": "NIFTY",
"segment": "FNO",
"lots": 1,
"transactionType": "BUY",
"orderType": "MARKET",
"direction": "CALL",
"anchorValue": 0,
"anchorType": "Moneyness",
"anchorDirection": "EQUAL",
"expiry": "M",
"isOvernight": false,
"seq": 1
}
]
}
Response (with increased quantity):
{
"success": true,
"message": "Job completed successfully",
"data": {
"jobId": "custom-trade-undefined-1767867587071",
"result": {
"success": true,
"message": "Positions processed successfully",
"data": {
"1": {
"positionId": "15bf55b3-53d6-4a54-bcb9-7b521355086d" (Same positionId as initial response),
"token": 58790,
"summary": {
"openQty": 130 (Increased quantity),
"pendingQty": 0
},
"positionStatus": "OPEN"
}
}
}
}
}
5. Decrease Quantity (NORMAL Type)
Request (using exit type):
{
"alertId": "b9d4d22a-290a-445b-a44d-0f55f8dfb4cf",
"status": null,
"message": null,
"isExitAll": false,
"isCustom": true,
"isPaused": false,
"positionId": null,
"legParams": [
{
"type": "exit",
"instrument": "NIFTY",
"segment": "FNO",
"lots": 1,
"transactionType": "SELL",
"orderType": "MARKET",
"direction": "CALL",
"anchorValue": 0,
"anchorType": "Moneyness",
"anchorDirection": "EQUAL",
"expiry": "M",
"isOvernight": false,
"seq": 1
}
]
}
Response (with decreased quantity):
{
"success": true,
"message": "Job completed successfully",
"data": {
"jobId": "custom-trade-undefined-1767867658204",
"result": {
"success": true,
"message": "Positions processed successfully",
"data": {
"1": {
"positionId": "15bf55b3-53d6-4a54-bcb9-7b521355086d",
"token": 58790,
"summary": {
"openQty": 65,
"pendingQty": 0
},
"positionStatus": "OPEN"
}
}
}
}
}
6. Entry (ENTRY_ONCE Type) with Target and Stop Loss
Request:
{
"alertId": "b9d4d22a-290a-445b-a44d-0f55f8dfb4cf",
"isExitAll": false,
"isCustom": true,
"isPaused": false,
"positionId": null,
"legParams": [
{
"type": "entry",
"instrument": "BANKNIFTY",
"segment": "FNO",
"lots": 1,
"transactionType": "BUY",
"orderType": "MARKET",
"direction": "CALL",
"anchorValue": 0,
"anchorType": "Moneyness",
"anchorDirection": "EQUAL",
"expiry": "M",
"isOvernight": false,
"seq": 1,
"target": 10,
"targetType": "POINTS",
"stoploss": 10,
"stoplossType": "POINTS"
}
]
}
Response:
{
"success": true,
"message": "Job completed successfully",
"data": {
"jobId": "custom-trade-undefined-1767868622369",
"result": {
"success": true,
"message": "Positions processed successfully",
"data": {
"1": {
"positionId": "a866240f-9dbf-4393-8f93-d2c27ad7bb4e",
"token": 52022,
"summary": {
"openQty": 30,
"pendingQty": 0
},
"positionStatus": "OPEN"
}
}
}
}
}
Note: In ENTRY_ONCE type, subsequent entry orders for the same token will be rejected. Only one entry position is allowed per token.
7. Entry (ENTRY_ONLY Type) with Target and Stop Loss
First Entry Request:
{
"alertId": "b9d4d22a-290a-445b-a44d-0f55f8dfb4cf",
"isExitAll": false,
"isCustom": true,
"isPaused": false,
"positionId": null,
"legParams": [
{
"type": "entry",
"instrument": "BANKNIFTY",
"segment": "FNO",
"lots": 1,
"transactionType": "BUY",
"orderType": "MARKET",
"direction": "CALL",
"anchorValue": 0,
"anchorType": "Moneyness",
"anchorDirection": "EQUAL",
"expiry": "M",
"isOvernight": false,
"seq": 1,
"target": 5,
"targetType": "POINTS",
"stoploss": 5,
"stoplossType": "POINTS"
}
]
}
First Entry Response:
{
"success": true,
"message": "Job completed successfully",
"data": {
"jobId": "custom-trade-undefined-1767868853771",
"result": {
"success": true,
"message": "Positions processed successfully",
"data": {
"1": {
"positionId": "1c23ba9f-2c95-49f6-a326-beca3d0abb52",
"token": 52022,
"summary": {
"openQty": 30,
"pendingQty": 0
},
"positionStatus": "OPEN"
}
}
}
}
}
Second Entry Request (Same Token - Creates New Position):
{
"alertId": "b9d4d22a-290a-445b-a44d-0f55f8dfb4cf",
"isExitAll": false,
"isCustom": true,
"isPaused": false,
"positionId": null,
"legParams": [
{
"type": "entry",
"instrument": "BANKNIFTY",
"segment": "FNO",
"lots": 1,
"transactionType": "BUY",
"orderType": "MARKET",
"direction": "CALL",
"anchorValue": 0,
"anchorType": "Moneyness",
"anchorDirection": "EQUAL",
"expiry": "M",
"isOvernight": false,
"seq": 1,
"target": 5,
"targetType": "POINTS",
"stoploss": 5,
"stoplossType": "POINTS"
}
]
}
Second Entry Response (Different Position ID):
{
"success": true,
"message": "Job completed successfully",
"data": {
"jobId": "custom-trade-undefined-1767868903326",
"result": {
"success": true,
"message": "Positions processed successfully",
"data": {
"1": {
"positionId": "f4d2ae26-8b73-43be-a0ec-19e6f483d577",
"token": 52022,
"summary": {
"openQty": 30,
"pendingQty": 0
},
"positionStatus": "OPEN"
}
}
}
}
}
Note: In ENTRY_ONLY type, each entry order for the same token creates a new separate position with a different
positionId. Positions are not consolidated by token.
Position Adjustment Rules (Detailed)
Core Logic
All trade updates are grouped by (strategyId, token) to form a consolidated position.
- If
transactionTypeis same as existing open side → increaseopenQty. - If
transactionTypeis opposite side →- Execute against existing openQty first.
- Reduce
openQtybymin(incomingQty, openQty). - If
openQtyreaches 0 → mark position CLOSED.
- If no open position exists, handle as pending or new depending on
orderType. - Before modifying or creating a new position, always reconcile any pendingQty.
Pending Quantity Matching
When an opposite-side trade arrives, it follows this evaluation order:
Matching priority → openQty → pendingQty → new order
| Case | Behavior |
|---|---|
| Incoming opposite-side ≤ openQty | Execute against open position; reduce openQty accordingly. |
| Incoming opposite-side > openQty | Fully close open position (openQty = 0), then recheck remaining quantity against pendingQty. |
| Remaining opposite-side ≤ pendingQty | Reduce pendingQty by that amount and do not execute. |
| Remaining opposite-side > pendingQty | Cancel all pending orders (pendingQty = 0) and do not execute remaining quantity. |
| No openQty and no pendingQty | Treat as a new position (open or pending depending on orderType). |
RELIANCE Examples
| # | Scenario | Before | Incoming | After | Result |
|---|---|---|---|---|---|
| 1 | Pending Buy 10 → Sell 2 | pendingBuy = 10 | sell = 2 | pendingBuy = 8 | Reduce pending; no execution |
| 2 | Pending Buy 10 → Sell 20 | pendingBuy = 10 | sell = 20 | pendingBuy = 0 | Cancel pending; no execution |
| 3 | Open Buy 10 → Sell 2 | openBuy = 10 | sell = 2 | openBuy = 8 | Execute 2; position remains OPEN |
| 4 | Open Buy 10 → Sell 20 | openBuy = 10 | sell = 20 | openBuy = 0 | Execute 10; position CLOSED; remaining sell discarded |
| 5 | Open 10 + Pending 10 → Sell 15 | openBuy = 10, pendingBuy = 10 | sell = 15 | openBuy = 0, pendingBuy = 5 | Execute 10 (close open), reduce pending by 5; position OPEN (5 pending left) |
| 6 | Open 10 + Pending 5 → Sell 20 | openBuy = 10, pendingBuy = 5 | sell = 20 | openBuy = 0, pendingBuy = 0 | Execute 10 (close open), cancel 5 pending; remaining 5 ignored; position CLOSED |
| 7 | Open 10 + Pending 5 → Buy 20 | openBuy = 10, pendingBuy = 5 | buy = 20 | openBuy = 30, pendingBuy = 5 | Same-side add: open increased; position remains OPEN |
Sequencing & Execution
- Execute legs in ascending
seqorder. - Each leg's result appears under its
seqkey in response. - If
seqis omitted → processed in received order.
Example Workflow
- Strategy system sends trade request →
POST /trades/custom - Receives per-leg results:
{"1": { "openQty": 10, "positionStatus": "OPEN" },"2": { "openQty": 0, "pendingQty": 5, "positionStatus": "PENDING" }}- Positions can later be queried with:
GET /positions/:strategyId