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