Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: Nordic Financial News Stories API
version: v1.0
contact:
name: API Support
email: hello@nordicfinancialnews.com
description: 'Operations tagged Stories across 2 of this provider''s published API definitions: nordic-financial-news-openapi-original.yml, nordic-financial-news-openapi.yml. Each path carries the servers of the definition it was published in.'
servers:
- url: https://nordicfinancialnews.com
description: Production server
tags:
- name: Stories
paths:
/api/v1/stories:
get:
summary: List Stories
tags:
- Stories
security:
- bearer_auth: []
description: 'Returns a paginated list of published stories ordered by publication date (newest first). Each story synthesizes coverage of a single developing event from multiple source articles into one narrative.
## Filtering
Supports filtering by country, category, source, and company ticker. Use `GET /api/v1/sources` to discover source IDs for the `sources` filter.
## Search
Use `q` for full-text search: relevance-ranked by default, newest-first with `sort=latest`, or matched by meaning with `mode=semantic`.
## Polling for updates
Use `updated_after` for incremental syncing.
## Caching
Requests with `updated_after` are not cached: they return `Cache-Control: no-cache, no-store` and no `ETag`. The `ETag` reflects the response as served, not only record timestamps: it changes when plan limits alter the visible set, and when a story''s visible article set changes, so a changed `ETag` does not necessarily mean a record was updated.'
parameters:
- name: limit
in: query
required: false
description: Number of stories to return per page (default 25, max 100).
example: 25
schema:
type: integer
- name: cursor
in: query
required: false
description: Opaque pagination cursor returned as `pagination.next_cursor` from a previous response. Mutually exclusive with `q` unless `sort=latest` is also given.
schema:
type: string
- name: updated_after
in: query
required: false
description: 'ISO 8601 datetime. Returns only stories updated after this timestamp. Use this for incremental sync: store the timestamp of your last completed sync, then request only newer results on each poll. Results are ordered by `updated_at` ascending and are cursor-paginated. Follow `pagination.next_cursor`, resending the same `updated_after` value, until it is null. Only advance your stored timestamp once you have drained every page (see the Real-Time Updates guide at https://docs.nordicfinancialnews.com). Mutually exclusive with `sort=latest` (with or without `q`). An `updated_after` walk is always ordered by `updated_at`.'
example: '2024-01-01T00:00:00Z'
schema:
type: string
- name: q
in: query
required: false
description: Full-text search query (2-200 characters). By default, results are ranked by relevance instead of publication date, as a single page with no cursor. Mutually exclusive with `cursor` unless `sort=latest` is also given. Disables caching.
example: Volvo earnings
schema:
type: string
- name: sort
in: query
required: false
description: Result order for `q`. `relevance` (default) is a single relevance-ranked page with no cursor; it requires `q`, and returns 400 without one. `latest` turns `q` into a match filter under the endpoint's normal newest-first order (`published_at` descending) and the response is cursor-paginated like any other request; without `q` it is an accepted no-op (that's already the default order). Mutually exclusive with `updated_after`.
example: latest
schema:
type: string
enum:
- relevance
- latest
- name: mode
in: query
required: false
description: 'Set to `semantic` to match `q` by meaning rather than by keyword. The match runs over each story''s title, summary and key entities. The narrative body is not part of the match, so it answers thematic questions a keyword match cannot ("defense contract wins", "battery cell capacity expansion").
Requires `q`, is mutually exclusive with `sort`, `cursor` and `updated_after`, and returns a single ranked page capped by `limit`; every other filter (`country`, `category`, `sources`, `ticker`, `company`, `market`, `domicile`, `exchange`, `index`, `sector`, `watchlist`, `published_after`/`published_before`) remains a hard filter applied before the match, not after.
Each story gains a `distance` (raw cosine, lower is closer, rounded to 4dp). Compare it only within one response, never across responses: the scale is model-dependent. Ranking also blends in a small recency bonus that `distance` does not reflect. A server-side relevance floor drops weak matches, so a page is frequently shorter than `limit` and can be empty. Raising `limit` does not recover floored rows.
On restricted plans, the story age window narrows the candidate set and the story count cap applies as a clamp on this page, so a capped plan receives its closest matches rather than its newest stories; either restriction sets `plan_limited: true`.
The response `pagination` object carries `count` only. There is no `next_cursor`, since a distance ranking cannot be cursor-paginated, and the response is uncacheable (`Cache-Control: no-cache, no-store`, no `ETag`). If the query cannot be embedded the request returns `503` rather than silently falling back to keyword matching.'
example: semantic
schema:
type: string
enum:
- semantic
- name: country
in: query
required: false
description: Filter by country using ISO 3166-1 alpha-2 code (e.g. `SE`, `NO`, `DK`). Comma-separated for multiple (e.g. `SE,NO`). Case-insensitive.
example: SE
schema:
type: string
- name: category
in: query
required: false
description: Filter by category `id` (from `GET /api/v1/categories`). Comma-separated for multiple (e.g. `cat_earnings1,cat_mna000001`); unknown ids are ignored.
example: cat_earnings1
schema:
type: string
- name: sources
in: query
required: false
description: Comma-separated list of source IDs (max 25). Use `GET /api/v1/sources` to discover source IDs. Also accepts array form (`sources[]=id1&sources[]=id2`). Returns stories with at least one article from a listed source.
example: abc123def456,ghi789jkl012
schema:
type: string
- name: ticker
in: query
required: false
description: 'Filter by company stock ticker symbol, comma-separated for several (e.g. `VOLV-B` or `VOLV-B,ERIC-B`; matches any of them). Accepts full tickers with exchange suffix (e.g. `VOLV-B.ST`). Returns stories mentioning the company. Former tickers resolve too: a company that renamed or moved venues is still reachable by an old ticker; the returned company `former_tickers` lists any such historical tickers. Matching is case-insensitive. Every value must resolve to a company, or the request is rejected with `400` naming the unknown values. Combined with `company`, the two filters intersect.'
example: VOLV-B
schema:
type: string
- name: company
in: query
required: false
description: Filter by company `id` (as returned by the companies endpoints), comma-separated for several (matches any of them). Complements `ticker`; use this to reach companies without a stock listing. Every value must resolve, or the request is rejected with `400` naming the unknown ids. Combined with `ticker`, the two filters intersect.
example: cmpny1234567
schema:
type: string
- name: exchange
in: query
required: false
description: Filter by exchange using ISO 10383 Market Identifier Code, comma-separated for multiple (e.g. `XSTO` for Nasdaq Stockholm, `XCSE` for Nasdaq Copenhagen, `XHEL` for Nasdaq Helsinki, `XOSL` for Oslo Børs). Returns stories about companies actively listed there. Case-insensitive.
example: XSTO,XCSE
schema:
type: string
- name: market
in: query
required: false
description: Filter to stories about companies with an active equity listing in these countries' exchanges (ISO 3166-1 alpha-2, comma-separated, e.g. `SE,NO`). `SE` covers Nasdaq Stockholm, First North, Spotlight and NGM. Distinct from `country` (the source that reported it) and `domicile` (legal domicile). Includes foreign-domiciled but locally-listed issuers.
example: SE
schema:
type: string
- name: domicile
in: query
required: false
description: Filter to stories about companies legally domiciled in these countries (ISO 3166-1 alpha-2, comma-separated). Reaches unlisted companies; excludes foreign-domiciled but locally-listed issuers. Use `market` for market-coverage questions.
example: SE
schema:
type: string
- name: index
in: query
required: false
description: Filter by stock index id or symbol (e.g. `OMXS30`, `OMXC25`, `OMXH25`, `OBX`). Returns stories about companies in that index. Case-insensitive for symbols.
example: OMXS30
schema:
type: string
- name: sector
in: query
required: false
description: 'Filter by company sector. Case-sensitive; must exactly match one of: `Communication Services`, `Consumer Discretionary`, `Consumer Staples`, `Energy`, `Financials`, `Health Care`, `Industrials`, `Information Technology`, `Materials`, `Real Estate`, `Utilities`.'
example: Industrials
schema:
type: string
- name: listed
in: query
required: false
description: When `true`, only return stories linked to at least one company with an active listing on any exchange. A company whose listings are all delisted does not count. Hidden and unverified companies do not count.
example: 'true'
schema:
type: string
- name: watchlist
in: query
required: false
description: Restricts to stories mentioning companies in a single watchlist, identified by its `id` (from the List Watchlists endpoint). An unknown id returns no stories. Requires `read:watchlist` scope.
example: wl_8fk2a1b3c4d5
schema:
type: string
- name: published_after
in: query
required: false
description: ISO 8601 datetime. Returns stories published on or after this timestamp.
example: '2024-06-01T00:00:00Z'
schema:
type: string
- name: published_before
in: query
required: false
description: ISO 8601 datetime. Returns stories published before this timestamp.
example: '2024-12-31T23:59:59Z'
schema:
type: string
- name: fields
in: query
required: false
description: 'Comma-separated list of fields to include in the response. Available fields: `id`, `title`, `summary`, `published_at`, `updated_at`, `article_count`, `article_ids`, `country`, `category`, `company_ids`.'
example: id,title,summary,published_at
schema:
type: string
- name: If-None-Match
in: header
required: false
description: ETag value from a previous response. Returns `304 Not Modified` if data has not changed.
schema:
type: string
- name: If-Modified-Since
in: header
required: false
description: HTTP date from a previous `Last-Modified` header. Returns `304 Not Modified` if nothing has changed since.
schema:
type: string
responses:
'200':
description: Stories retrieved
content:
application/json:
schema:
type: object
properties:
stories:
type: array
items:
$ref: '#/components/schemas/StorySummary'
pagination:
type: object
properties:
count:
type: integer
example: 25
next_cursor:
type:
- string
- 'null'
plan_limited:
type: boolean
description: Present and `true` when your plan restricted what this endpoint can return, so the result may be incomplete. Absent when your plan applies no restriction.
example: true
'304':
description: Not Modified
'401':
description: Unauthorized
content:
application/json:
examples:
unauthorized:
value:
type: https://docs.nordicfinancialnews.com/problems/auth-invalid
title: Unauthorized
status: 401
detail: Missing or invalid API key
instance: urn:request:abc123
schema:
$ref: '#/components/schemas/problem_details'
operationId: getApiV1Stories
x-operation-id-source: derived
servers:
- url: https://nordicfinancialnews.com
description: Production server
/api/v1/stories/{id}:
get:
summary: Get Story Details
tags:
- Stories
security:
- bearer_auth: []
description: Returns the full details of a single story, including content in Markdown format, associated articles, and companies. Supports conditional requests via `If-None-Match` for efficient caching.
parameters:
- name: id
in: path
required: true
description: Story ID (e.g. `sty_abc123def`).
example: sty_abc123def
schema:
type: string
- name: If-None-Match
in: header
required: false
description: ETag value from a previous response. Returns `304 Not Modified` if the story has not changed.
schema:
type: string
responses:
'200':
description: Story found
content:
application/json:
schema:
type: object
properties:
story:
$ref: '#/components/schemas/StoryDetail'
'304':
description: Not Modified
'404':
description: Story not found
content:
application/json:
examples:
not_found:
value:
type: https://docs.nordicfinancialnews.com/problems/not-found
title: Not Found
status: 404
detail: The requested resource could not be found
instance: urn:request:abc123
schema:
$ref: '#/components/schemas/problem_details'
operationId: getApiV1StoriesById
x-operation-id-source: derived
servers:
- url: https://nordicfinancialnews.com
description: Production server
components:
schemas:
StoryDetail:
allOf:
- $ref: '#/components/schemas/StorySummary'
- type: object
properties:
content:
type: string
format: markdown
description: Full story content in Markdown format
example: '## Volvo Q3 Results\n\nVolvo reported record earnings...'
updated_at:
type: string
format: date-time
description: When the story was last updated (ISO 8601)
example: '2026-03-15T14:00:00.000Z'
companies:
type: array
description: Companies mentioned in this story
items:
type: object
properties:
id:
type: string
description: Unique company identifier
example: uejazctgchj4
name:
type: string
description: Company name
example: AB Volvo (Volvo Group)
ticker:
type:
- string
- 'null'
description: Primary stock ticker
example: VOLV-B
example:
- id: uejazctgchj4
name: AB Volvo (Volvo Group)
ticker: VOLV-B
StorySummary:
type: object
required:
- id
- title
- summary
- article_count
- published_at
- updated_at
properties:
id:
type: string
description: Unique story identifier
example: story_volvo_q3
title:
type: string
description: Story title
example: Volvo Reports Record Q3 Results
summary:
type: string
description: Story summary
example: Volvo reported record third-quarter earnings, beating analyst expectations...
article_count:
type: integer
description: Number of articles in this story (matches the length of article_ids)
example: 4
published_at:
type: string
format: date-time
description: 'When the story''s newest source article was published (ISO 8601). This is editorial time, not the time the story reached this API: a story about older news carries an older date. Use it to filter by when news happened, never to track what is new to you.'
example: '2026-03-15T09:30:00.000Z'
updated_at:
type: string
format: date-time
description: 'When this record last changed (ISO 8601). This is the clock incremental sync runs on. It only ever moves forward, unlike published_at. To sync: pass your last completed sync time as `updated_after`, drain every page via `pagination.next_cursor`, and only then advance your stored timestamp.'
example: '2026-03-15T09:35:00.000Z'
article_ids:
type: array
items:
type: string
description: IDs of articles in this story, most relevant first
example:
- art_abc123def
- art_def456ghi
country:
type:
- string
- 'null'
description: Country code (ISO 3166-1 alpha-2)
example: SE
company_ids:
type: array
items:
type: string
description: IDs of companies mentioned in this story
example:
- uejazctgchj4
category:
type: object
description: Story category
properties:
id:
type: string
name:
type: string
event_id:
type:
- string
- 'null'
description: ID of the event this story was generated from. An event that has since been merged or removed still answers `GET /events/{id}` with a tombstone rather than a 404.
example: 8fzq2rmtnk5c
problem_details:
type: object
required:
- type
- title
- status
- detail
- instance
properties:
type:
type: string
description: URI that identifies the problem type
title:
type: string
description: Short human-readable summary
status:
type: integer
description: HTTP status code
detail:
type: string
description: Human-readable explanation
instance:
type: string
description: URI that identifies the specific occurrence
securitySchemes:
bearer_auth:
type: http
scheme: bearer
bearerFormat: API Key
description: API Key authentication using Bearer token
x-refined-from:
- nordic-financial-news-openapi-original.yml
- nordic-financial-news-openapi.yml