| 1 | # Phase 5D.2 — ICICI Direct / Breeze API Contract Verification |
| 2 | |
| 3 | Research date: 2026-08-29 |
| 4 | Status: research/design only; no live request or production integration was performed. |
| 5 | |
| 6 | ## Executive decision |
| 7 | |
| 8 | The current official retail product is **Breeze API by ICICI Securities / ICICI Direct**. It provides official HTTP documentation and Java, Python, and JavaScript SDKs. Verified reads include Customer Details, Demat Holdings, Portfolio Holdings, Portfolio Positions, Funds, Margin, Quotes, and instrument lookup/master data. |
| 9 | |
| 10 | Use a narrowly allowlisted direct Java HTTP client in the future. Authentication is interactive and daily: the user logs into the official Breeze page with the AppKey, completes ICICI Direct credentials plus OTP, receives an `API_Session`, and exchanges it through Customer Details for the signed-request `session_token`. The session key expires 24 hours after generation or at midnight, whichever is earlier. |
| 11 | |
| 12 | Live implementation remains blocked by two material gaps: no documented API logout/revocation operation; and Demat Holdings expose ISIN/quantity but no prices, while Portfolio Holdings expose prices but no ISIN/token. Joining by ticker alone is unsafe. Use ISIN as equity identity and never fabricate price/cost data. |
| 13 | |
| 14 | ## A. Official API/product name |
| 15 | |
| 16 | **Breeze API**, ICICI Direct's retail API and the ICICI Securities trading API named by the official SDKs. The portal indicates that an ICICI Direct account is required and links account opening. |
| 17 | |
| 18 | ## B. Official sources consulted |
| 19 | |
| 20 | - [Breeze HTTP API Reference](https://api.icicidirect.com/breezeapi/documents/index.html) |
| 21 | - [Breeze API portal](https://api.icicidirect.com/apiuser/home) |
| 22 | - [Official session-key article](https://www.icicidirect.com/futures-and-options/api/breeze/article/what-is-a-session-key-and-how-to-generate-it-for-using-breezeapi) |
| 23 | - [Official Breeze Java SDK](https://github.com/Idirect-Tech/Breeze-Java-SDK) |
| 24 | - [Official Breeze Python SDK](https://github.com/Idirect-Tech/Breeze-Python-SDK) |
| 25 | - [Official Python SDK examples](https://github.com/Idirect-Tech/Breeze-Python-SDK/blob/main/README.md) |
| 26 | - [Official Python SDK endpoint configuration](https://github.com/Idirect-Tech/Breeze-Python-SDK/blob/main/breeze_connect/config.py) |
| 27 | |
| 28 | The older [ICICIDirect API document](https://api.icicidirect.com/apiuser/ICICIDirectAPIDOC.htm) was checked only for legacy differences. Current Breeze documentation/SDKs take precedence. |
| 29 | |
| 30 | ## C. Authentication flow |
| 31 | |
| 32 | The official reference calls the mechanism OAuth 2.0, but documents a Breeze-specific flow—not a standard authorization-code/token endpoint exchange. Do not assume generic Spring OAuth behavior. |
| 33 | |
| 34 | 1. The ICICI Direct customer logs into the Breeze portal and registers an app with app name and redirect URL. |
| 35 | 2. Registration issues an **AppKey** and **secret_key** unique to the app. |
| 36 | 3. Send the user's browser to `https://api.icicidirect.com/apiuser/login?api_key=<URL-ENCODED-APPKEY>`. |
| 37 | 4. On the official page, the user enters ICICI Direct credentials, generates an OTP, and logs in. Our platform must never collect or automate those values. |
| 38 | 5. Successful login exposes an **API_Session** in the resulting URL/page. Exact callback method and parameter encoding are **UNVERIFIED**. |
| 39 | 6. Call Customer Details with JSON fields `SessionToken=<API_Session>` and `AppKey=<AppKey>`. The official reference specifies `GET /breezeapi/api/v1/customerdetails`, JSON content type, and no signed common headers. |
| 40 | 7. Customer Details returns `Success.session_token`, used as `X-SessionToken` thereafter. The SDK decodes it internally into user ID/session key; direct REST uses the returned value. |
| 41 | 8. For each subsequent v1 request, serialize the exact JSON body, create UTC ISO-8601 time with zero milliseconds (for example `2024-06-01T10:23:56.000Z`), and compute `SHA-256(timestamp + exactJsonBody + secret_key)`. |
| 42 | 9. Send `Content-Type: application/json`, `X-Checksum: token <hex-digest>`, `X-Timestamp`, `X-AppKey`, and `X-SessionToken`. Client/server time must be within 60 seconds. |
| 43 | |
| 44 | | Authentication item | Verification | |
| 45 | |---|---| |
| 46 | | AppKey, secret_key, registered redirect URL | VERIFIED | |
| 47 | | Browser login URL, ICICI credentials and OTP on official page | VERIFIED | |
| 48 | | API_Session and Customer Details exchange | VERIFIED | |
| 49 | | Signed headers and SHA-256 checksum | VERIFIED | |
| 50 | | Generic OAuth refresh token | UNVERIFIED; none documented | |
| 51 | | Programmatic credential/OTP authentication | UNVERIFIED and prohibited here | |
| 52 | | Exact callback method/parameter encoding | UNVERIFIED | |
| 53 | |
| 54 | ## D. Session lifecycle |
| 55 | |
| 56 | - Session key validity: **24 hours after generation or midnight, whichever is earlier**. |
| 57 | - A new key is required for the next trading day; the app need not be recreated but must be active. |
| 58 | - Known expiry/auth failures map to `SESSION_EXPIRED` and require interactive login. |
| 59 | - Refresh token/silent renewal: **UNVERIFIED / unsupported**. |
| 60 | - REST logout/revocation: **UNVERIFIED**. Disconnect must erase local secrets and must not claim server revocation. |
| 61 | - Whether a new session invalidates old sessions and permitted concurrency: **UNVERIFIED**. |
| 62 | |
| 63 | ## E. Required credential/configuration fields |
| 64 | |
| 65 | | Current field | Decision | Reason | |
| 66 | |---|---|---| |
| 67 | | `enabled` | KEEP | Local feature gate. | |
| 68 | | `baseUrl` | KEEP | Official v1 base is `https://api.icicidirect.com/breezeapi/api/v1/`; retain a test/config seam. | |
| 69 | | `clientId` | RENAME to `appKey` | Official term is AppKey. | |
| 70 | | `callbackUrl` | RENAME to `redirectUrl` | Official registration term. | |
| 71 | | `authMethod` | KEEP as fixed strategy selector | Use a local value such as `breeze-interactive-session`, not generic authorization-code OAuth. | |
| 72 | | `officialDocumentationVerified` | KEEP | Existing safety gate. | |
| 73 | |
| 74 | ## F. Recommended future `ICICIDirectProviderProperties` |
| 75 | |
| 76 | Future conceptual properties: |
| 77 | |
| 78 | ```text |
| 79 | enabled |
| 80 | baseUrl |
| 81 | loginUrl |
| 82 | appKey |
| 83 | redirectUrl |
| 84 | authMethod = breeze-interactive-session |
| 85 | secretKeyReference |
| 86 | officialDocumentationVerified |
| 87 | ``` |
| 88 | |
| 89 | `secretKeyReference` is an **ADD** and resolves through a secret provider. Add `loginUrl` only as an allowlisted test seam. Do not add persisted API/session tokens, ICICI password, OTP, or speculative security ID. Runtime session material does not belong in provider properties. |
| 90 | |
| 91 | ## G. Account contract |
| 92 | |
| 93 | Verified operation: `GET /breezeapi/api/v1/customerdetails` with `{"SessionToken":"<API_Session>","AppKey":"<AppKey>"}`. |
| 94 | |
| 95 | Verified response fields include `idirect_userid`, `idirect_user_name`, `idirect_lastlogin_time`, `segments_allowed`, exchange dates/status, and `session_token`. |
| 96 | |
| 97 | Safe normalized mapping: |
| 98 | |
| 99 | | `BrokerAccount` field | Mapping | |
| 100 | |---|---| |
| 101 | | `brokerAccountId` | Deterministic internal ID from provider + `idirect_userid`. | |
| 102 | | `userId` | Authenticated application user only. | |
| 103 | | `brokerType` | `ICICI_DIRECT`. | |
| 104 | | `externalAccountReference` | Masked `idirect_userid`; whether this is a trading-account number is **UNVERIFIED**. | |
| 105 | | `displayName` | `idirect_user_name`, with privacy controls. | |
| 106 | | `baseCurrency` | INR is implied by Funds' rupee semantics, but Customer Details has no currency field: **UNVERIFIED as an account attribute**. | |
| 107 | | `status` | Active only after current-session Customer Details succeeds. | |
| 108 | |
| 109 | Account type, Demat/trading account number, multiple-account enumeration, and explicit base currency are **UNVERIFIED**. The endpoint describes the authenticated customer, not an account list. |
| 110 | |
| 111 | ## H. Holdings contracts |
| 112 | |
| 113 | ### Demat Holdings — preferred long-term ownership source |
| 114 | |
| 115 | `GET /breezeapi/api/v1/dematholdings`, signed headers, exact `{}` body. |
| 116 | |
| 117 | Verified fields: `stock_code`, `stock_ISIN`, `quantity`, `demat_total_bulk_quantity`, `demat_avail_quantity`, `blocked_quantity`, `demat_allocated_quantity`. |
| 118 | |
| 119 | This explicitly represents Demat holdings and supplies ISIN. It does not supply average cost, price, value, or exchange. Use documented `quantity`; no official semantics justify a derived net formula from the other quantities. Preserve them as metadata when supported. |
| 120 | |
| 121 | ### Portfolio Holdings — richer portfolio/valuation view |
| 122 | |
| 123 | `GET /breezeapi/api/v1/portfolioholdings` with `exchange_code` (`NSE`/`NFO`), ISO dates, `stock_code`, and optional `portfolio_type`. The reference marks dates/stock code required while official SDK examples use blank stock code for all; complete-query semantics are **UNVERIFIED**. |
| 124 | |
| 125 | Responses include stock/exchange code, quantity, average/current price, product and derivative fields, realized/unrealized P&L, and open-position value. Official SDK guidance says `NSE` for equity holdings. It returns no ISIN/token, so it must not independently create canonical equity positions. A ticker-only join to Demat Holdings is unsafe and **UNVERIFIED**. |
| 126 | |
| 127 | ## I. Positions contract |
| 128 | |
| 129 | `GET /breezeapi/api/v1/portfoliopositions`, signed headers, `{}` body. |
| 130 | |
| 131 | Responses include segment/product/exchange/stock, derivative attributes, action, quantity, average price, LTP/price, settlement/margin, cover/stop-loss, MTF, pledge, P&L, and order-related fields. |
| 132 | |
| 133 | This is distinct from Demat Holdings and represents trading positions, including derivative and potentially intraday/MTF state. It must not substitute for delivery holdings. Initial live scope should exclude derivatives and leveraged/intraday products until their identity/lifecycle are separately designed. |
| 134 | |
| 135 | ## J. Funds/cash contract |
| 136 | |
| 137 | Verified read: `GET /breezeapi/api/v1/funds`, signed headers, empty JSON body. |
| 138 | |
| 139 | Verified fields: `bank_account`, `total_bank_balance`, segment allocations, per-segment trade blocks, `block_by_trade_balance`, and `unallocated_balance`. The write form documents amounts as rupees, supporting INR normalization. Do not invent arithmetic relationships. |
| 140 | |
| 141 | Safe initial mapping: INR; reported cash from `total_bank_balance` with provider-label provenance; settled cash `null`; preserve allocations/blocks/unallocated separately rather than calling them available or settled cash. Never invoke `POST /funds` (`setFunds`). Official `GET /margin` fields are margin, not cash, and must not populate settled cash. |
| 142 | |
| 143 | ## K. Stable instrument identity |
| 144 | |
| 145 | For delivery equities: |
| 146 | |
| 147 | ```text |
| 148 | provider = ICICI_DIRECT |
| 149 | externalInstrumentId = ISIN:<normalized stock_ISIN> |
| 150 | ``` |
| 151 | |
| 152 | `stock_ISIN` is an official Demat Holdings field and is a security identifier, unlike display ticker. Remove formatting whitespace and validate ISIN shape. Ticker alone is forbidden. |
| 153 | |
| 154 | Official `get_names` maps exchange/stock code to `isec_stock_code` and `isec_token`; the master is updated daily at 08:00. Token stability across corporate actions/exchanges is **UNVERIFIED**. Portfolio responses expose neither ISIN nor token, and their safe mapping to Demat ISIN is **UNVERIFIED**. Reject/quarantine records without verified ISIN rather than invent identity. Canonical derivative identity is also **UNVERIFIED**. |
| 155 | |
| 156 | The existing normalizer may structurally accept broker security ID or ISIN, but the future delivery-equity adapter should supply only verified ISIN unless ICICI documents token stability. |
| 157 | |
| 158 | ## L. Exchange representation |
| 159 | |
| 160 | The HTTP reference verifies `NSE` cash equity and `NFO` derivatives, and says BSE/MCX securities are currently unavailable. Current official SDK notes also list BSE/BFO, and Customer Details samples expose BSE/FNO/NDX status. Therefore: |
| 161 | |
| 162 | - `NSE`: VERIFIED for cash equity. |
| 163 | - `NFO`: VERIFIED for NSE derivatives. |
| 164 | - BSE/BFO: **UNVERIFIED due to conflicting official sources**. |
| 165 | - MCX/NDX applicability: **UNVERIFIED for Phase 5D.3**. |
| 166 | |
| 167 | Preserve upstream exchange codes and normalize only explicitly supported values; do not hard-code the broader SDK list yet. |
| 168 | |
| 169 | ## M. Rate limits and operational rules |
| 170 | |
| 171 | - REST limit: **100 calls/minute and 5,000 calls/day**. |
| 172 | - Timestamp skew: at most **60 seconds**. |
| 173 | - Session: **24 hours or midnight, whichever is earlier**. |
| 174 | - App must be active to generate a session. |
| 175 | - Session concurrency, login conflicts, read trading-hour restrictions, maintenance SLA, and throttling response/`Retry-After`: **UNVERIFIED**. |
| 176 | - Static IP is officially required for order requests. Its applicability to read-only calls is **UNVERIFIED**. |
| 177 | |
| 178 | Enforce per-user/per-connection budgets beneath both limits. Do not retry authentication failures; only bounded/backoff retries for safe reads. |
| 179 | |
| 180 | ## N. SDK vs direct HTTP recommendation |
| 181 | |
| 182 | Choose **A. direct Java HTTP client**. |
| 183 | |
| 184 | Direct REST is officially documented. The official Java SDK exists but is documented as a manually included JAR and exposes trading mutations alongside reads. A small allowlisted Spring client can expose only approved reads, avoids a Python sidecar, and simplifies Kubernetes deployment, observability, timeouts, connection pooling, and scoped sessions. The official Java SDK remains a contract oracle for signing/serialization tests. |
| 185 | |
| 186 | ## O. Connector mapping |
| 187 | |
| 188 | | Connector method | Verified upstream/design | |
| 189 | |---|---| |
| 190 | | `status(userId, connectionId)` | No status endpoint. Check scoped local expiry; optionally probe Customer Details within limits. Missing session → `AUTHENTICATION_REQUIRED`; known expiry/auth failure → `SESSION_EXPIRED`; transport/5xx → unavailable; success → `CONNECTED`. | |
| 191 | | `fetchAccounts(...)` | Customer Details; produce one current-customer account without fabricating type/number. | |
| 192 | | `fetchPositions(...)` | Initial delivery source is Demat Holdings. Portfolio Holdings may enrich only after safe identity joining; Portfolio Positions remains a distinct trading-position feed. | |
| 193 | | `fetchCashBalances(...)` | Funds GET; preserve semantic distinctions and never treat Margin as cash. | |
| 194 | | `disconnect(...)` | Erase scoped local API_Session/session token. Server logout is **UNVERIFIED**. | |
| 195 | |
| 196 | All state remains keyed by `(authenticated application userId, broker connectionId)`. No singleton client may hold mutable session credentials. |
| 197 | |
| 198 | ## P. Read-only capabilities |
| 199 | |
| 200 | Advertise only after implementation and fixture tests: |
| 201 | |
| 202 | | Capability | Safe decision | |
| 203 | |---|---| |
| 204 | | `ACCOUNTS_READ` | Supported via Customer Details, subject to one-current-customer limitation. | |
| 205 | | `ACCOUNT_METADATA_READ` | Supported for returned identity/name/segment/exchange metadata. | |
| 206 | | `POSITIONS_READ` | Supported only with explicit distinction between delivery Demat holdings and trading positions. | |
| 207 | | `CASH_READ` | Supported via Funds, without a settled-cash claim. | |
| 208 | | `PORTFOLIO_READ` | Enable only when ISIN holdings can be represented without fabricated price/cost; may require normalized-model adjustment or verified enrichment. | |
| 209 | | `ORDER_EXECUTION` | Always absent. | |
| 210 | |
| 211 | Until documentation, configuration, authentication, connector implementation, and tests are satisfied, capabilities remain empty. |
| 212 | |
| 213 | ## Q. Security/session storage |
| 214 | |
| 215 | - No secrets/session values in Git, logs, errors, API responses, portfolio-service, or portfolio tables. |
| 216 | - Handle AppKey conservatively; resolve `secret_key` through `secretKeyReference`. |
| 217 | - Production: Kubernetes Secret; future Azure Key Vault via existing secret-provider abstraction; local: ignored environment/process configuration. |
| 218 | - Encrypt API_Session/session token at rest, keyed by `(userId, connectionId)`, with created/expiry timestamps. Erase on disconnect/expiry. |
| 219 | - Build login URLs only on the allowlisted official host and URL-encode AppKey. |
| 220 | - Correlate callback completion to authenticated user/connection with one-time state/nonce. Upstream callback details remain **UNVERIFIED**. |
| 221 | - Never receive/store/proxy ICICI password or OTP. |
| 222 | - Sign exact sent bytes and redact AppKey, API_Session, session token, secret, checksum, bank account, and customer identifiers. |
| 223 | - Require TLS validation, strict timeouts, response-size limits, rate limits, and host allowlists. |
| 224 | |
| 225 | ## R. Consolidated UNVERIFIED items |
| 226 | |
| 227 | 1. Exact redirect callback method, parameter encoding, and error payload. |
| 228 | 2. Refresh/silent renewal; REST logout/revocation. |
| 229 | 3. Concurrent-session limits and web/mobile login interaction. |
| 230 | 4. Whether `idirect_userid` is a formal trading-account number; account type, multiple accounts, explicit currency. |
| 231 | 5. Demat quantity reconciliation beyond the reported `quantity`. |
| 232 | 6. Safe Portfolio stock-code → Demat ISIN join; ISEC token/code stability. |
| 233 | 7. Canonical derivative identity. |
| 234 | 8. BSE/BFO read support due to conflicting official material; MCX/NDX Phase 5D.3 applicability. |
| 235 | 9. Read-call static-IP requirement, trading-hour restrictions, maintenance/SLA, throttle response. |
| 236 | 10. Whether `total_bank_balance` is withdrawable/available trading cash. |
| 237 | 11. Whether blank filters/date ranges always yield the complete current equity Portfolio Holdings set. |
| 238 | |
| 239 | Resolve these through updated official documentation or written clarification from `breezeapi@icicisecurities.com`, not experiments against a funded account. |
| 240 | |
| 241 | ## S. Proposed Phase 5D.3 |
| 242 | |
| 243 | 1. Obtain written clarification for callback, logout, account ID/currency, complete holdings, instrument/token stability, and BSE. |
| 244 | 2. Capture sanitized official fixtures for success, empty, malformed, expired, throttled, and unavailable responses—no live calls in automated tests. |
| 245 | 3. Rename properties and add secret references; never store runtime tokens as properties. |
| 246 | 4. Implement allowlisted read-only Java HTTP signing with injected clock. |
| 247 | 5. Implement interactive login correlation and encrypted scoped sessions. |
| 248 | 6. Normalize Customer Details and Funds. |
| 249 | 7. Implement Demat Holdings with ISIN-only identity; decide how to represent missing cost/price before portfolio import. |
| 250 | 8. Keep Portfolio Holdings enrichment and Portfolio Positions under separate semantics/tests. |
| 251 | 9. Add dual-window limits, midnight/24-hour expiry, redaction, isolation, and no-order tests. |
| 252 | 10. Advertise capabilities one by one only after contract tests. |
| 253 | |
| 254 | Out of scope: fund mutations, margin additions, order/trade mutations, order streams/webhooks, and all order execution. |
| 255 | |
| 256 | ## T. Safety confirmation |
| 257 | |
| 258 | - No live brokerage action or ICICI API request was performed. |
| 259 | - No order was placed and no production trading capability was enabled. |
| 260 | - No endpoint/schema/identity guarantee/auth behavior was invented; gaps are marked **UNVERIFIED**. |
| 261 | - IBKR code was not changed. |
| 262 | - Phase 5C portfolio persistence was not changed. |
| 263 | - No production code changed in Phase 5D.2; only this research document changed. |