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.
- 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 ismarket_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. - Additive
portfolio breakdown fields, instrument sector / ISIN / indices, safer order modify
- Portfolio rows (stream snapshots and
portfoliomessages, thepositionswebhook, 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: positionpnlis still the total, holdingquantityis unchanged, marginutilisedis 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 existingProductvalues). - 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) andindices(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 optionalIdempotency-Keyheader (repeat returns first answer withIdempotent-Replayed: true; different body → 422idempotency_key_reused; in-flight → 409request_in_progress; invalid key → 400invalid_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}refusesquantitynot greater than filled quantity with 422invalid_request,field: "quantity".
- Portfolio rows (stream snapshots and
- 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/accountsshows the existinglogin_requiredstatus (and nosession_expires_at), and orders, modifies, cancels, exits and GTT calls on the account are refused before the broker with the existingno_sessionreason ("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. - Additive
Fyers, Alice Blue and Groww always served
The
FYERS_ENABLED,ALICEBLUE_ENABLEDandGROWW_ENABLEDrollout 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. - 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, asPATCH /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, sametrigger_id) takes over at the levels asked for and the change answers200with it; otherwise502 broker_refusedsaying the position has no stop-loss / target. Either way aprotection_failedactivity 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.
- 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 classesoutcome_unknown(Zerodha HTTP 502 "Error parsing response (kt-oms)."DataException, Tradejini "Unable to connect to endpoint. Kindly try again.") is no longer a definiterejected. The order book is read once: the order listed cancelled answersaccepted; still open (or filled, rejected, expired) answersrejectedwith 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 answersunknown. Such a cancel is never sent again.PATCH /v1/orders/bracket/{order_id}: such a reply answersunknown, as a modify already did. No field or value changes. - 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 answerstatus: "unknown"(the order book decides) instead ofrejected; 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 answersunknown.status/ended_reasonof 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:
utilisedis 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.
- Pocketful (and any broker whose pack classes the reply
- 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 fieldwarnings(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.statusis 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 slicemessageand the activity'smessagenow 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_messagekeeps Upstox's own words). Itsreasonis nowbroker_rejected(an existing value), notno_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. - 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. - 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
REJECTEDwithstatus_message"never reached the broker: ..." (order_updatewebhook,/v1/stream, order lists, activityorder_rejected), and leaves the open orders. Before, it stayedUNKNOWNand 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. - 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.
- 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 409bracket_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. - Additive
Tradejini positions listed once per product; live replies recorded
GET /v1/positionsfor 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; Zerodhaaccount_id/placed_byare redacted; two Pocketful refusals (order already gone on a cover-leg modify, wrong execution type on a cancel) are classed. - 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).
- 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. WhileFYERS_ENABLEDis 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 asTRIGGER_PENDING, notOPEN; the Fyers postback secret probe also logs header names containingsignorhmac(names only). Thefyersandalicebluepacks reachreplay_greenwith 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 refusalinvalid_price_or_qty; Groww GA001 invalid symbolinvalid_field). Thegrowwpack gains its place / modify / cancel / failure / margin-quote replies and every Order Status annexure status frozen. No existing recording or expectation changes. - Additive
Groww adapter behind GROWW_ENABLED; its conformance pack
No change for users while
GROWW_ENABLEDis 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/accountsreports themsupported, updatespolling). The broker conformance corpus gains a newgrowwpack atreplay_green: synthetic recordings of the order list, positions, holdings, trades and margins replies from Groww's API docs, their expectations, and theOPENstatus frozen. - Additive
internal: Fyers conformance pack (scaffolded)
No change for users (not in the API). A new
fyersbroker pack at stagescaffolded: its capability claims, fingerprint / redaction fields and emptytypes.json,errors.tomlandstatus_map.json(nothing handled yet; no existing pack, recording or expectation changes). The Fyers adapter it describes is off unlessFYERS_ENABLED=true. - Additive
internal: Alice Blue conformance pack scaffolded
No change for users (not in the API). A new
alicebluepack at stagescaffolded(emptytypes.json,errors.tomlandstatus_map.json) for the new Alice Blue (ANT A3) adapter, which is built only whenALICEBLUE_ENABLED=true(default off: Alice Blue accounts stay unsupported, as before). No existing pack changes. - 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.