Positions and portfolio
Read positions, holdings, trades and funds; exit or convert a position.
Convert a position #
/v1/positions/convert scope ordersMoves an open position in one account to another product (e.g.
More about this
MIS to CARRYFORWARD). Once the request is valid the answer is 200 with the broker's verdict in status.
Body fields
- account string required
Account id holding the position.
- instrument_token string required
Instrument id of the position.
- side string required
Side of the open position (BUY for long, SELL for short).
One of: BUY, SELL
- quantity integer (int64) required
Units to convert (greater than 0).
- from string required
The position's current product.
One of: MIS, CARRYFORWARD, DELIVERY, MTF
- to string required
The product to move it to (different from
from).One of: MIS, CARRYFORWARD, DELIVERY, MTF
- overnight boolean
The position was carried from a previous session (not opened today).
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | malformed_json | The body is not JSON, or does not fit the request shape (unknown or missing fields, wrong types). |
| 401 | unauthorized | No credential, or the credential is invalid, expired or revoked. |
| 403 | forbidden | The credential may not use this route (missing scope, app-session-only route, IP not allowed). |
| 404 | account_not_found | No linked broker account with this id for the caller. |
| 422 | invalid_request | The request is well-formed but not valid; |
| 429 | rate_limited | Too many requests for this credential (or too many failed attempts); retry after the |
| 503 | service_unavailable | A dependency is temporarily unavailable, or the feature is off on this server; retry later. |
All fields (request and response schema)
Loading fields…
curl -X POST "https://cirrus.pocketful.in/v1/positions/convert" \
-H "Authorization: token $CIRRUS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"account": "PF1",
"instrument_token": "CT:TEST:RELIANCE",
"side": "BUY",
"quantity": 5,
"from": "MIS",
"to": "DELIVERY"
}'import os
import requests
response = requests.post(
"https://cirrus.pocketful.in/v1/positions/convert",
headers={
"Authorization": f"token {os.environ['CIRRUS_API_KEY']}",
},
json={
"account": "PF1",
"instrument_token": "CT:TEST:RELIANCE",
"side": "BUY",
"quantity": 5,
"from": "MIS",
"to": "DELIVERY",
},
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://cirrus.pocketful.in/v1/positions/convert', {
method: 'POST',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"account": "PF1",
"instrument_token": "CT:TEST:RELIANCE",
"side": "BUY",
"quantity": 5,
"from": "MIS",
"to": "DELIVERY"
}),
});
const body = await response.json();
console.log(response.status, body);{
"status": "success",
"data": {
"account": "PF1",
"status": "accepted",
"message": null
},
"error": null
}Exit a position #
/v1/positions/exit scope ordersCloses the net position in instrument_token / product of one account at market.
More about this
The position is read from the broker at the time of the request (not the snapshot); exit orders already working on it (open orders on the exit side) are subtracted, and what is left must be whole lots (otherwise 422 partial_lot_position, reporting the remainder). An opposite-side MARKET order for that quantity is placed like POST /v1/orders (split above the freeze quantity, with market protection).
Only once the exit is accepted is the position's protection removed: server-side stop-loss / target triggers, and broker-held protection (GTT, bracket / cover legs) cancelled at the broker, on this account, instrument and product only (an intraday exit never touches carry-forward protection). If the exit is refused, nothing else changes and the answer carries the refusal in results. Protection that could not be removed after an accepted exit is listed in protection_errors: it may still fire, so remove it by hand.
A broker stop order protecting the position (kind stop_order) is settled first: an untriggered one is cancelled and read back final, so the exit covers what the broker still holds (it is put back as a server-side trigger if the exit does not go out). One that already went off is waited for (about 3 s): once it filled, only what is left is exited; still working, nothing is placed on top of it (503).
Market protection (on unless market_protection is false) prices the exit as a LIMIT a small band past the order book level that covers the whole exit quantity (the bids for a SELL, the asks for a BUY, walked from the best down), else past the best bid / ask, else the last traded price. When the visible depth does not cover the quantity, the deepest level is used and the rest rests at the limit. An exit trusts a book up to five protection bands from the last price (an entry: two), so a thin book does not keep the position open. A stale last trade does not block the exit while the book is live (09:15-15:40 IST, ticked today, levels with a positive price and quantity): it is priced from the book. With no live price and no live book (e.g. the market is closed) it is a LIMIT protected from the last price held, however old. With no price at all, or with the book level further than five bands, the exit is refused (reason: stale_quote; the message says which) rather than sent as a raw MARKET: place a LIMIT order instead. On an illiquid contract the protected LIMIT can rest unfilled; check results and the order book.
An Idempotency-Key is required: a retry with the same key answers with the first result instead of exiting twice.
Parameters
- Idempotency-Key header · string required
A new UUID per exit (1-128 printable characters).
Body fields
- account string required
Account id holding the position.
- instrument_token string required
Instrument id of the position.
- product string required
The position's product.
One of: MIS, CARRYFORWARD, DELIVERY, MTF
- market_protection boolean | null
Market protection on the exit order (default
true): it is sent as a LIMIT a small band past the last traded price instead of a bare MARKET. On an illiquid contract that last trade may be stale, so the protected LIMIT can rest unfilled in the order book; sendfalseto exit at MARKET.
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | malformed_json | The body is not JSON, or does not fit the request shape (unknown or missing fields, wrong types). |
| 400 | idempotency_key_required | The |
| 400 | invalid_idempotency_key | The |
| 401 | unauthorized | No credential, or the credential is invalid, expired or revoked. |
| 403 | forbidden | The credential may not use this route (missing scope, app-session-only route, IP not allowed). |
| 404 | account_not_found | No linked broker account with this id for the caller. |
| 409 | already_flat | There is no open position to exit for this account, instrument and product. |
| 409 | request_in_progress | A request with this |
| 422 | partial_lot_position | The open position (less exits already working) is not a whole number of lots, so it cannot be fully exited through this route; |
| 422 | invalid_request | The request is well-formed but not valid; |
| 422 | idempotency_key_reused | This |
| 429 | rate_limited | Too many requests for this credential (or too many failed attempts); retry after the |
| 503 | service_unavailable | A dependency is temporarily unavailable, or the feature is off on this server; retry later. |
All fields (request and response schema)
Loading fields…
curl -X POST "https://cirrus.pocketful.in/v1/positions/exit" \
-H "Authorization: token $CIRRUS_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"account": "P1",
"instrument_token": "CT:TEST:RELIANCE",
"product": "MIS"
}'import os
import uuid
import requests
response = requests.post(
"https://cirrus.pocketful.in/v1/positions/exit",
headers={
"Authorization": f"token {os.environ['CIRRUS_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"account": "P1",
"instrument_token": "CT:TEST:RELIANCE",
"product": "MIS",
},
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://cirrus.pocketful.in/v1/positions/exit', {
method: 'POST',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
"account": "P1",
"instrument_token": "CT:TEST:RELIANCE",
"product": "MIS"
}),
});
const body = await response.json();
console.log(response.status, body);{
"status": "success",
"data": {
"account": "P1",
"product": "MIS",
"net_qty": 5,
"working_exit_qty": 0,
"exit_qty": 5,
"side": "SELL",
"cancelled_triggers": [],
"cancelled_brackets": [],
"protection_errors": [],
"results": [
{
"order_index": 0,
"account": "P1",
"quantity": 5,
"status": "accepted",
"order_id": "PAPER-1",
"message": null,
"order_type": "LIMIT",
"price": 2487.5,
"instrument_token": "CT:TEST:RELIANCE",
"order_tag": "01M3PGPRYM2737PQRY9SS63E3P"
}
],
"instrument_token": "CT:TEST:RELIANCE"
},
"error": null
}Order book, positions, holdings, trades or funds #
/v1/portfolio/{kind} scope readOne kind of data for every linked account (or only account), from the live per-account snapshots: no broker call on the read path.
More about this
Each account with data is under accounts (a tracked account with no orders today has an empty orders list), each without under missing with a code (no_session: no active broker session; not_supported: broker not supported yet; stale: the only data is from an earlier broker session, read before today's 04:00 IST, so it is not shown) and the reason. The shape of each data depends on kind: orders (today's order book, newest first): a list of OrderData; positions: a list of PositionRow; holdings: a list of HoldingRow; trades (today's fills): a list of TradeRow; margins: one MarginRow object. Rows carry instrument details and live prices when known.
Parameters
- kind path · string required
Which data:
orders,positions,holdings,tradesormargins. - account query · string
Only this linked account (its id).
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_query | The query string does not fit the route's parameters. |
| 401 | unauthorized | No credential, or the credential is invalid, expired or revoked. |
| 403 | forbidden | The credential may not use this route (missing scope, app-session-only route, IP not allowed). |
| 404 | account_not_found | No linked broker account with this id for the caller. |
| 422 | invalid_request | The request is well-formed but not valid; |
| 429 | rate_limited | Too many requests for this credential (or too many failed attempts); retry after the |
| 503 | service_unavailable | A dependency is temporarily unavailable, or the feature is off on this server; retry later. |
All fields (request and response schema)
Loading fields…
curl "https://cirrus.pocketful.in/v1/portfolio/<kind>" \
-H "Authorization: token $CIRRUS_API_KEY"import os
import requests
response = requests.get(
"https://cirrus.pocketful.in/v1/portfolio/<kind>",
headers={
"Authorization": f"token {os.environ['CIRRUS_API_KEY']}",
},
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://cirrus.pocketful.in/v1/portfolio/<kind>', {
method: 'GET',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
},
});
const body = await response.json();
console.log(response.status, body);{
"status": "success",
"data": {
"accounts": [
{
"account": "P1",
"updated_at": "2026-09-29T12:02:21.872057Z",
"data": [
{
"account": "P1",
"tradingsymbol": "RELIANCE-EQ",
"product": "DELIVERY",
"quantity": 10,
"average_price": 2400,
"invested_amount": 24000,
"pnl": 1000,
"pnl_percent": 4.17,
"lot_size": 1,
"exchange": "NSE",
"instrument_type": "EQUITY",
"symbol": "RELIANCE",
"expiry": "",
"strike": 0,
"option_type": "",
"ltp": 2500,
"prev_close": 2490,
"instrument_token": "CT:TEST:RELIANCE"
}
]
}
],
"missing": []
},
"error": null
}Re-read positions, holdings and funds #
/v1/portfolio/refresh scope readAsks the live workers of every linked account (or only account) to re-read positions, holdings, trades and funds from the broker now.
More about this
Returns at once (202); the new data reaches GET /v1/portfolio/{kind} and the stream within seconds. Recorded in the activity log.
Parameters
- account query · string
Only this linked account (its id).
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_query | The query string does not fit the route's parameters. |
| 401 | unauthorized | No credential, or the credential is invalid, expired or revoked. |
| 403 | forbidden | The credential may not use this route (missing scope, app-session-only route, IP not allowed). |
| 404 | account_not_found | No linked broker account with this id for the caller. |
| 429 | rate_limited | Too many requests for this credential (or too many failed attempts); retry after the |
| 503 | service_unavailable | A dependency is temporarily unavailable, or the feature is off on this server; retry later. |
All fields (request and response schema)
Loading fields…
curl -X POST "https://cirrus.pocketful.in/v1/portfolio/refresh" \
-H "Authorization: token $CIRRUS_API_KEY"import os
import requests
response = requests.post(
"https://cirrus.pocketful.in/v1/portfolio/refresh",
headers={
"Authorization": f"token {os.environ['CIRRUS_API_KEY']}",
},
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://cirrus.pocketful.in/v1/portfolio/refresh', {
method: 'POST',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
},
});
const body = await response.json();
console.log(response.status, body);{
"status": "success",
"data": {
"requested": 1
},
"error": null
}