FounderReply

Public blog API

Publish your AI blog to your own website.

FounderReply generates and stores your SEO articles. Your site pulls the published ones over a single read API and renders them on your domain — with the schema.org structured data already attached. No webhooks to maintain, no scheduler to babysit.

  • Read-only · no API key · CORS-open · edge-cached for five minutes
  • Four endpoints: a list, an article, and an rss.xml / sitemap.xml pair for the crawlers
  • Only posts you marked published are ever exposed
A PULL, NOT A PUSHYour own siteyour domainGET ?workspace_id=…200 · posts[]/api/public/blogpublished · returnedgenerated · heldarchived · heldFounderReply never reaches out to your site. Your site asks, when it builds — so the blogkeeps serving between builds, and only what you marked published is ever exposed.

The model

A pull, not a push

FounderReply never reaches out to your site. Your site asks for posts when it builds. That keeps your blog fast, statically hosted, and independent — it keeps working even if this app is down between builds. Only posts you’ve marked published are ever exposed; drafts and archived posts stay private.

your build — shell
# list your published posts
curl "https://founderreply.com/api/public/blog?workspace_id=<your-workspace-id>"
Published content is public by design — no API key, just your workspace id.
The four public blog endpoints, what each one returns, and the query parameters it requires
EndpointReturnsRequires
GET /api/public/blogEvery published post, newest first, bodies omitted — twelve fields each, plus total, limit and offset.workspace_id
GET /api/public/blog/[slug]One article: the markdown body, embeddable schema.org JSON-LD, and the siblings worth linking to.workspace_id · base_url optional
GET /api/public/blog/sitemap.xmlA urlset of every published post, rooted at your blog base, with honest lastmod.workspace_id · base_url
GET /api/public/blog/rss.xmlRSS 2.0 with the full body in content:encoded — up to 50 items.workspace_id · base_url

All four are unauthenticated, CORS-open and edge-cached for five minutes (s-maxage=300, SWR 600), so they are cheap to call from a build step or straight from the browser. There is no write surface here at all: every route is a GET (plus its CORS preflight).

Step 1 · Pull

Pull your published posts

Two JSON endpoints do the work. The list gives you every published post with bodies omitted, so you can build an index and generate routes. The detail gives you one full article by slug, with the markdown body and ready-to-embed JSON-LD.

Your workspace id is what tells the API whose published posts to return. Copy it from your dashboard URL or settings and store it as an environment variable on your site. It is an unguessable identifier, it is not a secret in the credential sense, and it only ever surfaces public, published content — which is why none of these endpoints asks for a key.

Environment.env.local
FOUNDERREPLY_WORKSPACE_ID=<the uuid from your dashboard url>
ListGET /api/public/blog
# every published post, newest first

{
  "ok": true,
  "total": 24, "limit": 50, "offset": 0,
  "posts": [
    { "id", "slug", "title", "type",
      "keywords", "meta_description",
      "cover_image_url", "created_at",
      "published_at", "updated_at",
      "intent", "visibility" }, …
  ]
}
DetailGET /api/public/blog/[slug]
# one full article by slug

{
  "ok": true,
  "post": {
    …all list fields,
    "body_markdown": "…",
    "json_ld": [ Article, FAQPage? ]
  },
  "related": [
    { "title", "slug", "url"? }, …
  ]
}

Twelve fields per list item, and updated_atis the one to keep — it is what the sitemap’s lastmod is built from. related is scored on keyword overlap and gains an absolute url once you pass base_url — render those links server-side, because crawlers do not run your JavaScript.

workspace_id
Required on every endpoint. The UUID from your dashboard URL.There is no default workspace and no implicit one — missing or malformed is a 400.
visibility
listed (default) · unlisted · all — list endpoint only.Conquest pages are stored unlisted, so the default list omits them. Pass all if you want everything.
limit / offset
List endpoint. Default limit 50, hard cap 200; total is in the response.Page with offset once total exceeds your limit — a short list is not an error.
base_url
Your blog base, e.g. https://acme.com/blog. Detail, RSS and sitemap.On the detail endpoint it makes json_ld carry url/mainEntityOfPage and gives each related item an absolute url. RSS and sitemap require it, falling back to the blog base configured on your primary SEO site.
Every error the public blog endpoints return, with its HTTP status and when it happens
Response bodyStatusWhen, and what to do
workspace_id is required400All four endpoints. Nothing is implicit: there is no default workspace.
workspace_id must be a uuid400All four endpoints. The id is shape-checked before it reaches the database, so a typo comes back as a bad request with a message rather than a server error.
base_url is required (or configure the primary site blog_base_url)400sitemap.xml and rss.xml, when you pass no base_url and your primary SEO site has no blog base set. Better than guessing at a domain and emitting URLs that do not resolve.
blog post not found404The detail endpoint only. A draft, an archived post and a slug that never existed are all the same 404 — the API does not confirm that unpublished content exists.
too many requests429More than 120 requests a minute from one IP. A build that fans out one fetch per slug is the caller that finds this: pull the list once and stagger the details.
persistence not configured503The backing store is not available to this deployment. A build should fail loudly here rather than ship an empty index.

