Changelog
API Changelog
Track every change to the MyAllies Public API. We follow semantic versioning and clearly mark any breaking changes.
v2.20.16
August 25, 2026- FixedThe three API key management endpoints - GET /v1/account/keys, POST /v1/account/keys and DELETE /v1/account/keys/{keyId} - were documented as requiring "JWT authentication (from login)", and the code samples on this reference generated a single Authorization: Bearer header with no X-Api-Key at all. No endpoint in this API validates a bearer token: the service registers exactly one authentication scheme, the API key scheme, and all three of these endpoints are guarded by it like every other authenticated route. The published samples therefore could not have worked - copying one returned 401 because it omitted the only header that authenticates. All three now document the API key and generate a sample that sends it. Authenticate with a key you already hold; the one issued at registration can manage the rest.
- FixedPOST /v1/account/login was titled "Login and get JWT token" and stated that the JWT is used for account management endpoints while the API key is for data endpoints. That split does not exist. The endpoint still returns an account token for the MyAllies first-party apps, but the Public API does not accept it anywhere, so the page now says so plainly rather than describing a second credential a developer would waste time wiring up.
- FixedThe Fundamentals section claimed SEC EDGAR statements come "with up to 5 years of history", which reads as a ceiling on what the API can return. Five is only the default value of the `years` parameter, which has no upper bound in the service - the look-back window is whatever the caller asks for, and the statement table reaches back to fiscal 2008 for the longest-covered filers. The section now describes the window as caller-controlled and names the real floor, so nobody rules the API out for long-history work it can actually do.
v2.20.15
August 24, 2026- FixedGET /v1/account/tiers published the Free tier as 5 requests/minute and 100 per day. The real free-plan allowance is 60 per minute and 1,000 per day, and has been for every key ever issued: that is the pair written on every new subscription at signup, the pair stored on all 8 live subscriptions, the pair GET /v1/account/usage already returned, and the pair the X-RateLimit-Minute-Limit and X-RateLimit-Limit headers already enforced. The tier catalogue was the one surface that disagreed, under-reporting the per-minute allowance by 12x and the daily allowance by 10x, so a client self-throttling from it ran at a twelfth of the rate it was entitled to. Nothing about enforcement changed - this corrects the published figures to what the API has always granted.
- FixedThe Free row in GET /v1/account/tiers was described as "No API key required. Rate limited by IP address." That sentence describes keyless callers, who are a separate regime on separate numbers - they are bucketed by IP at 30 requests/minute and 500 per UTC day - so the row carried a description for one regime beside figures for the other, and matched neither. The row now describes the free API key it is actually a tier for, and states the keyless IP allowance alongside it so that the developers the old sentence was written for still find their numbers.
- FixedThe Rate Limits page gave the same two wrong figures in prose, quoting Free as 5 requests/minute and 100 requests/day. Both now read 60 and 1,000. The statement that keyless requests are bucketed by IP at 30 per minute and 500 per UTC day was already correct and is unchanged - it was verified against the live headers this run.
- ImprovedThe free-plan limits now have one definition inside the service instead of four hand-maintained copies. The tier catalogue and the rate-limit fallback both read it, so the published figure and the enforced figure can no longer drift apart the way they had. Note that the paid tiers are unaffected and no price changed.
- ImprovedThe `years` parameter on GET /v1/fundamentals/{code}/financials was documented as "number of historical years to return", which reads as a row count. It is a look-back window: the lower bound is the current calendar year minus the value and it is inclusive, so you normally receive years + 1 fiscal years. Measured on the live API for AAPL_NASDAQ, years=1 returns 2026 and 2025, years=3 returns 2026 back to 2023, and the default years=5 returns six fiscal years, 2026 back to 2021 - the newest of them the in-progress stub this endpoint already documents. The boundary is now spelled out on the parameter and the endpoint. No served value changed, and the window itself is deliberately left alone: narrowing it would silently drop a year of history from every existing caller.
- ImprovedGET /v1/news/multi documented a 400 for "too many symbols". It does not reject an over-long list - it removes duplicates, keeps the first 20 and drops the rest. Verified live: 25 distinct symbols returned 200 with 20 echoed on the response. The 400 remains for a missing or empty list, which is the only way it has ever fired. The parameter now says extras are truncated rather than rejected, and points callers at the echoed symbol list.
v2.20.14
August 23, 2026- FixedGET /v1/calendar/dividends is documented as returning stocks with UPCOMING ex-dividend dates, soonest first. The vendor supplies the ex-date as a display string (`MMMdd'yy`, e.g. `Apr01'25`) and the endpoint ordered it with a plain text comparison, which sorts by month NAME with the year as the least significant part - and no date filter was applied at all. At `limit=200` the endpoint returned 200 rows, every one of them in the past (2025-04-01 to 2026-08-18), and only three months were reachable at any limit (Apr, Aug, Dec - the alphabetically first). The date is now parsed before it is used, past ex-dates are excluded, and rows are ordered chronologically then by descending yield, so `limit` returns the soonest ex-dates instead of the alphabetically earliest months.
- ChangedGET /v1/calendar/dividends now returns fewer rows, and this is expected. Of the 1,356 instruments carrying dividend data, only 6 currently have an ex-dividend date today or later; the rest were being served from a last-known value that the market-data vendor has not refreshed. Callers who relied on the endpoint for dividend HISTORY should note it has only ever been documented as an upcoming calendar.
- ImprovedThe `exDividendDate` format is now documented on both the endpoint and the response field. It is the vendor display string `MMMdd'yy` (three-letter English month, two-digit day, apostrophe, two-digit year) - the served value is unchanged, but until now nothing told a caller how to parse it.
- ImprovedGET /v1/market/stocks/{code}/dividends described its value as "the upcoming ex-dividend date". It serves the vendor field unfiltered, and of the 1,356 instruments carrying dividend data only 6 currently have an ex-date in the future, so that word was wrong for almost every symbol. The description now says what the field is - the most recently reported ex-date, which is the next one for actively refreshed names and the last known one otherwise - documents the `MMMdd'yy` format, and points callers who want a genuinely forward-looking list at GET /v1/calendar/dividends. Served values are unchanged; the same wording was added to the endpoint remarks so generated clients carry it too.
v2.20.13
August 22, 2026- FixedGET /v1/company/{code}/events is documented as returning upcoming corporate events, but it applied no date filter at all — it returned the company's entire stored earnings history, newest first, so for most symbols every event in the response had already happened. Measured on the live API, AAPL_NASDAQ returned five events dated 2026-07-30 back to 2025-05-01, the most recent of them 23 days in the past, and PVH_NYSE returned seven of which six were past. The only indication was the daysToEvent field going negative, which the published example — a single event at daysToEvent 38 — gave no reason to expect, so a client reading data[0] as "next earnings" displayed a date that had already passed. Across the whole dataset this affected 5,063 of the 5,469 companies that carry earnings-calendar data: 92.6% of them have no future announcement on file, and every one of those was answering 200 with nothing but history. The endpoint now returns only events that have not happened yet, ordered soonest first, so daysToEvent is always zero or positive; a company with no future event scheduled returns the 404 NO_EVENTS this endpoint has always documented, instead of its past announcements. Nothing is lost: reported results, including actuals and surprise figures, have always been available in richer form from GET /v1/graph/{code}/earnings-history, which is unchanged.
- FixedThe earnings block on GET /v1/company/{code}/events served epsEstimate, epsActual, revenueEstimate and revenueActual as null for every company and every event, even though this endpoint's stated purpose is earnings announcements "with EPS estimates, revenue estimates, and announcement timing" and its published example showed populated figures. The four values were being read from storage and are present there — AAPL's 2026-07-30 announcement carries an EPS estimate of 1.9271 against an actual of 1.91, and GET /v1/graph/{code}/earnings-history has been serving exactly those numbers all along — but the internal conversion that builds a corporate event copied only the quarter, year, announcement hour and date, and silently dropped the four numeric fields, which then serialised as null. Two endpoints in the same API therefore disagreed about the same announcement, one reporting figures and the other reporting nothing. All four fields are now carried through and appear whenever the source has them; a future event still reports null actuals, because no actual exists yet.
- FixedThe reference documented the announcement-timing field as `hour: "AMC"` in upper case, while the API has always served it in lower case, so a caller branching on the documented value never matched. The stored vocabulary, counted across all 23,442 announcements, is amc (10,577), bmo (7,767) and dmh (117), with the field empty on 4,981 rows where the timing is not published. The example now shows the value the endpoint actually returns, and the field documents the full set including dmh and the empty case. No served value changed — this corrects the reference to the vocabulary the endpoint has always used. The response example for this endpoint, which was dated 2026-04-24 and had expired into the past, has also been refreshed to a future announcement.
v2.20.12
August 21, 2026- FixedThe Errors reference did not list 503 Service Unavailable, even though 24 endpoints document a 503 response — the news-backed company and market routes, GET /v1/market/nova/suggestions, and GET /v1/graph/{code}/report. A caller building error handling from that page had no entry for a status a quarter of the API can return. 503 is now listed alongside the other statuses, and says that the request itself was valid, that a backing service is temporarily unreachable, and that the error code names the subsystem — NEWS_UNAVAILABLE, NOVA_UNAVAILABLE or REPORT_UNAVAILABLE.
- FixedFifteen endpoints could return an error status that this reference never listed, so a client written strictly against the published contract had no branch for it. Measured on the live API: the four AI endpoints GET /v1/intelligence/{code}/analysis, /risk-assessment, /catalyst-timeline and /sentiment answered 500 AI_ERROR while the model backend was unavailable, having documented only 200 and 404 — POST /v1/intelligence/compare shares that path, and POST /v1/hedge/suggest and /analyze have documented that exact 500 since 2.20.6. GET /v1/volatility/{code}/skew and /gex answer 400 for a missing or malformed `expiry` and for an expiry already in the past, and /skew, /gex, /term-structure and /hv-iv all answer 404 for an unknown code or when there is too little option data to compute the figure; all four had documented only 200. GET /v1/company/{code}/description answers 404 for an unknown code or a description not yet generated, GET /v1/graph/{code}/report answers 503 while the analysis service is unavailable, POST /v1/portfolio/backtest answers 400 for a missing symbol or insufficient history, DELETE /v1/account/keys/{keyId} answers 404 for a key that does not exist or belongs to another account, and GET /v1/graph/search and /v1/regional-content answer 400 INVALID_PARAMETER when their required query parameters are omitted. Each is now listed with the specific error codes it carries. No endpoint changed behaviour and no status code changed — this release publishes responses that were already being returned.
- FixedThe OpenAPI description served at /swagger/v1/swagger.json now declares the 400 response on GET /v1/graph/search and GET /v1/regional-content. Both validate their required query parameters through the framework rather than in the endpoint body, so the generated description advertised only a 200 even though omitting `q`, or `lang`/`country`, has always returned 400 INVALID_PARAMETER. Ten of the twelve endpoints with a required query parameter already declared it; these two were the exception, which meant generated clients and validators built from the served description modelled a call that cannot fail. The endpoints themselves are unchanged.
v2.20.11
August 19, 2026- FixedThe published per-key rate limits could be switched off entirely by any caller willing to send one extra header. Requests made by our own website are exempt from rate limiting, and that exemption tested the Origin and Referer headers with a string prefix match against a list of trusted origins — but a prefix is not a host: "https://myallies.com.attacker.example" starts with "https://myallies.com", and "http://localhost.attacker.example" starts with "http://localhost". Measured on the live API, a request carrying either of those as its Referer returned 200 with no X-RateLimit-* headers at all and consumed none of the caller's daily quota, while the very same request with an unrelated Referer was counted normally — so the limiter was working and it was the match that was wrong. Origin and Referer are now parsed as URLs and compared on their host, so only the genuine first-party hosts match, an unlisted subdomain does not, and a value that is not an absolute http(s) URL is ignored rather than accidentally matched. Separately, a request that carries an X-Api-Key is now always counted against that key's quota no matter where it claims to come from: the exemption exists for the keyless requests our website makes from the browser, and a per-key quota that the key holder can disable with a header is not a quota. Callers using the API as documented see no change — the limits, headers and 429 behaviour are all unchanged. The Try It console on this site now spends your key's quota like any other request made with that key.
v2.20.10
August 18, 2026- FixedAn option symbol that resolves to no contract returned 500 INTERNAL_ERROR from the endpoints built on the option chain, instead of the "no data" answer each of them already documents. The chain lookup read the underlying contract, got nothing back for an unknown, delisted or mistyped symbol, and used it anyway — so the failure surfaced as an unexpected server error with no indication that the symbol was the problem. Measured on the live API, GET /v1/options/{code}/chain and /greeks returned 500 for an unresolvable symbol while their siblings /quote and /expirations correctly returned 404 for the very same symbol, and /chain itself returned a correct 404 for a real symbol asked for an expiry it does not list — so the status told you nothing about which part of the request was wrong. An unresolvable underlying now yields no chain, which every route reading it already handles: /options/{code}/chain and /greeks answer 404 NO_CHAIN_DATA, and the max-pain, expected-move, OI-distribution, premium-scanner, hedge-suggestion and volatility skew/surface/term-structure routes fall through to their documented empty or not-found responses. A symbol that does resolve is unaffected.
- FixedPOST /v1/options/calculator and POST /v1/options/implied-volatility priced a CALL for any `right` other than exactly C or P, and POST /v1/options/pnl-profile paid every leg off as a PUT under the same conditions — so the two most natural wrong values, "call" and "put", each returned the opposite side of the trade with HTTP 200 and success:true. That vocabulary is not arbitrary: this same API documents `optionType` as "call"/"put" on POST /v1/hedge/suggest, so a caller moving between the two endpoints had every reason to send it. Measured on the live API, calculator with `right: "put"` returned a theoretical price of 9.10 and a delta of +0.67 where `right: "P"` returns 3.51 and -0.33, and a single long 145 call leg sent as `right: "call"` inverted the entire P&L profile — +6,400 at an underlying of 75, where the call it described loses its full 600 premium. Nothing in either response indicated the requested side had been discarded. All three endpoints now answer 400 INVALID_PARAMETER for a right they cannot read, which is what the GET siblings /options/{code}/chain, /quote and /greeks and POST /hedge/suggest have always done. C and P continue to be matched case-insensitively (so `c` and `p` still work) and surrounding whitespace is now tolerated rather than silently mis-read; omitting `right` on calculator and implied-volatility still defaults to a call, as documented. On pnl-profile the rejection names the offending leg by index — legs[1].right — so a long combo does not have to be bisected by hand.
- FixedPOST /v1/portfolio/trade-journal treated any `direction` it did not recognise as "long", which silently inverts the sign of that trade's P&L and, through it, the win rate, profit factor, expectancy, drawdown and every by-symbol and by-direction breakdown built on top of it. The documented values buy, long, sell and short were all read correctly, but a value carrying surrounding whitespace — "sell " — was not, and that alone was enough to turn a winning short into a losing long in the analytics. An unreadable direction is now skipped the way a row with an unparseable date already was, and surrounding whitespace is trimmed before matching, so the documented values keep working. A request in which no row survives still returns 400 NO_VALID_TRADES.
- FixedThe reference now documents the accepted vocabulary for all four fields above. `right` on /options/calculator and `legs[].right` on /options/pnl-profile carry an explicit C/P enum (implied-volatility already did), each states that any other value is rejected rather than priced as the other side, and `trades[].direction` on /portfolio/trade-journal lists its four accepted values and says what happens to a row that matches none of them. No documented value changed meaning.
v2.20.9
August 17, 2026- FixedPOST /v1/screener/scan ignored the `supplyChainOf` filter entirely when the symbol did not resolve to a company, and answered the unfiltered query instead. A screen for the supply chain of a symbol the graph does not carry — a typo, a delisted ticker, or the canonical contract code `AAPL_NASDAQ` where this field expects the bare symbol `AAPL` — returned HTTP 200 with a full, well-formed page of companies drawn from the entire universe, with nothing to distinguish it from a correctly filtered result. Every other filter in the request was still applied, so the response looked right and the row count looked plausible; only the supply-chain restriction had silently disappeared. Measured on the live API, an identical query returned 12 rows for `supplyChainOf: "AAPL"`, and the same 200 rows as the unfiltered screen for an unresolvable symbol. An unresolvable symbol now returns an empty result set, which is the answer the query always implied: a company the graph does not carry has no supply chain, so nothing can be in it. A symbol that does resolve is unaffected and returns exactly what it returned before. `relationshipType` already failed closed and is unchanged.
- FixedThe reference for POST /v1/screener/scan documented four `sortBy` values, two of which did nothing. `score` and `insiderBuys` match no sort branch, so both silently fell through to the default conviction ordering — a caller asking for the highest insider-buy values got the highest conviction scores instead, in a response that gave no indication the sort had been ignored. The two values that do work, `insidervalue` and `institutions`, were not documented at all. The accepted set is conviction (the default), insidervalue, marketcap and institutions, matched case-insensitively, and that is what the reference now lists; `marketCap` was documented and does work, since matching ignores case. The request example, which used the inert `score`, now uses `insidervalue`. No sorting behaviour changed — this corrects the reference to the vocabulary the endpoint has always accepted.
- FixedPOST /v1/screener/scan accepts a `limit` query parameter, capped at 200 and defaulting to 50, which the reference did not list — so the documented request had no way to ask for more than the first 50 matches, and a caller reading the published parameter list would have concluded the endpoint returns an unbounded or fixed-size result. It is a query string parameter rather than a body field, which is why it sat outside the request schema where the rest of this endpoint is documented; the sibling routes GET /v1/screener/presets/{presetName} and POST /v1/graph/screener/custom have always documented the same parameter. Two request body fields were also missing: `insiderDays`, the lookback window for insider activity (default 90), and `relationshipType`, which narrows `supplyChainOf` to suppliers, customers or competitors. Both have always been read by the endpoint. No API behaviour changed.
v2.20.8
August 17, 2026- FixedAPI reference — eight published request examples had expired and no longer worked when copied. Seven were option expiration dates that have since passed: the `expiry` example on GET /v1/options/{code}/chain (`260320`), GET /v1/options/{code}/quote (`2026-04-17`, with `strike` 170), GET /v1/options/{code}/greeks, GET /v1/options/{code}/max-pain and GET /v1/options/{code}/oi-distribution (all `2026-04-17`), and GET /v1/volatility/{code}/skew and GET /v1/volatility/{code}/gex (both `2026-04-18`). Copying any of them returned 404 NO_CHAIN_DATA / OPTION_NOT_FOUND, or 400 EXPIRED on the two volatility routes — the documented call could not succeed for anyone, on any symbol, because those contracts no longer exist. The eighth was the `id` example on GET /v1/news/articles/{id} (`15100`), which returns 404 ARTICLE_NOT_FOUND. No endpoint behaviour changed and nothing was broken in the API: each of these routes returns 200 for a currently-listed expiration, which is how the stale examples were confirmed. The examples now use a long-dated expiration (`2028-01-21`, strike 230) and a live article id, and — because any hard-coded expiry eventually expires the same way — each `expiry` description now points at GET /v1/options/{code}/expirations as the source of currently-tradeable expirations, and the article-id description at the news browse routes. On GET /v1/volatility/{code}/gex, where `expiry` is optional, the guidance is to omit it and take the nearest expiration rather than pin a date. Response examples were refreshed to match on the routes whose request example moved, so the two halves of each page agree: GET /v1/options/{code}/quote, GET /v1/volatility/{code}/skew, GET /v1/volatility/{code}/gex, and GET /v1/options/{code}/expirations — the last of which illustrated four expirations that have all since passed.
v2.20.7
August 14, 2026- FixedEvery numeric threshold filter on the graph bulk feeds returned 500 INTERNAL_ERROR once the value passed a certain size, instead of answering the question. It affected `minConfidence` and `minRevenuePercent` on GET /v1/graph/supply-chain/bulk, `minConviction` on GET /v1/graph/insiders/clusters/recent, and `minComposite` on GET /v1/graph/scores/recent — all four of the decimal filters these feeds accept. Each one failed at the point where the value no longer fit the underlying column: `minConfidence` at 10 and above, the other three at 1000 and above, and each one identically on the negative side (-10, -1000). The comparison value is sent to the database carrying that column's own precision, so a value that does not fit was rejected before a single row was read — which is why the failure was an unexplained server error rather than an empty result or a validation message, and why it was abrupt: `minConfidence=9.99` returned data and `minConfidence=10` returned a 500. These are values outside each parameter's documented range, so a caller sending documented values was never affected; a caller forwarding a user-supplied or computed number, or probing the bounds, was. Both ends are now answered directly: a threshold higher than anything a row can hold returns an empty result set, and one lower than anything a row can hold matches every row — the results those queries always implied. The exact range limits (`minConfidence=9.99`, `minRevenuePercent=999.99`) still query the database unchanged, and no request that previously succeeded returns anything different. `?format=csv` is fixed on the same paths and still streams a CSV file, with its header row, for the now-empty case.
v2.20.6
August 13, 2026- FixedPOST /v1/hedge/suggest and POST /v1/hedge/analyze reported `success: true` when the AI model backend had failed. Both endpoints are generated by a language model, and when that model returned nothing the endpoints still answered HTTP 200 with a well-formed envelope containing an empty result — `suggestions: []` for /suggest, and an all-zero P&L profile, zero Greeks, zero margin impact and an empty assessment for /analyze. A caller could not tell "no hedge applies to this position" from "the analysis engine is down", and a monitoring check keyed on `success` or on the status code saw a healthy endpoint throughout. Both now return 500 with `error.code: "AI_ERROR"` when the model produces no usable response, matching the behaviour the /v1/intelligence AI endpoints already had. That covers both ways the call can fail: the model backend answering with an error, and the AI service being unreachable — the second raises an exception rather than returning an empty result, and previously escaped as a framework 500 carrying no error envelope at all, so a client parsing the documented shape hit a parse failure on top of the outage. 500 is now listed in the documented responses for both routes. Successful requests are unchanged in shape and content.
- FixedThe documented request body for POST /v1/hedge/suggest did not match the API, and the published example was rejected. `positionType` was documented as one of long_stock, short_stock, long_call, short_call, long_put or short_put; the endpoint accepts only "long" or "short", so all six documented values — including the "short_put" in the example — returned 400 INVALID_PARAMETER. The instrument is selected by a separate `contractType` field ("stock" or "option") that was not documented at all, and the three option fields were documented under names the endpoint does not read: `strikePrice`, `expiration` and `right` are really `strike`, `expiry` and `optionType`. Sent under the documented names they were silently ignored, so an option position was analysed as stock. The request schema and example now describe the parameters the endpoint actually accepts. No API behaviour changed — this corrects the reference only.
- FixedThe documented request body for POST /v1/hedge/analyze named a field the endpoint does not read. The proposed hedge was documented as `hedge`; the endpoint reads `proposedHedge`, so a body following the reference was accepted with HTTP 200 and analysed with no hedge at all — the response described the bare position while appearing to answer the question asked. The documented inner shape (`strategy`, `symbol`, `strikePrice`, `expiry`, `quantity`) did not correspond to any field either; the hedge is the same object returned in the `suggestions` array of /v1/hedge/suggest, so a suggestion can be passed straight back in. Both are now documented correctly. No API behaviour changed — this corrects the reference only.
- FixedThe Request Body table was missing entirely from POST /v1/options/calculator, /v1/options/implied-volatility, /v1/options/pnl-profile, /v1/options/ripeness and /v1/aramas/contact. All five had a full field list written against them — 39 fields with types, defaults and descriptions — but stored under a key the reference renderer does not read, so the whole section was skipped: no field names, no types, no defaults, no descriptions, and not even the heading. The example request still rendered, so the shape was guessable, but nothing documented what each field meant, which was optional, or what it defaulted to — and /v1/options/ripeness alone accepts thirteen fields, several of them thresholds whose behaviour is only described in that table. All five now render their documented fields. No API behaviour changed.
- FixedThe published example for POST /v1/options/implied-volatility could never return a result. It asked for the IV of a 145-strike call with the underlying at 150, 30 days out, priced at 5.25 — but the endpoint discounts the strike at the 5% default risk-free rate, which puts the lowest attainable price for that contract at 5.59. No volatility, at any value, produces 5.25, so the documented call could only ever return 400 NO_SOLUTION. The 200 example alongside it was unreachable for the same reason: it claimed an IV of 32.45% for those inputs. The example is now a solvable one (marketPrice 8.03, which solves to 28.01% — verified against the live endpoint), the response example shows that result, and both the request schema and the endpoint description now state the no-arbitrage lower bound and that `riskFreeRate` moves it. The error message points at plain intrinsic value, which is why this was easy to misread: 5.25 IS above the 5.00 intrinsic, and still below the real bound. The solver itself was correct throughout and is unchanged.
- FixedThe 200 response examples for POST /v1/hedge/suggest and POST /v1/hedge/analyze showed the wrong objects entirely. /suggest published the market-movers payload (`{ gainers: [], losers: [] }`) and /analyze published the list of strategy descriptions returned by GET /v1/hedge/strategies. Neither example shared a single field with what its endpoint returns. Both now show the real response: symbol, current price, implied volatility and scored suggestions with legs for /suggest; and symbol, current price, the P&L profile, Greeks impact, margin impact, breakeven points and assessment for /analyze.
v2.20.5
August 12, 2026- FixedGET /v1/options/premium-sell/stats reported a `totalOpportunities` that its own published buckets did not add up to, and GET /v1/options/premium-sell/opportunities could return a fourth, undocumented `decision`. Both routes are documented as exposing only actioned candidates — Submitted, Shadow and Rejected — but both filtered by excluding the internal "Skipped" outcome rather than by selecting the three published ones. That also let through "Pending", an in-flight candidate the engine has not actioned yet. On the demo book this is live and visible today: `stats` reports `totalOpportunities: 5929` while `submittedCount` + `shadowCount` + `rejectedCount` sum to 5741, a gap of exactly the 188 Pending rows, so anyone reconciling the parts against the total found 188 opportunities they could not account for in any bucket. Both routes now select the three documented decisions explicitly, so the buckets sum to the total and `decision` only ever carries a documented value. Counts on the three buckets themselves never changed and are unaffected; only the total and the previously undocumented rows are.
- FixedEvery CSV bulk export returned an EMPTY body once it grew past roughly 8 KB — about 55 rows. `?format=csv` is offered on GET /v1/graph/insiders/clusters/recent, /v1/graph/insiders/clusters/snapshot, /v1/graph/supply-chain/bulk, /v1/graph/scores/recent and /v1/options/premium-sell/opportunities, and on all five the response came back as HTTP 200 with `Content-Length: 0` — not a truncated file, not an error, not even the header row. Small exports worked perfectly, which made this hard to spot: the rows were written through a synchronous call that only ever reached the network once an internal 8 KB buffer filled, and the server rejects synchronous writes to the response body, so the request failed at exactly the point where the export became big enough to be worth requesting. The default `limit` on these endpoints is 1000, so the plain documented call — the one in the examples — returned nothing at all, while `limit=50` returned a valid file. This affected the CSV representation only; the JSON envelope for the same query, with the same filters, always returned the full correct result set, so nothing was wrong with the underlying data or with any filter. All five now stream complete CSV at any size. If you built an ingestion job against these feeds and concluded they were empty, re-run it. Present since the feeds shipped on 2026-05-08.
v2.20.4
August 9, 2026- FixedGET /v1/news/clusters/{id}, /timeline and /related returned 503 NEWS_UNAVAILABLE for a story cluster id that does not exist. All three collapsed every unsuccessful upstream response into "news service is temporarily unavailable", so a caller that asked for a missing or expired cluster was told the service was down and invited to retry an id that can never resolve — while a genuine outage looked identical. Cluster ids are not stable over time, so this is the ordinary outcome of holding one for too long, not an edge case. The three routes now return 404 with `error.code: "CLUSTER_NOT_FOUND"` when the cluster does not exist, and keep 503 NEWS_UNAVAILABLE for real upstream failures, matching GET /v1/news/articles/{id}, which already drew that distinction. 404 is now listed in the documented responses for all three. Successful requests are unchanged.
- FixedA rejected API key returned an EMPTY body instead of the documented error envelope. Every error response is documented as carrying `success: false` plus `error.code`, `error.message` and `meta`, but 401 Unauthorized was written by the authentication layer before that envelope was applied, so it came back as a bare status line with zero bytes. A client that parses the documented shape therefore hit a JSON parse failure at precisely the moment its credentials were wrong — the least useful time to lose the error message. 401 now returns the standard envelope with `error.code: "UNAUTHORIZED"`, and `error.details` distinguishes a missing or empty X-Api-Key header from a key that was supplied but is invalid or expired. 403 Forbidden returns the same envelope with `error.code: "FORBIDDEN"`. The status codes themselves are unchanged, so any client keying off the HTTP status is unaffected. Note that 405 Method Not Allowed is still rejected by the framework ahead of the envelope and remains bodyless — it is now documented as such in the status-code table rather than silently contradicting the envelope contract.
v2.20.3
August 8, 2026- FixedGET /v1/company/{code}/news — `totalCount` reported the number of articles on the page you were served instead of the size of the whole matching set, so it was always equal to `pageSize` and never described the result set at all. A client sizing its paging loop from it fetched page 1 and stopped: AAPL returned "totalCount: 20" against 1,700 matching articles, and the same request with `pageSize=3` reported a total of 3. Every later page was reachable the whole time — page 80 returns twenty real articles from April — so the data was never missing, only invisible to anyone who trusted the count. `totalCount` now counts the whole filtered set, including when `articleType` or `minImpact` narrows it, and no longer changes with `pageSize`. Note that it is counted before the same-story title deduplication that runs per page, so it is a slight over-count and should be treated as an upper bound: page until a request returns an empty array rather than deriving an exact page count. The articles themselves were always correct and are unchanged. The sibling GET /v1/company/{code}/filings was never affected — it has always counted the full set, which is why the two endpoints disagreed about what a total means.
- FixedThree endpoints returned HTTP 500 for a negative or zero paging value instead of handling it: GET /v1/glossary failed on `page=-5`, `page=0` and `pageSize=-1`, GET /v1/graph/{code}/related failed on `pageSize=-1`, and GET /v1/news/topics/{name} failed on a negative `page` (surfacing as a 503 because the error occurred upstream of the proxy). A client computing a page number from an offset that briefly went negative received a server error rather than the first page. All three now clamp to the first page and a minimum page size of 1. Only the lower bound is clamped — no existing upper limit changed, so a request with a large `pageSize` returns exactly what it always did.
v2.20.2
August 5, 2026- FixedGET /v1/market/analysis/breadth returned zero for every count it produces — `advancers`, `decliners`, `unchanged`, `advanceDeclineRatio`, `newHighs`, `newLows`, `percentAboveEMA50`, `percentAboveEMA200`, `totalVolume` and `totalMarketCap` were all 0 on every request, for as long as the endpoint has existed. Only `averageRSI`, `percentOverbought` and `percentOversold` ever carried values, because they are computed by a second loop that looked contracts up a different way. The endpoint now returns real breadth: an advance/decline split across roughly 4,400 classified listings, with the ratio, new-high/new-low counts and EMA percentages all populated. `totalMarketCap` sums an upstream per-listing market cap that is overstated for some foreign ADRs and is documented as indicative only — the counts do not depend on it.
- FixedGET /v1/market/analysis/options-unusual and GET /v1/calendar/dividends returned `lastPrice: 0` on every row. Both endpoints list real instruments with real options and dividend data, so the zero looked like a missing quote for that particular symbol rather than a field that could never be populated. Both now carry the last traded price (VALE 15.00, CSCO 121.24). Every other field on both endpoints was already correct and is unchanged.
- FixedPOST /v1/portfolio/analyze — the entire `stressTest` block and `betaWeightedDelta` were wrong by a factor of the reference price. The beta reference defaults to SPY, and that default could never be resolved to a price, so the reference silently fell back to 1: a 100-share AAPL position reported an estimated P&L of -10 for a 10% adverse move on a $31,110 position, where the correct figure is -7,727.20. `betaWeightedDelta` was inflated by the same factor (10,990 against a correct 14.22). Both are now computed against the real reference price. This changes the magnitude of every stress-test number a client has ever received from this endpoint; if you stored them, recompute.
- ImprovedPOST /v1/portfolio/analyze now accepts a bare symbol (`AAPL`) as well as the full `SYMBOL_EXCHANGE` code in `positions[].symbol` and `betaReference`, resolving bare symbols to the US primary listing exactly as the market and fundamentals endpoints do. A bare symbol previously matched no contract and the position was dropped from the analysis WITHOUT an error — a portfolio submitted entirely with bare symbols came back as a success response with an empty `positions` array and zero totals, which is easily mistaken for a flat book.
- ChangedGET /v1/market/analysis/movers is documented as returning empty arrays, and continues to do so. It has never served data. The cause is the same one behind the three fixes above, but unlike them this endpoint ranks purely on percentage change, and the per-listing close it ranks against is stale or unadjusted for corporate actions across part of the tracked universe. Wiring it up as-is put delisted and grey-market rows at the top — one listing that has quoted at 0.0001 for months against a live last of $6.12 ranks as a +6,119,900% gainer, and a large-cap industrial showed -66% purely from a pre-spin-off close. It is being held empty until that is corrected upstream rather than publishing a confidently wrong leaderboard. Use GET /v1/market/analysis/breadth or a Top % Gainers scanner scan instead.
- FixedGET /v1/reference/holidays — the documented `exchangeCode` filter did not work with any of its own documented values, and failed OPEN rather than closed. Only the MIC (`XNAS`) was matched, while the parameter description, its example and the response example all used the broker code (`NASDAQ`, `NYSE`, `LSE`); an unrecognised code left the query unfiltered, so `exchangeCode=NASDAQ` returned all 391 upcoming holidays across every exchange — a caller asking for NASDAQ closures received Singapore, Tokyo and Dubai holidays with nothing in the response to indicate they were not NASDAQ's. Both vocabularies are now accepted, and an unrecognised code returns an empty set instead of everything. The response example was also corrected: rows carry the MIC in `exchangeCode` (`XNAS`), not the broker code the example showed, so a client matching on that field against the documented value would never have matched a row.
- ChangedGET /v1/market/analysis/sectors now documents which of its fields are trustworthy. `medianChangePercent`, `advancers` and `decliners` are robust and unchanged. `avgChangePercent`, `topGainerChangePercent` and `topLoserChangePercent` are affected by the same stale/unadjusted per-listing change described above — a single bad row can push a sector average into the thousands of percent, which is why one sector has been reporting a +261% average against a -3.4% median. No behaviour changed here; the documentation now tells you which columns to read rather than leaving the discrepancy to be discovered.
v2.20.1
August 4, 2026- FixedGET /v1/options/{code}/chain — during US regular trading hours `livePrice`, `change` and `changePercent` were computed from the last TRADED price instead of the mark. Options trade thinly: a strike that has not printed today keeps a last price frozen at the prior close, so the change was measured from that close against that same close and came back as a flat 0.00 all session, even while the `mark` returned in the very same row had moved substantially. The chain was the only surface with this defect — the per-contract quote endpoint resolved its trading session correctly and reported the real move, so the two disagreed for the same strike at the same instant. The chain now resolves the underlying's session the same way: inside regular hours `livePrice` is the mark, outside them it stays the frozen last, which is the correct settled value. Expect non-zero intraday change on strikes that previously read 0.00; `hasResolvedChange` continues to tell you whether the basis is real, and a 0.00 with `hasResolvedChange: false` still means "no basis", not "unchanged". Quiet outside RTH, so a client that only polls after the close sees no difference.
- FixedGET /v1/company/{code}/news — the documented `articleType` and `minImpact` filters were accepted and then silently ignored, so every filtered request returned the same unfiltered page. `articleType=1` (SEC filings) came back full of newswire articles. Both are now applied. Values outside 0-4 are dropped rather than passed through, so a bad value narrows nothing instead of failing the whole request.
- FixedGET /v1/news/search — the documented `category` and `ticker` filters had the same defect and were never applied to the query: `category=Earnings` returned World and Technology articles, and `ticker=TSLA` returned whatever the text search matched. Both now narrow the result set. Two related behaviours are now documented rather than changed: `q` is effectively required (a request without it returns an empty set, not the latest articles), and `category`/`ticker` narrow a `q` search rather than standing on their own — use GET /v1/news/top, GET /v1/news/categories/{name} or GET /v1/news/ticker/{code} to browse without a query.
- FixedAPI reference — corrected published examples that listed fields the API has never returned, so a client written against them would have read undefined. `impactLevel`, `isHighImpact` and `articleType` are not part of any article payload and were removed from the GET /v1/company/{code}/news, GET /v1/company/{code}/filings and GET /v1/news/search examples (they remain valid as REQUEST filters, which now work — see above); the filings example also dropped `filingAccessionNumber`, which is not served, and both examples now show the full article shape including `sourceId`, `authorityLevel`, `topics` and `entities`. GET /v1/news/articles/{id} listed `sourceUrl` and `symbols`; the served names are `originalUrl` and `detectedTickers`, and `detectedTickers` carries lowercase contract codes (`aapl_nasdaq`), not bare symbols. GET /v1/congressional/ticker/{code} documented a `politician` field where the served name is `politicianName`, and its example now lists the eleven other trade fields it returns. GET /v1/news/multi had the same two wrong names as the article endpoint (`source`/`symbols` rather than `sourceName`/`detectedTickers`) and showed ids as strings when they are numbers. GET /v1/screener/presets/{presetName} was the furthest off: its example showed the PRESET DEFINITION (`name`/`title`/`description`/`query`) — the shape GET /v1/screener/presets returns — when running a preset actually returns the matching companies, with `symbol`, `marketCap`, `pe`, `insiderBuyCount`, `avgConviction`, `institutionCount` and a `matchReason` summary. No API behaviour changed in this entry — only the documentation, which had never matched.
- ImprovedAPI reference — three endpoints now document fields that are structurally null so you can stop waiting for them to fill. GET /v1/company/search always returns `contractType: null` (results come from the instrument database, which does not carry the field on this path) — every result is an equity listing; use GET /v1/market/stocks/{code}/details for the instrument type. On GET /v1/fundamentals/{code}/financials, `totalDebt` is not derived from any XBRL concept and is null for every filer, and the most recent fiscal year is normally an in-progress stub whose `annual` block is all-null while its `quarters` carry real figures — anchor year-over-year comparisons on the newest year with a non-null annual revenue. The financials example now shows the complete field set rather than a five-field excerpt.
- FixedGET /v1/fundamentals/{code}/financials — `operatingCashFlow` was null for every company and every period because the ingestion looked up a US-GAAP concept name that does not exist in the taxonomy, and `freeCashFlow` is derived from it, so both were permanently empty across all 465,331 statement rows. The correct concept is now used. Values appear as filers are re-ingested on the daily EDGAR cycle rather than all at once, so expect these two fields to fill in progressively; `capitalExpenditures` was unaffected and was already populated.
v2.20.0
August 3, 2026- FixedGET /v1/market/stocks/{code}/details — `currency` served a raw NUMBER instead of a currency code for every listing outside the US, UK, Swiss and Euro markets: AAPL_TSE returned `"currency": "7"` where it should read `"CAD"`. The field was rendered from an obsolete four-value enum (USD, GBP, CHF, EUR) frozen at the pre-multi-market era, and a value with no name in that enum stringifies as its underlying id. 21,464 of 58,778 contracts — 36.5%, every listing priced in JPY, HKD, CAD, AUD, SEK, SGD, INR or ILS — were affected. The code now comes from the currency reference table, so it is correct for all 24 supported currencies. A client that parsed this field defensively will see codes where it previously saw digits; one that displayed it verbatim was showing customers a number.
- FixedGET /v1/news/multi returned HTTP 500 for every request. The aggregation deduplicates articles by id and read that id as a JSON string, but the upstream news service serialises it as a number — so the very first article of every response threw, and the error escaped the per-symbol guard that would otherwise have skipped it. The endpoint had been unusable for all callers regardless of which symbols were requested. Ids of either shape are now accepted. No other news endpoint was affected; they do not deduplicate.
- FixedGET /v1/company/{code} — `marketCap` was null on every response for every symbol, AAPL and MSFT included. It was read from a company-level property that has no backing column and that nothing ever assigns, so the field could never have carried a value. It now resolves from the maintained per-listing record, the same source GET /fundamentals/{code}/ratios already used. Note that market cap is a property of the LISTING, not the ticker: a secondary listing of the same company can legitimately return null while the primary listing returns a value, so pass the full SYMBOL_EXCHANGE code for the listing you actually mean.
- ImprovedGET /v1/company/{code}/sector now returns the `industry` and `category` classification its name and documentation had always promised. The response previously carried only `symbol`, `name` and a null `marketCap` — no classification of any kind. Both fields resolve per listing and are null where that listing has not been classified (roughly 60% of contracts carry one today). `category` is the finer-grained of the pair: Apple is industry `Computers`, category `Computers`, while Microsoft is industry `Software`, category `Applications Software`. GET /v1/company/{code}/description also falls back to the same classification when the company record has no industry of its own, which is the case for 98% of companies.
- ChangedPOST /v1/market/scanner/scan now excludes leveraged ETFs and ETNs by default, and preserves the scan's native ranking. Two changes ship together. (1) STOCK TYPE — nothing previously applied a stock-type filter, so momentum scans came back dominated by 2x/3x leveraged ETFs rather than the operating companies the scan was meant to surface. The default is now `exc:ETF,ETN`, which drops leveraged ETFs and ETNs while KEEPING ADRs and REITs. It is visible and overridable: GET /v1/market/scanner/filters now returns curated options for filter `stkTypes` (id 125) — `exc:ETF,ETN` (default), `all` (an explicit sentinel meaning no type filter), `inc:CORP` (common stock only), and `inc:CORP,ADR,REIT`. Send `all` to restore the previous unfiltered behaviour exactly. (2) ORDERING — results previously came back re-sorted by market cap, which discarded the ranking the scan itself produced; on a Top % Gainers scan that meant the biggest movers were no longer first. Market cap is also unpopulated for much of the universe, so that sort was largely arbitrary. Scans now preserve their native rank order; custom SQL scans keep the market-cap ordering they always had.
- FixedAPI reference — the GET /v1/company/{code} example advertised an `isin` and an absolute logo URL, neither of which the endpoint can return. No ISIN is stored anywhere in the platform, so that field is null on every response and is now documented as permanently null rather than as sparse data a client should wait to fill in. `logoUrl` is a relative path on the MyAllies content host and resolves to a shared placeholder for the 99.5% of companies with no logo on file — the example showed a third-party absolute URL that would have sent clients to the wrong host entirely.
v2.19.0
July 31, 2026- ChangedPOST /v1/options/ripeness — the ripeness decision has been redesigned, and the request contract changes with it. The old model asked whether the position's ANNUALISED forward return on margin had fallen below a hurdle scaled by your account cushion. That measure inflates as expiry approaches (it multiplies by 365/DTE), so a put worth $0.08 with one day left read 129%/yr and looked rich — the gate stayed silent at exactly the moment there was nothing left to realize, which is the case it existed to catch. It is replaced by two DTE-independent dimensions, both required on top of the unchanged in-profit gate: (1) LITTLE LEFT TO REALIZE — at least `capturedPctThreshold` (85% by default) of the credit you originally collected has already been captured, priced at the ask; (2) NOTABLE MARGIN RELATIVE TO YOUR BOOK — the margin closing would release is at or above `notableMarginThreshold`, which you supply as the top quartile of your own open positions' measured margins. Ranking dimension (2) against your book rather than an absolute dollar bar is deliberate: freeing $103k matters on a $500k book and is noise on a $10m one. REQUEST — two new fields are required for a verdict, `creditCollected` (|average fill| × |quantity| × 100) and `notableMarginThreshold`; omitting either returns the new NO_CREDIT_BASIS or NO_BOOK_MARGIN status rather than a guess. `capturedPctThreshold` replaces `baseHurdleAnnualPct`, and `accountCushionPct` is gone — both retired fields are ignored if still sent, so nothing 400s. RESPONSE — new `capturedPct` carries the number the verdict is now decided on; `hurdleAnnualPct` is retained for compatibility and is always null; `remainingRocAnnualPct` is still returned but is informational only and no longer a gate. Two new status codes join the set, NOT_NOTABLE_MARGIN and NO_BOOK_MARGIN. Unchanged: capital is still the MEASURED broker margin and never a notional proxy, the exit is still priced at the ask plus commission, delta still only vetoes, and the endpoint still runs the same algorithm as the MyAllies trading dashboard.
- FixedGET /v1/graph/{code}/related — the `pagination` totals described the page you were served instead of the whole result set, so `totalItems` equalled the number of rows in your hand and `totalPages` was 1 on every request. A client sizing its paging loop from `totalPages` fetched page 1 and stopped, silently losing every later page: AAPL has 10 relationships, and a request with `pageSize=5` reported "5 items, 1 page" while five more sat behind page 2. Asking for page 2 still returned data the metadata said did not exist. Both totals now count the whole filtered set, and `totalItems` no longer changes with `pageSize`. The rows themselves were always correct and are unchanged.
- FixedGET /v1/graph/{code}/network — the `depth` and `maxNodes` parameters had no effect. The traversal honoured them, but the 10-minute result cache was keyed on the company alone, so the first request for a symbol fixed the graph every later request received: asking for depth=1 and depth=5, or maxNodes=1 and maxNodes=100, all returned byte-identical responses, and which one you got depended on whose request happened to warm the cache. The cache key now includes both parameters. Separately, edges are now filtered to the nodes actually returned — they are collected per level before the node cap trims the frontier, so a capped graph could carry edges pointing at ids absent from `nodes`, which no graph library can render. Uncapped graphs are unchanged.
- FixedAPI reference — the GET /v1/glossary `category` example was `Options`, which matches nothing. Categories are keyed by kebab-case slug (`options-strategies`, `options-concepts`, `technical-analysis`), so a client copying the documented value received an empty page and reasonably concluded the filter was broken. The example is now a real key and the description points at GET /v1/glossary/categories, whose `categoryKey` is the accepted value. GET /v1/news/multi also gained a worked example for its required `symbols` parameter, which had none — the only required parameter in the reference still missing one.
- FixedAPI reference — the GET /v1/glossary documentation stated there was no pagination envelope and no total count, and told you to page until a request returned fewer items than `pageSize`. That was an over-correction published in 2.18.3: the endpoint has always returned a `pagination` object alongside `data` carrying `page`, `pageSize`, `totalItems` and `totalPages` (1,000 terms over 20 default-size pages). The documented example now shows it. The same envelope is now documented on GET /v1/graph/{code}/related, whose example had omitted it entirely.
- FixedAPI reference — GET /v1/glossary accepts a `lang` parameter, but only English translations exist today, so every language returns English text through the endpoint's documented fallback. The parameter was described as if it selected between available translations. It is now documented as accepted and forward-compatible but not yet affecting the payload, so a client does not ship a language switcher that appears broken.
- FixedAPI reference — the GET /v1/languages example listed three languages (English, Arabic, Chinese) while the endpoint serves two. This endpoint returns the localization REGISTRY, which holds English and Arabic, and is not the set of locales the MyAllies website is published in (11) — a distinction that matters because GET /v1/geo can only ever detect a language present in that registry, which is why visitors from countries with no registry entry are served English. The example now matches the payload and the description states what the list is and is not for.
v2.18.4
July 30, 2026- FixedGET /v1/calendar/earnings — every row served an empty `symbol` and a null `companyName`, so the endpoint returned EPS and revenue estimates with no way to tell which company they belonged to. On 2026-07-30 that was all 1,847 rows in the default window. The projection simply never mapped either field, while the neighbouring GET /calendar/upcoming and GET /calendar/earnings/enhanced both already resolved them. Rows now carry the ticker and the company name. A row whose ticker has no company record keeps a null `companyName` — null means "no company record", never a placeholder.
- FixedGET /v1/calendar/earnings and GET /v1/calendar/upcoming — the `days` / `earningsDays` parameter was applied as a START offset rather than a look-ahead window. It skipped the next N days of announcements instead of covering them, and never bounded the far end, so `days=1` returned a month of announcements beginning tomorrow while `days=30` — the documented maximum — returned nothing at all. Both endpoints now return announcements from today through today+N inclusive, and a larger window is always a superset of a smaller one. Results are ordered by announcement date.
- FixedGET /v1/calendar/earnings/enhanced — `companyName` was null on every row, including for megacaps like AAPL. It was read from a contract-level column that is unpopulated across the universe; it now comes from the maintained company record, matching GET /company/{code}. `sector` is unchanged and still null wherever the company classification is absent.
- FixedAPI reference — the earnings-calendar `hour` field was documented as "BMO"/"AMC". It is actually served lower-case ("bmo"/"amc") and is an EMPTY STRING for roughly 85% of rows, where the company has not published its timing, so a client comparing against "AMC" matched nothing. The served values are unchanged — re-casing them would break existing consumers — and the documentation now describes them accurately, including that the field is optional rather than a reliable sort key.
- FixedAPI reference — the option greeks on GET /v1/market/stocks/{code}/extended (`gamma`, `theta`, `vega`) were shown in the example with numeric values. This endpoint describes an equity contract, where per-contract greeks do not apply, so all three are null on every response. They are now documented as option-only and shown as null; use GET /v1/options/{code}/greeks for an option chain.
- AddedAPI reference — published response examples for 50 documented GET endpoints that previously showed only a status description and no sample payload, across the graph, options, screener, calendar, market-analysis, hedge and intelligence groups. That closes the gap from 56 undocumented shapes down to 8, the remainder being endpoints that currently return an empty result set for every sample input. Every example is a real, verified response captured from the live API rather than hand-written, so field names and value shapes match what a client actually receives. No API behaviour changed.
v2.18.3
July 29, 2026- FixedGET /v1/market/stocks/{code}/chart — the `symbol` field was null on every response. The documented example has always shown the resolved ticker, but the internal chart model that backs this endpoint is built for the charting pipeline and never populates a symbol, so the public response echoed null for every code and every period. It now echoes the symbol resolved from the requested code, which also lets a consumer holding several charts in flight tell the responses apart without threading its own correlation state. Same class as the 2.18.1 option-chain expiry/right fix. Bars, indicators and every other field are unchanged.
- FixedGET /v1/market/stocks/{code}/details — `lastPrice`, `change` and `changePercent` were null on every response. The endpoint promised "current price" and declared all three fields, but this service resolves a CACHED contract record whose price fields are only ever filled by the internal contracts service, so no request has ever returned a price from it. All three are now populated from the same priced row GET /market/stocks/{code}/quote uses, and `change` is taken from the session-aware basis rather than the raw broker delta — the latter reads a stale last-trade print outside regular hours for the majority of the universe, which is the defect already fixed on the quote endpoint in 2.17.7. When a contract genuinely has no pricing, all three stay null: null means "no pricing", never zero.
- FixedAPI reference — corrected the published response examples for a group of endpoints whose documented shape did not match what the API serves, so a client written against the docs would have read undefined fields. GET /news/top and GET /news/ticker/{code} return an OBJECT (with the articles under `articles`, alongside breaking clusters or aggregate sentiment), not a bare article array; GET /news/breaking returns story CLUSTERS, not articles; article objects carry `source`/`url`/`sentimentScore`/`category`, not `sourceName`/`originalUrl`/`impactLevel`/`isHighImpact`/`detectedTickers`. GET /news/market-mood returns `period`/`articleCount`/`averageSentiment`/`mood`. GET /news/ticker/{code}/sentiment and GET /company/{code}/sentiment return `sentimentDistribution`/`averageScore`/`label`. GET /news/categories uses `articleCount`; GET /news/sources returns `id`/`baseUrl`/`sourceType`/`isActive`/`consecutiveErrors`. GET /congressional/recent, GET /congressional/top-traders, GET /company/{code}/clusters and GET /market/analysis/dark-money-flow examples now list the fields actually served. GET /glossary returns the term array directly — there is no `{ data, pagination }` envelope and no total count, so page until a request returns fewer items than `pageSize`. No API behaviour changed in any of these — only the documentation, which had never matched.
- FixedAPI reference — the technical-indicator fields on GET /v1/market/stocks/{code}/extended and GET /v1/fundamentals/{code}/ratios were documented under the wrong names. They serialize as `rsI14`, `rsI5`, `emA20`, `emA50`, `emA100` and `emA200` (an artifact of how the acronym-cased source properties are camelCased), not `rsi14`/`rsi5`/`ema20`/`ema50`/`ema100`/`ema200` as the examples showed — so a client reading the documented names got undefined for all of them. The served names are unchanged, since renaming them would break existing consumers; the documentation now matches the payload. GET /market/stocks/{code}/technicals is unaffected — its `rsi` and `rsi5` were already correct.
- FixedAPI reference — GET /v1/market/stocks/{code}/details documented `hasOptions` as reporting whether options are available. The underlying flag is never set true for any contract, so the field reads false or null even for heavily optionable symbols. It is now documented as not maintained, pointing at GET /v1/options/{code}/expirations as the authoritative check. The field is still served, unchanged, so no existing integration breaks.
v2.18.2
July 28, 2026- FixedAPI reference — the published rate limits were wrong, and wrong in the direction that breaks integrations. The Rate Limiting page advertised 120 requests/minute for "standard endpoints" and 300/minute for "market data endpoints". Neither figure corresponds to anything the API enforces: there is a SINGLE per-minute allowance that applies to every endpoint — market data and options included — and it is set by your subscription tier, from 5/minute on Free to 1,000/minute on Business. A client designed against the documented 120/minute would have been rejected with 429 on every tier below Professional. The page now describes the real per-minute and per-UTC-day limits, states plainly that no endpoint group gets its own allowance, and points at GET /v1/account/usage for your own effective figures and GET /v1/account/tiers for the full table. Requests sent with no API key are bucketed by IP at 30/minute and 500/UTC-day, which is now stated on both the Rate Limiting page and the Embeddable Widgets page — the latter had claimed 100/minute. No API behaviour changed — only the documentation, which had never matched it.
- FixedAPI reference — the rate limit header table listed 3 of the 6 headers the API actually returns, and described the two it did list inaccurately. `X-RateLimit-Limit` and `X-RateLimit-Remaining` report the DAILY quota, not the current minute, so a client polling them for per-minute headroom was reading the wrong number entirely. The per-minute figures have always been served separately as `X-RateLimit-Minute-Limit` and `X-RateLimit-Minute-Remaining`, which were undocumented, as was `X-RateLimit-Tier`. All six are now documented with what they actually measure.
- FixedAPI reference — the GET /v1/account/usage example response named the tier field `subscriptionTier`. The API has always returned it as `tier`, and also returns an `isActive` flag the example omitted. The example now matches the served payload exactly.
- FixedGET /v1/news/articles/{id} — an id that does not exist now returns 404 `ARTICLE_NOT_FOUND` instead of 503 `NEWS_UNAVAILABLE`. The endpoint has always documented both codes, but the 404 was unreachable: the news proxy collapsed every non-success upstream response into one "service temporarily unavailable" answer, so a caller asking for an article that had been purged (or never existed) was told the service was down and to retry — a retry that could never succeed. Genuine outages, timeouts and upstream 5xx still return 503, unchanged. Other /v1/news endpoints are unaffected: they document 503 only, and their behaviour has not changed.
v2.18.1
July 26, 2026- FixedGET /v1/options/{code}/chain — the `expiry` and `right` fields on the response body were always null. The documented example showed them carrying the resolved values, and a consumer holding several chains in flight had no way to tell which response belonged to which request without threading its own correlation state. Both are now echoed. `expiry` is always normalised to yyyy-MM-dd whichever accepted input format was sent (yyMMdd, yyyyMMdd, or yyyy-MM-dd), so it doubles as confirmation of how an ambiguous input was interpreted; `right` echoes the applied C/P filter including the default when the parameter is omitted. Items in the chain are unchanged.
v2.18.0
July 25, 2026- AddedPOST /v1/options/ripeness — short-put ripeness evaluator. Decides whether an open short put is "ripe": in profit, and no longer paying enough on the buying power it ties up to be worth holding to expiry. Two conditions, the first a HARD GATE — (1) the position is IN PROFIT (a losing short put is never ripe; closing one is loss management, a different decision), (2) the forward remaining return on capital, annualised, is below a hurdle scaled by your account's margin cushion (scarce buying power triples the bar to hold, abundant halves it). Capital is the MEASURED broker margin the position releases, never a notional proxy: under portfolio margin the buying power a short put consumes is risk-based and is not proportional to strike × 100 × quantity — on a real book a 27.7%-OTM put on $18.5k of notional released ~$0 of margin while a 25.7%-OTM put on the same expiry released ~$103k, so ranking on notional flags the first and ignores the second, exactly backwards. Supply the real figure or accept a NO_MARGIN_DATA verdict; the evaluator never substitutes an estimate. The exit is priced at the ASK plus commission (the pessimistic side — this is a decision about whether an exit is worth taking, not a limit price), and delta acts as a veto only: a put with real directional risk can never be flagged ripe, but low delta never promotes one. Returns a machine verdict (Ripe / NotRipe / Unknown / NotApplicable), a stable status code, the annualised return, the hurdle it was compared against, the exit cost, and a human-readable reason. Stateless and deterministic — no clock, no market data, no account lookup. Same algorithm the MyAllies trading dashboard ships on its positions screen.
- AddedGET /v1/options/{code}/chain and GET /v1/options/{code}/quote — new `hasResolvedChange` flag. Options are thinly traded and a strike that has never been captured at a session close has NO basis for a day change. The API previously served a bare `change: 0, changePercent: 0` in that case, which a consumer cannot distinguish from a genuinely flat strike. When `hasResolvedChange` is false the zeros are placeholders and should be rendered as "—". This is the contract the MyAllies trading dashboard has always honoured internally; it is now visible to API customers.
- AddedGET /v1/options/{code}/chain — chain items now also carry `mark` (IBKR bid/ask-clamped fair value), `livePrice` (Mark ?? Last — the canonical executable price for premium math), and `previousClose` (the basis `change` is measured from). Previously a consumer got only `last`, which for an option can be hours stale. GET /v1/options/{code}/quote gains the same `livePrice` and `previousClose` fields. Additive only — existing fields are unchanged.
- ImprovedGET /v1/options/{code}/chain and GET /v1/options/{code}/quote — `change` / `changePercent` are now sourced from the platform's server-resolved display basis rather than the raw broker delta. Options measure change against the PRIOR SESSION CLOSE in every session. Both alternatives cost real money to get wrong on this book: IBKR field 82 is computed off a thin or stale last trade (it produced a -22 "change" on a $0.70 option, and sign inversions), and today's open only exists if the strike streamed at the opening bell — chain-viewed strikes subscribe when the chain is opened, so the "open" was a mid-day price and the entire META chain read INVERTED on the 2026-07-10 earnings gap (stock +6.6%, calls showing "down"). One fixed basis in every session also means after-hours reads the same as the main session by construction, rather than collapsing to 0.00% on 0DTE strikes.
- ImprovedNews summaries across every news endpoint (GET /v1/news/*, GET /v1/company/{code}/news) — the `summary` field is now 2-3 short paragraphs (~100-150 words) carrying key facts, context, and market impact, instead of a 2-3 sentence gloss. Boilerplate (navigation, ads, cookie notices, ticker strips) is excluded during extraction. Applies to newly ingested articles only: the scraped body is purged after summarization, so previously summarized articles keep their original shorter text. No schema change — `summary` is the same field with more in it.
- FixedGET /v1/market/status — the next-session countdown targets the correct upcoming session. Three defects are resolved: during the 00:00-04:00 ET overnight window the response pointed at a pre-market session that had already begun rather than the first FUTURE one; from After-Hours it targeted the next pre-market instead of the overnight session that actually starts next at 20:00 local; and the Sunday-evening reopen resolved to Monday pre-market rather than the Sun-Thu overnight. `nextSession`, `nextSessionStartUtc`, and `secondsToNextSession` now agree with the exchange calendar in every session.
- FixedGET /v1/options/{code}/chain — the documented `expiry` format now works. The endpoint documented yyMMdd (e.g. 260320) while the underlying service parsed yyyy-MM-dd, so every caller who followed the documentation hit an unhandled parse failure surfaced as a 500 INTERNAL_ERROR with nothing indicating what was wrong. Both formats (plus compact yyyyMMdd) are now accepted and normalised — the sibling /options/{code}/quote endpoint has always taken yyyy-MM-dd, so callers using the two together no longer have to remember which is which. An unparseable value returns a 400 that names the offending input instead of a 500, and a two-digit year that would resolve before 2000 is rejected rather than silently querying a 1990s expiry.
- FixedAPI reference — POST /v1/congressional/ingest is now documented. It is anonymous and publicly reachable, and forces a refresh of the congressional-trade feed that otherwise runs on a background schedule. Swagger summary corrected to 182 endpoints / 24 controllers — it had read 178 / 23 since June.
v2.17.8
July 8, 2026- FixedEvery endpoint that accepts a bare stock symbol (no _EXCHANGE suffix) — the symbol now resolves to the US primary listing. The multi-currency rollout added foreign duplicate listings that could outrank the US row on the internal tie-break keys (AMD's ASX row carries a higher company id than its NASDAQ row; WMT's foreign duplicates tied past the priced NASDAQ row), so a bare-symbol quote could land on a foreign listing with no pricing — NO_PRICING for AMD, DIS, and WMT — or return a price in the wrong currency (AZN quoted in LSE pence, SentinelOne quoted in Tokyo). US listings are now pinned first, with the previous tie-break retained for genuinely non-US symbols. Explicit SYMBOL_EXCHANGE queries are unaffected: the exchange filter still applies and US-first only breaks ties within it.
v2.17.7
July 2, 2026- FixedGET /v1/market/stocks/{code}/quote — `change` and `changePercent` are now session-aware and correct outside regular trading hours. They previously derived from the last-trade print, which is stale overnight for the large majority of the universe (the upstream broker streams only a capped ~200-symbol set), producing wildly wrong day changes: COIN reported -10.5% and RIVN -13.5% overnight while the real close-based day changes were +8.9% and -1.0%. `close` and `previousClose` were already correct to the penny, so the quote now measures against them outside market hours and against the live delta during the session — the same value the MyAllies dashboard shows and what Yahoo/Google report as the headline day change. Regression-guarded by a new test suite.
v2.17.6
June 26, 2026- FixedMarket, Fundamentals, Portfolio, Congressional, Options, and Volatility endpoints — contract resolution is now multi-currency aware. The multi-currency rollout added duplicate symbol rows on foreign exchanges with no linked company record, and an unordered lookup could land on one of those listings, returning an endpoint with no pricing or company data. Resolution now honours an explicit exchange in a SYMBOL_EXCHANGE code and otherwise prefers the company/broker-linked listing. These controllers also pass the full code (rather than just the symbol) into the underlying contract, option, and expiration lookups, so the intended exchange listing resolves end to end.
- FixedAll endpoints — request-validation failures now return the documented `{ success, data, error, meta }` envelope. Model-binding 400s previously bypassed it and returned a raw ProblemDetails body instead, so a client parsing the standard envelope got an unexpected shape on exactly the errors it was most likely to hit.
- FixedGET /v1/volatility/{code}/gex — when no expiry is supplied the endpoint now auto-selects the nearest FUTURE expiration. It previously took the first stored entry, which returned EXPIRED whenever the stored list briefly carried a past date.
- FixedGET /v1/market/indices and GET /v1/market/crypto — rows now carry live prices. GET /v1/market/analysis/dark-pool/{code} resolves the company-linked listing and no longer fails on duplicate listings. Search results are filtered to contract rows, dropping non-contract entries that were leaking into responses.
v2.17.4
June 3, 2026- AddedGET /market/stocks/{code}/chart — the response now includes a `priorClose` field alongside `close`. `close` is today's settlement; `priorClose` is the prior trading day's close. Use `priorClose` as the anchor for a 1-day chart's previous-close reference line and the day-change (price vs prior close) calculation — previously consumers had to fetch the quote endpoint separately to obtain it. This mirrors the fix the MyAllies trading dashboard ships: after 4:00pm ET the 1D reference line and day-change are anchored on yesterday's close rather than today's settlement. Additive only — existing fields are unchanged. Surface stays 178 endpoints / 23 controllers.
v2.17.3
June 1, 2026- ImprovedGET /market/stocks/{code}/chart with period=1 (1-minute) — futures contracts now return a contiguous trailing-24-hour window of bars anchored on the most recent bar, instead of the regular-session window used for cash equities. Index and commodity futures (Globex / OSE / HKFE) trade ~23 hours continuously and roll across UTC midnight, so the equity session-window logic produced a sparse bar set spanning the weekend gap (e.g. a Nasdaq future returned two disjoint clusters — Friday RTH and the Sunday Globex reopen — with a ~49h gap between them). The period=1 futures response is now a clean trailing-24h series covering the current Globex session and the tail of the prior one. Equities, ETFs, and all other periods are unaffected.
v2.17.2
May 30, 2026- FixedGET /market/stocks/{code}/quote and GET /market/stocks/{code}/extended — `previousClose`, `change`, and `changePercent` are now correct for thinly-traded and rarely-subscribed symbols. Previously, contracts that were never streamed during their regular session kept `previousClose` frozen at the initial-snapshot value (often equal to `close`), so the quote rendered as a flat "0.00 (0.00%)" change every day — this affected roughly 27k instruments across the universe. A new hourly maintenance job now rolls `close` and `previousClose` from the persisted regular-session 1-minute bars (close = last regular-session bar on the most recent trading day; previousClose = the same on the prior trading day) and recomputes `change` / `changePercent` from those values, mirroring what the broker would have settled at the bell on a healthy subscription.
- ImprovedGET /market/stocks/{code}/quote — when you open a symbol that has never been viewed before (e.g. a cold ticker pulled up in pre-market), `previousClose` now resolves correctly within seconds instead of lagging until the next hourly maintenance pass. As soon as the daily history is fetched, the post-close daily bar is used to roll `close` / `previousClose` for that single contract immediately. Future-dated history bars are filtered out and only post-close daily snapshots are propagated, so an in-session daily bar can never overwrite a live `close`.
v2.17.1
May 28, 2026- FixedGET /market/stocks/{code}/quote and GET /market/stocks/{code}/extended — `Close` is now stable during extended-hours and overnight sessions. The internal pricing dispatcher snapshots the regular-session settlement and restores it after every IBKR tick, so the after-hours / pre-market last print no longer overwrites today's close. Earlier, customers polling during the 4:00pm–9:30am ET window could see `Close` drift toward `LastPrice` and a noisy day-change calculation; the value now matches what the closing print resolved to and updates only at the next regular-session close.
- FixedGET /market/stocks/{code}/quote and GET /market/stocks/{code}/extended — `Close` is additionally guarded against stale IBKR ticks. Incoming Close updates that fall outside today's [Low, High] band combined with the prior-close band are rejected (treated as a stale subscription echo), and during extended hours `Close` is sourced from the persisted regular-session close rather than the live tick. Fixes a class of $-value mismatches that surfaced as phantom day-P&L jumps after-hours.
- FixedGET /market/stocks/{code}/quote (and all session-aware endpoints) — `MarketSession` correctly reports `Closed` on holidays. The exchange session resolver no longer carries an overnight-session branch into holiday evenings; holidays are treated as fully closed for the entire day. Removes a class of false `Overnight` session labels on US holidays.
- ImprovedGET /market/stocks/{code}/quote — during extended-hours sessions, the resolver explicitly prefers the live last-trade tick for the headline price while keeping `Close` pinned to the regular-session settlement. Customers building intraday dashboards see prices update in pre/post sessions without the regular-session close drifting underneath them.
v2.17.0
May 25, 2026- AddedPOST /v1/options/pnl-profile — multi-leg expiry P&L profile calculator. Submit the current underlying price and one or more option legs (signed quantity: long > 0 / short < 0; per-share premium; strike; C or P; optional multiplier — defaults to 100) and the endpoint sweeps the underlying across a price range anchored on spot (stretched to include all strikes) and returns a 121-point price-vs-P&L curve at expiry, the breakevens (linearly interpolated zero crossings), max profit and max loss within the swept range, and the strategy P&L at the current spot. Purely mathematical — no live market data is fetched; you supply the spot and the premium each leg was bought/sold at. Powers any multi-leg strategy builder (vertical spreads, iron condors, butterflies, straddles, strangles, calendars, ratios) and complements the existing single-leg `/options/calculator` (which is Black-Scholes pre-expiry pricing). Same algorithm the MyAllies trading dashboard ships in its strategy builder.
- ImprovedJavaScript API client (`publicApiClient.js`) — new `client.options.pnlProfile({ underlyingPrice, legs })` wrapper around the new endpoint with JSDoc on the leg shape. Tag description and root endpoint catalog now surface `options_pnl_profile`. Swagger summary bumped to 178 endpoints / 23 controllers (previously 177 / 23).
v2.16.0
May 23, 2026- AddedGET /v1/market/stocks/{code}/chart — new `indicators` query parameter computes technical-analysis overlays on the returned OHLCV bars. Comma-separated list of: `rsi` (14-period), `macd` (12/26/9 — emits `macd`, `macd-signal`, `macd-hist` series), `bb` (Bollinger Bands 20/2 — emits `bb-upper`, `bb-middle`, `bb-lower`), `sma20`, `sma50`, `sma200`, `ema12`, `ema26`. Example: `?indicators=rsi,macd,sma50`. Series are returned under a new `indicators` map keyed by series name; each point is `{ timestamp, value }`, with `value` null during the indicator's warm-up window. Computed via Skender.Stock.Indicators on the same bars the chart returns, so consumers don't need to re-fetch and re-align quotes client-side. Powers the same overlays the MyAllies trading dashboard uses, now available to API customers building their own charting frontends.
- ImprovedJavaScript API client (`publicApiClient.js`) — `market.getChart()` now accepts an options object: `getChart(code, period, { extended, indicators })`. `indicators` accepts an array or a comma-separated string and is forwarded to the new query parameter. Existing two-argument callers continue to work unchanged.
v2.15.0
May 22, 2026- AddedGET /v1/intelligence/{code}/conviction — latest stored verdict from the Conviction Engine, a six-specialist + committee AI panel (Fundamentalist, Technician, Sentiment, Smart-Money, Risk, Portfolio + Committee) that scores a stock as a put-selling candidate. Returns FinalVerdict (`SELL_PUT` / `CAUTION` / `AVOID`), FinalConviction (0-100), CommitteeAgreement (consensus strength), per-specialist scores, and the committee’s reasoning narrative. Read-only: it returns the most recent CONVICTION_RESULT row and does NOT trigger a fresh LLM run — scores are refreshed by the trading platform on its own cadence. Returns 404 (`NO_VERDICT`) when no panel has executed for the contract yet, so customers can poll lazily and build dashboards without paying for Claude committee runs. API surface now spans 177 endpoints across 23 controllers (previously 176 / 23).
- ImprovedIntelligence tag description — now explicitly references the Conviction Engine alongside the existing AI-analysis / risk / catalyst / sentiment / compare endpoints, and Swagger root listing surfaces `intelligence_conviction` in the documented endpoint catalog.
v2.14.4
May 20, 2026- AddedPOST /v1/aramas/contact — corporate-site contact-form intake for the new Aramas GmbH site at www.aramas.ch. Accepts `{ name, email, message }` plus optional `company`, `role`, `inquiryType`, `source` and forwards the message to the corporate inbox via the shared mailer pipeline used by the product contact form. Anonymous (no API key required), CORS-allowlisted to aramas.ch / www.aramas.ch, and returns the standard envelope. Validates name/email/message presence — INVALID_EMAIL / INVALID_NAME / INVALID_MESSAGE on bad payloads; SEND_FAILED if the mailer hop rejects the message. Not intended for general API customers, but published for transparency. API surface now spans 176 endpoints across 23 controllers (previously 175 / 22).
- AddedOpenAPI spec — new `aramas` tag (label "Aramas", icon `corporate_fare`) wrapping the single corporate-site contact endpoint so it is grouped separately from `/v1/account/contact` (which remains the product-site intake) and Swagger consumers can hide it from customer-facing reference views.
v2.14.3
May 18, 2026- ImprovedGET /market/stocks/{code}/extended, GET /fundamentals/{code}/ratios, GET /graph/{code}/technicals, and every screener/scanner row that exposes `marketCap` — MarketCap is now computed in-process as `Shares × LivePrice` rather than read from IBKR field 7289, which IBKR deprecated in Jan 2026 and now streams as blank. Shares are sourced from a new EDGAR concept walk (DEI EntityCommonStockSharesOutstanding plus the us-gaap CommonStockSharesOutstanding/Issued fallbacks) refreshed hourly per contract. The value updates intraday alongside the price tick, so headline cap, sector roll-ups, and screener filters now reconcile with the displayed last price during the session.
- ImprovedGET /fundamentals/{code}/ratios — `PE` and `EPS` are now derived from EDGAR (TTM = sum of the last four quarterly EPS rows, with the latest annual as fallback when fewer than four quarters are available) instead of the deprecated IBKR fields 7290 / 7291. NULL means "EPS data not yet available for this issuer" — typically a recent IPO or non-US-domiciled name without EDGAR coverage — and is no longer a transient subscription gap.
- ImprovedGET /fundamentals/{code}/financials — financial statement rows now expose `sharesOutstanding` populated from the EDGAR DEI concept walk introduced for the MarketCap fix above. Annual + quarterly periods both carry the field; older periods that pre-date the backfill remain NULL until the next EDGAR re-ingest.
v2.14.2
May 17, 2026- AddedJavaScript API client (publicApiClient.js) — three new namespaces wrap the B2B surface that previously required raw fetch(): `client.webhooks` (list/create/delete/test, deliveries log, anonymous events catalog), `client.premiumSell` (opportunities feed with full filter set + CSV format flag, public stats), and `client.graphFeed` (insider-clusters/recent, insider-clusters/snapshot, supply-chain/bulk, supply-chain/coverage, scores/recent — all support `?format=csv` for streaming exports). All methods documented inline with JSDoc.
- FixedAPI reference & changelog text — corrected `/options/premium-sell/recent` → `/options/premium-sell/opportunities` and `/options/premium-sell/coverage` → `/options/premium-sell/stats`. These were aliases used during the 2.13.0 rollout draft; the canonical route names have always been `/opportunities` and `/stats` in the deployed API, and the JS client + endpoint reference were already correct.
- FixedWebhooks documentation — removed a phantom `PUT /v1/webhooks/{id}` entry that was never implemented (no in-place updates; revoke + recreate is the supported flow). Added the missing `GET /v1/webhooks/{id}/deliveries` entry, which has shipped since 2.13.0. Event-type examples updated to match the canonical names emitted by the delivery worker.
v2.14.1
May 16, 2026- ImprovedGET /market/stocks/{code}/quote and GET /market/stocks/{code}/extended — `Change` and `PercentChange` are now derived from LivePrice (Mark ?? Last) instead of the raw IBKR last-trade delta. Reconstruction: settlement = LastPrice − rawChange, then the returned Change = LivePrice − settlement. The displayed delta now reconciles with the displayed price during periods when the bid/ask midpoint drifts away from the most recent print. Behavior is unchanged when MarkPrice is null. Same change applies to the day-change fields.
- Improved/options/premium-sell/opportunities stream — StrategyProcessor now retries transient broker 503s with 1-2-4 sec exponential backoff inside the same cycle and skips the post-failure 5-minute cooldown when the failure was network-class. Customers polling the feed during IBKR gateway hiccups see fewer dropouts and a more contiguous opportunity stream.
v2.14.0
May 14, 2026- AddedGET /market/stocks/{code}/quote — new MarkPrice and LivePrice fields. MarkPrice is IBKR's bid/ask-clamped fair value; LivePrice = MarkPrice ?? LastPrice is the canonical executable price MyAllies uses internally for live P&L, strategy triggers, and price alerts. LastPrice remains for backward compatibility but can be stale, micro-lot, or printed outside the current NBBO — switch to LivePrice for any "what is this worth right now" use case.
- AddedGET /options/{code}/quote — new UnderlyingLivePrice field (Mark ?? Last of the underlying). The existing UnderlyingPrice field is preserved for backward compatibility but now also documents that it is the raw last trade.
- ImprovedGET /options/{code}/expected-move, /options/{code}/oi-distribution, /options/{code}/premium-scanner — spot price for Black-Scholes math now uses LivePrice (Mark ?? Last) instead of raw LastPrice. Results match the MyAllies platform's internal pricing model and are more accurate during volatile periods when prints can lag the bid/ask midpoint.
- AddedGET /fda/upcoming and GET /fda/ticker/{code} now surface a third source: "SEC-8K" — PDUFA target dates LLM-extracted from biotech/pharma SEC 8-K filings. This captures company-disclosed catalysts that the openFDA dataset does not publish. Filter or weight by the existing `source` field ("openFDA" | "FDA-AdComm" | "SEC-8K"); openFDA remains the most authoritative, the two LLM-derived sources may carry residual extraction noise.
- ImprovedNews pipeline quality fixes flow through to /news/breaking and /company/{code}/news: NewsProcessor Stage 1 now rejects articles older than 14 days, BreakingNewsEvent.DetectedUtc is write-once (no longer drifts on re-detection), and the breaking-news 24h cap is keyed off FirstSeenUtc rather than DetectedUtc — eliminates ghost-fresh entries for old stories.
- ImprovedGET /company/{code}/filings — Form 4 (insider transaction) articles now carry a prose summary built from parsed transaction data ("Verma Kuntal Kumar (Chief Manufacturing Officer) sold 3.7K shares @ $231.43 ($849.6K). Holds 5.5K shares after.") instead of the previous "SEC 4 submitted. View the full filing at SEC EDGAR." stub. Articles also expose `filingAccessionNumber` so consumers can join back to /graph/{code}/insiders rows. Existing articles with the stub summary are upgraded by a backfill service on a SEC-rate-limit-friendly cadence.
- ImprovedInternal LLM routing — AI-powered endpoints (/intelligence/*, /hedge/*) and AI Picks now default to the local Llama provider with Claude reserved for the AI Picks discovery shard. No behavior change for consumers; calls remain JSON-structured and cached for 30 minutes, but the underlying provider toggle is now per-endpoint rather than global.
- ImprovedStrategyProcessor — deterministic-ladder pricing with water-mark gating prevents premium-sell strategies from firing duplicate orders at backward-walking prices. Customers using /options/premium-sell/opportunities see a cleaner opportunity stream with __SCAN__ heartbeat rows already filtered out.
v2.13.1
May 10, 2026- FixedRemoved a duplicate /company/{code}/filings entry that listed the endpoint under both the company and news tags with conflicting auth requirements; the endpoint is publicly accessible (no API key required) and the canonical entry now lives under the news tag. Documented endpoint count: 174 user-facing routes (the 175th, POST /congressional/ingest, is an internal manual-ingest trigger and is intentionally undocumented for end users).
v2.13.0
May 9, 2026- AddedB2B data feeds — three new bulk/CSV-streaming feeds for downstream pipelines (newsletters, hedge funds, fintechs) with optional ?format=csv on every endpoint
- AddedGET /graph/insiders/clusters/recent — recent insider-cluster signals across the entire universe (filter by signal, conviction, insider count)
- AddedGET /graph/insiders/clusters/snapshot — point-in-time insider-cluster snapshot for backtesting (daily 4:30pm-ET capture)
- AddedGET /graph/supply-chain/bulk — bulk export of supply-chain relationships (filter by type, confidence, revenue percent, updatedSince)
- AddedGET /graph/supply-chain/coverage — public coverage stats (no auth) so prospective customers can audit dataset breadth before subscribing
- AddedGET /graph/scores/recent — recent composite-score updates across the universe with min-composite filter
- AddedGET /options/premium-sell/opportunities — premium-sell signal feed from BulkPremiumSellEngine (Demo account only — customer-private columns stripped)
- AddedGET /options/premium-sell/stats — public summary stats (track record, distinct symbols, decision mix) for prospective subscribers
- AddedWebhooks API — full B2B subscription CRUD with HMAC-SHA256 signed deliveries and 6-step exponential backoff retry
- AddedGET /webhooks — list active subscriptions; POST /webhooks — create (returns secret once, store securely); DELETE /webhooks/{id} — revoke; GET /webhooks/{id}/deliveries — delivery log; POST /webhooks/{id}/test — fire a test event
- AddedGET /webhooks/events — public list of emittable event types (insider.cluster.new, supply-chain.relationship.updated, premium-sell.opportunity.submitted, etc.)
- AddedSubscription tiers expanded — Hobby ($19) and Growth ($499) added between existing tiers, gating B2B feed window/limit access
- ChangedAPI now exposes 175 endpoints across 22 controllers (previously 162 across 19)
v2.12.0
May 2, 2026- AddedGET /calendar/upcoming — unified upcoming events combining economic calendar and corporate earnings into a single chronologically-sorted stream, with optional market code filtering and configurable earnings lookahead
- ImprovedGET /calendar/economic now accepts an optional "markets" query parameter (comma-separated market codes like "US,EU,UK") to filter economic events by region
- ImprovedPOST /market/scanner/scan filter input now supports "selectedCode" field for combo/dropdown-type filters (e.g., stock type, sector), in addition to min/max range filters
- ChangedAPI now exposes 162 endpoints across 19 controllers
v2.11.0
April 27, 2026- AddedPOST /portfolio/trade-journal — submit closed trades for comprehensive performance analytics including win rate, profit factor, drawdown, expectancy, and breakdowns by symbol, day of week, and direction
- ChangedAPI now exposes 161 endpoints across 19 controllers
v2.10.0
April 20, 2026- FixedFixed Localization API client routes — publicApiClient.js was using incorrect /v1/localization/... prefix instead of /v1/... for all 8 localization endpoints (languages, regions, regional-content, content, geo, glossary)
v2.9.0
April 19, 2026- ChangedCompany news proxy endpoints now return empty result sets on 404 instead of 503 — GET /company/{code}/news, /filings, /news-summary, /sentiment, /clusters all gracefully handle missing data
- ChangedGET /graph/{code}/report now returns 503 with REPORT_UNAVAILABLE error when analysis service is temporarily down (previously returned empty content)
- ChangedJavaScript API client (publicApiClient.js) now covers all 160 endpoints — added 67 missing methods across Company, Graph, Options, News, Market, Account, and 4 new groups: Congressional, FDA, Reference, Localization
v2.8.0
April 18, 2026- AddedGET /news/articles/{id} — retrieve a single article by ID with full content, sentiment, and related tickers
- AddedGET /news/clusters/{id}/related — discover related story clusters by shared symbols, topics, or temporal proximity
- FixedRemoved 4 duplicate endpoint entries from API documentation (short-interest, dividends, technicals, sector)
- ChangedAPI now exposes 160 endpoints across 19 controllers
v2.7.0
April 17, 2026- AddedGET /news/multi — aggregated news for multiple tickers in a single request, with deduplication by ID and title. Ideal for portfolio news feeds (up to 20 symbols, highImpactOnly filter)
- ChangedNews API now exposes 21 endpoints (previously 20) — added multi-ticker aggregation for portfolio use cases
- ChangedAPI documentation and OpenAPI spec updated to reflect all 158 endpoints across 19 controllers
v2.6.0
April 16, 2026- AddedGET /company/{code}/sentiment — news sentiment trend analysis over configurable periods (24h/7d/30d) with score distribution
- AddedGET /company/{code}/clusters — AI-powered story clusters for a company with velocity metrics and article counts
- AddedGET /news/search — full-text article search with category, ticker, and pagination filters
- AddedGET /news/categories — available news categories with article counts
- AddedGET /news/categories/{name} — paginated articles within a specific news category
- AddedGET /news/clusters — paginated story clusters with velocity metrics, event types, and article counts
- AddedGET /news/clusters/{id} — detailed story cluster with all articles and related clusters
- AddedGET /news/clusters/{id}/timeline — chronological article timeline within a story cluster
- AddedGET /news/sources — list of all active news sources with metadata
- ChangedNews API now exposes 20 endpoints (previously 11) — comprehensive article discovery, categorization, and narrative tracking
v2.5.0
April 15, 2026- AddedGET /options/{code}/expected-move — 1-sigma and 2-sigma expected price ranges derived from IV and ATM straddle pricing across the nearest 4 expirations
- AddedGET /options/{code}/earnings-move-history — historical actual stock move around past earnings events with average/median move and EPS beat rate (up to 12 quarters)
- AddedGET /options/{code}/oi-distribution — open interest distribution by strike with significance flags (>2x average) and put/call OI ratio
- AddedGET /options/{code}/premium-scanner — rank short-put / short-call premium-selling candidates for an underlying by annualized return, filtered by delta and DTE
- AddedGET /graph/{code}/earnings-history — quarterly earnings history with EPS/revenue surprise, beat flags, and aggregate beat rates
- AddedGET /graph/{code}/options-analytics — options analytics snapshot including IV, HV, put/call ratios, and shortability data
- AddedGET /graph/{code}/technicals — EMAs (20/50/100/200), price-to-EMA percentages, 52-week range, beta, volume, and market cap
- AddedGET /graph/{code}/peers — sector/industry peer companies ranked by market cap
- AddedGET /graph/{code}/summary — AI-generated company summary with generation timestamp
- AddedGET /graph/{code}/fragility — supply chain fragility score (0-100) with HHI concentration, SPOF count, and top risk factors
- AddedGET /graph/{code}/cascade-risk — comprehensive cascade risk assessment combining fragility, supply chain impact, insider conviction, and composite score
- AddedPOST /account/contact — public contact form endpoint for sales, support, and partnership inquiries
v2.4.0
April 14, 2026- AddedPOST /options/calculator — Black-Scholes option pricing calculator with full Greeks (delta, gamma, theta, vega), expected move, and intrinsic/extrinsic breakdown
- AddedPOST /options/implied-volatility — solve for implied volatility from a market price using Newton-Raphson with bisection fallback
- AddedGET /market/nova/suggestions — AI-powered trade suggestions from the Nova ML engine (LightGBM model, ranked by predicted profitability)
- ChangedLocalization now supports 11 languages: added German, French, Portuguese, Japanese, and Italian (previously 6 languages)
- ChangedAll contract code examples updated from AAPL_SMART to AAPL_NASDAQ for consistency
v2.3.0
April 13, 2026- AddedAlerts API — webhook-based price, IV, and volume spike alerts with cooldown management
- AddedCongressional Trading API — politician trades, ticker analysis with insider/supply chain enrichment, top traders
- AddedFDA Catalysts API — upcoming PDUFA dates, ticker-specific catalysts, recent drug approvals
- AddedFundamentals API — valuation ratios (PE, EPS, beta, RSI, EMAs) and SEC EDGAR financial statements (up to 5 years)
- AddedPortfolio Analysis API — aggregate Greeks, beta-weighted delta, stress testing, and options strategy backtesting
- AddedVolatility Analysis API — IV surface, term structure, skew, HV/IV comparison, gamma exposure (GEX)
- AddedLocalization API — multi-language support, regional content, financial glossary with translations, geolocation
- AddedGET /market/stocks/{code}/short-interest — short interest, shortable shares, and borrow fee rate
- AddedGET /market/stocks/{code}/dividends — dividend amount, yield, TTM, and ex-dividend date
- AddedGET /market/stocks/{code}/technicals — RSI, IV, beta, 52-week range, average volume
- AddedGET /company/{code}/sector — sector and industry classification
- AddedGET /company/{code}/description — AI-generated or 10-K sourced company description
- AddedGET /market/analysis/dark-money-flow — smart money convergence signals
- AddedGET /market/analysis/dark-pool/{code} — dark pool and short sale volume history (FINRA RegSHO)
- AddedGET /market/analysis/dark-pool/high-short-ratio — stocks with highest average short ratio
- ChangedAPI documentation now covers all 19 controllers and 140+ endpoints
v2.2.0
April 12, 2026- AddedGET /reference/countries — country list with default currencies and regions
- AddedGET /reference/timezones — timezone definitions with IANA identifiers and UTC offsets
- AddedGET /reference/holidays — upcoming market holidays for all or specific exchanges
- AddedGET /news/topics — trending news topics ranked by article count (last 24h)
- AddedGET /news/topics/{name} — paginated articles for a specific topic
v2.1.0
April 11, 2026- AddedGET /news/breaking — breaking news alerts with high-impact stories and affected tickers
- AddedGET /news/ticker/{code}/sentiment — AI-generated sentiment analysis for specific tickers
- AddedReference Data endpoints — currencies, exchanges, markets, and FX rates
- AddedGET /reference/fx-rates/{from}/{to}/history — historical daily FX rates (up to 365 days)
- AddedGET /reference/fx-rates/convert — currency conversion using live rates
v2.0.0
April 8, 2026- AddedCompany-centric financial news system — all news resolved to company symbols via AI entity extraction from 20+ financial sources
- AddedGET /company/{code}/news — paginated news for any company with article type and impact level filtering
- AddedGET /company/{code}/filings — SEC filings (8-K, 10-K, 10-Q, Form 4, S-1) as news articles from EDGAR RSS feeds
- AddedGET /company/{code}/news-summary — pre-aggregated news stats (article counts, sentiment, breaking news status)
- AddedGET /company/{code}/sentiment — sentiment trend analysis over 24h/7d/30d periods
- AddedGET /company/{code}/clusters — AI-powered story clustering showing related articles grouped by event
- ChangedNews endpoints now use IsHighImpact flag — only material events (earnings surprises, M&A, CEO changes, regulatory actions) marked as high impact
- ChangedCompany news, filings, and news-summary endpoints are public (no API key required)
- ChangedNews articles include AI-generated clean titles, summaries, and sentiment scores
- AddedSEC EDGAR integration — real-time monitoring of 8-K, 10-K, 10-Q, Form 4, S-1, DEF 14A filings for 4,400+ tracked companies
v1.5.0
March 27, 2026- AddedOptions analytics endpoints: unusual flow detection, put/call ratio, IV rank, max pain, volume analysis, Greeks visualization, and earnings straddle pricing
- AddedEnhanced earnings calendar with estimates, actuals, surprises, IV, and supply chain predictions
- AddedDividend calendar endpoint with yield filtering
- AddedSubscription tiers endpoint showing rate limits and pricing
v1.4.0
March 25, 2026- AddedGraph & Intelligence API — supply chain relationships (suppliers, customers, competitors)
- AddedInsider transactions with conviction scoring, cluster detection, and summary signals
- AddedInstitutional holders (13F filings) and company executives endpoints
- AddedSupply chain cascade impact simulation
- AddedCompany network graph for visualization with configurable depth
- AddedComposite health score aggregating insider, institutional, financial, and supply chain signals
- AddedAI-generated company analysis reports
- AddedSide-by-side company comparison (2-4 companies)
- AddedScreener presets and custom stock screening
- AddedEarnings impact predictions based on supply chain momentum
v1.3.0
March 23, 2026- ChangedAll endpoints now use contract code (SYMBOL_EXCHANGE format, e.g. AAPL_NASDAQ) instead of plain symbol
- AddedExchange lookup endpoint — GET /market/exchanges/{symbol} to find available exchanges and build contract codes
v1.2.0
March 23, 2026- AddedExtended contract data endpoint with volume, market cap, RSI, Greeks, dividends, and events
- AddedAll tradeable contracts endpoint for building symbol lists and autocomplete
- AddedTrending stocks endpoint based on platform activity
- AddedIndividual option contract quote endpoint with mark price, ITM status, and break-even
- AddedSocial sentiment endpoint with StockTwits data and bullish/bearish aggregation
v1.1.0
March 18, 2026- AddedMarket status endpoint for current trading session info
- AddedMarket indices endpoint (S&P 500, Dow Jones, Nasdaq, etc.)
- AddedCryptocurrency contracts endpoint with live pricing
- AddedOHLCV chart data endpoint with multiple period options
- AddedContract details endpoint with exchange and type info
- AddedStock scanner endpoints (types, filters, scan)
- AddedCorporate events endpoint for earnings and dividends
- AddedNews endpoints: top news, ticker news, market mood
- AddedEconomic calendar endpoint for upcoming economic events
- AddedEarnings calendar endpoint for upcoming announcements
v1.0.0
March 1, 2026- AddedPublic API launch with Account, Company, Market, and Options endpoints
- AddedAPI Key authentication via X-Api-Key header
- AddedSelf-service account registration and API key management
- AddedReal-time stock quotes with comprehensive price data
- AddedCompany profile lookup and search functionality
- AddedOption chain and expiration data endpoints
- AddedStandard API response envelope with metadata