API Reference
Complete API reference for Helix News Feeds. All endpoints use JSON for request and response bodies.
An interactive OpenAPI playground is available at api.feeds.onhelix.ai/docs. The spec is generated from the live route validators, so it cannot drift from the running implementation — useful for testing requests, inspecting response shapes, and generating client SDKs.
Base URL
https://api.feeds.onhelix.ai
Authentication
All requests require an API key in the Authorization header:
Authorization: Bearer YOUR_API_KEY
See the Authentication Guide for details.
News Feed Management
Create a Feed
Creates a new news feed to organize articles from multiple sources.
POST /feeds/news
Request
Headers:
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
Body:
{
"name": "Technology News",
"description": "Latest technology and startup news"
}
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Feed name. Minimum 1 character. |
description | string | Yes | Feed description. Minimum 1 character. |
config | object | No | version (1) and media.retentionDaysAfterAdded (see below). Other keys, including contentModeration, are refused with 400. |
config.media.retentionDaysAfterAdded is the number of days after an item enters the feed that its images stay available. A whole number from 1 to 365. The default is 30. Once you set it, you can change it but not remove it: to go back to the default, set it to 30.
A new feed starts disabled and with no filters. Add its websites (Sources) and its filters (Filters), then enable it. Enabling it brings in, from each of its enabled sources, the articles the source brought in during the past 14 days that pass its filters (once per source).
Response
Status: 200 OK
{
"success": true,
"data": {
"id": "7c1e5a90-3b2d-4f68-9a14-6d8e0b2c4f57",
"name": "Technology News",
"description": "Latest technology and startup news",
"organizationId": "660e8400-e29b-41d4-a716-446655440001",
"enabled": false,
"enabledSource": "system",
"enabledReason": null,
"config": { "version": 1 },
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-15T10:30:00.000Z"
}
}
Response object:
| Field | Type | Description |
|---|---|---|
id | string | Unique feed identifier. Use this when adding sources or retrieving items. |
name | string | Feed name |
description | string | Feed description |
organizationId | string | The organization that owns the feed |
enabled | boolean | Whether the feed is on. A new feed starts disabled: it decides no articles until you enable it |
enabledSource | string | How enabled was last set: system (the default), override (a change through this API or by our team), cascade (turned off with what it depends on) or error (turned off after failures). You cannot set it |
enabledReason | string or null | The reason recorded when enabled was last set, if any. You cannot set it |
config | object | The stored feed config: version and media.retentionDaysAfterAdded. |
createdAt | string (ISO 8601) | When the feed was created |
updatedAt | string (ISO 8601) | Last time the feed was modified |
config echoes the stored config. Keys that were never set are omitted. When media.retentionDaysAfterAdded is absent, the default of 30 days applies. Create, get, list and update return the same feed object.
Errors
400:INVALID_BODY: a missing or emptynameordescription, or aconfigkey other thanversionandmedia(includingcontentModeration), ormedia.retentionDaysAfterAddedoutside 1 to 365.
Example
curl -X POST https://api.feeds.onhelix.ai/feeds/news \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Technology News",
"description": "Latest technology and startup news"
}'
List Feeds
Retrieves the news feeds your organization owns and those it subscribes to.
GET /feeds/news
Request
Headers:
Authorization: Bearer YOUR_API_KEY
Response
Status: 200 OK
{
"success": true,
"data": [
{
"id": "7c1e5a90-3b2d-4f68-9a14-6d8e0b2c4f57",
"name": "Technology News",
"description": "Latest technology and startup news",
"organizationId": "660e8400-e29b-41d4-a716-446655440001",
"enabled": true,
"enabledSource": "override",
"enabledReason": "Enabled through the API",
"config": { "version": 1, "media": { "retentionDaysAfterAdded": 30 } },
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-15T10:30:00.000Z"
},
{
"id": "a2d49f10-8c6e-4b37-b5e1-0f93c7d1e842",
"name": "Business News",
"description": "Business and finance updates",
"organizationId": "660e8400-e29b-41d4-a716-446655440001",
"enabled": false,
"enabledSource": "system",
"enabledReason": null,
"config": { "version": 1, "media": { "retentionDaysAfterAdded": 30 } },
"createdAt": "2024-01-14T15:20:00.000Z",
"updatedAt": "2024-01-14T15:20:00.000Z"
}
]
}
Returns an array of feed objects sorted by creation date (newest first).
Example
curl https://api.feeds.onhelix.ai/feeds/news \
-H "Authorization: Bearer YOUR_API_KEY"
Get a Feed
Retrieves details for a specific feed.
GET /feeds/news/{feedId}
Request
Path parameters:
| Name | Type | Description |
|---|---|---|
feedId | string | Feed identifier returned when creating the feed |
Headers:
Authorization: Bearer YOUR_API_KEY
Response
Status: 200 OK
{
"success": true,
"data": {
"id": "7c1e5a90-3b2d-4f68-9a14-6d8e0b2c4f57",
"name": "Technology News",
"description": "Latest technology and startup news",
"organizationId": "660e8400-e29b-41d4-a716-446655440001",
"enabled": true,
"enabledSource": "override",
"enabledReason": "Enabled through the API",
"config": { "version": 1, "media": { "retentionDaysAfterAdded": 30 } },
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-15T10:30:00.000Z"
}
}
Example
curl https://api.feeds.onhelix.ai/feeds/news/7c1e5a90-3b2d-4f68-9a14-6d8e0b2c4f57 \
-H "Authorization: Bearer YOUR_API_KEY"
Subscribers to the feed can read it too.
Errors
400:INVALID_PARAMS:feedIdis not a UUID.404: Feed not found, or your organization neither owns it nor subscribes to it.
Update a Feed
Updates a feed's name, description, or enabled status.
PATCH /feeds/news/{feedId}
Request
Path parameters:
| Name | Type | Description |
|---|---|---|
feedId | string | Feed identifier |
Headers:
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
Body (all fields optional):
{
"name": "Tech & Startup News",
"description": "Updated description",
"enabled": true
}
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
name | string | No | Updated feed name |
description | string | No | Updated feed description |
enabled | boolean | No | Enable or disable the feed. A disabled feed keeps its items but takes no new articles in. Enabling needs at least one include filter on the feed. Enabling a feed adds, once per source, the articles each enabled source brought in during the past 14 days that pass its filters: a source already brought in is not brought in again when you enable the feed later, and a source added while the feed was disabled is brought in when you enable it. |
config | object | No | version (1) and media.retentionDaysAfterAdded (days, 1 to 365, default 30: how long an item's images stay available after it enters the feed). Keys you leave out keep their value. Other keys, including contentModeration, are refused with 400. |
Response
Status: 200 OK
{
"success": true,
"data": {
"id": "7c1e5a90-3b2d-4f68-9a14-6d8e0b2c4f57",
"name": "Tech & Startup News",
"description": "Updated description",
"organizationId": "660e8400-e29b-41d4-a716-446655440001",
"enabled": true,
"enabledSource": "override",
"enabledReason": "Enabled through the API",
"config": { "version": 1, "media": { "retentionDaysAfterAdded": 30 } },
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-15T14:25:00.000Z"
}
}
The updatedAt field reflects the modification time. A request whose
enabled changes the feed's status records enabledSource: "override" with
enabledReason "Enabled through the API" or "Disabled through the API";
one that leaves the status as it is leaves both as they are.
Which articles a feed carries is set by its filters, not by
config. A contentModeration key in config is refused with 400: the
safety floor applies to every news feed and cannot be switched off. See
Which Articles a Feed Carries.
Only the feed's owner can change it. A refused update changes nothing.
Errors
400:INVALID_BODY: invalid fields, aconfigkey other thanversionandmedia(includingcontentModeration), orconfig.media.retentionDaysAfterAddedoutside 1 to 365.403:PERMISSION_DENIED: your organization subscribes to the feed but does not own it.404: Feed not found, or your organization neither owns it nor subscribes to it.422:VALIDATION_FAILED:enabled: trueon a disabled feed that has no include filter. Add an include filter to the feed first.
Example
curl -X PATCH https://api.feeds.onhelix.ai/feeds/news/7c1e5a90-3b2d-4f68-9a14-6d8e0b2c4f57 \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Tech & Startup News",
"enabled": true
}'
Delete a Feed
Permanently deletes a feed with its sources, filters, items and the feedback on them. The articles themselves stay: other feeds that carry them keep them. Only the feed's owner can delete it.
Deleting a feed also removes it from every Event Delivery subscription's
pins. A feeds-scoped subscription left with zero pins becomes inactive
with inactiveReason: "feed deleted". Subscribers receive
news.item.removed for each article they were sent news.item.added for,
about 10 to 30 minutes after the deletion. See
Event Delivery: Feed deletion.
DELETE /feeds/news/{feedId}
Request
Path parameters:
| Name | Type | Description |
|---|---|---|
feedId | string | Feed identifier |
Headers:
Authorization: Bearer YOUR_API_KEY
Response
Status: 200 OK
{
"success": true
}
Warning: This action is permanent and cannot be undone.
Errors
403:PERMISSION_DENIED: your organization subscribes to the feed but does not own it.404: Feed not found, or your organization neither owns it nor subscribes to it.
Example
curl -X DELETE https://api.feeds.onhelix.ai/feeds/news/7c1e5a90-3b2d-4f68-9a14-6d8e0b2c4f57 \
-H "Authorization: Bearer YOUR_API_KEY"
Get Feed Items
Retrieves the items of a feed. Every item has the same shape, whether the article came from a website, Instagram, TikTok, Twitter or Facebook.
GET /feeds/news/{feedId}/items
Request
Path parameters:
| Name | Type | Description |
|---|---|---|
feedId | string | Feed identifier |
Query parameters:
All filters combine with AND.
| Name | Type | Default | Description |
|---|---|---|---|
limit | integer | 50 | Number of items to return. Range: 1-100. |
offset | integer | 0 | Number of items to skip. Use for pagination. |
addedSince | string (ISO 8601) | - | Only items added to the feed at or after this time. |
addedUntil | string (ISO 8601) | - | Only items added to the feed at or before this time. |
updatedSince | string (ISO 8601) | - | Only items whose updatedAt is at or after this time. Use it to poll for changes. |
publishedSince | string (ISO 8601) | - | Only items published at or after this time. Items without publishedAt never match. |
publishedUntil | string (ISO 8601) | - | Only items published at or before this time. Items without publishedAt never match. |
hasCover | true | - | Only items that have a cover image right now. The only accepted value is true. An item whose cover cannot be signed at request time still matches and is returned with cover: null. |
orderBy | string | added | added: newest added to the feed first. published: newest published first; items without publishedAt come last, newest added first among themselves. |
The five time filters take an ISO 8601 date-time with Z or a UTC offset, for example 2026-10-20T00:00:00Z or 2026-10-20T09:00:00-04:00. A date alone (2026-10-20) is a 400. include, since and until are not parameters of this endpoint and are refused with 400 (use addedSince and addedUntil).
Headers:
Authorization: Bearer YOUR_API_KEY
Response
Status: 200 OK
{
"success": true,
"data": {
"items": [
{
"id": "3f6c1d52-7a0e-4b8e-9d1c-5a2b8e4f6a10",
"newsPageId": "9b2e7c44-1d3a-4f65-8a7b-0c5d2e9f1b33",
"addedToFeedAt": "2026-10-20T14:03:00.000Z",
"updatedAt": "2026-10-20T14:03:00.000Z",
"title": "Sterling Heights approves library expansion",
"description": "The city council voted 6-1 on Monday to fund a 12,000-square-foot addition to the Sterling Heights Public Library.",
"url": "https://www.macombdaily.com/2026/10/19/sterling-heights-approves-library-expansion/",
"publishedAt": "2026-10-20T09:15:00.000Z",
"publisher": {
"platform": "web",
"name": "Macomb Daily",
"domain": "macombdaily.com"
},
"author": "Jane Smith",
"original": {
"publisher": {
"platform": "web",
"name": "Detroit Free Press",
"domain": "freep.com"
},
"url": "https://www.freep.com/story/news/local/michigan/macomb/2026/10/19/sterling-heights-library-expansion/1234567890/",
"publishedAt": "2026-10-19T11:30:00.000Z"
},
"about": {
"places": [
{
"name": "Sterling Heights",
"level": "place",
"displayText": "Sterling Heights, MI",
"geo": {
"lat": 42.57,
"lng": -83.025,
"bbox": [-83.08, 42.52, -82.97, 42.62]
},
"city": "Sterling Heights",
"district": "Macomb County",
"region": "Michigan",
"country": "United States"
},
{
"name": "Macomb County",
"level": "district",
"displayText": "Macomb County, MI",
"geo": {
"lat": 42.65,
"lng": -82.9,
"bbox": [-83.1, 42.5, -82.7, 42.8]
},
"district": "Macomb County",
"region": "Michigan",
"country": "United States"
}
]
},
"media": {
"cover": {
"url": "https://example.r2.cloudflarestorage.com/media/2026-10/library-expansion.jpg?X-Amz-Signature=...",
"expiresAt": "2026-10-28T12:00:00.000Z",
"usage": "display_with_attribution",
"kind": "article_image",
"source": {
"imageUrl": "https://www.macombdaily.com/wp-content/uploads/2026/10/library-expansion.jpg",
"pageUrl": "https://www.macombdaily.com/2026/10/19/sterling-heights-approves-library-expansion/",
"publisher": {
"platform": "web",
"name": "Macomb Daily",
"domain": "macombdaily.com"
},
"credit": "Jane Smith / The Macomb Daily"
},
"width": 1200,
"height": 800
},
"images": [
{
"url": "https://example.r2.cloudflarestorage.com/media/2026-10/library-expansion.jpg?X-Amz-Signature=...",
"expiresAt": "2026-10-28T12:00:00.000Z",
"usage": "display_with_attribution",
"kind": "article_image",
"source": {
"imageUrl": "https://www.macombdaily.com/wp-content/uploads/2026/10/library-expansion.jpg",
"pageUrl": "https://www.macombdaily.com/2026/10/19/sterling-heights-approves-library-expansion/",
"publisher": {
"platform": "web",
"name": "Macomb Daily",
"domain": "macombdaily.com"
},
"credit": "Jane Smith / The Macomb Daily"
},
"width": 1200,
"height": 800
}
]
}
}
],
"total": 1,
"pagination": {
"limit": 50,
"offset": 0,
"hasMore": false
}
}
}
Pagination object:
| Field | Type | Description |
|---|---|---|
limit | integer | Items per page (as requested) |
offset | integer | Current offset (as requested) |
hasMore | boolean | Whether more items are available. If true, increment offset by limit to get the next page. |
total is the number of items that match your filters across all pages.
An item that leaves the feed (for example, it no longer matches the feed's filters) disappears from the list. The list does not say so. Only the news.item.removed webhook does: see News feed changes.
Subscribers to the feed can read its items too.
Errors
-
400:INVALID_QUERY: a parameter out of range or malformed (limitoutside 1-100, a date-only or zone-less time filter,hasCoverother thantrue, an unknownorderBy), orinclude,sinceoruntil. The message names the parameter, and forsinceanduntilthe one to use instead:{"success": false,"error": {"code": "INVALID_QUERY","message": "Invalid query parameter 'since': `since` is not supported on news feed items; use `addedSince`"}} -
400:INVALID_PARAMS:feedIdis not a UUID. -
401: Missing or invalid API key. -
404: Feed not found, or your organization neither owns it nor subscribes to it.
The item
Optional fields are omitted when unknown, never sent as null. The exception is media.cover, which is null when there is no image or it cannot be served right now. Arrays are always present.
{
"id": "3f6c1d52-7a0e-4b8e-9d1c-5a2b8e4f6a10",
"newsPageId": "9b2e7c44-1d3a-4f65-8a7b-0c5d2e9f1b33",
"addedToFeedAt": "2026-10-20T14:03:00.000Z",
"updatedAt": "2026-10-20T14:03:00.000Z",
"title": "Sterling Heights approves library expansion",
"description": "The city council voted 6-1 on Monday to fund a 12,000-square-foot addition to the Sterling Heights Public Library.",
"url": "https://www.macombdaily.com/2026/10/19/sterling-heights-approves-library-expansion/",
"publishedAt": "2026-10-20T09:15:00.000Z",
"publisher": {
"platform": "web",
"name": "Macomb Daily",
"domain": "macombdaily.com"
},
"author": "Jane Smith",
"original": {
"publisher": {
"platform": "web",
"name": "Detroit Free Press",
"domain": "freep.com"
},
"url": "https://www.freep.com/story/news/local/michigan/macomb/2026/10/19/sterling-heights-library-expansion/1234567890/",
"publishedAt": "2026-10-19T11:30:00.000Z"
},
"about": {
"places": [
{
"name": "Sterling Heights",
"level": "place",
"displayText": "Sterling Heights, MI",
"geo": {
"lat": 42.57,
"lng": -83.025,
"bbox": [-83.08, 42.52, -82.97, 42.62]
},
"city": "Sterling Heights",
"district": "Macomb County",
"region": "Michigan",
"country": "United States"
},
{
"name": "Macomb County",
"level": "district",
"displayText": "Macomb County, MI",
"geo": {
"lat": 42.65,
"lng": -82.9,
"bbox": [-83.1, 42.5, -82.7, 42.8]
},
"district": "Macomb County",
"region": "Michigan",
"country": "United States"
}
]
},
"media": {
"cover": {
"url": "https://example.r2.cloudflarestorage.com/media/2026-10/library-expansion.jpg?X-Amz-Signature=...",
"expiresAt": "2026-10-28T12:00:00.000Z",
"usage": "display_with_attribution",
"kind": "article_image",
"source": {
"imageUrl": "https://www.macombdaily.com/wp-content/uploads/2026/10/library-expansion.jpg",
"pageUrl": "https://www.macombdaily.com/2026/10/19/sterling-heights-approves-library-expansion/",
"publisher": {
"platform": "web",
"name": "Macomb Daily",
"domain": "macombdaily.com"
},
"credit": "Jane Smith / The Macomb Daily"
},
"width": 1200,
"height": 800
},
"images": [
{
"url": "https://example.r2.cloudflarestorage.com/media/2026-10/library-expansion.jpg?X-Amz-Signature=...",
"expiresAt": "2026-10-28T12:00:00.000Z",
"usage": "display_with_attribution",
"kind": "article_image",
"source": {
"imageUrl": "https://www.macombdaily.com/wp-content/uploads/2026/10/library-expansion.jpg",
"pageUrl": "https://www.macombdaily.com/2026/10/19/sterling-heights-approves-library-expansion/",
"publisher": {
"platform": "web",
"name": "Macomb Daily",
"domain": "macombdaily.com"
},
"credit": "Jane Smith / The Macomb Daily"
},
"width": 1200,
"height": 800
}
]
}
}
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | Feed item id. Use it for the single-item endpoint, the feedback endpoint and de-duplication. |
newsPageId | string | yes | The article or post. The same value in every feed that carries it. |
addedToFeedAt | string (ISO 8601) | yes | When the item entered this feed. |
updatedAt | string (ISO 8601) | yes | When the item last changed in a way that is announced by a webhook. See updatedAt. |
title | string | no | Headline. Social posts usually have none. |
description | string | no | The article description, or the post text. |
url | string | no | The article or the post. An http(s) URL. |
publishedAt | string (ISO 8601) | no | When it was published. Omitted when unknown, or when the stated time has no timezone offset. |
publisher | object | yes | Who published the item. See Whose content it is. |
author | string | no | The byline as the article states it. "Staff" and similar are kept. Websites only. |
original | object | no | Who first published the item. Only for a syndicated article or a retweet. |
about | object | yes | { places[] }: the places the item is about. places is empty when there are none. |
media | object | yes | { cover, images[] }. See Images. |
Strip-null semantics: check for key presence ('title' in item), not for null.
Whose content it is
publisher is the source the feed crawls. It has no role.
| Field | Type | Description |
|---|---|---|
platform | string | web, instagram, facebook, tiktok or twitter. |
name | string | For a website: the site name from the page (og:site_name, unless it is a URL or just the site's domain), else the publisher name extracted from the page. For a social account: its display name. Omitted when unknown. |
domain | string | Websites only: the site's domain without www.. |
handle | string | Social accounts only: the account's username. |
author is the byline as written. It is not matched to a person.
original is present only when the item republishes someone else's content:
- A syndicated article:
original.publisheris the publisher the page names (name, anddomainwhen known, withplatform: "web").original.urlandoriginal.publishedAtare present when the page states them. - A retweet:
original.publisheris{ handle, platform: "twitter" }for the account that was retweeted.
Where it is about
about.places lists the places the item is about, most central first. There is no score. Each place is:
| Field | Type | Description |
|---|---|---|
name | string | The place name. |
level | string | The narrowest level the place names: country, region, district, place, locality or neighborhood. |
displayText | string | The name, plus its region when the place is below region level (a US state as its two-letter code), plus its country when the place is below country level and not in the United States. |
city | string | The city the place is in. Omitted when the place has none. |
district | string | The district or county. Omitted when the place has none. |
region | string | The state or region. Omitted when the place has none. |
country | string | The country. Omitted when the place has none. |
geo | object | { lat, lng, bbox }. bbox is the place's bounding box [west, south, east, north]. lat and lng are the centre of that box. |
Examples of displayText: Sterling Heights, MI, Macomb County, MI, Michigan, Tocumwal, New South Wales, Australia, Australia.
Names come from the geocoder's place hierarchy. A place the geocoder has not described yet is left out. When we fill in a place's hierarchy, it appears on the item at once, and the next time we process the item a news.item.updated announces it as about, even when nothing else changed.
Images
| Field | Type | Description |
|---|---|---|
cover | object|null | The main image, or null when there is none or it cannot be served right now. |
images | object[] | Every image the item shows, cover first, in rank order. At most 6. Empty when none. |
A website article has at most one image (its main image). A social post has up to 6: the slides of a carousel, or the cover of a reel, a TikTok or a video post. A tweet has none.
cover can be null while images has entries: the main image cannot be served right now (its link could not be made). Show no item image then; do not promote another image to cover. Read the cover null-safely (media.cover?.url).
Each image has:
urlandexpiresAt: a copy of the image that we host. The link is signed and temporary. Do not store the URL. If you want to keep the image, copy the file beforeexpiresAt. Fetch the item again for a current link.usage: alwaysdisplay_with_attribution. Show the image only with itssourcebeside it, includingcreditwhen present.kind:article_image(a website's main image),social_slide(a social image or one slide of a carousel) orvideo_thumbnail(the cover of a reel, a TikTok or a video post).width,height: pixels. Omitted when unknown.source: where it came from.imageUrlis the original address, for provenance only: do not display it.pageUrlis the page the image came from.publisheris the item's publisher.creditis the photo credit as the article states it.pageUrl,publisherandcreditare omitted when unknown.
An image is served only while it is inside the feed's image window: 30 days after the item entered the feed by default. A feed can change this with config.media.retentionDaysAfterAdded (1 to 365). When the window passes, the item shows no images (media is { "cover": null, "images": [] }) and no webhook is sent. Images smaller than 200 px on the short edge are not used. Every image is checked by our moderation first, and an image that fails is not served.
updatedAt
updatedAt moves only when we owe your webhooks an added or updated for the item. A re-signed image link, or an image leaving the window, does not move it. To poll for changes, ask for updatedSince with the time of your last poll:
curl "https://api.feeds.onhelix.ai/feeds/news/7c1e5a90-3b2d-4f68-9a14-6d8e0b2c4f57/items?updatedSince=2026-10-27T00:00:00Z" \
-H "Authorization: Bearer YOUR_API_KEY"
Poll from a few seconds before your previous poll and de-duplicate by id and updatedAt. Polling does not report items that left the feed: use the news.item.removed webhook for that.
Examples
# Newest added first (default)
curl "https://api.feeds.onhelix.ai/feeds/news/7c1e5a90-3b2d-4f68-9a14-6d8e0b2c4f57/items?limit=20" \
-H "Authorization: Bearer YOUR_API_KEY"
# Items with an image, newest published first
curl "https://api.feeds.onhelix.ai/feeds/news/7c1e5a90-3b2d-4f68-9a14-6d8e0b2c4f57/items?hasCover=true&orderBy=published" \
-H "Authorization: Bearer YOUR_API_KEY"
# Items published in a date range
curl "https://api.feeds.onhelix.ai/feeds/news/7c1e5a90-3b2d-4f68-9a14-6d8e0b2c4f57/items?publishedSince=2026-10-01T00:00:00Z&publishedUntil=2026-10-08T00:00:00Z" \
-H "Authorization: Bearer YOUR_API_KEY"
Get a Single Feed Item
Returns one item from a news feed by its itemId, in the same shape as each element of Get Feed Items. Use it when you have an item id (from the list, a webhook or a saved reference) and want the latest version of that item.
GET /feeds/news/{feedId}/items/{itemId}
Request
Path parameters:
| Name | Type | Description |
|---|---|---|
feedId | string (UUID) | Feed identifier |
itemId | string (UUID) | Feed item identifier (from the id field) |
There are no query parameters. include is refused with 400 and code INVALID_QUERY.
Headers:
Authorization: Bearer YOUR_API_KEY
Response
Status: 200 OK
The body wraps one item. The fields are the ones in The item.
{
"success": true,
"data": {
"id": "3f6c1d52-7a0e-4b8e-9d1c-5a2b8e4f6a10",
"newsPageId": "9b2e7c44-1d3a-4f65-8a7b-0c5d2e9f1b33",
"addedToFeedAt": "2026-10-20T14:03:00.000Z",
"updatedAt": "2026-10-20T14:03:00.000Z",
"title": "Sterling Heights approves library expansion",
"description": "The city council voted 6-1 on Monday to fund a 12,000-square-foot addition to the Sterling Heights Public Library.",
"url": "https://www.macombdaily.com/2026/10/19/sterling-heights-approves-library-expansion/",
"publishedAt": "2026-10-20T09:15:00.000Z",
"publisher": {
"platform": "web",
"name": "Macomb Daily",
"domain": "macombdaily.com"
},
"author": "Jane Smith",
"original": {
"publisher": {
"platform": "web",
"name": "Detroit Free Press",
"domain": "freep.com"
},
"url": "https://www.freep.com/story/news/local/michigan/macomb/2026/10/19/sterling-heights-library-expansion/1234567890/",
"publishedAt": "2026-10-19T11:30:00.000Z"
},
"about": {
"places": [
{
"name": "Sterling Heights",
"level": "place",
"displayText": "Sterling Heights, MI",
"geo": {
"lat": 42.57,
"lng": -83.025,
"bbox": [-83.08, 42.52, -82.97, 42.62]
},
"city": "Sterling Heights",
"district": "Macomb County",
"region": "Michigan",
"country": "United States"
},
{
"name": "Macomb County",
"level": "district",
"displayText": "Macomb County, MI",
"geo": {
"lat": 42.65,
"lng": -82.9,
"bbox": [-83.1, 42.5, -82.7, 42.8]
},
"district": "Macomb County",
"region": "Michigan",
"country": "United States"
}
]
},
"media": {
"cover": {
"url": "https://example.r2.cloudflarestorage.com/media/2026-10/library-expansion.jpg?X-Amz-Signature=...",
"expiresAt": "2026-10-28T12:00:00.000Z",
"usage": "display_with_attribution",
"kind": "article_image",
"source": {
"imageUrl": "https://www.macombdaily.com/wp-content/uploads/2026/10/library-expansion.jpg",
"pageUrl": "https://www.macombdaily.com/2026/10/19/sterling-heights-approves-library-expansion/",
"publisher": {
"platform": "web",
"name": "Macomb Daily",
"domain": "macombdaily.com"
},
"credit": "Jane Smith / The Macomb Daily"
},
"width": 1200,
"height": 800
},
"images": [
{
"url": "https://example.r2.cloudflarestorage.com/media/2026-10/library-expansion.jpg?X-Amz-Signature=...",
"expiresAt": "2026-10-28T12:00:00.000Z",
"usage": "display_with_attribution",
"kind": "article_image",
"source": {
"imageUrl": "https://www.macombdaily.com/wp-content/uploads/2026/10/library-expansion.jpg",
"pageUrl": "https://www.macombdaily.com/2026/10/19/sterling-heights-approves-library-expansion/",
"publisher": {
"platform": "web",
"name": "Macomb Daily",
"domain": "macombdaily.com"
},
"credit": "Jane Smith / The Macomb Daily"
},
"width": 1200,
"height": 800
}
]
}
}
}
Example
curl "https://api.feeds.onhelix.ai/feeds/news/7c1e5a90-3b2d-4f68-9a14-6d8e0b2c4f57/items/3f6c1d52-7a0e-4b8e-9d1c-5a2b8e4f6a10" \
-H "Authorization: Bearer YOUR_API_KEY"
Errors
400:INVALID_QUERYwhenincludewas sent;INVALID_PARAMSwhenfeedIdoritemIdis not a UUID.401: Missing or invalid API key.404: Feed or item not found, the item belongs to a different feed, or your organization neither owns the feed nor subscribes to it. An item the list does not show (its article is behind a paywall, or its content could not be read) is a404too.
Submit Item Feedback
Submits feedback for a specific feed item. Use this to indicate whether an article was relevant or useful.
POST /feeds/news/{feedId}/items/{itemId}/feedback
Request
Path parameters:
| Name | Type | Description |
|---|---|---|
feedId | string | Feed identifier |
itemId | string | Feed item identifier (from the id field) |
Headers:
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
Body:
{
"rating": "positive",
"comment": "This article was very relevant to my interests."
}
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
rating | string | Yes | Feedback rating: positive, negative, or neutral |
comment | string | No | Optional comment explaining the feedback. Maximum 5000 characters. |
Response
Status: 200 OK
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440099"
}
}
Response object:
| Field | Type | Description |
|---|---|---|
id | string (UUID) | Unique feedback identifier |
Examples
Positive feedback:
curl -X POST https://api.feeds.onhelix.ai/feeds/news/7c1e5a90-3b2d-4f68-9a14-6d8e0b2c4f57/items/3f6c1d52-7a0e-4b8e-9d1c-5a2b8e4f6a10/feedback \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"rating": "positive",
"comment": "Great article, very relevant!"
}'
Negative feedback without comment:
curl -X POST https://api.feeds.onhelix.ai/feeds/news/7c1e5a90-3b2d-4f68-9a14-6d8e0b2c4f57/items/3f6c1d52-7a0e-4b8e-9d1c-5a2b8e4f6a10/feedback \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"rating": "negative"
}'
Errors
400:INVALID_BODYfor an invalid rating or a comment over 5000 characters;INVALID_PARAMSwhenfeedIdoritemIdis not a UUID401: Missing or invalid API key404: The item is not in this feed, or your organization neither owns the feed nor subscribes to it
Notes
- Feedback is additive: you can submit multiple feedback entries for the same item
- You can only submit feedback for items in feeds you own or are subscribed to
Sources
| Route | Does |
|---|---|
GET /feeds/news/{id}/sources | Every source on the feed, newest first |
POST /feeds/news/{id}/sources | Add a website: { "type": "site", "url": … } |
PATCH /feeds/news/{id}/sources/{sourceId} | Enable or disable a source: { "enabled": … } |
DELETE /feeds/news/{id}/sources/{sourceId} | Remove a source from this feed |
The same routes and source shape serve event feeds. Only websites can be added
through the API (422 SOURCE_TYPE_NOT_ALLOWED for another type); sitemaps,
index pages and social accounts are added by our team on request. You can
list, enable, disable and remove every source. Adding a website starts a
14-day backfill of it; on an enabled feed, the articles the site already
brought in during the past 14 days are added too (a disabled feed receives
them when you enable it), as they are when you enable a source of an enabled
feed that has not brought them in yet. Subscribers to the feed can list its
sources but not change them (403 PERMISSION_DENIED). Adding sites to your
event and news feeds shares one limit of 30 per minute per organization: over
it, 429 RATE_LIMITED with a Retry-After header. See
Sources for the rules, the source shape and every
error.
curl -X POST https://api.feeds.onhelix.ai/feeds/news/7c1e5a90-3b2d-4f68-9a14-6d8e0b2c4f57/sources \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "site",
"url": "https://www.macombdaily.com/news/local/"
}'
{
"success": true,
"data": {
"id": "9b2f6c1e-4d3a-4e8b-9c7d-2a1b0f3e5d64",
"type": "site",
"identity": {
"domain": "www.macombdaily.com"
},
"enabled": true,
"addedAt": "2026-10-28T14:05:00.000Z"
}
}
Filters
| Route | Does |
|---|---|
GET /feeds/news/{id}/filters | The feed's own and opt-in filters |
GET /feeds/news/{id}/filters/{filterId} | One filter |
POST /feeds/news/{id}/filters | Add your own filter, or one of our opt-in filters |
PATCH /feeds/news/{id}/filters/{filterId} | Change one of your own filters |
DELETE /feeds/news/{id}/filters/{filterId} | Remove a filter |
POST /feeds/news/{id}/filter-sets/{setKey} | Add a set of opt-in filters: standard_news or creator_social |
An article is in the feed when no exclude filter matches and at least one
include filter does; the safety floor always applies and is not listed. Only
the feed's owner can read or change its filters. Filters read
processors (GET /processors), such as the
news_content_type built-in. See Filters and
Opt-in filters for the condition format,
the catalog and every error.
{
"name": "Strong news stories",
"action": "include",
"condition": {
"processor": "news_content_type",
"output": "editorial_news.significance",
"op": ">=",
"value": 7
}
}
Error Responses
All endpoints use the same error envelope: success: false and an error
object with a code, a message and, on some refusals, details.
400 Bad Request
Invalid request parameters or body. The code says which part of the request was refused:
INVALID_BODY: the JSON body does not match the endpoint's schema (a missing field, a wrong type, a value out of range, an unknown key inconfigor in a source or filter body), or it is not valid JSON.INVALID_PARAMS: a path parameter, such as a feed or item id that is not a UUID.INVALID_QUERY: a query parameter on the item endpoints:since,until,include, a date-only or zone-less time filter,hasCover=false, an unknownorderBy, alimitoutside 1-100.
{
"success": false,
"error": {
"code": "INVALID_BODY",
"message": "Invalid request body: Name is required"
}
}
401 Unauthorized
Missing or invalid API key.
{
"success": false,
"error": {
"code": "AUTHENTICATION_FAILED",
"message": "Authentication failed for strategy: api-key"
}
}
Common causes:
- Missing
Authorizationheader - Incorrect header format
- Invalid API key
403 Forbidden
Your organization subscribes to the feed but does not own it, and the request would change it (update, delete, its sources) or reads its filters.
{
"success": false,
"error": {
"code": "PERMISSION_DENIED",
"message": "Access denied to NewsFeed with id '7c1e5a90-3b2d-4f68-9a14-6d8e0b2c4f57'"
}
}
404 Not Found
The resource does not exist, or your organization neither owns the feed nor subscribes to it.
{
"success": false,
"error": {
"code": "NOT_FOUND",
"message": "NewsFeed with id '7c1e5a90-3b2d-4f68-9a14-6d8e0b2c4f57' not found in database"
}
}
Common causes:
- Incorrect ID
- Resource was deleted
- A feed of another organization
409 Conflict
Resource already exists.
{
"success": false,
"error": {
"code": "ALREADY_EXISTS",
"message": "News feed source with site 'www.macombdaily.com' already exists"
}
}
Common causes:
- Adding a website the feed already has, with or without
www. - Adding an opt-in filter the feed already has a filter for: a
409with its own code,FILTER_ALREADY_PRESENT, notALREADY_EXISTS(see Opt-in filters)
422 Unprocessable Entity
The request is well formed but a rule refuses it.
{
"success": false,
"error": {
"code": "VALIDATION_FAILED",
"message": "Validation failed for field 'enabled': the feed needs at least one include filter; add an include filter first"
}
}
Common causes:
- Enabling a feed that has no include filter (
VALIDATION_FAILED) - Adding a source of a type other than
site(SOURCE_TYPE_NOT_ALLOWED) or a site whose host is private or does not resolve: see Sources - A filter or opt-in the feed cannot take (
INVALID_CONDITION,MISSING_OPT_IN_PARAM,INVALID_OPT_IN_PARAM,LAST_INCLUDE_FILTER): see Filters and Opt-in filters
429 Too Many Requests
Adding sites to your feeds is limited to 30 per minute per organization. The
response has code RATE_LIMITED and a Retry-After header with the seconds
to wait.
500 Internal Server Error
Unexpected server error.
{
"success": false,
"error": {
"code": "INTERNAL_ERROR",
"message": "An internal server error occurred"
}
}
If this persists: Retry the request
503 Service Unavailable
A service the request needs is briefly unavailable. The response has code
SERVICE_UNAVAILABLE and a Retry-After header: retry after that many
seconds.
Best Practices
Paginate Large Result Sets
Always use pagination for feeds with many articles:
async function getAllFeedItems(feedId) {
const allItems = [];
let offset = 0;
const limit = 100; // Max allowed
let hasMore = true;
while (hasMore) {
const response = await fetch(
`https://api.feeds.onhelix.ai/feeds/news/${feedId}/items?limit=${limit}&offset=${offset}`,
{ headers: { Authorization: `Bearer ${API_KEY}` } }
);
const result = await response.json();
allItems.push(...result.data.items);
hasMore = result.data.pagination.hasMore;
offset += limit;
}
return allItems;
}
Use Date Filters
Filter by date range to reduce response size:
# Get today's articles
TODAY=$(date -u +%Y-%m-%dT00:00:00Z)
curl "https://api.feeds.onhelix.ai/feeds/news/7c1e5a90-3b2d-4f68-9a14-6d8e0b2c4f57/items?addedSince=$TODAY" \
-H "Authorization: Bearer YOUR_API_KEY"