Errors are never edge-cached, so a retry after a fix is answered fresh. Successful reads are, which is why a build that pulls the list once and then walks it usually stays well inside the rate limit.

Step 2 · Render

Render them on your domain, as fresh as you like

There is no schedule to configure inside FounderReply — you decide how often your site refreshes by choosing how it pulls. Pick whichever fits your stack, then generate a route per slug, render the markdown, and drop the JSON-LD into a script tag.

Time-based (ISR)
Set a revalidate window on the consuming page — the site re-pulls in the background on the next request after the window lapses. e.g. revalidate: 300 = at most 5 min stale.Simplest. Good default.
Scheduled rebuild
Run a cron (GitHub Action / Cloudflare cron / Vercel cron) that triggers a rebuild on a fixed cadence — nightly, hourly, whatever you choose. The site is 100% static between builds.Predictable, fully static.
On-publish trigger
Hit your host’s deploy hook the moment you mark a post published, so the new article goes live within a build cycle instead of waiting for a timer.Freshest. Event-driven.
Next.js — ISRapp/blog/[slug]/page.tsx
// re-pull at most every 5 minutes
export const revalidate = 300
Next.js App Routerapp/blog/[slug]/page.tsx
const WS = process.env.FOUNDERREPLY_WORKSPACE_ID
const BASE = "https://founderreply.com/api/public/blog"

// one route per published slug
export async function generateStaticParams() {
  const { posts } = await fetch(`${BASE}?workspace_id=${WS}`).then(r => r.json())
  return posts.map((p) => ({ slug: p.slug }))
}

// render one article
export default async function Page({ params }) {
  const { slug } = await params
  const { post } = await fetch(`${BASE}/${slug}?workspace_id=${WS}`).then(r => r.json())
  return (
    <article>
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(post.json_ld) }}
      />
      <Markdown>{post.body_markdown}</Markdown>
    </article>
  )
}

Use any markdown renderer (react-markdown, marked, MDX). The same pattern works in SvelteKit, Nuxt, Astro, or a plain build script — it is two GET requests. Pass base_url on the detail call and the JSON-LD comes back carrying your canonical URL, so you do not have to patch it in.

Going live

Mark it published, then point the crawlers at it

Two things are left, and neither is code you write: the gate that puts an article into the API at all, and the two XML surfaces that tell search engines the article exists.

What the public blog API will and will not serve

A post is generated, then published, then possibly archived. Only the middle state is visible here, and the transition into it is yours to make — the same rule the approval queue applies to everything else FounderReply writes.

Always
Serve only posts you marked published, for the workspace id you pass
Answer any origin, with no key, no token and no CORS proxy
Cache at the edge for five minutes, so a build burst mostly never reaches the app
Root every URL it emits at the blog base you supply
Never
Push anything to your site, or ask you to host a webhook
Expose a draft, an archived post, or another workspace’s content
Return HTML — the body is markdown, and your site stays the canonical render
Publish an article for you: the status flip happens in your dashboard
sitemap.xml
A urlset of every published post, rooted at your blog base. No item cap.lastmod is the content-change time, falling back to first publish — never row noise. Reference it from your robots.txt or sitemap index, or mirror it at build time.
rss.xml
RSS 2.0 with the full body in content:encoded, newest 50 posts.The body ships as markdown: this API stores markdown, your site stays the canonical HTML render, and crawlers that fetch feeds parse it fine.
your build — shell
# the two XML surfaces — rooted at your blog base
curl "https://founderreply.com/api/public/blog/sitemap.xml?workspace_id=$WS&base_url=$BLOG_BASE"
curl "https://founderreply.com/api/public/blog/rss.xml?workspace_id=$WS&base_url=$BLOG_BASE"
Same workspace id, same no-key rule, same five-minute edge cache as the JSON endpoints.

Leave base_url off and both fall back to the blog base configured on your primary SEO site. With neither, they return 400 — better than guessing at a domain and emitting a sitemap full of URLs that do not resolve.

Your domain, your blog

Two GET requests, and the articles live on your own site.

Generate the articles here, review them here, and serve them from your domain — where the SEO value belongs.

Free to start · Approval on by default · No card required