Looking for feature announcements and product news? Those live on Product Updates.
Calendar events from investor relations calendars
Calendar events can now come from an issuer’s own investor relations calendar. This fills in earnings report, trading update and AGM dates that no article has stated yet. Most dates still come from news articles and company announcements. Where the issuer’s calendar has supplied a date, a news report giving a different one does not overwrite it.Each calendar event says who published its date
Calendar events gain asource object naming the publisher of the current date, so you can cite it next to the date you show:source is null when an NFN editor set the date, and an article’s name and article_id are null once it is no longer available. Existing events carry source too.source_article_id still names the article the event was extracted from. It is null for an event found only on a company website. If a website later supplies the date for an event first reported in an article, source_article_id keeps the article and source names the website.source appears on GET /calendar_events, GET /calendar_events/{id}, GET /companies/{identifier}/calendar_events, is selectable with fields=source under field projection, and is returned by the MCP search_calendar_events and get_calendar_event tools. The change is additive, but every list endpoint’s ETag, and the GET /calendar_events/{id} ETag, changes once with this release, so a conditional request returns 200 once before revalidating normally. See Caching and How to get calendar events.News, grouped into events
An event groups related coverage of one development, such as an earnings report, an acquisition or a CEO change, with the source articles attached. When five outlets cover the same announcement, you get one event with five articles rather than five rows to reconcile yourself.Four new endpoints serve them:A company's material developments
title, status, significance, event_type, article_count, first_reported_at, last_article_at, updated_at, and the company_ids and story_ids it links to:An event row
GET /events/{id} adds an AI-generated summary, the IDs of the articles behind the event (oldest first), primary_disclosure_article_id pointing at the earliest official source, and a companies array.How events fit with articles and stories
Articles and stories gain anevent_id field linking each one to its event, or null when there is none. In the other direction, an event’s story_ids points to the long-form account of the development, where one exists. Use events to see what happened, articles for the individual reports behind it, and stories for the narrative.Significance and event types
Every event has asignificance tier: notable for material developments, routine for ledger records such as scheduled filings, and commentary for opinion, analysis and market wraps. Filter on it to separate the developments that matter from the coverage around them.event_type classifies what happened, using hyphenated slugs from GET /event_types such as earnings-report or acquisition-announced. These are separate from the underscored event_type values on calendar events, which describe scheduled dates rather than developments that have already happened.What behaves differently from articles
- All three significance tiers are returned by default. Pass
significance=notable,routineto leave commentary out. qis always a filter. Results stay newest first and page with a cursor, rather than coming back as one relevance-ranked page.mode=semanticis available onGET /eventsto match by meaning.- Company matching counts only companies that took part in the event, such as the acquirer or the target, not advisors or passing mentions. Pass
include_peripheral=trueto widen it. - Merged and removed events become tombstones, carrying only
id,status,merged_intoandupdated_at. They drop out of the feed, a sync withupdated_afterreturns them, andGET /events/{id}answers with the tombstone rather than404. - Conditional requests use
ETagonly.GET /events,GET /events/{id}and the company events route send noLast-Modified, so revalidate withIf-None-Match. See Caching.
Four new MCP tools
The MCP server gains four tools on the same data:get_company_news answers “what has happened to this company lately?” without a follow-up call per event. It leaves commentary out by default and has no REST equivalent.The API reference lists every filter on the events endpoints, and the company news guide builds a company feed from events end to end. This release is purely additive. Existing endpoints are unchanged apart from the new event_id field on articles and stories.Filter articles by more than one content type
Every article has acontent_type that says what kind of item it is: news, analysis, press_release, and so on. You can now filter articles by several content types at once. Pass a comma-separated list to content_type and you get back articles of any of the listed types. Fetching press releases and analysis together previously took two requests.Press releases and analysis in one request
GET /articles, on a company’s articles at GET /companies/{identifier}/articles, and in the MCP search_articles tool. The article content type filter now takes a list, as the country, category, sources, and source_type article filters already do. GET /meta lists the valid content types as content_types.Unknown content types are rejected
Unlikecategory, which ignores ids it does not know, every content type in the list has to be a recognized one. An unknown content type returns a 400 with problem type invalid-parameter, and detail names each content type that was not recognized.On MCP, the content_type input of search_articles is now a plain string rather than a single-value enum. Its description lists the valid content types, and an unknown content type returns a tool error naming it.This release is purely additive. Filtering on a single content type works as before, and every previously valid request returns the same results.Opt in to passing-mention company matches
A company can be attached to an article in one of two tiers. A principal association is a company the article is actually about. A peripheral association is a company named only in passing: an article’s source, an advisor on a deal, or an aside. By default, both the companies reported on an article and the company filters that match articles are limited to principal associations.GET /articles, GET /articles/{id}, and GET /companies/{identifier}/articles now accept include_peripheral, and so do the MCP search_articles and get_article tools. It defaults to false. Set it to true and both the associations reported and the company-filter matching widen to include peripheral associations as well.Every article touching a company, including in passing
Every company entry says why it is attached
Each company on the article detail now carriesassociation_tier, set to "principal" or "peripheral":A companies[] entry on GET /articles/{id}
GET /articles/{id} and MCP get_article. The field is always present, whether or not you send include_peripheral, so a widened match is always explainable. On a default response every entry reads "principal", and with include_peripheral=true a peripheral match is labeled as such.primary_only still wins
primary_only keeps its primary-subject restriction even alongside include_peripheral=true. A company that is a passing mention is never returned as a primary subject, so the two parameters do not conflict.This release is purely additive. Omitting include_peripheral leaves every request unchanged, and association_tier is a new field.Filter by more than one category
category accepts a comma-separated list of category ids and matches any of them. This applies to GET /articles, GET /stories, GET /companies/{identifier}/articles and GET /companies/{identifier}/stories. A request with more than one id previously returned an empty page.Articles in either of two categories
id from GET /categories, not its name. Unknown ids are ignored, and if none resolve the response is an empty page, the same as country and sources. The MCP search tools already accepted a list and are unchanged.Search by meaning instead of by keyword
GET /companies, GET /articles and GET /stories accept a new mode parameter. Set mode=semantic and your q is matched by meaning rather than by keyword, so a thematic query returns results that share no words with it.Defence contractors, not companies with defence in the name
search_companies, search_articles and search_stories take the same parameter.q is required with mode=semantic. Every filter you already use still applies as a hard filter, so country, ticker, sector, and date ranges narrow the candidate set exactly as they do without it. Only the text matching changes.What gets matched differs by resource. Companies match over their name, aliases, country, sector, industry and description, so ask what a company is rather than what has happened to it. Articles match over the translated title and summary. Stories match over the title, summary and key entities, not the narrative body.On companies, keep geography out of q. A place name there matches companies whose name contains it, such as those beginning with Nordic or Swedish, ahead of the ones actually doing what you asked about. Put it in market, domicile or country instead, as the example above does.What comes back
A semantic response is a single page ranked by closeness, with no cursor. Thepagination object carries count only, because a distance ranking cannot be cursor-paginated. cursor is rejected alongside mode=semantic, as are updated_after and sort on articles and stories, and ids on articles.A semantic response is also uncacheable. It is sent with Cache-Control: no-cache, no-store and no ETag, so the conditional request pattern does not apply to it. Keyword responses are unaffected.Each result carries a new distance field: the raw cosine distance between your query and that record, where lower is closer, rounded to four decimal places. Compare distance only within a single response. The scale is model-dependent, so a value from one query tells you nothing about another.Ranking is not distance alone. Articles and stories blend it with a small recency bonus, so equally close matches order newest first. Companies blend it with a small coverage bonus, so a widely covered company outranks an equally close but barely covered one. Without that, a thematic query is dominated by small companies whose name happens to contain your keyword. The distance you receive stays the raw value in every case: the blend reorders the page but is never exposed.Why a page can be shorter than your limit
A relevance floor drops weak matches before the page is assembled. A semantic response is therefore frequently shorter thanlimit, and can be empty even where the same filters return results without mode.Raising limit does not recover those rows. The floor excluded them, not the page size.Treat semantic matching as a recall aid rather than an exhaustive index. Some companies fall beyond the floor on a thematic query they plainly belong to, and no limit brings them back. Use mode=semantic to find candidates, and the exact filters (ticker, company, sector, exchange) when you need a complete set.Limits and errors
Semantic requests draw on a second hourly allowance, set by your plan and separate from your standard request limit: 100 an hour on Free and Plus, 500 on Pro. One allowance covers all three resources rather than each getting its own, so companies, articles and stories draw it down together.A semantic request counts against both counters. It passes your ordinary request limit like any other call, then draws on the semantic allowance as well. On Pro the semantic allowance is much the smaller of the two, so semantic mode can run out while plenty of ordinary requests remain. On Free and Plus the two are the same size, so you will usually meet the ordinary limit first.Every semantic request draws on the allowance, including one whose filters match nothing and one repeating a query you ran recently. An empty page still costs a call, and so does a repeat: the query embedding is cached for an hour and shared across surfaces, but the search itself still runs.Exceeding it returns429, with the same rate-limit-exceeded problem type your standard limit uses. The detail names which allowance you hit and how much of it you have. Handle both the same way: read Retry-After and wait. Dropping mode=semantic also keeps you working immediately if it was the semantic allowance, since keyword search is unaffected.If a semantic search cannot be completed, because the query could not be embedded or the vector scan was cancelled for taking too long, the request returns 503 with problem type semantic-unavailable. It never falls back to keyword matching silently, so a 200 from a mode=semantic request always contains a genuine semantic result. Choosing keyword ranking instead stays your decision.What your plan changes
plan_limited: true means your plan narrowed the result, either before or after matching ran.On articles it reports the source and article-age restrictions, which narrow the candidate set before matching. The article count cap does not gate semantic mode: a match over the few newest articles a capped plan can see would be noise rather than a restricted search.On stories either of two restrictions sets it, and they act at opposite ends. Your plan’s story age window narrows the candidate set before matching, and your plan’s story count cap clamps the result size after. The clamp is deliberately applied after the match, so a capped plan receives its closest matches rather than its newest stories.Where it is not available
The company-scoped routesGET /companies/{identifier}/articles and GET /companies/{identifier}/stories do not accept mode. They return a 400 naming the flat equivalent to use instead, such as GET /articles?company={id}&q=...&mode=semantic.This release is purely additive. Omitting mode leaves every existing request unchanged.Calendar windows read a bare date as the issuer’s local date
If you already send a bare date onscheduled_after or scheduled_before, check what your window now covers: a bare date means the issuer’s own calendar date, and scheduled_before includes that date instead of stopping short of it. Pass a datetime for an exclusive end, or for any exact instant: scheduled_before=2026-11-01T00:00:00Z. Datetime windows are unchanged.GET /calendar_events and GET /companies/{identifier}/calendar_events now read a bare YYYY-MM-DD on scheduled_after and scheduled_before as that calendar date in the issuer’s own timezone, inclusive at both ends. MCP search_calendar_events reads it the same way.Every event in October, in each issuer's local calendar
local_time are the issuer’s local date, and a bare-date bound compares against that same date.Why a UTC window lost days at both ends
An event withdate_precision: day, such as an earnings date, an AGM, or a dividend given only as a date, is stored at the issuer’s local midnight. A window cut at UTC midnight therefore falls inside the local day rather than at its edge, and it fails in opposite directions depending on the issuer:- For an issuer ahead of UTC, local midnight falls on the previous UTC day, so the first day of the range was dropped. A dividend dated 1 October in Stockholm is stored at
2026-09-30T22:00:00Z, outside a window opening at2026-10-01T00:00:00Z. - For an issuer at or behind UTC, local midnight falls at or after UTC midnight, so the last day was dropped. An event dated 31 October in Reykjavík is stored at
2026-10-31T00:00:00Z, outside a window ending strictly before that instant.
local_time afterward. A bare-date window returns exactly the days you asked for, for every issuer, whatever its offset. See How to get calendar events.Datetimes still mean an exact instant
An ISO 8601 datetime still means an exact UTC instant:scheduled_after is at or after it, scheduled_before is strictly before it. Send one whenever you want a time of day, or an exclusive end.A window that ends strictly before 1 November, everywhere
2026-10-01T00:00:00+02:00. It previously took only a Z suffix or no zone at all and returned 400 for an offset, so the form local_time itself uses could not be sent back.MCP takes a bare date and names the right parameter
search_calendar_events previously rejected a bare date outright, and the resulting error named published_after and published_before, parameters the calendar tool does not have. It now takes a bare date on the same terms as REST, and an unreadable value names scheduled_after or scheduled_before, so a model correcting its own call is pointed at the parameter it actually sent.What changes for you
Datetime windows are unaffected: the same instants, compared the same way.A bare-date window now covers exactly the local days you name, which moves both of its bounds. Each one picks up the local day it used to cut off, as described above. The start bound also stops reaching into the closing hours of the day before your range for an issuer behind UTC: adate_precision: exact event at 22:30 local on 30 September falls after 2026-10-01T00:00:00Z, so it used to answer scheduled_after=2026-10-01, and it is now outside the window along with the rest of 30 September. A date_precision: day event sits at local midnight, so it never falls in that gap.The change most likely to surprise a caller is the inclusive end. A bare-date scheduled_before now covers that date for every issuer, where it previously covered it only for issuers ahead of UTC, whose local midnight fell before the UTC one. A window ending at 2026-11-01 now returns 1 November events everywhere. Pass a datetime for an exclusive end.Calendar rows carry the issuer inline
Calendar event rows gaincompany, alongside the existing company_id:id is the same value as company_id, and ticker is the issuer’s primary active listing, null for an unlisted issuer. Every row these routes serve carries company, since they only return events whose issuer this API publishes, so ticker is the only nullable value inside it. The field appears on GET /calendar_events, GET /calendar_events/{id}, GET /companies/{identifier}/calendar_events, and the MCP search_calendar_events and get_calendar_event tools. On the REST routes it is selectable with fields=company, listed under field projection.Rendering an earnings calendar previously cost one company lookup per row to turn company_id into a name, and many calendar titles carry no company name at all, so that lookup was rarely optional. Batch company lookup, below, closes the same gap for the payloads that still hand back bare ids. See How to get calendar events.The change is additive: company_id stays on every surface and no existing field changed. It is a list-body shape change, so the ETag version component moved and every list endpoint’s ETag changes with this release. A conditional request returns 200 once and revalidates normally after that. Calendar ETags and Last-Modified now also fold in the embedded issuer, so renaming a company or changing its ticker invalidates the calendar responses that carry it.Batch company lookup on GET /companies
GET /companies accepts company and ticker, each taking a comma-separated list of up to 25 values, on the same terms as the flat article, story, and calendar feeds. They turn up to 25 identifiers back into names in one request, where the only route from an id to a name was previously one GET /companies/{identifier} per company. MCP search_companies gains both parameters.Resolve several tickers at once
ticker accepts the exchange-suffixed form (VOLV-B.ST), resolves former tickers, and is case-insensitive. Both are exact filters rather than searches, so unlike q they return no near-matches and support cursor pagination. Sending company and ticker together intersects them, as any other pair of filters on this endpoint does.Rows come back in name order rather than the order you asked for, so key each one by its own id. A ticker shared by more than one issuer resolves to every matching company, so a batch can return more rows than it sent: follow next_cursor rather than assuming a single page. Every value has to resolve, or the request returns 400 naming each one that did not, and that message does not distinguish an unknown id from a company this API does not publish. See Error handling.GET /companies normally leaves out companies with no published coverage and no active listing, the way search results do. An explicit company or ticker lookup drops that filter: whatever GET /companies/{identifier} serves, a batch lookup returns too. Otherwise a batch of 25 ids could come back with 24 rows and nothing in the response to say which one went missing, or why.Conditional requests on every cached endpoint
The API Reference now declaresIf-None-Match, If-Modified-Since (wherever the endpoint sets Last-Modified) and a 304 Not Modified response on every endpoint that supports them. Before this release only four operations declared If-None-Match and none declared a 304, so the reference gave you no way to tell which endpoints you could revalidate against. The full contract is now stated there:- Every list and single-resource endpoint returns an
ETag. All of them exceptGET /stories/{id}also returnLast-Modified, and on lists that header appears whenever the page is not empty. - A
304has no body and does not count against your monthly request allowance. - Full-text search (
q),updated_aftersync requests, andGET /searchare never cached.
GET /watchlists, GET /exchanges/{identifier}/companies, GET /indices/{identifier}/companies, and ids batch lookups on GET /articles return the same ETag and Last-Modified as every other list. They already answered a conditional request with a 304, but that 304 counted against your monthly allowance. It no longer does.Adding or removing a company also moves a watchlist’s timestamp now, so an If-Modified-Since request no longer receives a 304 carrying a stale company_count.On articles and stories, including the company-scoped routes, and on GET /calendar_events, the ETag reflects the response as served rather than only record timestamps. It changes when a plan limit alters the visible set, and on stories when a story’s visible article set changes. A changed ETag does not necessarily mean a record was updated, so callers watching a company feed no longer have to guess why one moved. See Caching.Read a company’s news the same way on any endpoint
The two ways to read a company’s news are now aligned: the flat feed with aticker filter, and the nested GET /companies/{identifier}/articles route. They take the same filters, apply the same plan caps, and resolve a ticker the same way. The company filters also reach several companies at once.primary_only on the flat feed
primary_only=true is now available on GET /articles and MCP search_articles, where it was previously only on the nested company route. It drops articles where the matched company is a passing mention. It requires ticker, company, or watchlist; without one of those the request returns 400 (REST) or a tool error (MCP).A company's own news via the flat feed
Full filter parity on the nested routes
GET /companies/{identifier}/articles and GET /companies/{identifier}/stories now accept the remaining filters their flat counterparts already had, scoped to the company in the path. The articles route gains q, sort, country, category, sources, source_type, content_type, published_after, and published_before; the stories route gains q, sort, fields, country, category, sources, published_after, and published_before.A company's articles from Swedish sources, newest first
GET /articles and GET /stories resources exactly. q on its own returns a relevance-ranked single page with no cursor. Adding sort=latest turns q into a match filter under the feed’s newest-first order and restores cursor pagination. A q request sends Cache-Control: no-cache, no-store, as on the flat feeds.ticker and company take several values
On the flat GET /articles, GET /stories, and GET /calendar_events, ticker and company accept a comma-separated list. A list matches any of the companies named, up to 25 values per parameter, the same cap as sources. Send ticker and company together and they intersect, as they always have.Articles about Volvo or Ericsson
ticker and company are among the parameters they reject.Plan caps run after filters
On limited plans, the article count cap onGET /articles and MCP search_articles now applies after every filter, so it selects the newest N articles matching your request. With ticker, company, or watchlist the cap is the per-company allowance; otherwise it is the feed allowance. Before, the cap selected the platform-wide newest articles first and filters narrowed within that window, so a ticker filter on a limited plan usually returned an empty list. The recency window and the preview-source restriction are unchanged and still apply first, and plan_limited: true is still set whenever a cap is in force. The per-company story cap on the nested stories route likewise now runs after the filters.Caching headers on the nested routes
GET /companies/{identifier}/articles, /stories, and /calendar_events now send Last-Modified. Their ETag is now computed from the record set before serialization and folds in plan-cap state (and, on /stories, each story’s visible article ids). A plan change or an article leaving the feed now changes the ETag even when no record’s updated_at moved. updated_after requests on these routes now send Cache-Control: no-cache, no-store, matching the flat feeds. See Caching for what the ETag covers. The spec now also documents fields and updated_after on the nested articles route, updated_after on the nested stories route, and the full parameter set on the nested calendar events route; those routes already accepted them.Ticker resolution
Ticker resolution onGET /companies/{identifier} and its member routes, and on MCP get_company, now uses the same resolver as the ticker filter. The exchange-suffixed form (for example VOLV-B.ST) is accepted, and former tickers resolve, inside a comma-separated list exactly as for a single value. Matching is case-insensitive on every surface, so volv-b, VOLV-B, and VOLV-B.ST all reach the same company. When a ticker is shared by more than one company, the company holding a listing on a Nordic exchange is returned, then the one with an active listing, then the most recently listed. Pass the company id for an unambiguous lookup. A verified, visible company with no published content now returns 200 with an empty list on its member routes rather than 404, and MCP get_company now resolves the same set of companies as GET /companies/{identifier}.In MCP
search_articles, search_stories, and search_calendar_events take comma-separated ticker and company on the same terms as the REST filters, and report an unresolved value as a tool error rather than an HTTP status. search_calendar_events now resolves former tickers as well as current ones, matching every other surface; previously only an active listing matched.What changes for you
On the nested routes, nine parameters that describe a different company or listing now return a400 with problem type invalid-parameter, naming the offending parameter: ticker, company, watchlist, listed, exchange, market, domicile, index, and sector. These were previously accepted and ignored, and the route returned the path company’s unfiltered feed regardless. watchlist=false and listed with any value other than true remain inert no-ops, as on the flat feeds. Values sent under the newly accepted names are validated as on the flat feeds, so a sort, source_type, content_type, date, or fields value the flat feed rejects now returns 400 here too. An unrecognized content_type on GET /articles and the nested articles route now returns 400 instead of 500.On the flat feeds, every value in a ticker or company list has to resolve to a company the API publishes. A typo, an unknown ID, or a company outside the published set returns 400, with parameter set to the filter that failed and a detail naming each value as you sent it.200 and an empty list, which read exactly like “this company had no news”. A comma-separated list was treated as one symbol and came back empty for the same reason. A value made only of separators, such as ticker=,, also returns 400, as does a list over the cap of 25. Sending ticker= with no value at all leaves the filter off, exactly like omitting it.Filters that select a group rather than a company are unchanged. An unrecognized exchange, market, or domicile value still never errors: it drops out of the filter, or returns an empty page when nothing in the filter matches.Filter by market and by domicile
GET /articles, GET /stories, GET /companies, and GET /calendar_events accept two new filters: market and domicile. Both take ISO 3166-1 alpha-2 country codes, comma-separated.Articles about companies trading on the Swedish market
market selects companies with an active equity listing on an exchange in those countries. One market=SE call covers Nasdaq Stockholm, First North, Spotlight, and NGM together, so you no longer have to name every venue yourself. It includes issuers that list locally but are registered abroad.domicile selects companies legally domiciled in those countries. It reaches unlisted companies, and it leaves out the foreign-domiciled, locally-listed issuers that market picks up.Three country questions, three filters
On articles and stories,country still means the country of the source that published the article. It is a coverage filter, not a company filter. The other two questions are now filterable on their own terms:A company listed only on a growth-market segment, such as First North Sweden (MIC
FNSE), used to sit outside any country-level “companies on the Swedish market” query. You could filter on the parent MIC XSTO or on legal domicile, and neither one answers that question for a company that trades in Stockholm but is registered somewhere else.domicile on companies
On GET /companies, country already meant legal domicile, so domicile is the canonical name for what you were already doing. country keeps working and returns the same results. Send both and they intersect, like any other pair of filters.exchange takes several MICs
exchange now accepts a comma-separated list of MIC codes on all four content endpoints, alongside the single code it has always taken.Stories about companies listed in Stockholm or Copenhagen
Calendar events and indices
OnGET /calendar_events, market and domicile carry the same meanings as on the other endpoints. Calendar events were the last content surface without them. The event’s own country filter is unchanged: it is the event’s country (the issuer’s, with a source-country fallback), distinct from both.Events for companies trading on the Swedish market
GET /indices now accepts a comma-separated list of MICs on exchange as well, and matches active exchanges only.The same filters in MCP
search_articles, search_stories, search_companies, and search_calendar_events accept market, domicile, and multi-MIC exchange, with the same meanings. list_stock_indices takes multi-MIC exchange too.Also fixed: filters now require an active listing
Filtering companies byexchange or listed=true on GET /companies now requires an active listing, matching what MCP search_companies already did for exchange. A company whose only listing was delisted used to match, and no longer does. GET /indices exchange likewise matches active exchanges only, so an index reachable only through an inactive or renamed exchange no longer matches. market and domicile are additive, so no call you already make changes because of them.Article text is documented as machine-translated
The OpenAPI spec and MCP tool descriptions now state explicitly that an article’stitle, summary, and key_points are machine-translated and AI-paraphrased from the source article. They are not verbatim quotations, and they are not human-verified. This applies across GET /articles, GET /articles/{id}, and the MCP get_article and search_articles tools.This content was always machine-generated; the documentation previously did not say so. If you surface these fields to end users, you now have an explicit basis for that caveat. No field or payload changed.story_ids no longer references unpublished stories
story_ids on the article detail, GET /articles/{id} and MCP get_article, now lists only published stories, matching what GET /stories/{id} and get_story serve. Previously it could include the id of a story that was drafted but never published, or withheld after generation, and following that id returned a 404.The documented path from an article through story_ids to a story now always resolves. You no longer need to guard against 404s on ids the API returned.No schema changed. Some articles that previously reported a story id now correctly report an empty story_ids, and every id that disappeared was one that already returned a 404.Company objects carry a company’s other listings
Company summary objects now includealso_listed_on, an array of the company’s other active stock listings. Each entry pairs a ticker with a mic_code:also_listed_on on Nordea's company object
ticker and exchange fields still describe that primary listing. also_listed_on is always present; an empty array means the company is single-listed or unlisted.Before this change, a multi-listed company looked identical to a single-listed one in search results. Resolving “Nordea” returned only the Helsinki listing, with no signal that Stockholm and Copenhagen listings existed. One search call now carries every venue.Where it appears
In the REST API, the field is on every company summary:GET /companies (both the list and ?q= search), GET /companies/{identifier}, GET /watchlists/{id}, GET /indices/{identifier}/companies, GET /exchanges/{identifier}/companies, and the company results in GET /search. On company detail, the full stock_listings array remains the authoritative source.In the MCP server, it is on search_companies, list_watchlist, and get_company.The change is purely additive. No existing field changed.Company search ranking favors exact matches and listed companies
Relevance ranking for company search now orders results in tiers. Exact ticker matches come first. Then exact name or alias matches. Then ticker-prefix matches. Then listed companies ahead of unlisted ones. Then coverage volume breaks the remaining ties. This applies to RESTGET /companies?q= and the MCP search_companies tool.An exact name or ticker match is now guaranteed to appear in the results, even when many looser matches compete for the page.Ranking was previously string proximity alone, so a closer partial match could outrank the company a query almost certainly means. Searching “Nokia” ranked a small brewery, Nokian Panimo Oyj, above Nokia Oyj. Take the top result now and you get the expected entity.What changes for you
Results for a company-name query may reorder. That is the intended effect. A company that matched only as a loose substring may now rank lower, or drop off a short page.Also fixed: q with the exchange filter
Combining q with the exchange filter on GET /companies or MCP search_companies previously returned a server error. The two parameters now compose correctly.Filter articles by source type
GET /api/v1/articles accepts a new source_type parameter. It narrows results to articles published by a given kind of source, without you having to name individual sources.Articles from exchange newsfeeds and wire services
news_publication, wire_service, press_release, government, trade_publication, blog, stock_exchange, and research. A value outside that set returns a 400 problem response naming the value or values it did not recognize.Articles from disabled sources are excluded. source_type combines with every filter you already use, including country, category, and ticker.Reaching the fastest disclosure tier
source_type=stock_exchange selects articles taken straight from exchange newsfeeds. That is the tightest-latency disclosure tier the API carries, so it is the filter to reach for when you are watching for a listed company’s own announcements rather than reporting about them.Source type on responses
The embeddedsource object on article responses now includes source_type, on both list and detail responses. GET /api/v1/sources returns source_type on every source, and you can request it on its own with field projection.Sources with their type
The same filter in MCP
Thesearch_articles MCP tool accepts a matching source_type filter, so an agent can scope a search to exchange disclosures the same way.MCP list_watchlist no longer repeats companies
Calling the MCP list_watchlist tool without a watchlist_id lists every company you follow across all of your watchlists. A company you follow on more than one watchlist appeared once per watchlist it belonged to. Each followed company is now listed once, regardless of how many of your watchlists it is on.This affects the combined view only. Passing a watchlist_id to scope the call to a single list was never affected, and the companies it returns are unchanged.Stories follow the same plan model as articles
On the free plan you previously saw the same two stories across the whole API: the most important one and the most recent one. Stories now work the way articles already did. Your plan applies a time window, then each endpoint applies its own count cap. Paid plans stay unlimited.The current free-plan window and caps are listed on the pricing page. See List stories for the parameters each endpoint accepts.MCP story tools return results instead of refusing
search_stories and get_story previously answered with a “not available on your current plan” refusal. They now return capped results, marked with plan_limited.Story search also applies the same window and cap as the list endpoints, which it did not do before.Telling a capped response apart
Any response your plan restricted carriesplan_limited: true, so you can tell a partial result from a complete one. The field is absent when your plan applies no restriction.Story list response on a capped plan