Nordic Financial News Stories API

The Stories API from Nordic Financial News — 2 operation(s) for stories.

Business capability
News & Publishing AE-170.60

Operations 2

GET /api/v1/stories List Stories #
GET /api/v1/stories/{id} Get Story Details #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/nordic-financial-news:nordic-financial-news-stories-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

nordic-financial-news-stories-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Nordic Financial News Stories API
  version: v1.0
  description: 'REST API for Nordic financial news. Each article is summarized in English (headline, short summary, and key points) with the original-language article linked via `article_url`. The full article body stays at the source. ## Authentication

    All endpoints require Bearer token authentication using an API key. Include the API key in the Authorization header:

    ```

    Authorization: Bearer YOUR_API_KEY

    ```


    ## Rate Limiting

    Rate limits apply per account, shared across every API key on that account — not per

    individual key:

    - **Free**: 100 requests/hour

    - **Plus**: 100 requests/hour

    - **Pro**: 5,000 requests/hour


    On Free and Plus, MCP tool calls have their own hourly and monthly allowance and do not

    count toward these REST limits. On Pro, REST requests and MCP tool calls share one allowance. Rate limit information is included in response headers:

    - `X-RateLimit-Limit`: Maximum requests per hour

    - `X-RateLimit-Remaining`: Requests remaining

    - `X-RateLimit-Reset`: Seconds until limit resets

    - `X-RateLimit-Policy`: Human-readable rate limit policy (e.g. `5000 per hour; fixed window`)


    A single client IP is additionally capped at 300 requests per 5-minute window, so

    reaching your account''s full hourly allowance requires spreading requests across more

    than one source IP. The `/health` endpoint is exempt from rate limiting and does not return rate limit headers. ## Monthly Usage Limits

    Free and Plus accounts have a fixed monthly allowance that resets at the start of each

    calendar month, shared across every API key on the account:

    - **Free**: 100 requests/month

    - **Plus**: 100 requests/month


    Responses on these plans include:

    - `X-Monthly-Limit`: Maximum requests for the current month

    - `X-Monthly-Remaining`: Requests remaining this month

    - `X-Monthly-Reset`: ISO 8601 timestamp when the allowance resets


    When the allowance is exceeded, the API returns `429 Too Many Requests` with a `Retry-After` header. Pro has no hard limit. It includes 25,000 requests per billing cycle, with usage-based

    pricing beyond that, so requests are never blocked. Responses on Pro include:

    - `X-Monthly-Usage`: Requests used in the current billing cycle

    - `X-Monthly-Reset`: ISO 8601 timestamp when the current billing cycle ends


    A Pro billing cycle follows your subscription''s billing period, which begins on the

    date you subscribed and is not necessarily a calendar month. Usage is counted by day,

    so the first day of a billing cycle is counted in full. ## Caching

    Every list and single-resource endpoint returns an `ETag`. Most also return

    `Last-Modified` (on lists, when the page is not empty); `GET /stories/{id}` and the

    Events endpoints do not. Send the ETag back as `If-None-Match`, or the date as

    `If-Modified-Since`, to receive `304 Not Modified` when nothing has changed. A 304

    has no body and does not count against the monthly request allowance. `GET /events`, `GET /events/{id}` and `GET /companies/{identifier}/events` return an

    `ETag` but no `Last-Modified`, so revalidate those routes with `If-None-Match`. An

    event''s visible company and story membership, and its live article count, all change

    without moving its `updated_at`, so a date-based validator would report "not

    modified" across a change the `ETag` catches. Some requests are never cached and return `Cache-Control: no-cache, no-store` with

    no `ETag`: full-text search (`q`), `updated_after` sync requests, and `GET /search`. The `ETag` reflects the response as served, not only record timestamps. On articles

    and stories, including the company-scoped routes, and on `GET /calendar_events`, 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. On the Events endpoints the `ETag` additionally covers each event''s visible

    company and story ids and its live article count, none of which move the event''s

    `updated_at`. ## Pagination

    List endpoints support cursor-based pagination.'
  contact:
    name: API Support
    email: hello@nordicfinancialnews.com
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
  /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
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