---
source: sighthound-developer-portal
url: https://dev.sighthound.com/redactor/examples/embedded-ui/
markdown-url: https://dev.sighthound.com/redactor/examples/embedded-ui.md
title: "Embedded Redactor UI"
description: "Embed the Redactor editor in your own application using same-domain or cross-domain proxy deployment."
content-type: text/markdown
---

> For AI agents: a documentation index is available at [llms.txt](https://dev.sighthound.com/llms.txt). Markdown versions are available at matching `.md` URLs.

# Embedded Redactor UI

<div class="portal-doc-hero portal-doc-hero--compact" markdown>
<p class="portal-doc-kicker">Redactor tutorial</p>
<p class="portal-doc-intro">Embed the Redactor editor inside your application with proxy routing, project-scoped sessions, and JavaScript API controls.</p>
<div class="portal-doc-actions">
    [Start the tutorial](#getting-started)
    [Review API basics](../api-basics/)
    [Open JavaScript reference](../../reference/embedded-editor/)
</div>
</div>

<div class="portal-feature-grid">
    <div class="portal-feature-card">
        <h3><i class="portal-icon" data-lucide="route"></i> Proxy deployment</h3>
        <p>Choose same-domain routing for simpler cookies or cross-domain routing when infrastructure requires separation.</p>
    </div>
    <div class="portal-feature-card">
        <h3><i class="portal-icon" data-lucide="folder-kanban"></i> Project sessions</h3>
        <p>Group prepared media by project ID so embedded users see the correct sessions in the editor.</p>
    </div>
    <div class="portal-feature-card">
        <h3><i class="portal-icon" data-lucide="code"></i> JavaScript API</h3>
        <p>Mount the editor, listen for events, and customize export/session behavior from your web app.</p>
    </div>
</div>

This guide shows how to embed the Redactor editor into your web application. The embedded editor lets users perform video redaction directly within your application's interface, providing a seamless experience without redirecting to a separate Redactor instance.

## Overview

Embedding Redactor requires three components:

1. **Redactor server** configured with API mode enabled
2. **A reverse proxy** that routes requests from users' browsers to the Redactor server
3. **JavaScript code** that renders the Redactor editor into your web page

!!! success "Everything is included in our example project"

    Don't worry! We provide a Docker Compose example that has all of this set up and ready to run. You can follow along with the tutorial below without configuring anything yourself.

There are two deployment approaches based on where your proxy lives:

| Approach | When to Use |
|----------|-------------|
| **Same-Domain** | Your web application is served from `https://yourdomain.com/`, and you can create a reverse proxy for Redactor like `https://yourdomain.com/proxy/redactor/`. This keeps cookies first-party and requires minimal configuration. |
| **Cross-Domain** | Redactor has a different domain or subdomain than your web application. This requires explicit CORS headers and cookie configuration but offers more deployment flexibility. |

This guide walks through the **same-domain** approach step by step. If you ultimately need a cross-domain deployment, we still recommend starting with the same-domain approach due to its simplicity. When ready to move on to a cross-domain deployment, use the [Embedded Redactor UI Deployment Reference](embedded-ui-deployment.md).

## Getting Started

### Example Project Files

To help you get started quickly, Sighthound provides the [Redactor Agent Toolkit](../reference/agent-toolkit.md): a package with Docker Compose example configurations for both deployment approaches, along with reference materials, and LLM-optimized documentation so you can ask your AI coding agent questions about Redactor as you work. Contact [sales@sighthound.com](mailto:sales@sighthound.com) to obtain the toolkit and an evaluation serial number.

The example project includes:

- Docker Compose file with profiles for each deployment approach
- NGINX configuration files for same-domain and cross-domain proxies
- Example HTML files demonstrating the embedded editor
- CSS override and translation files for customization

!!! warning "Apple Silicon Not Supported"

    The Redactor server currently requires an Intel/AMD (x86_64) processor. M-series Macs (Apple Silicon) are not supported at this time.

To run the same-domain example:

```bash
# Extract and enter the directory
cd sighthound-redactor-api-examples

# Start the same-domain demo environment
docker compose --profile with-same-domain-proxy up -d
```

If this is your first time running Redactor, set up your admin user and license at http://localhost:9000/admin.

### Key Concepts

Before diving in, it's important to understand two concepts used throughout this guide:

**Session**

A redaction session represents a video along with all its associated redaction data (detected objects, manual redactions, settings, etc.). Sessions can be exported and saved, allowing users to restore and continue their work later. The same source video can be used to create multiple independent redaction sessions, each with its own `sessionId`.

We recommend always providing your own `sessionId` when making your initial MEDIA_PREPARE API requests, since it makes it easier to reference a specific session in subsequent API calls. This ID is also included in events like Operation webhooks and those emitted by the editor (such as when a user exports a video). The `sessionId` is immutable once set.

**Project**

A project is a logical container that groups one or more redaction sessions (videos) together. Projects are useful for organizing sessions by customer, case, user, or any other logical grouping that fits your application. The embedded editor displays one project at a time, showing all sessions that share the same `projectId` in the video list.

You must specify a `projectId` during the initial `MEDIA_PREPARE` API request. This same `projectId` is used when initializing the editor in JavaScript to display the correct sessions to the user.

Since the `projectId` controls which sessions are visible in the embedded editor, we recommend using a complex, unguessable string (such as a UUID) rather than predictable values like "customer-1". This helps prevent users from accessing sessions they shouldn't see. For stronger access control, your proxy can validate user authentication from your application and enforce project-level permissions before forwarding requests to Redactor.

---

## Tutorial

This tutorial walks through loading videos, embedding the editor, and exporting redacted videos using the Docker Compose example environment.

!!! tip "Keep Redactor open while making API calls"

    We recommend opening the Redactor UI at [http://localhost:9000](http://localhost:9000) in your browser while working through this tutorial. It allows you to see in real-time what's happening on the server as you make API calls.

### Step 1: Load Videos via API

Before the embedded editor can display videos, they must be loaded into Redactor using the `MEDIA_PREPARE` API.

You load a video by providing a path to the input video (`inputUri`) and an optional path where all of the redaction state data will be stored (`outputUri`). If an `outputUri` is not provided, files are stored in Redactor's internal storage and can be moved to a different location (e.g., AWS S3, Azure Blob Storage, local filesystem) in subsequent API calls.

See the [videos:process API Reference](../reference/videos/process.md) for all available options and supported input/output paths (HTTP, file system, AWS S3, Azure Blob Storage, etc.).

In this example, we'll load two videos for a user to redact in the browser. We'll use the same `projectId` for both videos so they appear together in the editor, and each video gets a unique `sessionId`.

**API Request**

`POST /api/v1/videos:process`

**Video 1:**

=== "curl"

    ```sh
    curl --location --request POST 'http://localhost:9000/api/v1/videos:process' \
        --header 'Content-Type: application/json' \
        --header 'Authorization: Bearer YourRedactorApiToken' \
        --data-raw '{
            "inputUri": "https://sh-io-downloads.s3.amazonaws.com/videos/garage-top.mp4",
            "features": ["MEDIA_PREPARE"],
            "videoContext": {
                "prepareConfig": {
                    "sessionId": "media-id-240403-1"
                }
            },
            "projectId": "my-project-id"
        }'
    ```

=== "JSON Body"

    ```json
    {
        "inputUri": "https://sh-io-downloads.s3.amazonaws.com/videos/garage-top.mp4",
        "features": ["MEDIA_PREPARE"],
        "videoContext": {
            "prepareConfig": {
                "sessionId": "media-id-240403-1"
            }
        },
        "projectId": "my-project-id"
    }
    ```

**Video 2:**

This example also includes `HEAD_DETECTION` in the features array, demonstrating how you can kick off auto-detection directly from the API call.

=== "curl"

    ```sh
    curl --location --request POST 'http://localhost:9000/api/v1/videos:process' \
        --header 'Content-Type: application/json' \
        --header 'Authorization: Bearer YourRedactorApiToken' \
        --data-raw '{
            "inputUri": "https://sh-io-downloads.s3.amazonaws.com/videos/Dancing.mp4",
            "features": ["MEDIA_PREPARE", "HEAD_DETECTION"],
            "videoContext": {
                "prepareConfig": {
                    "sessionId": "media-id-240403-2"
                }
            },
            "projectId": "my-project-id"
        }'
    ```

=== "JSON Body"

    ```json
    {
        "inputUri": "https://sh-io-downloads.s3.amazonaws.com/videos/Dancing.mp4",
        "features": ["MEDIA_PREPARE", "HEAD_DETECTION"],
        "videoContext": {
            "prepareConfig": {
                "sessionId": "media-id-240403-2"
            }
        },
        "projectId": "my-project-id"
    }
    ```

**API Response**

```json
{
   "name": "projects/{project-id}/locations/{location-id}/operations/{operation-id}"
}
```

!!! note

    API calls to `/api/v1/videos:process` start operations that may take time to complete (e.g., loading large videos). Redactor uses an asynchronous API that returns an operation name you can use to check status. Monitor the operation by querying `/api/v1/{operation-name}` or by specifying a webhook endpoint for progress and completion events. Subscribing to `COMPLETION` events is recommended so you know when the video is ready or if errors occurred.

For more API examples, see [Redactor API Basics](api-basics.md).

---

### Step 2: Embed the Editor

Once your `MEDIA_PREPARE` operation is complete (from Step 1), you can display the editor UI on your website.

The Docker Compose example includes a webpage that demonstrates the embedded editor running in a page your create. Open `https://127.0.0.1:3443/` in your browser to see it in action. We use `127.0.0.1` instead of `localhost` to keep cookies separate in case you need to access Redactor directly at `http://localhost:9000`. You'll see a browser security warning due to the self-signed certificate—select the option to proceed. Once loaded, Redactor's Projects page will display, showing any videos you've loaded and giving you full access to the redaction tools.

The sample HTML is at `config/same-domain/nginx/html/index.html` in the example project. Here's a general overview:

```html
<!-- Redactor mounts into the following container. It will size itself to fill this element. -->
<div id="redactor-editor"></div>

<script src="https://127.0.0.1:3443/proxy/redactor/public/sighthound-redactor.js"></script>
<script>
  // Reference to the mount element for Redactor.
  const redactorElement = document.getElementById("redactor-editor");

  // The Redactor server/proxy base URL. Update this if your server
  // is not running at https://127.0.0.1:3443/proxy/redactor/.
  const redactorServer = "https://127.0.0.1:3443/proxy/redactor";

  // Where to connect inside Redactor:
  // - Use a stable projectId that matches your MEDIA_PREPARE calls.
  // - Provide sessionId to open a specific video immediately, or leave
  //   it empty to show the project's video list first.
  const redactorWhereTo = { projectId: "my-project-id", sessionId: "" };

  // Create the embedded editor instance. Shadow DOM is recommended.
  // When shadowed is true, include font and CSS dependencies in cssHref.
  const redactorEditor = new redactor.default(
    redactorElement,
    redactorServer,
    redactorWhereTo,
    {
      shadowed: true,
      cssHref: {
        fonts: [
          "/proxy/redactor/public/sighthound-redactor-fonts.css",
        ],
        main: [
          "/proxy/redactor/public/sighthound-redactor.css",

          // Optional CSS override file to customize the editor UI
          "/public/sighthound-redactor-override.css",
        ],
      },
      // Customization options for the editor UI.
      customize: {
        export: {
          download: "show", // The download button behavior: show, hide, or eventOnly
          save: "show", // Displays a custom button next to the download button
        },
        session: {
          remove: "show", // The context menu item for removing a video: show, hide, eventOnly
          deleteRedactionData: "show", // The context menu item for deleting redaction data: show, hide
        },
      },
      // Optional: overlay translation strings to change wording in the UI.
      translationOverwritesUrl: "/public/translations.overlay.hjson",
    }
  );

  // Render the editor after MEDIA_PREPARE has been completed for the target project/session.
  redactorEditor.renderEditor((err) => {
    if (err) { console.error("EDITOR RENDER FAILED:", err); }
  });

  // Observable event names for context menu, editor open/close, and export flow.
  const sessionEventNames = ["redactor.session.remove", "redactor.session.delete_redaction_data"];
  const editorEventNames = ["redactor.editor.enter", "redactor.editor.exit"];
  const exportEventNames = ["redactor.export.cancel", "redactor.export.download", "redactor.export.save", "redactor.export.submit"];

  [...editorEventNames, ...exportEventNames, ...sessionEventNames].forEach(evtName =>
    redactorElement.addEventListener(evtName, evt => {
      console.log("EVENT -", evt.type, "-", JSON.stringify(evt.detail, null, 4));
      // Tip: For export, you can intercept and run rendering via your backend
      // instead of Redactor handling it automatically by calling evt.preventDefault().
      // evt.preventDefault();
    })
  );

  // Close the currently open session. Required before making API calls that operate on the session.
  function onClose() {
    redactorEditor.cleanup()
      .then(result => { if (result) console.log("cleanup completed"); })
      .catch(err => { console.error("CLEANUP FAILED:", err); });
  }

  // Completely destroy the embedded editor instance and free resources.
  function onDestroy() { redactorEditor.destroy(); }

  // Open the export dialog for the active session.
  function onExport() {
    if (!redactorEditor.sendCommand("export")) {
      console.warn("Export command ignored. The editor must be open/active.");
    }
  }
</script>
```

!!! tip "Configuration Locations"

    When changing your domain or proxy path, update these **two locations**:

    1. The `<script src="...">` tag in the `<head>`
    2. The `redactorServer` variable in JavaScript

**Open a Specific Video Directly**

To open a specific video immediately instead of showing the project's video list, provide the `sessionId`:

```javascript
const redactorWhereTo = {
    projectId: "my-project-id",
    sessionId: "video-001"  // Opens this video directly
};
```

For the full JavaScript API documentation including constructor parameters, methods, and events, see the [Embedded Editor Reference](../reference/embedded-editor.md).

---

### Step 3: Export Redacted Videos

To trigger a render and export, you need to tell Redactor which session to export and where to put the files. The `inputUri` uses the `session://` scheme with the `sessionId` you defined when loading the video, and the `outputUri` points to the destination path.

Redactor determines whether to output only the redacted video or all of the redaction state based on whether the `outputUri` ends in a trailing slash:

- **No trailing slash** (e.g., `.../redacted.mp4`) — outputs only the redacted video with your specified filename
- **Trailing slash** (e.g., `.../output/`) — outputs all redaction state, including the redacted video named `redacted.mp4`

!!! tip "UI Export vs API Export"

    By default, Redactor's UI displays an Export Video button that users can click to render and download directly to their computer. If you prefer to control this process instead, you can hide the button by setting `customize.export.download="hide"` in the editor initialization, then trigger exports via the API using the `MEDIA_RENDER` feature.

**API Request**

`POST /api/v1/videos:process`

**Video 1** (Export only the redacted video):

=== "curl"

    ```sh
    curl --location --request POST 'http://localhost:9000/api/v1/videos:process' \
        --header 'Content-Type: application/json' \
        --header 'Authorization: Bearer YourRedactorApiToken' \
        --data-raw '{
            "inputUri": "session://media-id-240403-1",
            "features": ["MEDIA_RENDER"],
            "outputUri": "file:///data/media-id-240403-1-output/redacted.mp4"
        }'
    ```

=== "JSON Body"

    ```json
    {
        "inputUri": "session://media-id-240403-1",
        "features": ["MEDIA_RENDER"],
        "outputUri": "file:///data/media-id-240403-1-output/redacted.mp4"
    }
    ```

**Video 2** (Export all redaction state and redacted.mp4):

=== "curl"

    ```sh
    curl --location --request POST 'http://localhost:9000/api/v1/videos:process' \
        --header 'Content-Type: application/json' \
        --header 'Authorization: Bearer YourRedactorApiToken' \
        --data-raw '{
            "inputUri": "session://media-id-240403-2",
            "features": ["MEDIA_RENDER"],
            "outputUri": "file:///data/media-id-240403-2-output/"
        }'
    ```

=== "JSON Body"

    ```json
    {
        "inputUri": "session://media-id-240403-2",
        "features": ["MEDIA_RENDER"],
        "outputUri": "file:///data/media-id-240403-2-output/"
    }
    ```

**API Response**

```json
{
   "name": "projects/{project-id}/locations/{location-id}/operations/{operation-id}"
}
```

---

## Operation Status

All requests sent to `/api/v1/videos:process` are asynchronous, returning an operation name that you can use to check status.

**API Request**

`GET /api/v1/{operation-name}`

Example: `GET /api/v1/projects/{project-id}/locations/{location-id}/operations/{operation-id}`

**API Response**

```json
{
   "name": "projects/{project-id}/locations/{location-id}/operations/{operation-id}",
   "metadata": {
       "@type": "sighthound.cloud.v1.Progress",
       "progress": {
           "inputUri": "http://example.com/path/to/video.mp4",
           "progressPercent": 100,
           "startTime": "2020-04-01T22:13:17.978847Z",
           "updateTime": "2020-04-01T22:13:29.576004Z"
       }
   },
   "done": true,
   "response": {
       "@type": "sighthound.cloud.v1.videos.ProcessResponse",
       "processResults": {
           "outputUri": "file:///data/media-id-240403-1-output/"
       }
   }
}
```

When the `done` value is `true`, the operation is complete, and either a `response` or `error` property will exist to indicate success or failure.

---

## Webhook Notifications

Webhook notifications can be enabled using the `notifications` property in your API request. We recommend subscribing to `COMPLETION` events, though `START` and `PROGRESS` are also available. The Redactor server will POST messages containing the same content as the Operation Status response to the `uri` you provide.

!!! tip "Testing webhooks without a server"

    During evaluation, you can use [webhook.site](https://webhook.site) to receive and inspect webhook payloads without setting up your own server.

Example request body with webhooks:

```json
{
   "inputUri": "https://example.com/path/to/video.mp4",
   "features": ["MEDIA_PREPARE"],
   "videoContext": {
        "prepareConfig": {
            "sessionId": "my-media-id1"
        }
   },
   "projectId": "my-project-id",
   "notifications": [{
      "method": "HTTP_POST",
      "uri": "https://example.com/mywebhook/endpoint",
      "events": ["START", "PROGRESS", "COMPLETION"]
   }]
}
```

---

## Customization

### CSS Overrides

Create a CSS file to customize the editor's appearance:

```css
/* Example: Hide the Redactor logo */
.sh-rui-logo {
    display: none !important;
}

/* Example: Custom header background */
.sh-rui-header {
    background-color: #1a365d !important;
}
```

Include your override file in the `cssHref.main` array:

```javascript
cssHref: {
    main: [
        "/proxy/redactor/public/sighthound-redactor.css",
        "/path/to/your-overrides.css"
    ]
}
```

### Translation Overrides

Customize UI text by providing a translation overlay file in [HJSON format](https://hjson.github.io/):

```hjson
{
    status.exporting.complete.title: {
        en-US: "Render complete"
    }
    button.export: {
        en-US: "Process Video"
    }
}
```

Reference the file in your options:

```javascript
translationOverwritesUrl: "/path/to/translations.overlay.hjson"
```

---

## Production Considerations

When moving from the demo environment to production, you'll need to set up the following infrastructure:

### Infrastructure Requirements

- **Redactor server** - Self-hosted via Docker or on Windows
- **API access and Bearer Token** - The API must be enabled and a bearer token specified for backend communication with Redactor's API
- **HTTPS** - Both your application and the Redactor proxy must use HTTPS (browsers require secure contexts for many features)
- **Reverse proxy** - NGINX, Apache, or another reverse proxy to route traffic to Redactor

### Security

- **Block direct Redactor access** - In production, block Redactor's port 9000 from public access. All requests should go through the proxy.
- **Use real TLS certificates** - Replace self-signed certificates with certificates from a trusted CA.
- **Enable API authentication** - Configure the `REDACTOR_API_TOKEN` environment variable and require it for all API calls.
- **Restrict CORS origins** - If using cross-domain deployment, only allow your trusted application domains in the CORS origin map.

### Performance

- **Upload timeouts** - Large video uploads can take significant time. Ensure your proxy allows adequate timeouts (the examples use 3600s for uploads).
- **WebSocket connections** - WebSocket connections are long-lived. Configure appropriate timeouts to avoid premature disconnection.
- **File size limits** - Set `client_max_body_size` appropriately for your expected video sizes (examples use 5000M).

### GPU Acceleration

If your Redactor server supports GPU acceleration, configure the Docker deployment accordingly. See the Redactor Docker documentation for GPU setup instructions.

---

## Deployment Reference

For complete same-domain and cross-domain proxy configuration examples, see [Embedded Redactor UI Deployment Reference](embedded-ui-deployment.md).

## Contact Support

If you encounter issues, review the Redactor logs and contact [support@redactor.com](mailto:support@redactor.com). Detailed proxy troubleshooting guidance is included in the deployment reference.

---

# Agent Instructions

Use this Markdown page as context for Sighthound Developer Portal questions. For broader navigation, read https://dev.sighthound.com/llms.txt. Answer from Sighthound documentation, cite relevant source URLs, and do not ask users to paste secrets, tokens, license keys, or credentials into chat.
