Skip to content
Cirrus home
Sign in

Reference

Positions and portfolio

Read positions, holdings, trades and funds; exit or convert a position.

Convert a position #

POST /v1/positions/convert scope orders

Moves 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

HTTPCodeWhen
400malformed_json

The body is not JSON, or does not fit the request shape (unknown or missing fields, wrong types).

401unauthorized

No credential, or the credential is invalid, expired or revoked.

403forbidden

The credential may not use this route (missing scope, app-session-only route, IP not allowed).

404account_not_found

No linked broker account with this id for the caller.

422invalid_request

The request is well-formed but not valid; field names the input.

429rate_limited

Too many requests for this credential (or too many failed attempts); retry after the Retry-After header (seconds; error.retry_after_ms has the exact wait).

503service_unavailable

A dependency is temporarily unavailable, or the feature is off on this server; retry later.

All fields (request and response schema)

Loading fields…

POST
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"
  }'
Response · 200
{
  "status": "success",
  "data": {
    "account": "PF1",
    "status": "accepted",
    "message": null
  },
  "error": null
}

Exit a position #

POST /v1/positions/exit scope orders

Closes 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; send false to exit at MARKET.

Errors

HTTPCodeWhen
400malformed_json

The body is not JSON, or does not fit the request shape (unknown or missing fields, wrong types).

400idempotency_key_required

The Idempotency-Key header is missing (use a new UUID per order request).

400invalid_idempotency_key

The Idempotency-Key header is not 1-128 printable characters.

401unauthorized

No credential, or the credential is invalid, expired or revoked.

403forbidden

The credential may not use this route (missing scope, app-session-only route, IP not allowed).

404account_not_found

No linked broker account with this id for the caller.

409already_flat

There is no open position to exit for this account, instrument and product.

409request_in_progress

A request with this Idempotency-Key is still being processed.

422partial_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; message says how much remains.

422invalid_request

The request is well-formed but not valid; field names the input.

422idempotency_key_reused

This Idempotency-Key was already used with a different request.

429rate_limited

Too many requests for this credential (or too many failed attempts); retry after the Retry-After header (seconds; error.retry_after_ms has the exact wait).

503service_unavailable

A dependency is temporarily unavailable, or the feature is off on this server; retry later.

All fields (request and response schema)

Loading fields…

POST
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"
  }'
Response · 200
{
  "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 #

GET /v1/portfolio/{kind} scope read

One 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, trades or margins.

  • account query · string

    Only this linked account (its id).

Errors

HTTPCodeWhen
400invalid_query

The query string does not fit the route's parameters.

401unauthorized

No credential, or the credential is invalid, expired or revoked.

403forbidden

The credential may not use this route (missing scope, app-session-only route, IP not allowed).

404account_not_found

No linked broker account with this id for the caller.

422invalid_request

The request is well-formed but not valid; field names the input.

429rate_limited

Too many requests for this credential (or too many failed attempts); retry after the Retry-After header (seconds; error.retry_after_ms has the exact wait).

503service_unavailable

A dependency is temporarily unavailable, or the feature is off on this server; retry later.

All fields (request and response schema)

Loading fields…

GET
curl "https://cirrus.pocketful.in/v1/portfolio/<kind>" \
  -H "Authorization: token $CIRRUS_API_KEY"
Response · 200
{
  "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 #

POST /v1/portfolio/refresh scope read

Asks 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

HTTPCodeWhen
400invalid_query

The query string does not fit the route's parameters.

401unauthorized

No credential, or the credential is invalid, expired or revoked.

403forbidden

The credential may not use this route (missing scope, app-session-only route, IP not allowed).

404account_not_found

No linked broker account with this id for the caller.

429rate_limited

Too many requests for this credential (or too many failed attempts); retry after the Retry-After header (seconds; error.retry_after_ms has the exact wait).

503service_unavailable

A dependency is temporarily unavailable, or the feature is off on this server; retry later.

All fields (request and response schema)

Loading fields…

POST
curl -X POST "https://cirrus.pocketful.in/v1/portfolio/refresh" \
  -H "Authorization: token $CIRRUS_API_KEY"
Response · 202
{
  "status": "success",
  "data": {
    "requested": 1
  },
  "error": null
}