oEmbed & Embeds

Docs API

oEmbed & Embeds

Embed mood posts on any page — the oEmbed protocol, the raw iframe widget, and the postMessage contract.

Embed mood posts on external platforms using the oEmbed protocol.

oEmbed Discovery

GET /api/oembed.json?url={url}

Returns oEmbed JSON response for embedding mood content.

Parameters

Parameter Type Required Description
url string Yes URL to embed (must be a same-host /mood or /mood/{id} URL)
maxwidth number No Maximum width (200-800, default: 400)
maxheight number No Maximum height (150-800). When omitted, height is estimated based on content.
theme string No Theme: light, dark, or auto (default: auto)
count number No Number of posts to show (1-10, default: 5). Ignored for /mood/{id} URLs.
frame string No Card framing: true or false (default: true).
density string No Density: regular or compact (default: regular).
font string No Font: mono or system (default: mono).
origin string No Allowed parent origin for postMessage (e.g. https://example.com).
link string No Show “View all” link (true/false, default: true).

Example Request

GET /api/oembed.json?url=https://buxx.me/mood&maxwidth=400&maxheight=400&theme=dark

Example Response

JSON
{
  "type": "rich",
  "version": "1.0",
  "title": "Mood Feed",
  "provider_name": "buxx.me",
  "provider_url": "https://buxx.me",
  "width": 400,
  "height": 400,
  "html": "<iframe src=\"https://buxx.me/mood/embed?theme=dark&count=5\" width=\"400\" height=\"400\" frameborder=\"0\" ...></iframe>",
  "cache_age": 3600
}

Embed Widget

GET /mood/embed

Renders an embeddable HTML widget for mood posts.

Parameters

Parameter Type Required Description
id string No Specific post ID to display
count number No Number of posts (1-10, default: 1)
theme string No Theme: light, dark, or auto
refresh number No Auto-refresh interval in seconds (30-3600). Disables caching when set.
link string No Show “View all” link (true/false, default: true)
frame string No Card framing: true or false (default: true).
density string No Density: regular or compact (default: regular).
font string No Font: mono or system (default: mono).
origin string No Allowed parent origin for postMessage (e.g. https://example.com).

Examples

HTML
<!-- Latest mood post -->
<iframe
  src="https://buxx.me/mood/embed"
  style="border:0;display:block;width:100%;max-width:400px"
  height="300"
  loading="lazy"
></iframe>

<!-- Specific post with dark theme -->
<iframe
  src="https://buxx.me/mood/embed?id=123&theme=dark"
  style="border:0;display:block;width:100%;max-width:400px"
  height="300"
  loading="lazy"
></iframe>

<!-- Multiple posts -->
<iframe
  src="https://buxx.me/mood/embed?count=5&theme=light"
  style="border:0;display:block;width:100%;max-width:400px"
  height="600"
  loading="lazy"
></iframe>

Recommended embed (no ugly outer frame, keeps inner card styling):

HTML
<iframe
  src="https://buxx.me/mood/embed"
  style="border:0;display:block;width:100%;max-width:400px"
  height="300"
  loading="lazy"
></iframe>

HTML Discovery

Pages at /mood and /mood/{id} include oEmbed discovery links:

HTML
<link rel="alternate" type="application/json+oembed"
      href="https://buxx.me/api/oembed.json?url=https://buxx.me/mood"
      title="Mood Embed" />

Features

  • Auto Theme: Respects prefers-color-scheme when theme is auto
  • Responsive Height: Posts mood-embed-resize message to parent for dynamic iframe sizing (oEmbed HTML includes a listener to resize the iframe)
  • Origin Lock: Optionally restricts postMessage to a specific parent origin via the origin parameter
  • Theme Sync: Listens for mood-embed-theme messages to sync theme with parent
  • Auto Refresh: Optional periodic refresh for live updates (30-3600 seconds, disables caching)

Parent Page Integration

If your platform strips scripts from the oEmbed html field, add the listener manually on the parent page to enable dynamic height adjustment (and optionally validate event.origin if you set origin):

JavaScript
window.addEventListener('message', (event) => {
  if (event.data?.type === 'mood-embed-resize') {
    const iframe = document.querySelector('iframe');
    iframe.style.height = event.data.height + 'px';
  }
});

To sync theme with embed:

JavaScript
const iframe = document.querySelector('iframe');
iframe.contentWindow.postMessage({
  type: 'mood-embed-theme',
  theme: 'dark' // or 'light'
}, '*');

Errors and validation

The url parameter is checked in four stages, and each failure has its own status. All of them carry the CORS headers below, so a browser can read the error body rather than seeing an opaque network failure.

Status Body Cause
400 {"error":"Missing required parameter: url"} url absent or empty after trimming.
400 {"error":"Invalid URL format"} url is not parseable by new URL().
400 {"error":"Unsupported URL protocol"} Parsed fine, but the scheme is not http: or https:.
403 {"error":"URL host not allowed for embedding"} The host in url does not match the host serving the request.
404 {"error":"URL not supported for embedding"} Host matched, but the path is neither /mood nor /mood/{id}.
429 {"error":"Too Many Requests"} Over 120 / 60s. Advertised only — see Rate limits.

A trailing slash is stripped before the path check, so /mood/ and /mood are the same request. Anything deeper than two segments — /mood/123/comments — is a 404.

Every other parameter is permissive: maxwidth, maxheight, and count are clamped into range rather than rejected, and an unrecognized density, font, or theme silently falls back to its default. Only url can fail the request.

www. and the bare host are one origin

The host comparison strips a leading www. from both sides before comparing, so all four combinations resolve:

Endpoint host url host Result
buxx.me buxx.me OK
buxx.me www.buxx.me OK
www.buxx.me buxx.me OK
www.buxx.me www.buxx.me OK

That is the shape a real oEmbed consumer produces — discover the endpoint from one host, hand back the page URL from another — so a www.-served page can embed itself. Any other host is 403 URL host not allowed for embedding.

CORS and caching

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, OPTIONS
Access-Control-Allow-Headers: Content-Type

OPTIONS returns 204 with those headers and nothing else. This is one of the few endpoints on the API that is genuinely cross-origin readable — the mood JSON routes are not, which is the main reason this endpoint exists.

There is no Cache-Control header on any oEmbed response, success or error. The successful body carries "cache_age": 3600, which is an oEmbed protocol field advising the consumer to hold the result for an hour; it is a hint to the client, not an HTTP directive, and nothing on the Cloudflare edge acts on it. If you are polling this endpoint, honor cache_age yourself.