This is a hand-written guide. The canonical, always-current reference is generated from the live OpenAPI spec → api-reference.html.
API/v1/Feeds/Create

Create a feed

Register an RSS or Atom URL (need one? Start from the developer feed catalog). Emit polls it on a schedule (or on a webhook ping), tracks which items it has already seen, and emails any new <item> to your confirmed subscribers from your verified domain.

POST https://api.rssemit.com/v1/feeds stable · since v1.0

Each call creates a new feed and returns 201 Created. The same URL can be registered more than once on an account.

Request curl
curl -X POST https://api.rssemit.com/v1/feeds \
  -H "Authorization: Bearer $EMIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url":        "https://blog.dev/rss.xml",
    "from_email": "posts@blog.dev",
    "schedule":   "on_publish"
  }'
201 Created · application/json 12 ms
{
  "id":             "3f8a1c2e-9b4d-4e7a-bf12-6c0d9a2e7f31",
  "url":            "https://blog.dev/rss.xml",
  "title":          null,
  "schedule":       "on_publish",
  "from_email":     "posts@blog.dev",
  "active":         true,
  "last_polled_at": null,
  "last_status":    null,
  "last_error":     null,
  "digest_hour":    9,
  "digest_weekday": 0,
  "digest_tz":      "UTC",
  "created_at":     "2026-05-25T20:30:29Z"
}

Body parameters #

Field Type Description
url required string · url An HTTP(S) URL returning RSS 2.0 or Atom 1.0.
from_email required string · email Sender address. Its domain must be verified — see POST /v1/domains. The local part can be anything you own.
schedule optional enum One of on_publish, daily, or weekly. Default: on_publish (email each new item as it appears). daily / weekly batch new items into a digest.
title optional string A display name for the feed. Defaults to the feed's own title once polled.
template_html optional string · html A raw HTML string that overrides the default email layout for this feed. Omit to use Emit's maintained default template.
digest_hour optional integer Hour of day (0–23) to send the digest. Only used for daily / weekly schedules. Default: 9.
digest_weekday optional integer Day of week (0 = Monday … 6 = Sunday) for the weekly digest. Default: 0.
digest_tz optional string · tz IANA timezone (e.g. America/New_York) that digest_hour / digest_weekday are interpreted in. Default: UTC.
i
Conditional GET is on by default. Emit stores the ETag + Last-Modified returned by your feed and replays them on every poll. If your origin responds 304, the request is free and we don't re-parse — 92% of polls land here in practice. Make sure your CDN passes these headers.

Returns #

A feed object. The id is stable and safe to store — we never recycle it, even after a delete.

Status codes

201 created
400 domain not verified
422 invalid body

Smart feeds (watchers): anything → feed #

A smart feed — a watcher in the API — is the ingest half of the pipe: sources + filter → feed. Describe an interest in plain language and emit discovers matching sources, monitors them on your cadence, scores every new item against the prompt, and serves the survivors at stable /w/{token}/atom.xml and /w/{token}/feed.json URLs. Or skip discovery and bring your own sources — feed URLs, or plain pages we watch by link diffing — with an optional filter on top.

Before creating a feed, call POST /v1/watchers/sample with {"url":"https://example.com/feed.xml"} and your bearer key. Returns kind (feed or page), resolved_url, title, and up to ten items with title, URL and published timestamp (null when unknown). Pages use same-site link extraction. Sampling writes no source or item rows, establishes no baseline and charges no credits; it uses direct fetching without paid rendering. Limited to ten calls per account per minute (429 with Retry-After); unsafe, invalid or unreachable URLs return 422. URLs are limited to 2,048 characters. MCP agents can call sample_source with the same URL argument. Existing-feed preview instead re-evaluates stored candidates.

curl -X POST https://api.rssemit.com/v1/watchers \
  -H "Authorization: Bearer $EMIT_API_KEY" \
  -d '{
    "prompt":   "security advisories affecting postgres or redis",
    "cadence":  "6h",
    "threshold":"balanced"
  }'

# filter feeds you already have (no discovery):
curl -X POST https://api.rssemit.com/v1/watchers \
  -H "Authorization: Bearer $EMIT_API_KEY" \
  -d '{
    "prompt":  "only items about breaking changes or deprecations",
    "sources": ["https://blog-a.dev/rss.xml", "https://blog-b.dev/atom.xml"],
    "discover": false
  }'

# broadcast the feed as a daily email digest:
curl -X POST https://api.rssemit.com/v1/watchers/$ID/pipe \
  -H "Authorization: Bearer $EMIT_API_KEY" \
  -d '{"from_email": "digest@yourdomain.dev", "schedule": "daily"}'

Worth knowing: a source's first check records a silent baseline (no back-catalog blasts); filtered-out items stay auditable via GET /v1/watchers/{id}/items?include_suppressed=true with a score and one-line reason each; POST …/items/{id}/feedback (more/less) tunes the threshold; syndicated duplicates collapse on canonical URL; and a summarize toggle writes an LLM summary onto each emitted item. New items also fan out to your webhook endpoints as watcher.item events and to any Slack, Discord, Teams, Telegram, Google Chat, and JSON-webhook sinks subscribed to the feed. Filtering meters the same credit balance as sending: 1 credit per 5 judged items, 1 per summary, 25 credits per explicit discovery re-run, 2 credits per JS-rendered page check (when enabled); creation, polling, and feeds out are free. Sink deliveries get 250 free per account per calendar month (UTC), then 1 credit each.

