Skip to content
Cirrus home
Sign in

Changelog

Every change you can see, newest first. Additive changes never break a client that ignores unknown fields; breaking ones are announced before they ship.

  1. Additive

    broker refusals classed from production replies

    Pocketful refusals now carry their class: an expired login (HTTP 401 "session expired. please relogin" / "request unauthorised") is session_expired, a regular order outside market hours is market_closed, a cancel answered "something went wrong" is settled from the order book, and the gateway's HTML 403 page is a passing gateway block (never a refused login). Alice Blue's "Failed to retrieve the order book" is a passing read failure. Users see the same refusal reasons as before; no field, enum value or status code changes.

  2. Additive

    portfolio breakdown fields, instrument sector / ISIN / indices, safer order modify

    • Portfolio rows (stream snapshots and portfolio messages, the positions webhook, portfolio reads) carry new optional fields, sent only when the broker reports them (never derived from a guess) and omitted otherwise. Existing fields keep their meaning: position pnl is still the total, holding quantity is unchanged, margin utilised is still a percent.
    • Positions: overnight_qty (integer; positive long, negative short carried in), day_buy_qty, day_sell_qty (integers), day_buy_value, day_sell_value, realised_pnl, unrealised_pnl (numbers, rupees).
    • Trades: trade_id (string), product (MIS / CARRYFORWARD / DELIVERY / MTF, the existing Product values).
    • Holdings: t1_qty, pledged_qty (integers), isin (string; the instrument list's ISIN replaces it when the list has one).
    • Margins: used, payin, payout, collateral, realised_pnl (numbers, rupees). Mapped for Zerodha, Upstox, Fyers, 5Paisa, Motilal Oswal, Alice Blue, Tradejini, Pocketful, Groww and Dhan (each sends a subset); other brokers omit them.
    • Enriched rows (orders, positions, holdings, trades; stream, webhooks and portfolio reads) now carry sector (string), isin (string) and indices (array of strings) from the instrument list, omitted when absent. GET /v1/instruments/{id} served from the live master now fills them too.
    • PATCH /v1/orders/{order_id} accepts an optional Idempotency-Key header (repeat returns first answer with Idempotent-Replayed: true; different body → 422 idempotency_key_reused; in-flight → 409 request_in_progress; invalid key → 400 invalid_idempotency_key). A transient rejection that changed nothing at the broker (rate_limited, no_session, stale_quote) is not stored, so a retry with the same key is tried again.
    • PATCH /v1/orders/{order_id} refuses quantity not greater than filled quantity with 422 invalid_request, field: "quantity".
  3. Additive

    Dhan orders leave from the shared partner IP

    Dhan only accepts order writes from a whitelisted IP. Every Dhan write (place, modify, cancel, super / forever orders, position conversion) now leaves from the Cirrus partner gateway 168.144.93.54 (port 7777, DHAN_ORDER_STATIC_IP), whitelisted by Dhan for all partner accounts; reads (order book, positions, holdings, trades, funds, margin quotes) go straight to Dhan. A Dhan account's own Static IP is no longer used, so Static IP is dropped from Dhan's account fields: GET /v1/accounts no longer lists it for Dhan nor reports it missing. Other brokers are unchanged. No field, enum value or status code changes.

  4. Additive

    a broker login ends by our clock

    Every broker's login is used only until our daily cutoff: the next IST midnight after Last Login At (04:00 IST for a login made between midnight and 04:00), even when the broker would still accept the token (Alice Blue, 2026-10-06). After it, GET /v1/accounts shows the existing login_required status (and no session_expires_at), and orders, modifies, cancels, exits and GTT calls on the account are refused before the broker with the existing no_session reason ("Broker session missing. Please log in to your broker again."); the token is never sent. Brokers whose tokens die earlier are still caught by their own refusal. No field, enum value or status code changes.

  5. Additive

    Fyers, Alice Blue and Groww always served

    The FYERS_ENABLED, ALICEBLUE_ENABLED and GROWW_ENABLED rollout switches are gone: every OMS pod builds and serves the Fyers, Alice Blue and Groww adapters, like every other broker. Accounts on these brokers get actors and can trade (subject to the broker onboarding gate, OMS_BROKER_GATE, as for any broker) wherever app-cirrus lets the user add them (ALLOWED_BROKERS / DISABLED_BROKERS). The Fyers postback route no longer has a "switched off" case; every request is still answered 200. No field, enum value or status code changes; the old variables are ignored if still set.

  6. Additive

    moving a GTT the broker no longer holds

    PATCH /v1/triggers/{trigger_id} on broker-held GTT protection (and signal-hook stop-loss / target moves), when the broker refuses the change because the GTT is no longer active (Kite "Trigger is not active."): the GTT is read back, as PATCH /v1/gtt/{trigger_id} already does.

    • triggered: 409 trigger_fired, "Your GTT already triggered; order <id> was placed."; the trigger ends (ended_reason "broker GTT cancelled"). Nothing is placed again.
    • deleted / cancelled / expired / disabled / rejected: unless the rules say fallback: reject, a Cirrus trigger (cirrus_exits: true, same trigger_id) takes over at the levels asked for and the change answers 200 with it; otherwise 502 broker_refused saying the position has no stop-loss / target. Either way a protection_failed activity record says the GTT is gone.
    • the GTT cannot be read: 504 broker_outcome_unknown, "... its state could not be read; list the account's GTTs before retrying."; the trigger is kept. A filled entry whose GTT is grown on a later fill gets the same reading: a fired GTT is not grown and the later quantity is protected on its own; an ended one hands over to a Cirrus trigger (protection_failed). No field or value is added; codes and statuses are existing ones.
  7. Additive

    cancels the broker did not confirm settle from the book

    DELETE /v1/orders/{order_id}, DELETE /v1/orders/bracket/{order_id} (and the cancels Cirrus sends itself: protection removal, exits, chase, signal-hook cancels): a broker reply its pack classes outcome_unknown (Zerodha HTTP 502 "Error parsing response (kt-oms)." DataException, Tradejini "Unable to connect to endpoint. Kindly try again.") is no longer a definite rejected. The order book is read once: the order listed cancelled answers accepted; still open (or filled, rejected, expired) answers rejected with the broker's text and "(the order is still OPEN at the broker)" or "(the order is FILLED at the broker)"; not listed or the book unreadable answers unknown. Such a cancel is never sent again. PATCH /v1/orders/bracket/{order_id}: such a reply answers unknown, as a modify already did. No field or value changes.

  8. Additive

    unconfirmed broker replies settle from the book; broker data fixes

    • Pocketful (and any broker whose pack classes the reply outcome_unknown, e.g. Tradejini "unable to connect"): a bracket / cover place answered with a bare HTTP 500, and a modify answered "Something went wrong", now answer status: "unknown" (the order book decides) instead of rejected; a bracket is never re-sent as a plain order after such a reply. The message ends "(the broker did not confirm the outcome; check the order book before retrying)".
    • PATCH / DELETE /v1/gtt/{id} (Zerodha): "Trigger is not active." is read back from Kite and answered with what happened (already triggered and the order it placed, deleted, rejected and why); a read failure answers unknown. status / ended_reason of the ended GTT stay "broker GTT cancelled".
    • Alice Blue holdings: average_price, invested amount and P&L use the buy price (investedPrice), not the market price (P&L read about 0).
    • 5paisa trades are filed under their order (matched by exchange order id through the order book); refused rows' 1980 placeholder time reads as no time.
    • Upstox / Zerodha margins: utilised is never below 0 (a sale's credit read as -2,502,800%).
    • An order that never reached the broker reads "Not placed: ..." in the activity log, not "Rejected by <broker>". No field or value changes.
  9. Additive

    a missing static IP at the broker is said as one, with the account's IP

    GET /v1/accounts, GET /v1/accounts/{account}: new field warnings (array of snake_case codes, empty when nothing needs fixing; more codes may be added later, show an unknown one as is). First code: static_ip_not_registered, set when the broker refused an order because no static IP is registered for the account's API app, cleared by the account's next accepted order. status is unchanged by it. POST /v1/orders (and the signal hooks that place through it): when Upstox refuses a place with UDAPI1154 ("Access to this API is blocked due to static IP restrictions. Reason: No static IP has been configured."), the slice message and the activity's message now say what to do, naming the account's static IP: "Upstox blocked this order: no static IP is registered for your API app. Register <ip> in your Upstox developer console, then try again." (broker_message keeps Upstox's own words). Its reason is now broker_rejected (an existing value), not no_session: a new login cannot fix it, so it is no longer read as an expired login, retried with fresh credentials or pausing the account's reads. The order is sent once, never retried. No new enum value.

  10. Additive

    5paisa order times are IST, not 5 h 30 m ahead

    5paisa sends order times as /Date(<ms>+0530)/ whose milliseconds are IST wall-clock; they were read as UTC, so every 5paisa order's broker time (broker_updated_at, placed time, activity time) was 5 h 30 m late (a 14:20 IST rejection listed at 19:50). They are now read as IST. For every broker, an activity is never listed later than when it was seen. No field or value changes.

  11. Additive

    an unanswered order the broker never lists ends REJECTED

    An order whose placing call got no answer (or whose refusal had to be confirmed in an unreadable book) is UNKNOWN until the broker's order book shows it. When it never does (no broker order id, its tag missing from 3 full order-book reads in a row over at least 3 minutes) it now ends REJECTED with status_message "never reached the broker: ..." (order_update webhook, /v1/stream, order lists, activity order_rejected), and leaves the open orders. Before, it stayed UNKNOWN and listed as open until the next trading day. A later row with its tag still moves it to the broker's state. No field or value changes. Upstox's "Session is under transition" refusal is now said as the broker not taking orders, not as an expired login.

  12. Additive

    internal: Upstox live replies recorded

    No change for users (not in the API). Upstox replies from 8BAMR3 join the corpus: invalid-token, missing-static-IP, session-transition and circuit-band refusals, book / position / trade rows, WS order frames and postbacks. 8BAMR3 may trade in live conformance runs. The live runner keeps far prices inside the price band and Upstox's own range.

  13. Additive

    exits never sell broker cover / bracket quantity; Pocketful cancel by basket id

    POST /v1/positions/exit (Pocketful, Tradejini): when part of the position is held by an open cover / bracket order placed at the broker, the exit is refused with the existing 409 bracket_position (its message names both quantities) and nothing is placed; before, the whole net was sold as a plain intraday order and could open a reverse position while the bracket's stop stayed live. DELETE /v1/orders/{id} with a Pocketful basket id (the id a cover / bracket place returns) now cancels that cover / bracket instead of being refused. No field or value changes. Internal (not in the API): Pocketful, Motilal Oswal, 5paisa and Tradejini corpus additions; Motilal AEGING reads as MTF.

  14. Additive

    Tradejini positions listed once per product; live replies recorded

    GET /v1/positions for Tradejini: the broker's intraday, cover and bracket rows for one instrument (all MIS here) are now one MIS position, quantities and P&L summed, averages weighted by quantity. Before, the same instrument was listed as MIS more than once. No field or value changes. Internal (not in the API): live replies from Pocketful, Motilal Oswal, Tradejini and Zerodha runs and production samples join the corpus; recordings that held a client id are revoked; Zerodha account_id / placed_by are redacted; two Pocketful refusals (order already gone on a cover-leg modify, wrong execution type on a cancel) are classed.

  15. Additive

    internal: first Tradejini live trading recorded

    No change for users (not in the API). Tradejini live trading replies (HHS003: far LIMIT place / modify / cancel, fills, positions, trades, bracket place / cancel) and the scenarios that passed. A waiting Tradejini stop-loss order is now TRIGGER_PENDING (an existing value).

  16. Additive

    Fyers postback route; Fyers, Alice Blue and Groww packs at replay_green

    New inbound route POST /v1/webhooks/fyers/{username}/{account} (#71): where a user registers it on their own Fyers app, Fyers order postbacks are accepted as untrusted nudges that make the account's book re-read sooner. Every request is answered 200 {"status":"success"} whatever happens (Fyers blacklists a URL after a non-200), the same for every account; nothing is trusted from the body. While FYERS_ENABLED is off it answers the same and does nothing. Internal (not in the API): a Fyers SL / SL-M order with nothing filled that Fyers lists as pending is read as TRIGGER_PENDING, not OPEN; the Fyers postback secret probe also logs header names containing sign or hmac (names only). The fyers and aliceblue packs reach replay_green with synthetic recordings of their documented examples (Fyers: books, funds, margin, place / modify / cancel replies and requests, postback; Alice Blue: books, limits, place), their statuses frozen and two error templates classed (Fyers -99 circuit-limit refusal invalid_price_or_qty; Groww GA001 invalid symbol invalid_field). The groww pack gains its place / modify / cancel / failure / margin-quote replies and every Order Status annexure status frozen. No existing recording or expectation changes.

  17. Additive

    Groww adapter behind GROWW_ENABLED; its conformance pack

    No change for users while GROWW_ENABLED is off (the default): a Groww account stays an unsupported broker. On, Groww accounts are served like other brokers (same request and response shapes; GET /v1/accounts reports them supported, updates polling). The broker conformance corpus gains a new groww pack at replay_green: synthetic recordings of the order list, positions, holdings, trades and margins replies from Groww's API docs, their expectations, and the OPEN status frozen.

  18. Additive

    internal: Fyers conformance pack (scaffolded)

    No change for users (not in the API). A new fyers broker pack at stage scaffolded: its capability claims, fingerprint / redaction fields and empty types.json, errors.toml and status_map.json (nothing handled yet; no existing pack, recording or expectation changes). The Fyers adapter it describes is off unless FYERS_ENABLED=true.

  19. Additive

    internal: Alice Blue conformance pack scaffolded

    No change for users (not in the API). A new aliceblue pack at stage scaffolded (empty types.json, errors.toml and status_map.json) for the new Alice Blue (ANT A3) adapter, which is built only when ALICEBLUE_ENABLED=true (default off: Alice Blue accounts stay unsupported, as before). No existing pack changes.

  20. Additive

    internal: more Pocketful and first Motilal Oswal live trading recorded

    No change for users (not in the API). The broker conformance corpus gains the replies from today's live trading runs on Pocketful (S2372: fills, positions, trades, brackets) and Motilal Oswal (EBOM053637: far LIMIT place / cancel, fills, positions, trades, stream frames), and the scenarios that passed.