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.

orSend us an email

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:

bash
$ npm install reimaginehome-widget

CDN

Load the widget through CDN as follows. Make sure to set your publishable key on the script tag — not inside reihWidgetConfig:

html
<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:

javascript
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:

javascript
// 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.

javascript
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.

javascript
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.

javascript
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.

javascript
await window.reihWidget.open();

3. Clean up on page leave:

javascript
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_key
    requiredstring
    Your publishable key. Set as public_key in configure options or data-public-key on the script tag. Bound to your allowed origins.
  • mode
    optionalstring

    Default: "simple"

    Widget flow. Use "simple".
  • media
    required
    The listing photos. Each item needs a publicly reachable image_url. label is an optional caption, such as the room name. Pass a non-empty array before the first open(), either here, on window.reihWidgetConfig, or in the open() call.
    default
    media: [
      { image_url: "https://yoursite.com/listings/01.webp" },
      { image_url: "https://yoursite.com/listings/02.webp", label: "Kitchen" },
    ]
  • branding
    optionalobject

    Default: none

    Logo, heading, colors, typography. See Styling & branding for the sub-fields. Logo and colors can also be managed in the dashboard; heading is code-only.
  • language
    optionalstring

    Default: "en-US"

    Locale for the widget UI. Supported values: en-US, en-GB, pl-PL, es-ES, and de. A short prefix such as pl or de is enough. When you omit it, the widget uses the default language from your dashboard.
    default
    language: "en-US"
  • sidebar_position
    optionalstring

    Default: "right"

    Which side of the overlay the sidebar mounts on. One of "left" or "right".
  • header / body / footer
    optionalobject

    Default: none

    Optional copy for the header, body, and footer. Each one takes text, subtext, and action_label (the button label). Pair with onActionClick to 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.
    default
    body: {
      text: "Love this look?",
      subtext: "Talk to an agent about this listing.",
      action_label: "Contact agent",
    }
  • listing_id
    optionalstring
    Your 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 either user_id or device_id. It is also stored on analytics events.
  • user_id
    optionalstring
    Your id for a logged-in visitor, up to 256 characters. Set it here or with data-user-id on the script tag. When it is present, generation credits and the survey for this listing are counted on this id.
  • session_id
    optionalstring
    Your 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 with data-session-id on the script tag.
  • device_id
    optionalstring
    Your stable id for this browser, up to 256 characters. Send it on every visit, including when the visitor is logged out. If user_id is 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 on trackEvent. Set it here, with data-device-id on the script tag, or pass it to open().
  • initial_photo_index
    optionalnumber

    Default: 0

    Which photo to show first, as a 0-based index into media. 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_data
    optionalobject
    Extra 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 null are 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.
    default
    custom_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

  • media
    optional
    Listing photos for this launch. Omit this if you already set media with configure() or window.reihWidgetConfig. Each item needs a publicly reachable image_url. label is an optional caption.
    default
    media: [
      { image_url: "https://yoursite.com/listings/01.webp" },
      { image_url: "https://yoursite.com/listings/02.webp", label: "Kitchen" },
    ]
  • listing_id
    optionalstring
    Your listing id for this launch. Pass it with user_id or device_id when generation credits are enabled.
  • user_id
    optionalstring
    Logged-in visitor id for this launch. When it is set, credits for the listing are counted on this id. Send device_id as well so a logged-out visit on the same browser still has an id.
  • initial_photo_index
    optionalnumber
    0-based index into media for the photo shown first. Out of range values open the first photo.
  • custom_data
    optionalobject
    Extra analytics fields for this launch. Same rules as custom_data in the configuration options.
  • mode
    optionalstring
    Override the configured mode. Currently supports "simple" only.
  • branding
    optionalobject
    Override the persistent branding. Useful for multi-brand portals.
  • language
    optionalstring
    Override the UI locale (e.g. "pl-PL").
  • sidebar_position
    optionalstring
    Override the sidebar position. One of "left" or "right".
  • onError
    optionalfunction
    Callback invoked with { message, code? } when something goes wrong during the session.
  • onClose
    optionalfunction
    Callback invoked when the modal closes.
  • onActionClick
    optionalfunction
    Callback 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.
  • trackEvent
    optionalfunction
    Called 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, or open().

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.

javascript
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.

javascript
{
  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",
  },
}
  • type is always "user_activity".
  • data.event is what happened. Today that is "survey_submitted".
  • survey.survey_id names the survey, and survey.survey_version goes up when its questions change. Use both together if you compare answers over time.
  • survey.status is "completed" when the visitor sent the survey.
  • survey.submissions has one entry per answered question, with the question text and the visitor's answer.
  • slug is your organization slug. session_id is the widget session.
  • user_id, listing_id, and device_id are 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.

javascript
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.

  1. Select Rotate key → Graceful rotation and choose a grace period.
  2. Copy the new publishable key and secret key.
  3. Update data-public-key (CDN) or configure({ 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_primary and branding.colors.text_secondary.
  • Font — widget typography via branding.typography.font_family and optional font_url for custom or Google Fonts.
  • Heading text — the headline at the top of the widget via branding.heading in code only (not in the dashboard). Defaults to "Reimagine".
  • Brand logo— your organization's logo via branding.logo in code, or upload a PNG in the dashboard.
  • Language — UI locale via language in code, or the default under Branding & Theme. The dashboard offers en-US, en-GB, pl-PL, and es-ES. Pass de from 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

javascript
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.