Replacing a leaked feed URL

A feed's /w/{token}/atom.xml and /w/{token}/feed.json URLs are bearer access: anyone holding one can read the feed. Rotating the token replaces both URLs and keeps the Watcher, its sources, feedback and Reader history. Every existing reader and downstream consumer of the old URLs stops working (they receive 404 immediately), and nothing is rewritten for you. That includes newsletters created with /pipe, which keep the old Atom URL until you update them. Move your consumers to the URLs the call returns, then rotate. Existing tokens never change unless you call it. MCP clients use rotate_feed_token.

curl -X POST https://api.rssemit.com/v1/watchers/$ID/rotate-feed-token \
  -H "Authorization: Bearer $EMIT_API_KEY"

Push a feed to chat or your own code #

A sink subscribes to any feed URL — a feed emit built, your blog's RSS, a feed you don't own — and pushes its new items somewhere. kind: "slack", "discord", "teams" and "google_chat" post to a channel through an incoming webhook (for Teams, the URL of a Workflows “When a Teams webhook request is received” trigger; each message is sent as an Adaptive Card); kind: "webhook" POSTs to any HTTP endpoint as JSON.

For kind: "telegram", create a bot with BotFather and add it to your group or channel with permission to post. Use https://api.telegram.org/bot<token>/sendMessage?chat_id=<id> as the destination URL. The chat can be a numeric ID (including negative group IDs) or an @channel username. Treat this URL as a secret. Messages are plain text with link previews disabled; templates and digest schedules work as for the other chat destinations. The shared limit is 1,900 characters. See the Telegram Bot API.

For kind: "google_chat", create an incoming webhook for the space under Apps & integrations → Webhooks in Google Chat and use its URL, https://chat.googleapis.com/v1/spaces/<space>/messages?key=<key>&token=<token>, as the destination. The host, path and both key and token parameters are required. Treat this URL as a secret: the key and token are redacted from stored errors and logs. Messages are plain text; templates and digest schedules work as for the other chat destinations. The shared limit is 1,900 characters. See the Google Chat incoming webhooks guide.

curl -X POST https://api.rssemit.com/v1/sinks \
  -H "Authorization: Bearer $EMIT_API_KEY" \
  -d '{
    "kind":     "webhook",
    "url":      "https://yourapp.dev/feed-events",
    "feed_url": "https://anyblog.com/rss.xml"
  }'

A webhook sink sends application/json with an X-Emit-Event header of sink.item and a body of {"event","sink_id","name","item"}, where item carries guid, title, url, summary, and published_at. Because the body is a fixed shape it takes no message template — that's the one thing the chat kinds have and it doesn't.

Set schedule to daily or weekly (with digest_hour, digest_weekday, digest_tz) and items are held and sent as a single sink.digest POST carrying an items array, instead of one request per item. The default is on_publish.

Worth knowing: connecting a sink records a silent baseline, so it never replays the feed's back-catalog; items are deduplicated by guid; any 2xx counts as delivered and anything else is retried on the next cycle rather than dropped; and POST /v1/sinks/{id}/test sends one real delivery and returns the exact body it posted, so you can build your handler against it. Each account gets 250 free sink deliveries per calendar month (UTC), then each delivery costs 1 credit; a digest counts as one delivery and test sends are free. When the allowance is used and the balance can't cover more, the sink pauses and holds items (none are dropped) until credits are added; the allowance resets on the 1st. See RSS to webhook for the full walkthrough.

Recover a failed signed account event

Account-event endpoints (/v1/webhook-endpoints) are separate from unsigned JSON sinks: they receive HMAC-signed watcher.item, subscriber.confirmed, and broadcast.sent events with a durable delivery log and six attempts over about ten hours. Inspect GET /v1/webhook-endpoints/{endpoint_id}/deliveries, then re-queue a failed row with POST /v1/webhook-endpoints/{endpoint_id}/deliveries/{delivery_id}/retry (MCP: retry_event_delivery). This returns the pending delivery with a fresh retry budget; the worker sends its original body, event identity, and signature. Pending and delivered rows return 409 without changes; unknown or foreign endpoints and deliveries return 404. Restore your receiver before retrying.

MCP server #

Emit hosts a remote MCP server at https://api.rssemit.com/mcp — there's nothing to install. Point any MCP-aware client (Claude Code, Cursor, Codex) at the URL with your API key. See the agent setup guide and complete tool reference.

~/.claude/mcp.json copy
{
  "mcpServers": {
    "emit": {
      "type": "http",
      "url":  "https://api.rssemit.com/mcp",
      "headers": { "Authorization": "Bearer emit_live_…" }
    }
  }
}
!
The server runs inside the Emit API: every tool call executes against your account using the key in your Authorization header — the same auth, validation, and rate limits as the REST API. Your subscribers' data is never proxied through a third party, and there's no package to keep updated.

Claude Code skill #

The skill is a single static file at https://rssemit.com/skill.md. Save it into ~/.claude/skills/ and Claude Code will know how to wire Emit into any blog repo — it reads your CLAUDE.md, finds your RSS feed, verifies your sending domain (DKIM/SPF/DMARC), imports subscribers, and sends the first broadcast.

terminal zsh
mkdir -p ~/.claude/skills/emit
curl -fsSL https://rssemit.com/skill.md \
  -o ~/.claude/skills/emit/SKILL.md

# Then, in any repo:
claude "add a newsletter to this blog using emit"
← previous
Authentication
getting-started
next →
List feeds
GET /v1/feeds