Embeds: allowed sites and feed polling
Two integrations control what flows between your own website and your forum. The embeddable-hosts allowlist decides which outside sites may embed your comment threads. Feed polling goes the other way: it watches an RSS or Atom feed and turns each new item into a topic. For the embed widgets themselves, see SDKs and integrations. Both features need org.manage_integrations. To put a whole reader page of any type in an iframe, see ?embed=1 below.
Restrict which sites can embed
The allowlist guards the call an embedded comment thread makes to find or create the topic for the page it sits on (POST /public/orgs/{orgId}/spaces/{spaceId}/embed/resolve). By default that call answers any origin. The moment you add a single host, that changes: only listed hosts may embed, and a request from anywhere else (or with no Origin or Referer at all) is refused with a 403. A listed origin gets its own origin back in Access-Control-Allow-Origin instead of *. Remove the last host and the open behaviour returns.
POST /orgs/{orgId}/embeddable-hosts
{
"host": "blog.acme.com",
"categoryId": "…",
"postAuthor": "Acme Blog",
"allowedPaths": ["/posts/*", "/about"],
"className": "article-body"
}Field | Required | Notes |
|---|---|---|
| yes | A hostname, an origin or a URL. It is reduced to a bare lowercase hostname, so |
| no | Where topics auto-created for this site's pages land. Matched on the host of the page's URL; an unmapped host uses the space default. |
| no | Byline for those topics, up to 80 characters. Defaults to |
| no | Up to 100 paths, checked against the path of the embedding page's |
| no | Up to 200 characters, stored and returned with the host. Nothing in the embed reads it yet. |
Adding a host that already exists updates its mapping instead of failing.
Call | What it does |
|---|---|
| List the allowlist. |
| Remove one host. |
Turn a feed into topics
A feed rule polls an external RSS or Atom feed and creates one topic per new item in a space, so a company blog or a release feed shows up in the community without anyone copying it over.
POST /orgs/{orgId}/embed-feeds
{
"spaceId": "…",
"feedUrl": "https://blog.acme.com/feed.xml",
"categoryId": "…",
"author": "Acme Blog",
"pollIntervalMinutes": 60
}Field | Required | Notes |
|---|---|---|
| yes | The space the topics are created in. |
| yes | An |
| no | Category for the new topics. |
| no | Byline, up to 80 characters. |
| no | Whole minutes, 5 to 1440. Defaults to 60. |
| no | Pause a feed without deleting it. |
Every 10 minutes the platform polls each enabled feed whose interval has passed. Each topic remembers the item's URL, so polling the same item twice never makes a duplicate. A single poll imports at most 20 items, which bounds the backlog when a feed is first added. Adding a URL that is already a feed updates that feed.
Call | What it does |
|---|---|
| List feeds. |
| Change space, category, author, interval or |
| Remove it. Topics already created stay. |
| Poll now, ignoring the schedule. Returns |
Run the poll call right after adding a feed to confirm it imports, rather than waiting for the next scheduled pass.
The widget scripts
The widgets themselves are plain scripts the API serves, which you drop onto your own site with a <script> tag. They are public, need no token, and are cached for an hour:
GET /embed/widget.js is the docs search box (ask and search).
GET /embed/comments.js is a forum comment thread for the page it sits on.
GET /embed/forum.js is a forum's topic list (latest, new, hot or top; a category or a tag).
GET /embed/changelog.js is the changelog "What's new" bell and panel.
Each one reads its organization and space from data-org and data-space on the tag; SDKs and integrations lists the other data- attributes. GET /embed/check?org=…&space=… echoes the two ids back as { "ok": true, "org": "…", "space": "…" }, with ok false when either is missing. It does not look them up, so it confirms what your page sends, not that the space exists.
Any reader page in an iframe: ?embed=1
Add ?embed=1 to the address of any reader page — a docs page, a roadmap, a status board, an event, a legal document, the contact form, a careers list, on every type that has reader pages (all but CONTENT, which is delivered by key and has none) — and it renders as a bare frame you can put in an <iframe> on your own site:
<iframe src="https://acme.com/status?embed=1&scheme=dark"
title="Acme status" style="width:100%;height:480px;border:0"></iframe>What the frame keeps and what it drops:
Kept | Dropped |
|---|---|
The page itself, and the space's theme: colours, fonts, | The header, footer, announcement bar and sign-in |
The space's colour scheme, or the one | Analytics tags, chat widgets, the consent banner and your |
The space's access rules: a space only some networks may read still refuses the rest, and an address that is not a page is still a 404 | Scroll motion, the skip link and ads |
?scheme= exists because the frame has to match the page around it: a changelog you keep light for your own readers can be embedded dark in a dark product without repainting the whole space. An unrecognised value keeps the space's own choice.
Why there are no analytics or consent banner inside a frame. An embed is a fragment of a page that someone else owns, with that site's consent banner. A consent banner cannot work in a card-sized frame, so a tag in the frame would either fire before anybody was asked or never fire at all, and a framed view counted as a page view would double-count the host page's own. So the frame stays inert, and the page that embeds it measures it.
A page that is embedded is framable from any site (its security policy names no frame-ancestors restriction); the same address without ?embed=1 keeps the reader's normal policy. Inside the frame the page root carries data-reader-embed, so customCss can style the embedded copy differently (see Theming hooks).
In the API
Every operation below is also reachable from the other surfaces without a dedicated command. forumulate api <Operation> calls it from the CLI, the JavaScript SDK has a typed method for each one (the last column names it; the Python and Go clients are generated from the same spec), and an agent reaches the organization-scoped ones through the MCP call_api tool. Where a CLI command or MCP tool exists too, the last column names it. Operation names link to the API reference.
Embeddable Host
Operation | Request | What it does | Shortcuts |
|---|---|---|---|
| List embeddable hosts in an organization |
| |
| Create an embeddable host in an organization |
| |
| Delete the embeddable host |
|
Embed Feed
Operation | Request | What it does | Shortcuts |
|---|---|---|---|
| List embed feeds in an organization |
| |
| Create an embed feed in an organization |
| |
| Update the embed feed |
| |
| Delete the embed feed |
| |
| Create the poll in an organization |
|