---
title: "Get new articles"
method: POST
path: "/get_new_articles"
---

# Get new articles

`POST /get_new_articles`

## Request

- Base URL: `https://newsmind.app/api/v1`
- URL: `https://newsmind.app/api/v1/get_new_articles`
- Auth: OAuth 2 (authorization code, authorization URL `https://newsmind.app/oauth/authorize`, token URL `https://newsmind.app/oauth/token`, scopes: `mcp`)

Articles since this token's last visit, oldest-first; advances a per-token cursor. has_more=true means more remain. mark_seen=false peeks; exclude_feed_ids skips noisy feeds. `feed_warnings` and `watch_summaries`, when present, report failing feeds and saved-watch matches.

## Request body

- object
  - `max_items` integer: Max articles per batch (1-100, default 50). Values above 100 are silently clamped. Cursor only advances over what's returned, so older unseen articles aren't silently skipped. If the result hits this cap, `has_more` is true and you should call again to fetch the next batch.
  - `mark_seen` boolean: If false, peek without advancing the cursor (subsequent default-mode call commits).
  - `exclude_feed_ids` integer[]: Exclude articles from these feeds. Useful when a high-volume feed (HN, WIRED) is overwhelming the queue.

## Response `200`

Success

- object
  - `articles` Article[]
    - `id` integer
    - `feed_id` integer: ID of the feed this article belongs to. Useful for chaining into list_feeds or unsubscribe.
    - `feed_title` string, nullable
    - `title` string, nullable
    - `url` string, nullable
    - `image_url` string, uri, nullable: Thumbnail URL for the article, when available. HTTPS-only; tracking pixels filtered out. Sourced from <enclosure>, <media:thumbnail>, <media:content>, or first <img> in content. Forward-only — articles ingested before 0.3.0 have null.
    - `image_width` integer, nullable: Pixel width of image_url, when the source feed exposed it (MRSS attrs or HTML width=). Lets clients lay out before fetching the bytes. Null when unknown.
    - `image_height` integer, nullable: Pixel height of image_url, when known. Null otherwise.
    - `image_mime` string, nullable: MIME type of image_url (image/jpeg, image/png, image/webp, image/gif, image/avif, image/svg+xml). Sourced from <enclosure type=>, <media:content type=>, or inferred from URL extension. Null when neither available.
    - `summary` string, nullable
    - `published_at` string, date-time, nullable: When the source published the article (from the RSS feed).
    - `added_at` string, date-time, nullable: When Newsmind ingested the article into the user's feed. Use this when the user asks about 'when did this show up for me' or 'what's new since I last checked'; use published_at for 'how old is the story'.
    - `matched_watches` object[]: Saved watches this article matched. Present on unread/new-article responses when the user has active watches.
      - `watch_id` integer
      - `label` string, nullable
    - `likely_archival` object: Present when the title states a date a year or more before published_at — an aggregator re-indexed an old page under today's date. Don't present these as new.
      - `title_year` integer
      - `matched_text` string
      - `pattern` 'slash' | 'written' | 'paren'
    - `also_in` object[]: Other feeds that carried this same story. The duplicate was collapsed to one match (issue #59).
      - `feed_id` integer
      - `feed_title` string, nullable
    - `user_excluded` object: This row matched one of the watch's exclude_terms. Demoted, never dropped, and the term is named so an over-broad guess can be corrected (issue #23).
      - `term` string
    - `low_signal` object: Present when a Google News item's source is an encyclopedia, film database, statistics page or obituary listing rather than reporting. `rule: "obituary"` comes from a headline rule and may name a legitimate newsroom — describe the item, not the outlet.
      - `source` string
      - `rule` 'reference' | 'entertainment_db' | 'sports_stats' | 'obituary' | 'archive_page' | 'scraped_video'
  - `count` integer
  - `has_more` boolean
  - `cursor_advanced` boolean
  - `feed_warnings` FeedWarnings: Failing feeds — 3+ consecutive fetch failures AND nothing delivered in 48h. The delivery clause keeps a host that merely flaps out of this list. Returned on get_account_info and get_new_articles, only when there is something to report. failing_samples is capped at 5; get_feed_health has the full picture.
    - `failing_count` integer, required: Total subscribed feeds that have failed 3+ fetches in a row and returned no items for 48 hours. They are still retried (#368).
    - `note` string: Plain-language statement of what the count means: these feeds keep failing and have returned no items for 48 hours, and they are still retried. v0.55.0; #368.
    - `failing_hosts` object[]: Every host failing 2+ of the user's feeds, biggest group first. Turns a count into a shape: 42 feeds on one host that answers 451 is one breakage, not 42. Absent when no host is failing more than one feed. v1.259.0 (#327).
      - `host` string, required: Hostname, spelled as the feeds' last_error text spells it.
      - `feed_count` integer, required: How many of this user's failing feeds are on this host.
      - `shared_error` string: The error text every failing feed on this host reports. DESCRIPTIVE, not causal — it does not claim one upstream cause, which matters on shared feed hosts where each feed has a different owner. Absent when the feeds report different errors.
    - `failing_samples` object[], required: Up to 5 sample failing feeds, round-robined across hosts with the biggest group first — so the samples span causes rather than repeating one dead host. v1.259.0 (#327); previously the 5 highest error_counts.
      - `feed_id` integer, required
      - `title` string, nullable
      - `url` string, required: The address that stopped working — so a client can search for the publication's current feed without a second get_feed call. v1.165.0 (#199).
      - `diagnosis` string: Facts about the publisher, established once for this feed and identical for every subscriber: whether the site still answers and whether it advertises a feed elsewhere. States what is true, never what to subscribe to. Absent until diagnosed. v1.166.0 (#199).
      - `last_error` string, nullable: Most recent error text from the poller (HTTP status, parser error, etc.).
  - `import_failures` ImportFailures: Import-failure notice — feeds the user asked to import that failed in the last window_days days. Returned on the discovery tools and get_account_info only when non-zero. Two states: `import_failures` (permanent, relay them and offer next steps) and `import_retrying` (a retry is still scheduled, nothing to do — do not raise it unprompted). Call get_import_status for failed URLs and reasons. v0.33.0, retry state documented v1.150.0 (#173).
    - `state` 'import_failures' | 'import_retrying', required: `import_retrying` means every recent failure still has a retry scheduled — nothing is lost and there is nothing for the user to do. Emitted since v0.54.0 and undocumented until v1.150.0 (#173).
    - `failed_imports` integer, required: Permanently-failed imports inside the window. Zero on an `import_retrying` notice.
    - `retrying_imports` integer: Failures with a retry still scheduled. Omitted when zero. v1.150.0 (#173).
    - `window_days` integer, required: Recency window in days (currently 7).
    - `message` string, required: Assistant-directed instruction: relay the failures and offer next steps.
  - `import_recovered` ImportRecovered: Import-recovery notice — feeds the user asked to import that failed, were re-validated after a discovery fix shipped (#161), and have now been added automatically. Returned alongside the other notices only when non-zero. Worth mentioning ONCE: the user was previously told these feeds could not be added, so a feed appearing on its own is otherwise confusing. Nothing is required from them. v1.151.0 (#162).
    - `state` 'import_recovered', required
    - `recovered_imports` integer, required: Distinct URLs recovered inside the window.
    - `urls` string[], required: Up to 10 of them, so the assistant can name names. May be fewer than recovered_imports.
    - `window_days` integer, required: Recency window in days (currently 7).
    - `message` string, required: Assistant-directed instruction.
  - `watch_summaries` WatchSummary[], nullable: Present only when the user has saved watches. Per-watch new_match_count tells the assistant which watches to mention. Call get_watch_matches for detail. v0.21.0.
    - `watch_id` integer, required
    - `label` string, nullable
    - `query` string, required
    - `new_match_count` integer, required

## Other responses

- `400`: Bad request
- `401`: Unauthorized
- `402`: Subscription required (trial expired or cancelled)
- `429`: Too many calls. Either the per-account rate limit ({error: "rate_limited", message}) or the account's in-flight work ({error: <sentence>, code: "busy"}). Wait Retry-After seconds.
- `500`: Server error on our side (error: internal_error). The team has been notified.
- `503`: Temporarily unable to answer. error: timeout (the request ran too long; narrow it), server_busy or tier_unavailable (retry after Retry-After seconds when sent).

---

[API](https://skmtc.dev/newsmind/apis/newsmind-api.md) · [All operations](https://skmtc.dev/newsmind/apis/newsmind-api/llms.txt) · [OpenAPI document](https://skmtc.dev/newsmind/apis/newsmind-api/revisions/f8af23fb5a10?raw)
