Partner Integration Documentation
Add ReimagineHome to your platform
Bring AI property visualization into your platform. Let users stage, redesign, declutter, and enhance property photos directly inside your product.
For a quick end-to-end experience, use the snippet below:
import { reihWidget } from 'reimaginehome-widget';
reihWidget.configure({ public_key: 'public_key' });
await reihWidget.open({
media: [
{ image_url: 'https://yoursite.com/listings/01.webp' },
],
});Get started with the widget
You'll need a publishable key to initialize the widget. Once you're onboarded, find and rotate keys anytime under Integration in your . Don't have one yet? Reach out and we'll get you set up.
Then follow the install and usage steps below. Pick CDN for a script-tag embed, or npm for React, Next.js, Vue, or Angular.
Install the widget
NPM
Install the package from npm:
$ npm install reimaginehome-widgetCDN
Load the widget through CDN as follows. Make sure to set your publishable key on the script tag — not inside reihWidgetConfig:
<script async
src="https://widget.styldod.com/widget.js"
data-public-key="public_key"
data-mode="simple"
data-user-id="user-123"
data-device-id="device-123"
data-session-id="sess_abc"></script>A few scalars can also live on the script tag: data-mode, data-user-id, data-device-id, and data-session-id. Everything else — including media, listing_id, branding, custom_data, and trackEvent — goes through window.reihWidgetConfig, configure(), or open().
Initialize the widget
npm integration
1. Import the widget:
import { reihWidget } from 'reimaginehome-widget';The namespaced reihWidget object is the recommended import. The same methods are also available as named exports and a default export, so all three forms below are equivalent:
// Named functions
import { configure, open, close, destroy } from 'reimaginehome-widget';
// Default export (same instance as the namespaced object)
import reihWidget from 'reimaginehome-widget';2. Configure once on page load:
Pass your publishable key and any persistent defaults. The widget keeps this until you reconfigure or destroy it.
reihWidget.configure({
public_key: 'public_key',
mode: 'simple',
sidebar_position: 'right',
onError: (err) => console.error(err),
onClose: () => console.log('widget closed'),
});3. Open on user action:
Call reihWidget.open() when the user clicks your trigger. Pass the listing photos here, along with listing_id and user_id when this click is for a specific listing. If you already set those in configure(), call open() with no arguments.
await reihWidget.open({
media: [
{ image_url: 'https://yoursite.com/listings/01.webp', label: 'Living room' },
{ image_url: 'https://yoursite.com/listings/02.webp', label: 'Kitchen' },
],
listing_id: 'listing-42',
user_id: 'user-123',
initial_photo_index: 1,
});4. Close or destroy when done:
Use close() to hide the overlay while keeping the session warm, and destroy() on route change in SPAs. See Session lifecycle for details.
CDN integration
1. Set config before opening:
Assign window.reihWidgetConfig with media and options. The publishable key stays on data-public-key.
window.reihWidgetConfig = {
media: [
{ image_url: 'https://yoursite.com/listings/01.webp', label: 'Living room' },
],
listing_id: 'listing-42',
user_id: 'user-123',
mode: 'simple',
sidebar_position: 'right',
};2. Open after the script loads:
After the loader script has loaded, call open(). With media already in reihWidgetConfig, you can call reihWidget.open() with no arguments; pass overrides when you need per-launch changes.
await window.reihWidget.open();3. Clean up on page leave:
window.reihWidget?.destroy();Widget configuration options
These fields apply to every open() until you reconfigure or call destroy(). Set them with reihWidget.configure() (npm) or window.reihWidgetConfig (CDN). Callbacks such as onError and onClose can live here or on a single open().
When the same field is set in more than one place, open() wins, then configure(), then window.reihWidgetConfig, then the script tag.
On a listing page, send the photos with listing_id, device_id for the browser, and user_id when the visitor is logged in. If your organization has generation credits turned on, a visit is charged when listing_id is set and at least one of those ids is present. Credits are counted on the user when user_id is present, and on the device otherwise. Each new style on a photo uses one credit. Opening a style already generated for that photo is free, and a failed generation returns the credit. Once the visitor runs out, they can still look at styles they already generated for that listing.
CDN exception. For CDN integrations, public_key is set on the script tag's data-public-key attribute, not inside window.reihWidgetConfig. All other fields below go in the config object.
Configuration params
- public_keyrequiredstringYour publishable key. Set as
public_keyin configure options ordata-public-keyon the script tag. Bound to your allowed origins. - modeoptionalstring
Default: "simple"
Widget flow. Use"simple". - mediarequiredThe listing photos. Each item needs a publicly reachable
image_url.labelis an optional caption, such as the room name. Pass a non-empty array before the firstopen(), either here, onwindow.reihWidgetConfig, or in theopen()call.defaultmedia: [ { image_url: "https://yoursite.com/listings/01.webp" }, { image_url: "https://yoursite.com/listings/02.webp", label: "Kitchen" }, ] - brandingoptionalobject
Default: none
Logo, heading, colors, typography. See Styling & branding for the sub-fields. Logo and colors can also be managed in the dashboard;headingis code-only. - languageoptionalstring
Default: "en-US"
Locale for the widget UI. Supported values:en-US,en-GB,pl-PL,es-ES, andde. A short prefix such asplordeis enough. When you omit it, the widget uses the default language from your dashboard.defaultlanguage: "en-US" - header / body / footeroptionalobject
Default: none
Optional copy for the header, body, and footer. Each one takestext,subtext, andaction_label(the button label). Pair withonActionClickto run your own handler on the host page when that button is clicked, for example to open a lead form. You can also set the same copy in the dashboard.defaultbody: { text: "Love this look?", subtext: "Talk to an agent about this listing.", action_label: "Contact agent", } - listing_idoptionalstringYour id for the listing this visit is about, up to 256 characters. Set it on the config object or pass it to
open(). Generation credits and the feedback survey need this plus eitheruser_idordevice_id. It is also stored on analytics events. - user_idoptionalstringYour id for a logged-in visitor, up to 256 characters. Set it here or with
data-user-idon the script tag. When it is present, generation credits and the survey for this listing are counted on this id. - session_idoptionalstringYour own reference for this visit, up to 256 characters. It is stored as
client_session_idand is separate from the widget's internal session. Set it here or withdata-session-idon the script tag. - device_idoptionalstringYour stable id for this browser, up to 256 characters. Send it on every visit, including when the visitor is logged out. If
user_idis absent, generation credits and the survey for this listing are counted on this id, and saved designs resume on it. It is also sent back ontrackEvent. Set it here, withdata-device-idon the script tag, or pass it toopen(). - initial_photo_indexoptionalnumber
Default: 0
Which photo to show first, as a 0-based index intomedia. If you leave it out, or it is not a whole number of 0 or more, or it points past the last photo, the widget opens on the first photo. An out-of-range index also logs a console warning. - custom_dataoptionalobjectExtra key/value pairs stored with analytics events for this visit, for your own reporting. Up to 20 keys are kept, in the order you pass them. Keys are lowercased and may contain only letters, digits, and underscores, up to 40 characters. Values can be strings (trimmed, and cut at 250 characters), numbers, or booleans. Objects, arrays, and
nullare dropped. A bad value never stops the widget from opening. The console lists anything that was skipped. Keep personal data out of here: names, emails, phone numbers.defaultcustom_data: { entry_point: "gallery_cta", }
Launch the widget
reihWidget.open(overrides?) is async and returns a Promise. It merges configure(), window.reihWidgetConfig, the script tag, and the overrides you pass, with the overrides winning. For a listing page, that merged config needs a non-empty media array.
reihWidget.open() overrides
- mediaoptionalListing photos for this launch. Omit this if you already set
mediawithconfigure()orwindow.reihWidgetConfig. Each item needs a publicly reachableimage_url.labelis an optional caption.defaultmedia: [ { image_url: "https://yoursite.com/listings/01.webp" }, { image_url: "https://yoursite.com/listings/02.webp", label: "Kitchen" }, ] - listing_idoptionalstringYour listing id for this launch. Pass it with
user_idordevice_idwhen generation credits are enabled. - user_idoptionalstringLogged-in visitor id for this launch. When it is set, credits for the listing are counted on this id. Send
device_idas well so a logged-out visit on the same browser still has an id. - initial_photo_indexoptionalnumber0-based index into
mediafor the photo shown first. Out of range values open the first photo. - custom_dataoptionalobjectExtra analytics fields for this launch. Same rules as custom_data in the configuration options.
- modeoptionalstringOverride the configured mode. Currently supports
"simple"only. - brandingoptionalobjectOverride the persistent branding. Useful for multi-brand portals.
- languageoptionalstringOverride the UI locale (e.g.
"pl-PL"). - onErroroptionalfunctionCallback invoked with
{ message, code? }when something goes wrong during the session. - onCloseoptionalfunctionCallback invoked when the modal closes.
- onActionClickoptionalfunctionCallback invoked with the section slug (
"header","body", or"footer") when the user clicks that section's action button. Runs on your host page, so use it to open a contact form, route to a listing, or fire your own analytics. - trackEventoptionalfunctionCalled on your page when the visitor sends the feedback survey, with their answers. See Widget activity for the payload. Set it in
configure(),window.reihWidgetConfig, oropen().
Widget activity
trackEvent runs on your page when something happens inside the widget. The event it sends today is "survey_submitted", when a visitor sends the feedback survey. The survey is an organization setting. It appears after the visitor has used their generations for that listing, and only on visits that include listing_id and either user_id or device_id.
The callback includes the visitor's answers, so you can store them on your side as they come in. The same answers can also be downloaded from Export in the dashboard.
reihWidget.configure({
public_key: 'public_key',
user_id: 'user-123',
listing_id: 'listing-42',
device_id: 'device-123',
trackEvent: (activity) => {
// activity.type === "user_activity"
// activity.data.event === "survey_submitted"
},
});The argument is one object. A field is left off when the widget does not have it, for example user_id or device_id when you did not pass one.
{
type: "user_activity",
data: {
event: "survey_submitted",
survey: {
survey_id: "widget_feedback",
survey_version: 1,
status: "completed",
submissions: [
{ question: "Can you imagine living here?", answer: "Yes" },
{
question: "Has your interest in this listing changed?",
answer: "More interested",
},
{
question: "Anything else you'd like to tell us?",
answer: "Loved the Scandinavian look!",
},
],
},
slug: "your-org",
session_id: "6a27e9058da96db2674ae0ca",
device_id: "device-123",
user_id: "user-123",
listing_id: "listing-42",
},
}typeis always"user_activity".data.eventis what happened. Today that is"survey_submitted".survey.survey_idnames the survey, andsurvey.survey_versiongoes up when its questions change. Use both together if you compare answers over time.survey.statusis"completed"when the visitor sent the survey.survey.submissionshas one entry per answered question, with thequestiontext and the visitor'sanswer.slugis your organization slug.session_idis the widget session.user_id,listing_id, anddevice_idare the values from your config.
If trackEvent throws, the widget logs a warning and keeps running.
Session lifecycle
close() hides the widget and keeps the session, so the next open() with no new options shows it again quickly. destroy() removes the iframe. Call destroy() when the user navigates to another page in a single-page app, otherwise the hidden widget stays attached to the page they left.
reihWidget.close();
reihWidget.destroy();Opening the widget pushes a history entry on the same URL. The browser Back button closes the widget, and the Back after that leaves the page. Forward opens the widget again.
Back removes the session. Forward then starts a new one from the config saved with configure() or window.reihWidgetConfig. Put the listing photos and ids there if Back and Forward should restore the same listing. Values passed only on that open() call apply to that launch. Closing from inside the widget, or with close(), keeps the session, and Forward shows that same session again.
Control integration defaults from the Dashboard
Most of what the widget shows can be managed without shipping code from your . Settings you save there become your organization's defaults and apply to every session automatically — so you can re-brand or switch the default language without a redeploy.
Code overrides dashboard defaults. Anything you pass to configure() or open() takes precedence for that session, so you can let the dashboard own the baseline and override per-launch when you need to (e.g. multi-brand portals).
- Branding & theme — logo, primary/secondary and text colors, typography, default language, and header, body, and footer CTA copy.
- AI & providers — choose a provider and bring your own API key per AI capability.
- Export — download widget activity, the photos and the generations made from them, as a CSV. When feedback surveys are enabled, the same screen can export the answers.
- Integration — publishable keys, allowed domains (origins), and the embed snippet.
- Team — invite teammates to your organization and manage their roles.
Rotate and revoke publishable keys
Publishable keys are managed under Integration in your . Each key shows its current status: Active, Grace (still valid but expiring), Expired, or Revoked.
Graceful rotation
Issues a new key while your current key stays valid for a grace period you set, so you can update your integration without interruption. The previous key expires automatically when the grace period ends.
- Select Rotate key → Graceful rotation and choose a grace period.
- Copy the new publishable key and secret key.
- Update
data-public-key(CDN) orconfigure({ public_key })(npm) with the new key before the grace period ends.
Immediate rotation
Issues a new key and revokes your existing key immediately, with no grace period. Use this when a key has been compromised. Any integration still using the previous key stops working at once.
Revoke a key
Revokes a single key immediately, without issuing a replacement. At least one usable key must remain, so revoke is unavailable when only one key is active — rotate instead.
The secret key is shown only once. Store it securely when it appears — it cannot be retrieved later. If it is lost, rotate again to issue a new pair.
Customize branding and appearance
The widget renders inside its own overlay and isn't styleable by your page CSS — but it accepts a constrained set of branding tokens so it can match your visual identity. You can set these in code via branding, or manage them visually under Branding & Theme in your .
What you can customize
- Primary color — buttons, highlights, and focus states. Pass any hex value that meets WCAG AA contrast with white text.
- Secondary color — accents, borders, and supporting UI elements via
branding.colors.secondary. - Text colour — heading and body text via
branding.colors.text_primaryandbranding.colors.text_secondary. - Font — widget typography via
branding.typography.font_familyand optionalfont_urlfor custom or Google Fonts. - Heading text — the headline at the top of the widget via
branding.headingin code only (not in the dashboard). Defaults to"Reimagine". - Brand logo— your organization's logo via
branding.logoin code, or upload a PNG in the dashboard. - Language — UI locale via
languagein code, or the default under Branding & Theme. The dashboard offersen-US,en-GB,pl-PL, andes-ES. Passdefrom your integration when you want German. - Style choices— the AI workflow options users see (staging, redesign, decluttering, exterior refresh, and more) are determined per session from room-type detection and your organization's backend configuration — not from host integration code.
Example
reihWidget.configure({
public_key: 'public_key',
branding: {
logo: 'https://yoursite.com/logo.png',
heading: 'Restyle this room',
colors: {
primary: '#1F6F4A',
secondary: '#03A1AC',
text_primary: '#071121',
text_secondary: '#555555',
},
typography: {
font_family: "'Inter', sans-serif",
font_url: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap',
},
},
language: 'en-US',
});Need something beyond this list?
We're open to additional branding and workflow customization for enterprise partners. Reach out and we'll work through what's possible for your platform.