---
title: "Get unread articles"
method: POST
path: "/get_unread_articles"
---

# Get unread articles

`POST /get_unread_articles`

## Request

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

Unread articles, newest first. Slots are shared across feeds so one high-volume source cannot fill the page; balance_feeds=false restores strict recency. since_hours narrows the window; feed_id scopes to one source; lean=true (limit up to 1000) fits the whole set in one call.

## Request body

- object
  - `limit` integer: Max articles to return (1–1000). Default 50; raise toward 1000 when clustering the whole unread set.
  - `since_hours` number: Only articles published in the last N hours.
  - `feed_id` integer: Limit to a specific feed.
  - `exclude_feed_ids` integer[]: Exclude articles from these feeds. Useful when one or two high-volume feeds (HN, WIRED) drown out the rest.
  - `lean` boolean: Return a slim payload — id, feed_id, feed_title, title, url, published_at only — dropping summary, image_url/width/height/mime, and added_at. ~5x smaller per row, lets a single call carry hundreds of articles for AI-side topic clustering.
  - `boost_engaged_feeds` boolean: Re-rank the recent unread pool by per-feed engagement (how often the user reads/stars that feed) instead of pure recency. Feeds the user values rise; skipped feeds sink; recency breaks ties. Use for 'what should I read first' / 'the good stuff'. Default false (strict newest-first).
  - `balance_feeds` boolean: Share the page out across feeds that published recently, so one high-volume source cannot fill it. Default true. Set false for strict newest-first, e.g. when the user explicitly wants the raw timeline or is chasing a fast-moving story.

## Response `200`

Success

- object
  - `articles` Article[], required
    - `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'
  - `balanced` object: Present only when balancing changed the result.
  - `unavailable` object: Present only when the engagement ranking could not be computed on this call (#390); the articles are then newest first.
    - `boost_engaged_feeds` string

## 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)
