Lumi SDK script tag
One async <script> tag in your <head>, one or more <div data-lumi-slot> elements where you want ads. No backend changes. Works with any LLM provider (OpenAI, Anthropic, Gemini, open-source) and any framework (LangChain, Vercel AI SDK, CrewAI, plain React) — Lumi attaches at the rendering surface.
boostboss.ai/lumi.js today.
The slot-based API documented on this page is shipped. <script src="https://boostboss.ai/lumi.js"> resolves and works against the live Boost Boss backend. Try the sandbox flow with data-publisher-id="pub_test_demo" — no signup required to verify integration end-to-end. The legacy BoostBoss.init API at boostboss.ai/sdk.js remains supported for backward compatibility. Demand-side fill is in early beta; expect lower fill rates than mature ad networks until our Founding Publisher cohort fills out.
Prerequisites
- A Boost Boss publisher account — sign up if you haven't.
- A web app you control where you can edit the HTML
<head>and place markup in templates/components. - Modern browsers (last 2 versions of Chrome, Safari, Firefox, Edge). No IE11.
Installation
Add the snippet to your <head>. Async; no impact on first paint.
<script async src="https://boostboss.ai/lumi.js" data-publisher-id="pub_xxx"> </script>
Replace pub_xxx with your publisher ID from the dashboard.
Slot placement
Drop a <div> with data-lumi-slot wherever you want an ad. Lumi auto-discovers slots on page load and on DOM mutations (so SPA route changes work without ceremony).
<div data-lumi-slot="banner"></div> <div data-lumi-slot="sidebar"></div> <div data-lumi-slot="inline" data-lumi-context="checkout flow"></div>
Slot data attributes
| Attribute | Values | Description |
|---|---|---|
data-lumi-slot | banner · sidebar · inline · interstitial | Ad format. Lumi picks creative dimensions accordingly. |
data-lumi-context | any string | Context signal for matching. Defaults to page <h1> + URL path. |
data-lumi-frequency | once · session · always | How often this slot refreshes. Default: session. |
data-lumi-fallback | any HTML id | Element to show when no ad fills the slot (e.g. your own promo). |
Theming
Lumi reads CSS variables from the slot's computed style — set them once on :root or scope per slot.
:root { --lumi-primary: #FF2D78; /* CTA button color */ --lumi-text: #0F0F1A; /* body text */ --lumi-muted: #6B7280; /* sponsored label */ --lumi-bg: #FFFFFF; /* card background */ --lumi-radius: 12px; /* corner radius */ --lumi-font: 'Inter', sans-serif; }
The default look is neutral; styled themes match your brand without a CSS reset war.
Programmatic API
For SPA route changes or programmatic refreshes, use the global window.Lumi object. Available after the script loads (lumi:ready event fires).
Lumi.refresh(slotId?)
Re-fetches and re-renders ads. Pass a slot element or selector to refresh just one slot; omit to refresh all.
// refresh all slots after a route change router.on('navigated', () => Lumi.refresh()); // refresh a specific slot Lumi.refresh('#main-banner');
Lumi.destroy()
Removes all rendered ads and disconnects observers. Call when you tear down a page section that contained Lumi slots.
Lumi.render(slotEl, opts)
Manually mount a slot. Useful for slots created after page load that aren't auto-discovered (rare; the MutationObserver covers most cases).
const el = document.getElementById('late-bound-slot'); Lumi.render(el, { format: 'inline', context: 'pricing page' });
Conversion tracking
For conventional advertiser-side conversions (the user clicks an ad, lands on a separate thank-you page, fires the conversion there) advertisers should install pixel.js on their landing/thank-you page — see the conversion beacon docs.
For publisher-side conversions — when the user converts inside the same surface that hosted the ad (in-app signup, in-page checkout) — call Lumi.trackConversion():
// After the user signs up / completes the action your ad promoted: Lumi.trackConversion({ type: 'signup', // matches campaigns.conversion_event_types value: 29.99, // USD; required for ROAS / target_roas optimization currency: 'USD', // optional; defaults to 'USD' externalId: 'order_98123', // optional — your order/user id for reconciliation });
Lumi auto-resolves the adId + auctionId from the most recent slot it rendered. To target a specific slot when multiple ads are on screen, pass slot:
Lumi.trackConversion({ type: 'purchase', slot: '#sidebar-ad', value: 49.0 });
Failures emit a lumi:error event and are surfaced via the silent-failure observability endpoint.
Event handling
Lumi dispatches DOM events on window. Listen for any of:
window.addEventListener('lumi:ready', () => { /* SDK loaded */ }); window.addEventListener('lumi:impression', (e) => { const { adId, advertiserId, slot, format } = e.detail; analytics.track('ad_impression', { adId, format }); }); window.addEventListener('lumi:click', (e) => { /* user clicked */ }); window.addEventListener('lumi:close', (e) => { /* dismissed */ }); window.addEventListener('lumi:no_fill', (e) => { /* slot stayed empty */ }); window.addEventListener('lumi:error', (e) => { /* see e.detail.code */ });
SSR and SPA support
Lumi is fully client-side — render the slot DOM server-side, the SDK hydrates on load. Specifics for popular frameworks:
Next.js (App Router)
Add the script in app/layout.tsx via next/script with strategy="afterInteractive". Slots can live in any server or client component.
import Script from 'next/script'; export default function RootLayout({ children }) { return ( <html><body>{children} <Script src="https://boostboss.ai/lumi.js" data-publisher-id="pub_xxx" strategy="afterInteractive" /> </body></html> ); }
Nuxt 3
Use useHead in app.vue or a layout, with defer: true.
Remix
Add to root.tsx via <Scripts> sibling. No SSR-time fetch needed — Lumi only runs in the browser.
SPA route changes
Lumi's MutationObserver auto-detects slots that mount/unmount as you change routes. If you find specific slots aren't refreshing, call Lumi.refresh() explicitly in your route hook.
Error handling
The snippet never throws into your app. Failures resolve to a lumi:error event and the slot stays empty (or shows your data-lumi-fallback element if configured).
| Error code | Meaning |
|---|---|
BBX_AUTH | Publisher ID unknown or revoked. Check dashboard. |
BBX_CSP | Content Security Policy blocks the script. See "CSP" below. |
BBX_NETWORK | Network failure or CORS issue. Slot stays empty. |
BBX_NO_FILL | No matching advertiser. Not an error per se — emitted as lumi:no_fill for clarity. |
CSP (Content Security Policy)
If your site uses CSP, allow Lumi's host:
script-src 'self' https://boostboss.ai; connect-src 'self' https://boostboss.ai; img-src 'self' https://boostboss.ai data:;
Testing
Use data-publisher-id="pub_test_demo" in development. Sandbox always returns a fixed creative regardless of context, so you can verify slot rendering in dev without hitting real demand-side calls.
<script async src="https://boostboss.ai/lumi.js" data-publisher-id="pub_test_demo" data-debug="true"> </script>
Set data-debug="true" to log every event to the console. Switch to your live publisher ID before deploying.
Going live
- Replace sandbox publisher ID with your live
pub_xxx. - Remove
data-debug="true". - Confirm Stripe Connect is set up.
- Add Boost Boss to your CSP if you have one (see above).
- Verify one impression appears in your dashboard within 60 seconds of going live.
Troubleshooting
The slot is empty.
Check the console for lumi:no_fill or lumi:error events. no_fill means there's no matched advertiser right now; error means something's misconfigured (auth, CSP, network).
The script blocks page load.
It shouldn't — the snippet uses async. If you see a blocking warning in Lighthouse, double-check the async attribute is actually present on your <script> tag.
Ads don't refresh between SPA routes.
Most apps work without intervention thanks to MutationObserver. If yours doesn't, call Lumi.refresh() in your route-change hook (Next.js: useRouter().events; Vue Router: router.afterEach; React Router: useLocation with an effect).
Theming doesn't apply.
Make sure the CSS variables are set on a parent of the slot (most commonly :root). The slot reads getComputedStyle at mount time.
Ads pass page-speed budgets in dev but not production.
Caching: lumi.js is cached aggressively. Bust it by appending ?v=YYYYMMDD if you suspect stale SDK behavior. Most production issues are CDN-caching mismatches, not SDK regressions.
Changelog
See the API reference changelog for SDK release notes.