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

# RSS & News

> What a keyword captures on a feed or a news term, and what arrives in each event.

This Source is a **format**, not a site. It reads whatever feed you point it at — RSS 2.0, Atom 1.0 or RDF — and it follows recent news when you give it a search term instead of a URL. You are responsible for the terms of the feeds you point us at.

## The entry

Every entry becomes an **Article**: a title, a URL, a text summary, a publication date, the publisher's domain, and the feed it came from.

**Text only.** The summary is the feed's own description or content with markup stripped and capped at 2 000 characters. There is no HTML field: Nephia reads feeds, it does not render them, and a stored HTML blob is a sanitising problem for every consumer downstream.

### Identity

An entry's `id` is, in order: its `guid`, then Atom's `id`, then its link, then a hash of its title and publication date. The fallback matters — a feed with none of the first three is a real feed, not a broken one, and a poll that cannot tell entries apart would re-emit the whole page every tick.

## Coverage

A search takes **exactly one** of `feedUrl` or `query`. A URL polls that feed; a term follows the news for it. Both at once is not a filtered feed, it is two different requests, so it is rejected with **400**.

| Criterion | Values | Default |
| - | - | - |
| `feedUrl` | an HTTPS feed URL | none |
| `lang` | news language | `en` |
| `country` | news country | `US` |

These are this Source's own criteria: on the keyword they go in the search's `overrides.filters` (see [Keywords](/keywords#create)).

On a news search, `lang` (default `en`) and `country` (default `US`) select an **edition**, not a filter: they have to agree with each other, and the API assembles the pair for you. Each result names its publisher in `source`, and its `url` can be an aggregator redirect rather than the publisher's own address.

A `feedUrl` must be **HTTPS**, and it is checked the same way a webhook URL is: `localhost`, literal private addresses, and any hostname that *resolves* to a private or link-local address are rejected with **400**. Bodies over 5 MB are refused, and the fetch times out after 15 s.

Interval: **120 to 86400** seconds. At most **20** active RSS searches per Account. Each tick costs **1 credit**, including a tick that finds nothing.

## What an event carries

The first tick establishes a silent baseline and emits nothing: a feed's front page is its whole recent history, and firing fifty webhooks about posts that predate the keyword is not what "watch this blog" means. Later ticks emit `article.created`. `webhookUrl` is optional: without it the keyword still records every match, readable through `GET /v1/keywords/{id}/events` in the same shape. See [Webhooks](/webhooks#replay).

```bash theme={null}
curl -X POST https://api.nephia.cc/v1/keywords \
  -H "x-api-key: $NEPHIA_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "name": "Rust blog",
    "refreshIntervalSeconds": 900,
    "globalCriteria": { "market": "global" },
    "sources": [
      {
        "source": "rss",
        "enabled": true,
        "searches": [
          {
            "overridesEnabled": true,
            "overrides": {
              "filters": {
                "feedUrl": "https://blog.rust-lang.org/feed.xml"
              }
            }
          }
        ]
      }
    ],
    "webhookUrl": "https://example.com/hooks/nephia"
  }'
```

Following the news for a brand instead:

```bash theme={null}
curl -X POST https://api.nephia.cc/v1/keywords \
  -H "x-api-key: $NEPHIA_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "name": "Acme in the news",
    "refreshIntervalSeconds": 900,
    "globalCriteria": { "market": "global", "terms": ["acme", "acme.com"] },
    "sources": [
      {
        "source": "rss",
        "enabled": true,
        "searches": [
          {
            "overridesEnabled": true,
            "overrides": {
              "filters": {
                "lang": "en",
                "country": "US"
              }
            }
          }
        ]
      }
    ]
  }'
```

### Conditional GET

Each search remembers the `ETag` and `Last-Modified` the feed last sent, and sends them back on the next tick. A feed that answers **304 Not Modified** costs the tick and nothing else: no parse, no diff, no events.

A 304 is not an empty feed: the search keeps the entries it already knew about. That distinction is what stops a quiet feed from looking like a feed that lost all its posts, and then re-emitting everything on the tick after.

## Finding a feed

RSS is the only Source whose criterion is a URL nobody knows by heart, so the dashboard can find one for you: paste a site's address and it reports the feeds the page declares, or import an OPML export from your reader. Both write an ordinary `feedUrl`. There is no separate "preset" or "catalog" criterion, and a search created that way is indistinguishable from one you pasted a URL into. Neither is part of this API.

### DEV.to, Medium and App Store reviews

These are **feeds, not Sources**, deliberately. Their content is articles, `article` already stores exactly what they carry, and inventing a `dev.to` Source would add an envelope key for what RSS holds today.

| What | Feed URL |
| - | - |
| DEV.to, one tag | `https://dev.to/feed/tag/{tag}` |
| DEV.to, one author | `https://dev.to/feed/{user}` |
| Medium, one tag | `https://medium.com/feed/tag/{tag}` |
| Medium, one author | `https://medium.com/feed/@{user}` |
| Medium, one publication | `https://medium.com/feed/{publication}` |
| App Store reviews | `https://itunes.apple.com/{country}/rss/customerreviews/page=1/id={appId}/sortby=mostrecent/xml` |

The App Store URL is the one worth reading closely: `sortby=mostrecent` is not optional. Apple's default order is "most helpful", which on a review feed means a search would re-read the same page forever and never see a new review. Each entry arrives as an `article`: the review's title, the reviewer in `author`, and the star rating among `categories`.

## Errors

A feed that cannot be reached, or that answers with something that is not a feed, is a **503** and is not charged; the keyword reports it in its activity, readable through `GET /v1/keywords/{id}/activity`. A `feedUrl` that is malformed, not HTTPS, or pointed at a private address is rejected with **400** at create time and never fetched.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.