Developer guide

Blue Machines Chat Preview

How the product works and how to integrate with its public APIs. Share this page with your engineering team.

How the product works

Blue Machines Chat Preview lets sales and customer-success teams see how the BlueMachines chat widget looks on a client's website before it is embedded in production.

A user provides three inputs on the home page:

  1. Client URL — the website to preview (e.g. https://bluemachines.ai)
  2. Chat widget code — the <script> embed snippet from BlueMachines
  3. Backdrop mode — how to render the site behind the widget: auto, iframe, or thumbnail

The app opens a full-screen preview. The client site appears as a live iframe or a screenshot, and the chat widget is overlaid on top in a separate layer. The widget always renders — even when the site backdrop fails to load.

Live iframe

Embeds the real client site. Best when the site allows framing (no X-Frame-Options block).

Thumbnail screenshot

Server-generated PNG via Microlink (optional Firecrawl fallback). Cookie-banner hiding, retries, and static logo fallback on failure.

Auto mode (default)

Checks iframe headers first, then tries live iframe with a short timeout before falling back to thumbnail.

Short links

Save a preview config as /s/{id}. Share the link — recipients land directly in preview.

API logs

Every public API call is logged with type, provider, success status, client URL, and full request/response JSON.

User journeys

JourneyEntryResult
Open preview Home → Open Preview New browser tab at /preview?…
Copy short link Home → Copy short link Clipboard gets {origin}/s/{id}
Share short link Recipient visits /s/{id} Redirect to /preview?… with saved params
History Home → History, or /history Paginated table of past short links (20 per page); search by client URL substring
API logs Home → API Logs, or /api-logs Paginated log of screenshot, iframe-check, and shorten API calls
Docs Home → Docs, or /docs This product and API guide
sequenceDiagram participant User participant Home as Home page participant Storage as sessionStorage participant DB as Database participant Short as /s/id participant Preview as Preview page participant Widget as Widget layer User->>Home: URL + widget + backdrop Home->>Home: Validate and extract widget attrs Home->>Storage: Save url and widget snippet alt Open preview Home->>Preview: New tab /preview?params else Copy short link Home->>DB: Create or reuse short link Home-->>User: /s/id copied User->>Short: Visit /s/id Short->>Preview: Redirect /preview?params end Preview->>Preview: Resolve backdrop Preview->>Widget: Inject widget script Widget-->>User: Chat widget on top of backdrop

Home form processing

When the user submits, the app:

  1. Normalizes the URL (adds https:// if missing)
  2. Validates URL format and non-empty widget code
  3. Extracts widget attributes: data-widget-token, data-agent-id, data-agent-profile-id, data-base-url, src
  4. Builds query params (includes backdrop only when not auto)
  5. Stores preview:url and preview:widget in sessionStorage as a same-tab fallback

The Additional params section lets you attach arbitrary key/value pairs (e.g. utm_source, ref) that are carried through to the preview URL and persisted on the short link. Reserved keys (url, backdrop, src, and the data-* widget attributes) are blocked.

Preview page

The preview page (/preview) is the core of the product. It stacks three layers inside a device frame:

flowchart TB subgraph stack [Layer stack bottom to top] L1["1. Site iframe OR thumbnail image"] L2["2. Loading logo OR static fallback logo"] L3["3. Widget iframe always on top"] end L1 --> L2 --> L3

Backdrop modes

URL paramBehavior
auto (default) Server header check + iframe probe → fallback to thumbnail
iframe Live site iframe only; user can switch to thumbnail via settings
thumbnail Screenshot only via screenshot API

Auto mode resolution

In auto mode the app shows a loading logo while checking headers — the site iframe is not mounted until iframe embedding is likely allowed. This avoids stuck states and long waits.

flowchart TD Start["auto mode: probing"] --> HeaderCheck["GET /api/public/iframe-check"] HeaderCheck -->|not iframeable| Thumbnail HeaderCheck -->|iframeable| Iframe["status=iframe, mount site iframe"] HeaderCheck -->|hang 4s| Iframe Iframe -->|onLoad| Reveal["Reveal iframe"] Iframe -->|5s no reveal| Thumbnail Iframe -->|onError| Thumbnail Thumbnail["GET /api/public/screenshot"] -->|success| Done["Show thumbnail"] Thumbnail -->|502 / error| Retry{"Client retry up to 3 attempts"} Retry -->|still failing| StaticLogo["Static BM logo centered"] Retry -->|retry with fresh=1| Thumbnail

Timeouts

TimeoutDurationEffect
iframe-check fetch4sServer header probe; on timeout → not iframeable
Header check hang (auto)4sIf check stalls, try iframe anyway
Iframe reveal5sFall back to thumbnail if iframe never becomes visible
Screenshot per attempt12sClient safety timeout before retry or static logo
Screenshot upstream28sServer fetch to Microlink (2 strategies) + optional Firecrawl

Thumbnail retries

The screenshot API retries Microlink with escalating wait strategies and can fall back to Firecrawl when FIRECRAWL_API_KEY is configured. The preview page retries up to 3 times (attempts 0–2) with fresh=1 before showing the static logo. Cookie consent banners are suppressed via Microlink adblock, injected CSS, and an accept-button click script.

Logo states

StateVisual
Loading backdropAnimated BlueMachines logo with shine effect
Both iframe and thumbnail failedStatic centered logo, no animation
Backdrop loadedLogo hidden; site iframe or thumbnail visible

Device frames & settings

Users can switch between desktop (full viewport), tablet (820×1180), and mobile (390×844) frames via the settings gear in the bottom-left corner. On real mobile browsers, the preview renders full-viewport without a simulated bezel.

The settings panel also lets users manually toggle between iframe and thumbnail backdrop modes.

System architecture

flowchart TB subgraph browser [Browser] Home["/ Home"] Preview["/preview"] History["/history"] ApiLogs["/api-logs"] Docs["/docs"] Short["/s/id"] end subgraph app [Preview app server] ServerFns["Server functions"] IframeCheck["iframe-check API"] Screenshot["screenshot API"] ShortenAPI["shorten API"] Logger["api_logs writer"] end subgraph external [External] DB[(Supabase)] Microlink[Microlink.io] Firecrawl[Firecrawl optional] ClientSite[Client website] end Home --> Preview Home --> ServerFns ServerFns --> DB Short --> DB Short --> Preview Preview --> IframeCheck Preview --> Screenshot Preview --> ClientSite Screenshot --> Microlink Screenshot --> Firecrawl History --> ServerFns ApiLogs --> ServerFns ShortenAPI --> DB IframeCheck --> Logger Screenshot --> Logger ShortenAPI --> Logger Logger --> DB

App routes

PathPurpose
/Home form
/previewLive preview with widget overlay
/historyPaginated short-link history with client URL search
/api-logsPaginated public API request logs
/docsThis guide (served as HTML)
/s/{id}Short-link redirect to preview

Tech stack

LayerTechnology
FrameworkTanStack Start (React 19, SSR)
RoutingTanStack Router
StylingTailwind CSS v4 + shadcn/ui
DatabaseSupabase
DeployCloudflare Workers

Public API reference

Three HTTP endpoints are exposed under /api/public/. All support CORS (Access-Control-Allow-Origin: *) for cross-origin use.

GET /api/public/iframe-check No auth

Checks whether a target URL can be embedded in an iframe by the preview app.

Query parameters

ParamRequiredDescription
urlYesTarget site URL to check

How it works

  1. Validates the URL
  2. Fetches the target with a browser User-Agent (4 second timeout)
  3. Reads X-Frame-Options and CSP frame-ancestors headers
  4. Compares allowed origins against the preview app's origin

Response

{
  "iframeable": true,
  "status": 200,
  "xFrameOptions": null,
  "csp": null
}

Returns iframeable: false when headers block embedding, or on network/timeout errors. Cached for 1 hour (max-age=3600). Each call is logged with provider embeddable or non-embeddable.

GET /api/public/screenshot No auth

Returns a PNG/JPEG/GIF screenshot of a target URL. Proxies Microlink.io server-side with optional Firecrawl fallback when FIRECRAWL_API_KEY is set.

Query parameters

ParamRequiredDefaultDescription
urlYesValid HTTP(S) URL
widthNo1440320–2560 viewport width
heightNo900480–2560 viewport height
freshNoSet to 1 to bypass cache and force re-capture
attemptNoRetry attempt number (used with fresh=1)

Upstream behavior

  • Microlink (primary): waitUntil=auto, waitForTimeout 5–8s, adblock, cookie-banner CSS/scripts, 2 server-side strategies
  • Firecrawl (fallback): used when Microlink fails and API key is configured
  • Provider logged as microlink or firecrawl

Response

Raw image bytes with appropriate Content-Type. Returns 502 JSON on failure (28s server timeout). Cached for 24 hours unless fresh=1.

Example

GET /api/public/screenshot?url=https%3A%2F%2Fexample.com&width=1440&height=900

POST /api/public/shorten Bearer token

Programmatically create a short link for a preview configuration. Used by integrations and automation.

Authentication

Authorization: Bearer <your-token>

Returns 401 Unauthorized if the token is missing or invalid.

Request body

{
  "url": "https://example.com",
  "widget": "<script src=\"...\" data-widget-token=\"...\" ...></script>",
  "backdrop": "auto",
  "params": { "utm_source": "newsletter", "ref": "abc" }
}
FieldRequiredDescription
urlYesClient site URL (max 2000 chars)
widgetYesFull widget embed script (max 20000 chars)
backdropNoauto, iframe, thumbnail, or screenshot
paramsNoExtra query params merged into the preview URL (max 20 keys, key ≤ 100 chars, value ≤ 2000 chars). Reserved keys (url, backdrop, src, data-widget-token, data-agent-id, data-agent-profile-id, data-base-url) return 400.

Response

{
  "id": "abc1234",
  "shortUrl": "https://preview.example.com/s/abc1234",
  "previewUrl": "https://preview.example.com/preview?url=...",
  "deduped": false
}

Same URL + widget + backdrop + extra params returns the existing ID (deduplicated, with deduped: true). Different extras produce a new short link. The app normalizes URLs and extracts widget attributes automatically from the script string.

Example

curl -X POST https://preview.example.com/api/public/shorten \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <your-token>" \
  -d '{
    "url": "https://bluemachines.ai",
    "widget": "<script src=\"...\" data-widget-token=\"...\" data-agent-id=\"...\" data-agent-profile-id=\"...\"></script>",
    "backdrop": "auto"
  }'

ALL /api/public/widget-proxy/* No auth

Transparent reverse proxy to https://api.bluemachines.ai. Used by the preview page so the embedded widget can call the BlueMachines API from the preview origin without CORS issues. Forwards method, path, query, headers (minus Authorization, Cookie, Set-Cookie), and body; copies the upstream Content-Type back.

GET  /api/public/widget-proxy/v2/agents/<id>
POST /api/public/widget-proxy/v2/chat/messages

All requests are recorded in api_logs with api_type = "widget-proxy". Binary upstream bodies are summarized as [binary N bytes] in the log.

API summary

EndpointMethodAuthPurpose
/api/public/iframe-check GET None Check if URL is iframe-embeddable
/api/public/screenshot GET None Get page screenshot as image
/api/public/shorten POST Bearer token Create shareable short link
/api/public/widget-proxy/* ALL None Reverse proxy to api.bluemachines.ai

All endpoints support OPTIONS for CORS preflight. Error responses return JSON: { "error": "description" } with appropriate HTTP status codes (400, 401, 502, etc.). Every request is recorded in the api_logs table — see API logs.

API logs

All public API endpoints write a row to the api_logs Supabase table on every request. View them at /api-logs (linked from the home page header).

Logged fields

FieldDescription
api_typescreenshot, iframe-check, shorten, or widget-proxy
providermicrolink / firecrawl for screenshots; embeddable / non-embeddable for iframe checks; widget-proxy for proxy calls; empty for shorten
successWhether the API returned a successful result
status_codeHTTP status returned to the client
duration_msServer-side request duration
requestJSON payload — includes client URL, dimensions, etc.
responseJSON summary of the response (not raw image bytes)

UI features

  • Paginated table (20 per page) sorted newest first
  • Client URL column extracted from request.url
  • Success / failed badge and HTTP status code
  • View JSON dialog with full request and response objects

Apply the api_logs migration in supabase/migrations/ before using this feature. Logging is fire-and-forget — a logging failure never blocks the API response.

Error handling & edge cases

ScenarioBehavior
Site blocks iframe (X-Frame-Options / CSP) Auto mode falls back to thumbnail screenshot
Thumbnail screenshot fails (WAF, timeout) Up to 3 client retries with fresh=1, then static BlueMachines logo; widget still works
Auto mode iframe slow or blocked Falls back to thumbnail within ~5–9s (header check + iframe reveal caps)
Cookie consent banners in screenshots Microlink adblock + injected CSS/click script; fresh=1 bypasses bad cached captures
Invalid or missing preview params "No preview data" message with link back to home
Short link ID not found Redirect to home page
Widget renders regardless Chat widget overlay always appears on top of any backdrop state