> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nordicfinancialnews.com/llms.txt
> Use this file to discover all available pages before exploring further.

# List Events

> Returns a paginated list of Events. An event groups the articles reporting a single real-world development (an earnings report, an M&A announcement, a dividend, an executive change, and so on). Sorted by when the event was first reported, most recent first.

An event is only returned if at least one of its live member articles survives your plan's age and source restrictions. This is the same visibility rule the event feed on the website uses. If none survive, the event does not appear at all. `GET /api/v1/events/:id` distinguishes that case from an event your plan can see but whose full coverage it cannot (see below).

## Merges and removals
Events sometimes merge (two events turn out to describe the same development) or are removed (no live articles remain, or the event was found to be incorrect). Both are terminal. The event never reappears in a normal list, and `GET /api/v1/events/:id` on one returns a **minimal tombstone**: `id`, `status`, `merged_into` (the surviving event's `id`, when merged), and `updated_at` only. A tombstone carries no title, summary, counts or id arrays.

Poll with `updated_after` to learn about merges and removals. Tombstones for events that changed since your watermark are included in that sync walk (uncapped, on every plan), which is otherwise the only way to learn that an event you previously fetched is gone. They are never returned by the default list.

A tombstone still respects your event-grain filters: `company`, `ticker`, `exchange`, `index`, `sector`, `listed`, `event_type`, `significance`, `watchlist`, `first_reported_after`/`before`, and `last_article_after`/`before`. A merged event that involved the company you filtered by still surfaces its tombstone.

Those filters are checked against the tombstone's companies as they are now. With `listed`, a tombstone whose only listed company was delisted or stopped appearing in the API's company data before the merge or removal no longer matches, so it is not returned. Poll without `listed` for full-fidelity tombstone coverage.

On `exchange`, `market`, `domicile`, `index`, `sector` and `listed`, a merged or removed commentary event's tombstone is returned when any matching company is on it, in any role, so a filtered sync can include a tombstone for an event it never returned. A filtered sync returns events that match the filters now: an event that stops matching (for example, when its articles are no longer about a matching company) is not returned by it. Poll without those filters to see every change.

A tombstone does not respect `country`, `category`, `sources`, `source_type` or `content_type`, which are article-grain filters. It has no live articles left to filter by, so poll without them for full-fidelity tombstone coverage.

A merged event's `last_article_at` freezes at merge time, so `last_article_after`/`before` filter a tombstone against that frozen value rather than against when the merge happened. `window` cannot be used for sync polling at all (see below).

## Polling for updates
Use `updated_after` with the timestamp of your last completed sync to fetch only events that have changed since. Results are then ordered by `updated_at` ascending and cursor-paginated: follow `pagination.next_cursor`, resending the same `updated_after`, until it is null. Advance your stored timestamp only once every page is drained.

A merge takes the earlier of the two `first_reported_at` values, so it only ever moves the surviving event earlier in the default sort. Under the default (non-sync) ordering a cursor walk can therefore re-emit a row you have already seen, but it can never skip one. Treat the walk as at-least-once and dedupe by `id`.

`company_ids`/`story_ids` membership can drift out from under `updated_after` without bumping `updated_at`: a company merge, a company leaving or rejoining the API's company data, or a story being withdrawn from an event you already hold. A change to a story itself is tracked, and bumps the parent event. Treat the ids in those arrays as current as of your last poll rather than as ground truth.

Some low-value wire items are deleted outright rather than marked `removed`. Like every other resource on this API, deletions of that kind are not signaled by `updated_after`, so treat a local mirror as append-and-update rather than an exact replica.

## Pagination
List responses include `pagination.next_cursor` when more pages are available. Pass it back as the `cursor` parameter on the next request. Cursors are opaque. Do not parse them.

## Caching
Responses carry an `ETag` and `Cache-Control: private, max-age=0, must-revalidate`; send the ETag back as `If-None-Match` to receive a `304 Not Modified`. There is no `Last-Modified` on this endpoint, so `If-Modified-Since` alone never revalidates: an event's visible company and story membership and its live article count all change without moving its `updated_at`, and only the `ETag` reflects them. A changed `ETag` therefore does not necessarily mean a record was updated. It also moves when a plan limit alters the visible set.

Requests carrying `q` or `updated_after` are not cached and return `Cache-Control: no-cache, no-store`.



## OpenAPI

````yaml https://nordicfinancialnews.com/openapi/v1/openapi.yaml get /api/v1/events
openapi: 3.0.1
info:
  title: Nordic Financial News 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. Use the `cursor` parameter

    with the value from `pagination.next_cursor` in the response to fetch the
    next page.

    Paginated responses also include a `Link` header with `rel="next"` pointing
    to the next page URL.


    ## Incremental Sync

    To keep a local copy current, poll `updated_after`, not `published_after`.


    1. Request `?updated_after=<your last completed sync>`.

    2. Results are ordered by `updated_at` ascending and cursor-paginated.

    3. Follow `pagination.next_cursor`, resending the same `updated_after`,
    until it is `null`.

    4. Only then store the time the sync started, minus a small overlap, as your
    new
       watermark. A minute is plenty.

    Step 3 matters: a response is capped at `limit` (default 25, max 100), so a
    changed set

    larger than one page arrives across several. Advancing your watermark before
    draining

    every page skips the remainder.


    The overlap in step 4 matters for the same reason: a record's `updated_at`
    is set when

    the write happens, but it only becomes visible when that write commits a
    moment later.

    Without an overlap a record can be stamped just before your watermark and
    land just

    after it, and you would never ask for it again. Re-reading a minute of
    changes is

    cheap and idempotent.


    `published_at` is editorial time: when the news happened, not when the
    record reached

    this API. A record can therefore appear with a `published_at` older than one
    you already

    hold. Records are re-sent when they change, so reconcile by `id`.


    Removals are not signaled: an article withdrawn from the feed stops being
    returned.

    Incremental sync tells you what changed, not what disappeared, so treat a
    local mirror

    as append-and-update rather than an exact replica.


    ## Error Handling

    Errors follow the RFC 9457 Problem Details format with appropriate HTTP
    status codes.
  contact:
    name: API Support
    email: hello@nordicfinancialnews.com
servers:
  - url: https://nordicfinancialnews.com
    description: Production server
security: []
paths:
  /api/v1/events:
    get:
      tags:
        - Events
      summary: List Events
      description: >-
        Returns a paginated list of Events. An event groups the articles
        reporting a single real-world development (an earnings report, an M&A
        announcement, a dividend, an executive change, and so on). Sorted by
        when the event was first reported, most recent first.


        An event is only returned if at least one of its live member articles
        survives your plan's age and source restrictions. This is the same
        visibility rule the event feed on the website uses. If none survive, the
        event does not appear at all. `GET /api/v1/events/:id` distinguishes
        that case from an event your plan can see but whose full coverage it
        cannot (see below).


        ## Merges and removals

        Events sometimes merge (two events turn out to describe the same
        development) or are removed (no live articles remain, or the event was
        found to be incorrect). Both are terminal. The event never reappears in
        a normal list, and `GET /api/v1/events/:id` on one returns a **minimal
        tombstone**: `id`, `status`, `merged_into` (the surviving event's `id`,
        when merged), and `updated_at` only. A tombstone carries no title,
        summary, counts or id arrays.


        Poll with `updated_after` to learn about merges and removals. Tombstones
        for events that changed since your watermark are included in that sync
        walk (uncapped, on every plan), which is otherwise the only way to learn
        that an event you previously fetched is gone. They are never returned by
        the default list.


        A tombstone still respects your event-grain filters: `company`,
        `ticker`, `exchange`, `index`, `sector`, `listed`, `event_type`,
        `significance`, `watchlist`, `first_reported_after`/`before`, and
        `last_article_after`/`before`. A merged event that involved the company
        you filtered by still surfaces its tombstone.


        Those filters are checked against the tombstone's companies as they are
        now. With `listed`, a tombstone whose only listed company was delisted
        or stopped appearing in the API's company data before the merge or
        removal no longer matches, so it is not returned. Poll without `listed`
        for full-fidelity tombstone coverage.


        On `exchange`, `market`, `domicile`, `index`, `sector` and `listed`, a
        merged or removed commentary event's tombstone is returned when any
        matching company is on it, in any role, so a filtered sync can include a
        tombstone for an event it never returned. A filtered sync returns events
        that match the filters now: an event that stops matching (for example,
        when its articles are no longer about a matching company) is not
        returned by it. Poll without those filters to see every change.


        A tombstone does not respect `country`, `category`, `sources`,
        `source_type` or `content_type`, which are article-grain filters. It has
        no live articles left to filter by, so poll without them for
        full-fidelity tombstone coverage.


        A merged event's `last_article_at` freezes at merge time, so
        `last_article_after`/`before` filter a tombstone against that frozen
        value rather than against when the merge happened. `window` cannot be
        used for sync polling at all (see below).


        ## Polling for updates

        Use `updated_after` with the timestamp of your last completed sync to
        fetch only events that have changed since. Results are then ordered by
        `updated_at` ascending and cursor-paginated: follow
        `pagination.next_cursor`, resending the same `updated_after`, until it
        is null. Advance your stored timestamp only once every page is drained.


        A merge takes the earlier of the two `first_reported_at` values, so it
        only ever moves the surviving event earlier in the default sort. Under
        the default (non-sync) ordering a cursor walk can therefore re-emit a
        row you have already seen, but it can never skip one. Treat the walk as
        at-least-once and dedupe by `id`.


        `company_ids`/`story_ids` membership can drift out from under
        `updated_after` without bumping `updated_at`: a company merge, a company
        leaving or rejoining the API's company data, or a story being withdrawn
        from an event you already hold. A change to a story itself is tracked,
        and bumps the parent event. Treat the ids in those arrays as current as
        of your last poll rather than as ground truth.


        Some low-value wire items are deleted outright rather than marked
        `removed`. Like every other resource on this API, deletions of that kind
        are not signaled by `updated_after`, so treat a local mirror as
        append-and-update rather than an exact replica.


        ## Pagination

        List responses include `pagination.next_cursor` when more pages are
        available. Pass it back as the `cursor` parameter on the next request.
        Cursors are opaque. Do not parse them.


        ## Caching

        Responses carry an `ETag` and `Cache-Control: private, max-age=0,
        must-revalidate`; send the ETag back as `If-None-Match` to receive a
        `304 Not Modified`. There is no `Last-Modified` on this endpoint, so
        `If-Modified-Since` alone never revalidates: an event's visible company
        and story membership and its live article count all change without
        moving its `updated_at`, and only the `ETag` reflects them. A changed
        `ETag` therefore does not necessarily mean a record was updated. It also
        moves when a plan limit alters the visible set.


        Requests carrying `q` or `updated_after` are not cached and return
        `Cache-Control: no-cache, no-store`.
      parameters:
        - name: limit
          in: query
          required: false
          description: Number of events 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. Works with `q` too; `q` is a filter, not a
            separate search mode. Not accepted with `mode=semantic`. When
            continuing an `updated_after` sync, resend the same `updated_after`
            value alongside it.
          schema:
            type: string
        - name: fields
          in: query
          required: false
          description: >-
            Comma-separated list of fields to include in the response. Reduces
            payload size. `id` is always included.
          example: title,status,article_count
          schema:
            type: string
        - name: updated_after
          in: query
          required: false
          description: >-
            ISO 8601 datetime. Returns only events updated after this timestamp,
            including merge/removal tombstones. Use for incremental sync.
          example: '2026-03-01T00:00:00Z'
          schema:
            type: string
        - name: q
          in: query
          required: false
          description: >-
            Full-text filter over the event's own title and summary as well as
            its live member articles' indexed content (title, summary, key
            points, plus tagged company names and key facts), so an event can
            match on a company its coverage only tags. Matches stay in the
            default feed order (most recently reported first) and are
            cursor-paginated like any other request; combine with
            `window`/`last_article_after` for "latest on X". This differs
            deliberately from `q` on `GET /api/v1/articles` and `GET
            /api/v1/stories`, which returns a relevance-ranked single page
            unless `sort=latest` is given. Here `q` is a filter over the
            ordinary feed order, so it composes with `window` and the cursor.
            `mode=semantic` replaces this with a ranked match (see `mode`).
          example: Volvo earnings
          schema:
            type: string
        - name: mode
          in: query
          required: false
          description: >-
            Set to `semantic` to match `q` by meaning rather than by keyword,
            for thematic questions a keyword match cannot answer ("defense
            contract wins", "battery cell capacity expansion").


            The match runs over the translated title and summary of each event's
            live member articles, not over the event's own title or summary, and
            not over the key points, tagged company names or key facts that
            plain `q` also reaches. It is therefore not a strict recall
            improvement over plain `q`: an event whose title is on-topic while
            its member articles are oblique can match `q` and not
            `mode=semantic`.


            Results are ranked by distance blended with a small recency bonus,
            not in feed order. Requires `q`, is mutually exclusive with `sort`,
            `cursor` and `updated_after`, and returns a single ranked page
            capped by `limit`. Every other filter remains a hard filter applied
            before the match, not after. `window` and
            `last_article_after`/`last_article_before` still bound the event's
            activity, not the date of the matched article, so the member article
            behind a match can be considerably older than the event's last
            activity.


            Each event gains a `distance` (raw cosine, lower is closer): the
            distance of its best-matching member article that your plan can
            access, so the same event can carry different distances on different
            plans. Compare it only within one response, never across responses:
            the scale is model-dependent. Ranking uses the mean of an event's
            two closest member articles and a recency bonus, neither of which
            `distance` reflects. A server-side relevance floor drops weak
            matches, so a page is frequently shorter than `limit` and can be
            empty.


            The plan cap on recent events does not apply in this mode: the match
            runs over every event with member articles inside your plan's age
            and source restrictions, and `plan_limited: true` reports only that
            those restrictions are in force. `truncated: true` means the
            article-match horizon was exhausted, so further matching events
            exist that this response cannot reach, and no cursor can page to
            them.


            The response `pagination` object carries `count` only. There is no
            `next_cursor`, 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: event_type
          in: query
          required: false
          description: >-
            Filter by event type slug, comma-separated (e.g.
            `earnings-report,acquisition-announced`). These are Events taxonomy
            slugs (hyphenated) from `GET /api/v1/event_types` and are the only
            values accepted here. The underscored calendar `event_type` values
            used by `GET /api/v1/calendar_events` are rejected. A few spellings
            overlap across the two vocabularies (e.g. `delisting`) while
            denoting different things. An unrecognized slug returns 400 naming
            the bad slug(s).
          example: earnings-report
          schema:
            type: string
        - name: significance
          in: query
          required: false
          description: >-
            Editorial importance, the feed's materiality threshold,
            comma-separated (e.g. `notable,routine`). Valid values: `notable`
            (material, front-page-worthy events), `routine` (ledger records:
            routine filings, scheduled disclosures), `commentary`
            (opinion/analysis pieces). Use `significance=notable` to restrict
            the feed to material events. An unrecognized value returns 400
            naming the bad value(s).
          example: notable,routine
          schema:
            type: string
        - name: ticker
          in: query
          required: false
          description: >-
            Filter to events involving the company with this stock ticker (e.g.
            `VOLV-B`), comma-separated for several (matches any of them).
            Accepts the exchange-suffixed form; former tickers resolve. 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. Matches
            active-involvement roles only unless `include_peripheral=true`.
          example: VOLV-B
          schema:
            type: string
        - name: company
          in: query
          required: false
          description: >-
            Filter to events involving a company by its `id`, comma-separated
            for several (matches any of them). Complements `ticker`; reaches
            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. Matches active-involvement
            roles only unless `include_peripheral=true`.
          example: cmpny1234567
          schema:
            type: string
        - name: country
          in: query
          required: false
          description: >-
            Filter to events with a live member article from this country (ISO
            3166-1 alpha-2, comma-separated for multiple). Applied at the
            article grain, so sync tombstones do not respect it.
          example: SE
          schema:
            type: string
        - name: category
          in: query
          required: false
          description: >-
            Filter to events with a live member article in any of these
            categories, by their `id` (comma-separated; unknown ids are
            ignored). Applied at the article grain, so sync tombstones do not
            respect it.
          example: cat_earnings1
          schema:
            type: string
        - name: sources
          in: query
          required: false
          description: >-
            Filter to events with a live member article from these sources, by
            their `id` (comma-separated, max 25). Applied at the article grain,
            so sync tombstones do not respect it.
          example: src123456789
          schema:
            type: string
        - name: source_type
          in: query
          required: false
          description: >-
            Filter to events with a live member article from this source type,
            comma-separated. Valid values: `news_publication`, `wire_service`,
            `press_release`, `government`, `trade_publication`, `blog`,
            `stock_exchange`, `research`. Articles from disabled sources are
            excluded. Applied at the article grain, so sync tombstones do not
            respect it. An unrecognized value returns 400 naming the bad
            value(s).
          example: stock_exchange
          schema:
            type: string
        - name: content_type
          in: query
          required: false
          description: >-
            Filter to events with a live member article of this content type,
            comma-separated. Valid values: `news`, `analysis`, `press_release`,
            `market_commentary`, `market_news`, `trading_halt`, `trading_event`,
            `other`. Applied at the article grain, so sync tombstones do not
            respect it. An unrecognized value returns 400 naming the bad
            value(s). Content type is an AI-assigned label per article. To find
            events with a member article from a primary disclosure source,
            filter on `source_type` (`wire_service`, `stock_exchange`,
            `press_release`, `government`) instead.
          example: press_release
          schema:
            type: string
        - name: exchange
          in: query
          required: false
          description: >-
            Filter by exchange MIC code, comma-separated for multiple (e.g.
            `XSTO`, `XSTO,XCSE`).
          example: XSTO
          schema:
            type: string
        - name: market
          in: query
          required: false
          description: >-
            Filter to events involving a company 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. Note: a just-listed company's listing record
            can lag its IPO event.
          example: SE
          schema:
            type: string
        - name: domicile
          in: query
          required: false
          description: >-
            Filter to events involving a company 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 to events for companies in this stock index, by `id` or
            symbol.
          example: OMXS30
          schema:
            type: string
        - name: sector
          in: query
          required: false
          description: Filter by company sector. Must exactly match a standard sector name.
          example: Industrials
          schema:
            type: string
        - name: listed
          in: query
          required: false
          description: >-
            When `true`, only return events involving at least one company with
            an active listing on any exchange, matched by the same rule as
            `exchange`/`market`/`domicile`/`index`/`sector`. On a factual event,
            the company must have an active-involvement role (subject, acquirer,
            target, investor, issuer, partner); an advisor, speaker or passing
            mention does not qualify. On a commentary event, any role counts
            when a live article in the event is about that company rather than
            only mentioning it. `include_peripheral` does not change this. 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: >-
            Restrict to events for companies in a single watchlist, by its `id`.
            Requires the `read:watchlist` scope. Matches active-involvement
            roles only unless `include_peripheral=true`.
          example: wl_8fk2a1b3c4d5
          schema:
            type: string
        - name: first_reported_after
          in: query
          required: false
          description: ISO 8601 datetime. Only events first reported at or after this time.
          example: '2026-01-01T00:00:00Z'
          schema:
            type: string
        - name: first_reported_before
          in: query
          required: false
          description: ISO 8601 datetime. Only events first reported before this time.
          example: '2026-12-31T23:59:59Z'
          schema:
            type: string
        - name: last_article_after
          in: query
          required: false
          description: >-
            ISO 8601 datetime. Only events whose most recent activity
            (`last_article_at`, or `first_reported_at` when that is null) is at
            or after this time. This matches when the event was last active, not
            when it broke. Mutually exclusive with `window`.
          example: '2026-03-01T00:00:00Z'
          schema:
            type: string
        - name: last_article_before
          in: query
          required: false
          description: >-
            ISO 8601 datetime. Only events whose most recent activity is before
            this time. Mutually exclusive with `window`.
          example: '2026-03-02T00:00:00Z'
          schema:
            type: string
        - name: window
          in: query
          required: false
          description: >-
            Relative shorthand for `last_article_after`, e.g. `8h`, `24h`, `7d`
            (hours or days only, max 365d equivalent). Evaluated per request
            against the server clock. Compute `last_article_after` yourself for
            a stable multi-page snapshot. Mutually exclusive with
            `last_article_after`, `last_article_before`, and `updated_after`.
          example: 24h
          schema:
            type: string
        - name: sort
          in: query
          required: false
          description: >-
            Sort order. `latest` (default) sorts most recently reported first,
            the unfiltered feed order. `significance` groups events into 3
            coarse editorial tiers (`notable`, then `routine`, then
            `commentary`), most recently reported first within each tier; expect
            large ties within a tier. A mid-walk `routine`→`notable` escalation
            may be skipped, since the ordering is a best-effort snapshot. For a
            broad importance filter, prefer `significance=notable` with the
            default sort; page 1 is identical. Mutually exclusive with
            `updated_after` (an unrecognized value, or either sort value
            combined with `updated_after`, returns 400).
          example: significance
          schema:
            type: string
        - name: related_to
          in: query
          required: false
          description: >-
            Filter to commentary events that comment on or react to the factual
            event with this `id` (see `related_to` on the response).
            Unresolvable `id` returns an empty result set, not an error.
          example: evtrswagsh01
          schema:
            type: string
        - name: include_peripheral
          in: query
          required: false
          description: >-
            When `true`, widen `company`/`ticker`/`watchlist` matching, and the
            `company_ids` returned on each event, to companies with a peripheral
            role (advisor, speaker, or a passing mention) as well. Default
            `false` restricts matching and `company_ids` to companies with an
            active-involvement role (subject, acquirer, target, investor,
            issuer, partner). Does not widen `exchange`, `market`, `domicile`,
            `index`, `sector` or `listed`: on a factual event those match an
            active-involvement role; on a commentary event, any role, when a
            live article in the event is about that company rather than only
            mentioning it.
          schema:
            type: boolean
        - 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
      responses:
        '200':
          description: Events retrieved successfully
          content:
            application/json:
              examples:
                events_list:
                  value:
                    events:
                      - id: evt_volvoq3ma1
                        title: Volvo AB announces Q3 2026 results
                        status: active
                        significance: notable
                        event_type:
                          slug: earnings-report
                          label: Earnings Report
                          category: earnings
                        article_count: 3
                        first_reported_at: '2026-10-22T05:00:00.000Z'
                        last_article_at: '2026-10-23T09:15:00.000Z'
                        updated_at: '2026-10-23T09:20:00.000Z'
                        company_ids:
                          - uejazctgchj4
                        story_ids:
                          - story_volvo_q3
                        merged_into: null
                        related_to: null
                    pagination:
                      count: 1
                      next_cursor: null
              schema:
                type: object
                required:
                  - events
                  - pagination
                properties:
                  events:
                    type: array
                    items:
                      $ref: '#/components/schemas/EventSummary'
                  pagination:
                    type: object
                    properties:
                      count:
                        type: integer
                        example: 1
                      next_cursor:
                        type: string
                        nullable: true
                        description: >-
                          Opaque cursor to fetch the next page. `null` when
                          there are no more results.
                        example: IlVtcHByZXRPQVZTWnJrVS4uLiI=
                  plan_limited:
                    type: boolean
                    description: >-
                      Present and `true` when your plan capped the result to its
                      top-N most recent events. Absent when your plan applies no
                      cap. With `mode=semantic`, present and `true` when your
                      plan's source or age restrictions are in force instead.
                    example: true
                  truncated:
                    type: boolean
                    description: >-
                      Only with `mode=semantic`. Present and `true` when the
                      article-match horizon was exhausted, so further matching
                      events exist that this response cannot reach.
                    example: true
        '304':
          description: Not Modified
        '400':
          description: Invalid parameter
          content:
            application/json:
              examples:
                invalid_significance:
                  value:
                    type: >-
                      https://docs.nordicfinancialnews.com/problems/invalid-parameter
                    title: Invalid parameter
                    status: 400
                    detail: >-
                      Unknown significance value(s): not_a_significance. Valid
                      values: notable, routine, commentary
                    instance: urn:request:a1b2c3
                    parameter: significance
              schema:
                $ref: '#/components/schemas/problem_details'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              examples:
                unauthorized:
                  value:
                    type: https://docs.nordicfinancialnews.com/problems/auth-invalid
                    title: Authentication required
                    status: 401
                    detail: Missing or invalid API key
                    instance: urn:request:a1b2c3
              schema:
                $ref: '#/components/schemas/problem_details'
      security:
        - bearer_auth: []
components:
  schemas:
    EventSummary:
      type: object
      required:
        - id
        - status
        - updated_at
      description: >-
        An event: one real-world development, grouping the articles that report
        it. A `merged` or `removed` event (see `status`) returns only the
        minimal tombstone fields (`id`, `status`, `merged_into`, `updated_at`).
        Title, significance, event_type, counts, and every id array are omitted,
        never null-but-present, so a tombstone carries no content.
      properties:
        id:
          type: string
          description: Unique event identifier
          example: k3n8vdczq1mp
        title:
          type: string
          description: Event title (omitted on a tombstone)
          example: Volvo AB announces Q3 2026 results
        status:
          type: string
          description: >-
            Lifecycle status. `active`/`closed` are live (closed = coverage
            window lapsed but the event stays visible and can reopen). `merged`
            = absorbed into another event (see `merged_into`); `removed` =
            withdrawn from the feed (no live articles remain, or it was found to
            be incorrect). `merged`/`removed` never appear in the default list
            and carry only the tombstone fields.
          enum:
            - active
            - closed
            - merged
            - removed
          example: active
        significance:
          type: string
          description: >-
            Editorial importance (omitted on a tombstone). `notable` =
            front-page-worthy; `routine` = factual ledger record; `commentary` =
            single-article opinion/analysis.
          enum:
            - notable
            - routine
            - commentary
          example: notable
        event_type:
          type: object
          nullable: true
          description: The kind of event (omitted on a tombstone)
          properties:
            slug:
              type: string
              example: earnings-report
            label:
              type: string
              example: Earnings Report
            category:
              type: string
              example: earnings
        article_count:
          type: integer
          description: >-
            Number of live articles reporting this event (omitted on a
            tombstone). This is the total count. Your plan's age and source
            restrictions do not reduce it; use GET on the event to see which of
            them your plan can fetch.
          example: 3
        first_reported_at:
          type: string
          format: date-time
          nullable: true
          description: >-
            When this event was first reported (ISO 8601, omitted on a
            tombstone): the earliest known publication time among this event's
            member articles. Falls back to `last_article_at` for ordering when
            null. This is editorial time, not the time the record reached this
            API.
          example: '2026-10-22T05:00:00.000Z'
        last_article_at:
          type: string
          format: date-time
          nullable: true
          description: >-
            Publication time of the newest article reporting this event (ISO
            8601, omitted on a tombstone).
          example: '2026-10-23T09:15: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. 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-10-23T09:20:00.000Z'
        company_ids:
          type: array
          items:
            type: string
          description: >-
            IDs of companies associated with this event, verified and visible
            only (omitted on a tombstone). Restricted to companies with an
            active-involvement role (subject, acquirer, target, investor,
            issuer, partner) unless `include_peripheral=true`.
          example:
            - uejazctgchj4
        story_ids:
          type: array
          items:
            type: string
          description: >-
            IDs of published stories generated from this event, if any (omitted
            on a tombstone)
          example:
            - 8fzq2rmtnk5c
        merged_into:
          type: string
          nullable: true
          description: >-
            ID of the surviving event this one was merged into. Present only
            when `status` is `merged`; null for every other status, including
            `removed` and on some older merged events.
          example: v3xdq8fmz1kn
        related_to:
          type: string
          nullable: true
          description: >-
            Commentary-only metadata: the `id` of the factual event this
            commentary event comments on or reacts to, when the piece is about a
            specific factual event. Always null for non-commentary events, and
            often null for commentary events too (the piece may not be about a
            specific event). Filter with `related_to=<id>` to find commentary
            reacting to a given event.
          example: v3xdq8fmz1kn
    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

````