{"_id":"@bogdanripa/bt-trade","_rev":"6-b845429d94b4a6870b7d0f1e7ce87923","name":"@bogdanripa/bt-trade","dist-tags":{"latest":"0.3.2"},"versions":{"0.1.0":{"name":"@bogdanripa/bt-trade","version":"0.1.0","keywords":["bt-trade","bttrade","banca-transilvania","bvb","bucharest-stock-exchange","trading","stocks","etf","romania","api-client"],"author":{"name":"Bogdan Ripa","email":"bogdanripa@gmail.com"},"license":"MIT","_id":"@bogdanripa/bt-trade@0.1.0","maintainers":[{"name":"bogdanripa","email":"bogdanripa@gmail.com"}],"homepage":"https://github.com/bogdanripa/bt-trade#readme","bugs":{"url":"https://github.com/bogdanripa/bt-trade/issues"},"bin":{"bt-trade":"bin/bt-trade.js"},"dist":{"shasum":"875161e39887fd7c27b8b179aad04cf64a59125f","tarball":"https://registry.npmjs.org/@bogdanripa/bt-trade/-/bt-trade-0.1.0.tgz","fileCount":15,"integrity":"sha512-5YLnPyclPzwLQxvV9m/+6iRUXliiynYOsdinBGpazaLTTzA3gec2sDWYzONT/WOuO8R/qqYSKLTv+bGQEl8f5Q==","signatures":[{"sig":"MEUCIELstFDC3RUQ/9DjnfluYunCU+uclKanM5uPec3xII4cAiEA2BX5V8arbkVBLcf4u8W4HDbpnyS7+i7wogtEcok1Gbc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":66312},"main":"./src/index.js","type":"module","engines":{"node":">=18"},"exports":{".":"./src/index.js","./package.json":"./package.json"},"gitHead":"efbc087928b5a8f44ee8c017c440f4c29185ad0a","scripts":{"check":"node --check src/index.js && node --check src/client.js && node --check bin/bt-trade.js","start":"node bin/bt-trade.js"},"_npmUser":{"name":"bogdanripa","email":"bogdanripa@gmail.com"},"repository":{"url":"git+https://github.com/bogdanripa/bt-trade.git","type":"git"},"_npmVersion":"11.11.0","description":"Unofficial Node.js client for the bt-trade.ro trading platform (HTTP only, no SignalR, zero deps)","directories":{},"_nodeVersion":"25.8.1","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/bt-trade_0.1.0_1776670774792_0.3213223594224319","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@bogdanripa/bt-trade","version":"0.1.1","keywords":["bt-trade","bttrade","banca-transilvania","bvb","bucharest-stock-exchange","trading","stocks","etf","romania","api-client"],"author":{"name":"Bogdan Ripa","email":"bogdanripa@gmail.com"},"license":"MIT","_id":"@bogdanripa/bt-trade@0.1.1","maintainers":[{"name":"bogdanripa","email":"bogdanripa@gmail.com"}],"homepage":"https://github.com/bogdanripa/bt-trade#readme","bugs":{"url":"https://github.com/bogdanripa/bt-trade/issues"},"bin":{"bt-trade":"bin/bt-trade.js"},"dist":{"shasum":"e4b23953de220aee48c8094b509be237304cab17","tarball":"https://registry.npmjs.org/@bogdanripa/bt-trade/-/bt-trade-0.1.1.tgz","fileCount":18,"integrity":"sha512-h4UBlHIG4FUXog6U7r77Tr69MWoNwRE+dyVt1TbJtluFlmsXPE7AnE6brdb+ltsoYsZdOS9jU5YKrh//WrzTaw==","signatures":[{"sig":"MEYCIQDX2RSkdbtyppqCLvZXJtGb4SWO8UQH6QkQVJ5Dp/xmvAIhAM8S7RLEw2+qiS6J4Wwkg3AI/cXrYDcC3JDowdZw5vll","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":125489},"main":"./src/index.js","type":"module","engines":{"node":">=18"},"exports":{".":"./src/index.js","./package.json":"./package.json"},"gitHead":"046a6d234e881ede430c6ab29ffff2f31f3375bd","scripts":{"check":"node --check src/index.js && node --check src/client.js && node --check bin/bt-trade.js","start":"node bin/bt-trade.js"},"_npmUser":{"name":"bogdanripa","email":"bogdanripa@gmail.com"},"repository":{"url":"git+https://github.com/bogdanripa/bt-trade.git","type":"git"},"_npmVersion":"11.11.0","description":"Unofficial Node.js client for the bt-trade.ro trading platform (HTTP only, no SignalR, zero deps)","directories":{},"_nodeVersion":"25.8.1","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/bt-trade_0.1.1_1776674517963_0.5346185634842577","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@bogdanripa/bt-trade","version":"0.2.0","keywords":["bt-trade","bttrade","banca-transilvania","bvb","bucharest-stock-exchange","trading","stocks","etf","romania","api-client"],"author":{"name":"Bogdan Ripa","email":"bogdanripa@gmail.com"},"license":"MIT","_id":"@bogdanripa/bt-trade@0.2.0","maintainers":[{"name":"bogdanripa","email":"bogdanripa@gmail.com"}],"homepage":"https://github.com/bogdanripa/bt-trade#readme","bugs":{"url":"https://github.com/bogdanripa/bt-trade/issues"},"bin":{"bt-trade":"bin/bt-trade.js"},"dist":{"shasum":"62330e8b1f6d556c850b90246532769f91f1696e","tarball":"https://registry.npmjs.org/@bogdanripa/bt-trade/-/bt-trade-0.2.0.tgz","fileCount":19,"integrity":"sha512-N1HuBWPb+VCXgW/xI58igflX6JHJkXR7e5JKZykv2nb8tMJlJy41MnzJiFPQdrGr//t3PdS2DEwU8TEqYjqjqw==","signatures":[{"sig":"MEQCIEaiiENpR7VTOqzVoSQxKdBPA0IuHnJq5bjc7avMAwoOAiAKEfvlAnX+AERrLLO7rUSkVF6kkhvfDr2Bwlt3ojeLWQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":129117},"main":"./src/index.js","type":"module","engines":{"node":">=18"},"exports":{".":"./src/index.js","./package.json":"./package.json"},"gitHead":"71b627c781149f6ecba1c24cce4725ac0cf19d37","scripts":{"check":"node --check src/index.js && node --check src/client.js && node --check bin/bt-trade.js","start":"node bin/bt-trade.js"},"_npmUser":{"name":"bogdanripa","email":"bogdanripa@gmail.com"},"repository":{"url":"git+https://github.com/bogdanripa/bt-trade.git","type":"git"},"_npmVersion":"11.11.0","description":"Unofficial Node.js client for the bt-trade.ro trading platform (HTTP only, no SignalR, zero deps)","directories":{},"_nodeVersion":"25.8.1","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/bt-trade_0.2.0_1776675176172_0.302475088399641","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@bogdanripa/bt-trade","version":"0.3.0","keywords":["bt-trade","bttrade","banca-transilvania","bvb","bucharest-stock-exchange","trading","stocks","etf","romania","api-client"],"author":{"name":"Bogdan Ripa","email":"bogdanripa@gmail.com"},"license":"MIT","_id":"@bogdanripa/bt-trade@0.3.0","maintainers":[{"name":"bogdanripa","email":"bogdanripa@gmail.com"}],"homepage":"https://github.com/bogdanripa/bt-trade#readme","bugs":{"url":"https://github.com/bogdanripa/bt-trade/issues"},"bin":{"bt-trade":"bin/bt-trade.js"},"dist":{"shasum":"e9767d833c277f5f66b35368a4c9577f6d2a0d81","tarball":"https://registry.npmjs.org/@bogdanripa/bt-trade/-/bt-trade-0.3.0.tgz","fileCount":18,"integrity":"sha512-ZOG3VfK42Epwk+BWKx9yDCeCc/tW6hrv4C2mFB14TxwAeT+sVsKbGWdboqVj7Pe4ff+2CghpbM3M2fLapUCauw==","signatures":[{"sig":"MEUCIGayUDNMd26Jl9yvDGWFkcDSTFMF4PgYC47hG4PBOgDgAiEAkjtQ0EAw/3h8B0BfdfqK9yHBHacy1wpbMYYRtxSHfXw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":116706},"main":"./src/index.js","type":"module","engines":{"node":">=18"},"exports":{".":"./src/index.js","./package.json":"./package.json"},"gitHead":"0c1bc43003a8062e15806e4cad30c63cb8e8949e","scripts":{"check":"node --check src/index.js && node --check src/client.js && node --check bin/bt-trade.js","start":"node bin/bt-trade.js"},"_npmUser":{"name":"bogdanripa","email":"bogdanripa@gmail.com"},"repository":{"url":"git+https://github.com/bogdanripa/bt-trade.git","type":"git"},"_npmVersion":"11.11.0","description":"Unofficial Node.js client for the bt-trade.ro trading platform (HTTP only, no SignalR, zero deps)","directories":{},"_nodeVersion":"25.8.1","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/bt-trade_0.3.0_1776760587641_0.9765075735069026","host":"s3://npm-registry-packages-npm-production"}},"0.3.1":{"name":"@bogdanripa/bt-trade","version":"0.3.1","keywords":["bt-trade","bttrade","banca-transilvania","bvb","bucharest-stock-exchange","trading","stocks","etf","romania","api-client"],"author":{"name":"Bogdan Ripa","email":"bogdanripa@gmail.com"},"license":"MIT","_id":"@bogdanripa/bt-trade@0.3.1","maintainers":[{"name":"bogdanripa","email":"bogdanripa@gmail.com"}],"homepage":"https://github.com/bogdanripa/bt-trade#readme","bugs":{"url":"https://github.com/bogdanripa/bt-trade/issues"},"bin":{"bt-trade":"bin/bt-trade.js"},"dist":{"shasum":"e446c7a17cd73c85137af5ea9894be4a71fca49a","tarball":"https://registry.npmjs.org/@bogdanripa/bt-trade/-/bt-trade-0.3.1.tgz","fileCount":18,"integrity":"sha512-el9IBamNv3ptWKEUQF3BVQbRlj//VtSGjczP+a87mQWTiJL5I5KoPjvSjUeMRpeFTeeZzPX3n24/ZYAfVqFoGQ==","signatures":[{"sig":"MEYCIQCHhSCY0pJAc2wx/mzCFOJiMUJcXY4ojgsbAbPalUJ2ZwIhAI5jzoNvIEyNNgHIQdRLYPqKtqUiY7oZ0iPBdJdCrH+c","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":116952},"main":"./src/index.js","type":"module","engines":{"node":">=18"},"exports":{".":"./src/index.js","./package.json":"./package.json"},"gitHead":"402c43c0d684ed518300f2427914656d8f8ed1cb","scripts":{"check":"node --check src/index.js && node --check src/client.js && node --check bin/bt-trade.js","start":"node bin/bt-trade.js"},"_npmUser":{"name":"bogdanripa","email":"bogdanripa@gmail.com"},"repository":{"url":"git+https://github.com/bogdanripa/bt-trade.git","type":"git"},"_npmVersion":"11.11.0","description":"Unofficial Node.js client for the bt-trade.ro trading platform (HTTP only, no SignalR, zero deps)","directories":{},"_nodeVersion":"25.8.1","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/bt-trade_0.3.1_1776872494646_0.1584457278218887","host":"s3://npm-registry-packages-npm-production"}},"0.3.2":{"name":"@bogdanripa/bt-trade","version":"0.3.2","description":"Unofficial Node.js client for the bt-trade.ro trading platform (HTTP only, no SignalR, zero deps)","type":"module","main":"./src/index.js","exports":{".":"./src/index.js","./package.json":"./package.json"},"bin":{"bt-trade":"bin/bt-trade.js"},"engines":{"node":">=18"},"scripts":{"start":"node bin/bt-trade.js","check":"node --check src/index.js && node --check src/client.js && node --check bin/bt-trade.js"},"keywords":["bt-trade","bttrade","banca-transilvania","bvb","bucharest-stock-exchange","trading","stocks","etf","romania","api-client"],"author":{"name":"Bogdan Ripa","email":"bogdanripa@gmail.com"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/bogdanripa/bt-trade.git"},"bugs":{"url":"https://github.com/bogdanripa/bt-trade/issues"},"homepage":"https://github.com/bogdanripa/bt-trade#readme","gitHead":"32579d5ebf1f37bbd3cbab43aab1ef50f2436ffa","_id":"@bogdanripa/bt-trade@0.3.2","_nodeVersion":"22.23.1","_npmVersion":"12.0.2","dist":{"integrity":"sha512-rSZpGV5/veIXwEe2QFI7Wp27R8uq4gbAzkQNJxIcwgm4Mtg1LrpoXszgUSWlqIEDP/YV/GiMkZd7id0+N+v5Tw==","shasum":"a2f9a1eaf954a205cf9688389d20b58b552a0166","tarball":"https://registry.npmjs.org/@bogdanripa/bt-trade/-/bt-trade-0.3.2.tgz","fileCount":18,"unpackedSize":118986,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bogdanripa%2fbt-trade@0.3.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCWYxFNWJLRw5ElHpzChpaTvVhJEA+5wa7lpU9rFigQnwIhALPb+eAP8z46MT7nEuzZmnkC5jARdCTtIR9bqvHRHUEU"}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:8f2e674d-428c-463c-9150-007573c242ad"}},"directories":{},"maintainers":[{"name":"bogdanripa","email":"bogdanripa@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/bt-trade_0.3.2_1786450813349_0.4871584151091537"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-20T07:39:34.732Z","modified":"2026-08-11T12:20:13.819Z","0.1.0":"2026-04-20T07:39:34.926Z","0.1.1":"2026-04-20T08:41:58.164Z","0.2.0":"2026-04-20T08:52:56.312Z","0.3.0":"2026-04-21T08:36:27.761Z","0.3.1":"2026-04-22T15:41:34.813Z","0.3.2":"2026-08-11T12:20:13.492Z"},"bugs":{"url":"https://github.com/bogdanripa/bt-trade/issues"},"author":{"name":"Bogdan Ripa","email":"bogdanripa@gmail.com"},"license":"MIT","homepage":"https://github.com/bogdanripa/bt-trade#readme","keywords":["bt-trade","bttrade","banca-transilvania","bvb","bucharest-stock-exchange","trading","stocks","etf","romania","api-client"],"repository":{"type":"git","url":"git+https://github.com/bogdanripa/bt-trade.git"},"description":"Unofficial Node.js client for the bt-trade.ro trading platform (HTTP only, no SignalR, zero deps)","maintainers":[{"name":"bogdanripa","email":"bogdanripa@gmail.com"}],"readme":"# bt-trade\n\nUnofficial Node.js client for the **bt-trade.ro** trading platform. HTTP only\n— no SignalR, no WebSocket, no browser automation. Zero runtime dependencies.\n\n> Requires Node 18+ (uses built-in `fetch`).\n\n## Disclaimer\n\nThis project is **not affiliated with, endorsed by, or sponsored by** Banca\nTransilvania or BT Capital Partners. It is an independent third-party client\ncreated by reverse-engineering the publicly accessible web-app's HTTP API.\n\"BT Trade\" and related marks are the property of their respective owners.\n\nUse it at your own risk: the platform is not required to maintain a stable\nAPI, and it may change, rate-limit, or block clients using this library\nwithout notice. Authentication credentials are only ever sent to the official\nbt-trade.ro / evo.bt-trade.ro endpoints; the library makes no network calls\nanywhere else.\n\nThe author and contributors are not liable for any financial loss, account\nsuspension, or other consequences arising from the use of this software. Read\nthe terms of service of the platform before using.\n\n## Install\n\n```bash\nnpm install @bogdanripa/bt-trade\n```\n\nOr drop the source folder into your project — there are no dependencies.\n\n## Quick start\n\n```js\nimport { BTTradeClient, stdinOtpProvider } from '@bogdanripa/bt-trade';\n\nconst client = new BTTradeClient({\n  otpProvider: stdinOtpProvider(),   // prompts the terminal for the SMS code\n});\n\nawait client.login({ username: 'MYUSER', password: process.env.BT_PASS });\n\nconst accounts = await client.accounts.list();\nconst { portfolioKey } = accounts[0];\n\nconst holdings = await client.portfolio.getHoldings({ portfolioKey });\nconst orders   = await client.orders.search({ portfolioKey });\n```\n\nRun the bundled interactive CLI:\n\n```bash\nnpx bt-trade\n# or\nnode bin/bt-trade.js [--demo] [--debug] [--ntfy-topic <topic>] [--otp-stdin]\n```\n\n## Authentication\n\n1. `login()` posts username + password to `POST /api/RefreshToken`.\n2. If 2FA is required the server returns a pending `access_token` and sends an SMS.\n3. `BTTradeClient` calls your `otpProvider({ username, prefix, details, expiresIn })` to get the code.\n4. Step 2 re-posts with `?code=<otp>` and the pending token as `Authorization: Bearer`.\n5. On success you receive an `access_token` (~10 min) and a `refresh_token`.\n\n### Token lifecycle\n\nThe refresh token is a rotating credential. Each time the access token is refreshed, a new pair is issued. The library handles this automatically:\n\n- **Background timer** fires a few seconds before the refresh token expires and refreshes both tokens silently, keeping the session alive indefinitely as long as the process is running.\n- **On-request lazy refresh** — if the access token has expired by the time a request fires (e.g. the process was paused), it is refreshed transparently before the call.\n- **Reactive 401 retry** — if the server rejects a request, the token is refreshed and the request is retried once.\n- **Auto re-login** — if the refresh token itself expires (process was idle too long), and the client has credentials in memory from a fresh login this session, it logs back in automatically including re-prompting for the OTP via the configured provider.\n\n### Persisting sessions\n\n```js\n// After login, save for later:\nconst snap = client.toSnapshot();\n// → { username, accessToken, refreshToken, refreshTokenExpires, expiresAt, sessionId }\n// Persist however you like (file, keychain, env var, …)\n\n// Next process start — restore without re-entering credentials:\nconst client2 = new BTTradeClient({ otpProvider: stdinOtpProvider() });\nclient2.restore(snap);\nawait client2.profile.get();   // works immediately; refreshes token if needed\n```\n\n## Public API\n\n```js\n// Client lifecycle\nclient.login({ username, password })     // → SessionSnapshot\nclient.restore(snapshot)\nclient.toSnapshot()                      // → SessionSnapshot | null\nclient.logout()\n\n// Profile\nclient.profile.get()\n\n// Markets\nclient.markets.list()                    // all exchanges\nclient.markets.searchInstrument(code)    // find instrument by ticker → [{ code, marketId, market, currency, … }]\nclient.markets.getInstrument({ portfolioKey, code, marketId })\n                                         // live bid/ask + trading rules for one instrument\n\n// Reference data (enums — cache locally, rarely change)\nclient.reference.listCurrencies()\nclient.reference.listEvaluationCurrencies()\nclient.reference.listAccountTypes()\nclient.reference.listOrderStatuses()\nclient.reference.listTradeTypes()\n\n// Accounts\nclient.accounts.list()                   // normalized list from profile\nclient.accounts.getAvailableTypes(portfolioKey)\n\n// Portfolio\nclient.portfolio.getCash({ portfolioKey, currencyId })\nclient.portfolio.getCashDetails({ portfolioKey, currencyId })\nclient.portfolio.getCashAccounts({ portfolioKey })\nclient.portfolio.getBankAccounts({ portfolioKey })\nclient.portfolio.getHoldings({ portfolioKey, market?, endDate? })\n\n// Orders\nclient.orders.search({ portfolioKey, statuses?, side?, symbol?,\n                        startDate?, endDate? })\nclient.orders.get(orderNumber)\nclient.orders.getActions(orderNumber)\nclient.orders.getHistory(orderNumber)\nclient.orders.preview({ portfolioKey, symbol, marketId, quantity?,\n                         price, side, type? })\nclient.orders.placeOrder({ portfolioKey, symbol, marketId, quantity,\n                            price?, side, type?, valability? })\n```\n\nAll methods return the parsed JSON response from the server.\n\n### Placing an order\n\n```js\n// 1. Find the instrument and its marketId\nconst [instrument] = await client.markets.searchInstrument('TVBETETF');\nconst { code, marketId } = instrument;\n\n// 2. (Optional) preview fees before committing\nconst preview = await client.orders.preview({\n  portfolioKey, symbol: code, marketId,\n  quantity: 10, price: 12.50, side: 'buy', type: 'limit',\n});\nconsole.log(preview.netValue, preview.commission);\n\n// 3. Place\nconst result = await client.orders.placeOrder({\n  portfolioKey, symbol: code, marketId,\n  quantity: 10, price: 12.50, side: 'buy',\n  type: 'limit',      // 'limit' | 'market'\n  valability: 'day',  // 'day'   | 'gtc'\n});\n```\n\nFor **market orders**, always preview with a `type: 'limit'` and the current\nask/bid as the price — the server's market-order preview is uncapped and\nreturns worst-case figures that are meaningless. Use `markets.getInstrument()`\nto get live ask/bid:\n\n```js\nconst info = await client.markets.getInstrument({ portfolioKey, code, marketId });\nconst preview = await client.orders.preview({\n  portfolioKey, symbol: code, marketId,\n  quantity: 1, price: side === 'buy' ? info.ask : info.bid,\n  side, type: 'limit',\n});\n```\n\n## OTP providers\n\n`otpProvider` is an `async ({ username, prefix, details, expiresIn }) => string`.\nThree are included:\n\n```js\nimport { BTTradeClient, stdinOtpProvider, ntfyOtpProvider } from '@bogdanripa/bt-trade';\n\n// Interactive terminal\nnew BTTradeClient({ otpProvider: stdinOtpProvider() });\n\n// Headless: phone shortcut forwards SMS to ntfy.sh (see below)\nnew BTTradeClient({ otpProvider: ntfyOtpProvider({ topic: 'my-secret-topic' }) });\n\n// Fully custom (Telegram bot, IMAP, webhook, …)\nnew BTTradeClient({\n  otpProvider: async ({ prefix, details }) => {\n    // fetch the code however you like, return just the digits\n    return '12345';\n  },\n});\n```\n\n### ntfy.sh + phone shortcut (recommended for headless use)\n\n`ntfyOtpProvider()` subscribes to a free [ntfy.sh](https://ntfy.sh) topic and\nwaits for the SMS code to arrive. A shortcut on your phone forwards the raw\nBT Trade SMS to that topic. No inbound port, no tunnel, no account required.\n\n**Pick a topic:**\n\n```js\nimport { defaultNtfyTopic } from '@bogdanripa/bt-trade';\nconsole.log(defaultNtfyTopic('MYUSER'));\n// → bt-trade-otp-a1b2c3d4e5f60718\n```\n\nOr pass your own unguessable string:\n\n```js\nntfyOtpProvider({ topic: 'bt-trade-' + crypto.randomUUID() })\n```\n\n**iOS automation (Shortcuts):**\n\n1. **Automation** → **+** → trigger: **Message** → filter on the BT Trade sender.\n2. Enable **Run Immediately**.\n3. Action: **Get Contents of URL** — `POST https://ntfy.sh/<topic>`, body type `JSON`, one key `body` = the **Message** magic variable.\n\n**Android:** Tasker / MacroDroid / any SMS-forwarder app — POST the raw SMS body to `https://ntfy.sh/<topic>`.\n\nThe provider extracts the 5-digit code from the raw SMS text using the `prefix`\nthe server returned (e.g. `25-74456` → `74456`). Multiple processes can share\none topic safely — each filters by its own `username`.\n\n## Errors\n\nAll errors extend `BTTradeError`:\n\n| Class             | When                                              |\n| ----------------- | ------------------------------------------------- |\n| `AuthError`       | 401/403, wrong OTP, expired or rejected tokens    |\n| `NetworkError`    | fetch failures (DNS, TLS, timeout)                |\n| `ApiError`        | other non-2xx responses                           |\n| `ValidationError` | bad arguments before any request was sent         |\n\n`err.status`, `err.body`, `err.cause` are set where applicable.\n\n## What this client does not do\n\n- **No SignalR / WebSocket.** The web app uses SignalR for live price/PNL\n  updates. This client only exposes the underlying HTTP snapshot endpoints.\n- **No disk-based session persistence.** `toSnapshot()` / `restore()` give you\n  the data; where you store it is up to you.\n\n## Files\n\n```\nsrc/\n  index.js          public exports\n  client.js         BTTradeClient\n  auth.js           AuthSession — login, refresh, OTP, auto re-login\n  transport.js      fetch wrapper with auth injection and 401 retry\n  errors.js         BTTradeError hierarchy\n  endpoints/\n    profile.js\n    markets.js      list, searchInstrument, getInstrument\n    accounts.js\n    portfolio.js    getCash, getHoldings, …\n    orders.js       search, preview, placeOrder, …\n    reference.js    currencies, account types, order statuses, …\nbin/\n  bt-trade.js       interactive CLI (demo of every endpoint)\n```\n","readmeFilename":"README.md"}