# Drafts from RSS and Atom feeds

Add a blog, changelog or GitHub releases feed to a workspace. The jobs cron reads each subscription hourly, after
posting and billing maintenance. **Feeds only create drafts.** They never check or request approval, choose a posting
account or time, schedule, or post. An owner or an agent with `drafts` can add/remove a subscription; `read` lists it.

## Add, list and remove

The API prefix below is `https://app.flaghand.com/api/v1/workspaces/{workspace}`. Find your workspace with `GET /api/v1/me`.

```http
POST /feeds
Authorization: Bearer tok_…
Content-Type: application/json

{"feed_url":"https://github.com/example/project/releases.atom"}
```

The response is the subscription (`fed_…`). `initialized_at: null` means it has not yet completed a first read.
`GET /feeds` returns `{ "feeds": [...] }`, including the feed's title, URL, next read, last successful read and last
error. `DELETE /feeds/{id}` removes the subscription and its seen-item records; drafts already made stay in the desk.
Adding the same workspace URL again returns the same subscription without resetting its baseline.

CLI (set `FLAGHAND_TOKEN`, or use `flaghand login --token-stdin`):

```sh
flaghand feeds create --workspace ws_… --feed-url https://github.com/example/project/releases.atom --json
flaghand feeds list --workspace ws_… --json
flaghand feeds delete --workspace ws_… --id fed_… --json
```

MCP has the same operations: `createFeed {feed_url}`, `listFeeds {}` and `deleteFeed {id}`. As with other workspace
operations, a token's own workspace is filled in. With the default compact toolset, discover them with
`getOperation {operation_id:"createFeed"}` and invoke via
`callWriteOperation {operation_id:"createFeed",arguments:{feed_url:"https://github.com/example/project/releases.atom"}}`
(`listFeeds` runs through `callReadOperation`).
The full OpenAPI reference also documents all three.

## What becomes a draft

- **First successful read:** record every current item identity; create no drafts, regardless of publication dates.
  An empty, valid feed is a successful baseline too. A broken first read is not a baseline.
- **Later reads:** one new item makes an `x_post` draft and a `linkedin_post` draft. Both contain the plain-text title,
  a blank line and the article link. The LinkedIn draft also has a card whose URL, title and description come from the
  item, not a separate scrape of the page. Card title/description fit the existing card fields; the post text itself
  is not shortened. Feed images are not automatically uploaded.
- Drafts are unassigned, with no time or approval request. Feed and article URLs appear in their source notes.
  Choose each account, edit/check the drafts and request the owner's approval normally. Long titles can still need
  editing to meet a platform's character limit.
- RSS `guid` and Atom `id` identify an item; its article URL is the fallback. A repeated identity, including one whose
  title or contents changed or which disappeared and later returned, never makes another pair or overwrites edits.
  Recording the item and both drafts is atomic. Concurrent cron reads and interrupted writes cannot duplicate them.
- RSS 2.0, Atom and GitHub `releases.atom` work, with XML entities, CDATA and relative links. RSS titles remain plain
  XML text (so `List<T>` is preserved); descriptions and explicitly HTML/XHTML Atom titles become plain text.
  Items without a usable HTTP(S) article link are skipped. XML DTDs/external entities are not supported.

Use a public HTTP(S) feed URL, without a username or password. No workspace credentials are sent to it. Redirects
are rechecked; LinkedIn pages are not scraped. Successful HTTP validators (`ETag`/`Last-Modified`) are reused.
If a read fails, `last_error` is `http_NNN`, `feed_unreadable`, or `invocation_budget` (the existing shared cron/D1
budget was exhausted); the prior baseline and successful validators remain intact and the next hourly read retries.
An error never imports an old backlog or changes existing drafts. XML is decoded according to its BOM, HTTP charset
or encoding declaration and parsed incrementally. Items are staged separately in D1, not packed into a whole-feed
JSON value, and no draft is committed until the entire XML validates.

Workspace export (JSON and the JSON inside the Markdown/media zip) includes `feeds` and `feed_items`, alongside the
drafts they created. Workspace deletion removes the subscriptions and item records too.
