Implementation Guide
A quick and easy guide to getting NeuraFeed data to show up on your website.
1. Knowing What You Get
When you call the API, you get back an object that has a mix of plain text and HTML in it. Knowing which field is which is pretty important so nothing breaks on your page.
- Plain Text: Fields like
title,summary,whyItMatters, andtagsare just regular strings so you can use them directly in your components. - HTML Content: The
articlefield comes back as pre-formatted HTML. You need to inject it into the DOM instead of printing it out as raw text. - Category:
categoryis the section the article belongs to, one oftechnology,politics,economics,society, orworld. Use it to group or filter.topicis different, that is the specific story. Older articles predate the field and omit it, so read it asarticle.category ?? "technology". - Sources: Prefer
sourceDetails, which gives youid,number,title,url, anddomainready to use. The oldersourcesarray holds the same thing as strings shaped like[1] Site Name: https://...and only needs parsing when you are supporting articles from before the structured fields existed. - Structured Citations:
citationslinks each cited passage back to itssourceIds, matching thedata-citation-idattribute on the inline markers. Use it for custom footnotes, source popovers, or a non-HTML renderer. Note there are far fewer markers than sources, usually 5 to 7 against 10 to 20, because most attribution is written into the prose instead. - Cover Image:
coverImageis a direct image URL (or null if unavailable). Always check for null before rendering. When you show it, link it toimageSourceUrland label it withimageSourcefor attribution. - Rich Media:
mediais an array of optional attributed images, YouTube videos, audio, or direct videos. UseafterSectionto place an item and show itssourceName/sourceUrl.embedMediaremains as a legacy YouTube shortcut.
2. Grabbing the Data
Call the API from your backend, a server component, or any other server-side code. Fetching it straight from browser JavaScript will not work: the endpoints send no Access-Control-Allow-Origin header, so the browser blocks a cross-origin request. If you need the data client-side, put a small proxy route on your own domain and fetch that instead. The second tab in the next section shows exactly that.
If you are on Next.js, an async Server Component is the simplest option: it fetches on the server and ships finished HTML, so CORS never enters the picture.
async function getLatestNews() {
const response = await fetch('https://feed.neuraspheres.com/api/latest-news');
const data = await response.json();
return data.article;
}3. Building the UI
Pick whichever tab matches your setup.
- Next.js (server) fetches on the server, so there is no CORS problem at all. This is the shortest path and what we would reach for first. It also shows how to render
mediaand fall back to the legacysourcesstrings on older articles. - React (via proxy) is for when the data genuinely has to arrive in the browser. It adds a one-line route on your own origin and points the client at that. Without the proxy the fetch is blocked.
- Python renders server-side HTML and escapes every value it interpolates, which matters because only the
articlefield is meant to be treated as markup.
All three inject article with an HTML renderer rather than printing it, only render sections that actually have content, and read sourceDetails in preference to parsing source strings.
// Server Component — runs on the server, so CORS never applies.
export default async function NewsViewer() {
let article = null;
try {
const res = await fetch("https://feed.neuraspheres.com/api/latest-news", {
// Articles publish twice a day; an hour of caching is plenty.
next: { revalidate: 3600 },
});
if (res.ok) article = (await res.json()).article ?? null;
} catch {
// Network error — fall through to the "no news" message.
}
if (!article) return <div>No news found right now.</div>;
// Prefer sourceDetails; older articles only have the legacy strings.
const sources = article.sourceDetails?.length
? article.sourceDetails
: (article.sources ?? []).map((raw, i) => {
const m = raw.match(/^[(d+)]s+(.+?):s*(https?://S+)/);
return m
? { id: `legacy-${i}`, number: Number(m[1]), title: m[2], url: m[3] }
: { id: `legacy-${i}`, number: i + 1, title: raw, url: null };
});
return (
<article>
{/* category is the section; topic is the specific story */}
<span className="badge">{article.category}</span>
<h1>{article.title}</h1>
<p>{article.summary}</p>
{article.coverImage && (
<figure>
<img src={article.coverImage} alt={article.title} style={{ width: "100%" }} />
{article.imageSource && (
<figcaption>
<a href={article.imageSourceUrl ?? article.coverImage} target="_blank" rel="noopener noreferrer">
Source: {article.imageSource}
</a>
</figcaption>
)}
</figure>
)}
{/* Already HTML, so inject rather than print */}
<div dangerouslySetInnerHTML={{ __html: article.article }} />
{/* media replaces embedMedia; it may hold images, video or audio */}
{article.media?.map((item) => (
<figure key={item.id}>
{item.type === "image" && <img src={item.url} alt={item.caption ?? ""} />}
{item.type === "youtube" && (
<iframe
width="100%"
height="400"
src={`https://www.youtube-nocookie.com/embed/${item.youtubeId}`}
title={item.title}
allowFullScreen
/>
)}
{item.type === "audio" && <audio controls src={item.url} />}
{item.type === "video" && <video controls src={item.url} />}
<figcaption>
{item.caption}
{item.sourceUrl && <> — <a href={item.sourceUrl}>{item.sourceName}</a></>}
</figcaption>
</figure>
))}
{sources.length > 0 && (
<section>
<h3>Sources</h3>
<ol>
{sources.map((src) => (
<li key={src.id} id={`ref-${src.number}`}>
{src.url ? <a href={src.url}>{src.title}</a> : src.title}
</li>
))}
</ol>
</section>
)}
</article>
);
}4. Making It Look Good
The article body comes with its own HTML tags like h2, p, and sup already in it. You will probably want to write some CSS to style those so they match the rest of your site.
.article-container h2 {
font-size: 24px;
margin-top: 32px;
font-weight: bold;
}
.article-container p {
line-height: 1.6;
margin-bottom: 16px;
}
/* Inline citation markers. They carry data-source-ids, so you can also
use them as hooks for a popover instead of just styling them. */
.article-container sup {
color: #2563eb;
font-size: 12px;
vertical-align: super;
cursor: pointer;
}
/* The category badge from the examples above. */
.badge {
display: inline-block;
padding: 2px 10px;
border-radius: 999px;
background: #eef2ff;
color: #4338ca;
font-size: 12px;
text-transform: capitalize;
}
/* Attributed media from the media[] array. Always keep the caption:
it carries the credit back to the original publisher. */
.article-container figure {
margin: 24px 0;
}
.article-container figure img,
.article-container figure video {
width: 100%;
border-radius: 8px;
}
.article-container figcaption {
font-size: 13px;
opacity: 0.7;
margin-top: 6px;
}Wrapping Up
Every example handles article coming back null, which happens if nothing has published yet or the request failed. New articles land twice a day, around 00:17 and 12:17 UTC, so caching for an hour is plenty.
Want to page through the whole archive rather than just the latest? The cursor-paginated /api/articles endpoint is covered in the API Reference, along with the full field table. If you would rather your AI agent pull the news itself, there is an MCP server too.