Skip to content

Embeddable Martha Chat ​

Martha ships an embeddable chat client that third-party applications can use as a normal Martha chat surface. The v1 distribution model has two first-class install paths:

  • Hosted static bundle from the canonical Martha app host for plain HTML, CMS, and no-build pages.
  • Public npm package for React, Svelte, and modern JavaScript apps that want typed imports, version pinning, and normal dependency review.

What Is Published ​

Hosted assets are served from:

text
https://martha.nomadriver.co/embed/v1/

The default web component install is:

html
<martha-chat
  stylesheet-url="https://martha.nomadriver.co/embed/v1/martha-chat.css?v=0.3.0"
></martha-chat>
<script type="module" src="https://martha.nomadriver.co/embed/v1/martha-chat.ce.js?v=0.3.0"></script>

The v=0.3.0 query is a one-time migration key for browsers that may have cached the former immutable stable files. Apply it to both the loader and an explicit stylesheet-url; the script query is not inherited by CSS.

The v1 assets are:

  • martha-chat.ce.js - web component bundle for plain HTML and any host framework
  • martha-chat.es.js - ESM bundle with the imperative mountMarthaChat API
  • martha-chat.react.js - React wrapper
  • martha-chat.svelte.js - Svelte wrapper
  • martha-chat.css - shared styles

The npm package is:

bash
npm install @aiaiai-pt/martha-chat

Package exports:

  • @aiaiai-pt/martha-chat - imperative mount API, client helpers, and shared types
  • @aiaiai-pt/martha-chat/web-component - custom element entrypoint
  • @aiaiai-pt/martha-chat/react - React wrapper
  • @aiaiai-pt/martha-chat/svelte - Svelte component exports
  • @aiaiai-pt/martha-chat/css - shared styles

Use the hosted bundle when the host page does not have a build step. Use the npm package when a framework app wants autocomplete, TypeScript declarations, semver pinning, and Renovate/Dependabot support.

How It Works ​

The host page loads the widget through either the hosted bundle or the npm package, then passes a client-key and an auth callback.

At runtime the widget:

  1. Reads the embed manifest from GET /api/embed/manifest/{client_key}.
  2. Applies the configured display mode, feature flags, and theme defaults.
  3. Gets a Martha-compatible access token from the host page.
  4. Sends chat traffic to Martha APIs with embed request markers.
  5. Lets Martha enforce the configured Client, origin allowlist, auth mode, and feature permissions.

The host application does not get admin-level Martha permissions. It only gets the chat capabilities enabled for the configured Client and origin.

Supported V1 Modes ​

Display Modes ​

Use launcher mode when the widget should behave like the usual bottom-corner support chat.

html
<martha-chat
  api-url="https://martha.nomadriver.co"
  client-key="your-client-key"
  stylesheet-url="https://martha.nomadriver.co/embed/v1/martha-chat.css?v=0.3.0"
  launcher
  placement="bottom-right"
></martha-chat>

Use inline mode when Martha chat should occupy a specific region of the host application.

html
<section style="height: 720px">
  <martha-chat
    api-url="https://martha.nomadriver.co"
    client-key="your-client-key"
  ></martha-chat>
</section>

Auth Modes ​

Use host-token when the host app already signs users in with Martha or Keycloak and can provide a valid Martha-compatible user token.

Use embed-token when the host app has its own authentication system. In this mode, the host backend authenticates its own user, calls Martha server-to-server, and returns a short-lived embed token to the browser.

Standalone widget PKCE login is not a supported v1 mode. Do not advertise the embeddable widget as able to open its own Keycloak login flow in arbitrary third-party pages yet.

Admin Setup ​

Configure embeds from Martha Admin under the Client's Embed tab.

Required setup:

  1. Enable embeds for the Client.
  2. Add each allowed host origin.
  3. Choose the default display mode.
  4. Enable only the features that host should receive.
  5. Configure theme defaults if the host should inherit Martha-side branding.
  6. For embed-token mode, provision backend credentials and store the secret in the host backend.
  7. Copy the generated snippet into the host app.

Allowed origins are exact origins only:

text
https://partner.example.com
https://app.partner.example.com
http://localhost:5173

Do not include paths, query strings, fragments, or wildcard domains. For local development, Martha must be configured to allow localhost embed origins.

You can do the same setup from martha-cli:

bash
martha clients embed create \
  --name "Partner Support" \
  --client-key partner-support \
  --enable \
  --origin https://partner.example.com \
  --mode launcher \
  --enable-feedback \
  --provision-credentials \
  --theme accent=#155eef

For an existing Client:

bash
martha clients embed configure partner-support \
  --enable \
  --add-origin https://app.partner.example.com \
  --mode launcher \
  --feature uploads=true \
  --feature history=false

The public client-key is the value the host page passes to the widget — it's safe to ship in browser code, never use a server-side credential here.

Useful operator commands:

bash
# Show stored embed configuration.
martha clients embed show partner-support

# Verify that an origin can read the public manifest.
martha clients embed show partner-support \
  --manifest \
  --origin https://partner.example.com

# Print hosted asset URLs.
martha clients embed urls

# Print a copy-paste install snippet.
martha clients embed snippet partner-support \
  --framework html \
  --auth embed-token \
  --mode launcher

# Provision backend credentials for embed-token mode.
martha clients embed credentials provision partner-support

# Rotate the backend secret when needed.
martha clients embed credentials rotate partner-support

# Revoke embed-token credentials.
martha clients embed credentials revoke partner-support

# Run manifest, denied-origin, and hosted-bundle checks.
martha clients embed smoke partner-support \
  --origin https://partner.example.com

For local or CI verification, the smoke command can create and clean up a temporary embed Client:

bash
martha clients embed smoke \
  --create-temp-client \
  --origin http://127.0.0.1:5188

Embed token minting is only for trusted backend/test flows. The command requires the Client's dedicated embed credential service account or a Martha super-admin token. The CLI credential provisioning command prints:

text
MARTHA_CLIENT_ID=...
MARTHA_CLIENT_SECRET=...
Token endpoint: ...

Store these values on the host backend. Then the host backend can obtain a Martha service-account token and mint short-lived embed tokens:

bash
export MARTHA_CLIENT_ID=...
export MARTHA_CLIENT_SECRET=...

martha clients embed token partner-support \
  --origin https://partner.example.com \
  --external-user-id user-123 \
  --display-name "Jane Partner"

Human output hides the token by default. Use --print-token or --json only in automation that handles secrets safely.

Quick Start: Plain HTML With Host Token ​

This is the simplest integration when the host already has a Martha-compatible user token.

html
<martha-chat
  api-url="https://martha.nomadriver.co"
  client-key="your-client-key"
  launcher
  placement="bottom-right"
></martha-chat>

<script>
  window.MarthaChat = {
    getAccessToken: async () => window.hostAuth.getMarthaAccessToken()
  };
</script>
<script type="module" src="https://martha.nomadriver.co/embed/v1/martha-chat.ce.js?v=0.3.0"></script>

Quick Start: Plain HTML With Embed Token ​

Use this mode when the host app has its own login system.

Browser code:

html
<martha-chat
  api-url="https://martha.nomadriver.co"
  client-key="your-client-key"
  stylesheet-url="https://martha.nomadriver.co/embed/v1/martha-chat.css?v=0.3.0"
  launcher
></martha-chat>

<script>
  window.MarthaChat = {
    getAccessToken: async () => {
      const response = await fetch("/api/martha-embed-token", {
        method: "POST",
        credentials: "include"
      });

      if (!response.ok) {
        throw new Error("Could not create Martha embed token");
      }

      const payload = await response.json();
      return payload.access_token;
    }
  };
</script>
<script type="module" src="https://martha.nomadriver.co/embed/v1/martha-chat.ce.js?v=0.3.0"></script>

Host backend example:

js
async function getMarthaServiceToken() {
  const response = await fetch(process.env.MARTHA_TOKEN_ENDPOINT, {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      grant_type: "client_credentials",
      client_id: process.env.MARTHA_CLIENT_ID,
      client_secret: process.env.MARTHA_CLIENT_SECRET
    })
  });

  if (!response.ok) {
    throw new Error("Could not authenticate with Martha");
  }

  return (await response.json()).access_token;
}

app.post("/api/martha-embed-token", requireUser, async (req, res) => {
  const serviceToken = await getMarthaServiceToken();
  const response = await fetch(`${process.env.MARTHA_URL}/api/embed/tokens`, {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${serviceToken}`,
      "Content-Type": "application/json",
      "Origin": process.env.PUBLIC_HOST_ORIGIN
    },
    body: JSON.stringify({
      client_key: process.env.MARTHA_EMBED_CLIENT_KEY,
      external_user_id: req.user.id,
      display_name: req.user.name,
      expires_in: 900
    })
  });

  const body = await response.json();
  res.status(response.status).json(body);
});

Keep MARTHA_CLIENT_SECRET and service-account tokens server-side. Do not expose them to the browser.

React ​

React hosts should use the npm package.

tsx
import "@aiaiai-pt/martha-chat/css";
import { MarthaChat } from "@aiaiai-pt/martha-chat/react";

export function SupportChat() {
  return (
    <MarthaChat
      apiUrl="https://martha.nomadriver.co"
      selectedClientId="your-client-key"
      launcher
      placement="bottom-right"
      markEmbedRequests
      getAccessToken={async () => window.hostAuth.getMarthaAccessToken()}
    />
  );
}

React is an optional peer dependency and is only required by the React entrypoint. The base, web component, and Svelte entrypoints do not import React.

If a React host cannot install npm packages, use the hosted web component bundle instead of importing React code from a URL.

Generate this snippet from the CLI:

bash
martha clients embed snippet partner-support --framework react --auth host-token

Svelte ​

Svelte and SvelteKit hosts should use the npm package.

svelte
<script lang="ts">
  import "@aiaiai-pt/martha-chat/css";
  import { MarthaChatLauncher } from "@aiaiai-pt/martha-chat/svelte";

  async function getAccessToken() {
    return window.hostAuth.getMarthaAccessToken();
  }
</script>

<MarthaChatLauncher
  apiUrl="https://martha.nomadriver.co"
  selectedClientId="your-client-key"
  placement="bottom-right"
  markEmbedRequests
  {getAccessToken}
/>

Svelte is an optional peer dependency and is only required by the Svelte entrypoint.

If a Svelte host cannot install npm packages, use the hosted web component bundle.

Generate this snippet from the CLI:

bash
martha clients embed snippet partner-support --framework svelte --auth host-token

Imperative Mount API ​

Use the npm package's ESM entry when a host needs to mount and unmount the widget manually.

ts
import "@aiaiai-pt/martha-chat/css";
import { mountMarthaChat } from "@aiaiai-pt/martha-chat";

const instance = mountMarthaChat(document.getElementById("martha-root"), {
  apiUrl: "https://martha.nomadriver.co",
  selectedClientId: "your-client-key",
  launcher: true,
  placement: "bottom-right",
  markEmbedRequests: true,
  getAccessToken: async () => window.hostAuth.getMarthaAccessToken()
});

// Later, if the host page is being torn down:
// instance.destroy();

No-build pages can use the hosted ESM bundle instead:

html
<div id="martha-root"></div>

<script type="module">
  import { mountMarthaChat } from "https://martha.nomadriver.co/embed/v1/martha-chat.es.js?v=0.3.0";

  mountMarthaChat(document.getElementById("martha-root"), {
    apiUrl: "https://martha.nomadriver.co",
    selectedClientId: "your-client-key",
    launcher: true,
    getAccessToken: async () => window.hostAuth.getMarthaAccessToken()
  });
</script>

Generate this snippet from the CLI:

bash
martha clients embed snippet partner-support --framework esm --auth embed-token

Options ​

The same options exist as mount/React/Svelte props and as custom-element attributes. An attribute set on the element wins over the client manifest.

PropAttributeDefaultWhat it does
apiUrlapi-url—Martha base URL.
selectedClientIdclient-key / selected-client-id—The Martha client every request is scoped to (sessions, chat, feedback, stop).
getAccessTokenwindow.MarthaChat.getAccessToken—Returns a host token or an embed token; called before each request.
titletitleassistant nameHeader title.
welcomeMessagewelcome-messagelocalized greetingFirst, plain-text message.
placeholderplaceholderlocalizedComposer placeholder.
localelocalethe host page's <html lang>, else EnglishWidget copy language: pt or en. Indicators, buttons and confirmations follow it, never the language of the question.
resumeHistoryresume-historytrue (custom element: manifest features.history)Reopen the user's most recent conversation on mount instead of starting a new one.
sessionSwitchingsession-switchingfalseAdds a Conversations button: switch between, search, rename and delete the user's conversations (see below).
allowFeedbackallow-feedbacktrue (manifest features.feedback)Thumbs + optional comment on each answer.
allowUploadsallow-uploadsfalse (manifest features.uploads)Attach a file to a message.
autoStart—trueOpen (or resume) a conversation on mount.
markEmbedRequests— (set from client-key)falseSends X-Martha-Embed: 1 so Martha applies the client's embed origin policy.

Behaviour ​

  • History. On mount the widget lists the user's sessions (GET /api/sessions, scoped to the embed user and client) and reopens the most recent one; it creates a session only if there is none. "New conversation" starts a fresh one.
  • Answers render Markdown (sanitized, links open in a new tab); while an answer streams, unfinished Markdown is completed so raw ** never flashes. User messages and the welcome copy stay plain text.
  • Progress. "Thinking", "Looking up data" and "Writing the answer" are distinct indicators.
  • Stop stops the turn on the server, not just in the browser: the widget calls POST /api/chat/{session_id}/cancel before closing the stream. Martha stops the model, runs no further tool or LLM round, and stores the reply as far as it got, marked interrupted (tool_metadata.interrupted); the widget shows "Response stopped." on it, also after a reload. A question sent right after Stop waits until the stop has finished. Needs Martha with #1500 and #1514; against an older server the stop works in the browser only.
  • Feedback is sent for the embed's client and shows sending, sent and failed states; a failure can be retried.
  • Conversations (sessionSwitching). A panel that replaces the chat while open, like the main chat client's drawer: conversations grouped by date (today, yesterday, previous 7 days, older), title search, new conversation, resume on select, rename, and delete after a confirmation. An untouched conversation is discarded when you switch away from it; deleting the open conversation starts a fresh one. Escape closes it. Uses GET/PATCH/DELETE /api/sessions, which accept embed tokens.

Feature Flags ​

Feature flags are configured on the Martha Client and returned by the manifest.

  • uploads enables scoped file uploads into the current embed chat session.
  • history controls whether the widget reopens the user's latest conversation on load (resumeHistory). The conversations panel (sessionSwitching) is a separate, host-side opt-in.
  • feedback controls message feedback affordances.
  • voice controls voice UI, subject to the Client's normal voice enablement.

Embed uploads are intentionally scoped. The widget can upload files for the active chat session only. It does not grant the host browser generic document collection read or write access.

Styling ​

Hosts can style the widget with CSS custom properties. Martha Admin can also store default theme values for a Client, which the manifest returns to the widget.

For package installs:

  • React, Svelte, and imperative ESM mounts should import @aiaiai-pt/martha-chat/css.
  • Web component installs render inside Shadow DOM. With bundlers such as Vite, import the CSS asset URL and pass it through window.MarthaChat.stylesheetUrl or the stylesheet-url attribute:
ts
import stylesheetUrl from "@aiaiai-pt/martha-chat/css?url";
import "@aiaiai-pt/martha-chat/web-component";

window.MarthaChat = {
  stylesheetUrl,
  getAccessToken: async () => window.hostAuth.getMarthaAccessToken()
};

Hosted web component installs normally derive the sibling CSS URL. For the one-time 0.3.0 cache migration, set stylesheet-url explicitly as shown in the quick starts so an old immutable CSS response is bypassed too.

Every visual value in the widget is a token: colours, type, spacing, shape, sizes and motion. Set them on any ancestor of the widget (the martha-chat element, or the element you mount into). Each token has a default; set only the ones you want to change. Region tokens fall back to the semantic ones, so --martha-chat-surface alone recolours the header, the assistant bubbles and the activity pill.

Spacing and the smaller radii are derived, so one value re-scales the whole widget: every padding, gap and margin is a multiple of --martha-chat-space, and inline-code and small-control radii are fractions of --martha-chat-radius.

css
martha-chat {
  /* map the widget onto the host design system */
  --martha-chat-accent: var(--color-bg-primary-base);
  --martha-chat-on-accent: var(--color-fg-primary-on-color);
  --martha-chat-bg: var(--color-bg-neutral-lighter);
  --martha-chat-surface: var(--color-bg-neutral-light);
  --martha-chat-text: var(--color-fg-neutral-base);
  --martha-chat-muted: var(--color-fg-neutral-subtitle);
  --martha-chat-border: var(--color-border-neutral-medium);
  --martha-chat-font: inherit;
  --martha-chat-space: 2px;
  --martha-chat-radius: 8px;
}

martha-chat::part(launcher) {
  box-shadow: none;
}

Colour ​

TokenDefaultUsed for
--martha-chat-bg#ffffffWidget background; default for messages, composer, inputs and controls
--martha-chat-surface#f7f8faRaised areas; default for the header, assistant bubbles and activity pill
--martha-chat-text#1b1d22Body text; default for inputs, controls and the open launcher
--martha-chat-muted#68707dSecondary text: sender, status, placeholders, feedback row
--martha-chat-border#d6d9dfEvery border and divider
--martha-chat-accent#155eefPrimary colour: Send, links, focus, selected states, the launcher
--martha-chat-on-accent#ffffffText and icons on an accent fill (Send, user bubble, launcher icon)
--martha-chat-accent-hoveraccent darkened 15%Hover fill of accent buttons
--martha-chat-accent-softaccent at 10% over bgHover and selected background of quiet controls
--martha-chat-feedback-bg—Legacy alias for --martha-chat-accent-soft
--martha-chat-focus-ringaccent at 45%Keyboard focus outline on every control and field
--martha-chat-success#167647"Feedback sent" confirmation
--martha-chat-error#b42318Error text
--martha-chat-error-bg#fff5f5Error message background
--martha-chat-error-border#f0a8a8Error message border
--martha-chat-code-bgtext at 6%Inline code and code blocks
--martha-chat-header-bgsurfaceHeader
--martha-chat-messages-bgbgMessage list
--martha-chat-composer-bgbgComposer bar
--martha-chat-agent-bgsurfaceAssistant bubble
--martha-chat-user-bgaccentUser bubble
--martha-chat-user-texton-accentUser bubble text
--martha-chat-user-borderuser-bgUser bubble border
--martha-chat-input-bgbgComposer input and feedback comment field
--martha-chat-input-texttextText in those fields
--martha-chat-control-bgbgIcon buttons (new conversation, attach, stop)
--martha-chat-control-texttextIcon button glyphs

Typography ​

TokenDefaultUsed for
--martha-chat-fontInter, ui-sans-serif, system-ui, sans-serifFont family (inherit to use the host's)
--martha-chat-font-family—Alias for --martha-chat-font (manifest theme.font_family)
--martha-chat-font-size14pxMessages, inputs
--martha-chat-font-size-sm13pxStatus, activity, file chip, code, tables, comment field
--martha-chat-font-size-xs12pxFeedback row, Submit, interrupted marker
--martha-chat-font-size-2xs11pxSender label, session pill
--martha-chat-font-size-title15pxHeader title
--martha-chat-font-weight-strong650Title, sender, Send, bold text, headings
--martha-chat-font-weight-medium600Feedback Submit
--martha-chat-line-height1.5All text

Spacing, shape and size ​

TokenDefaultUsed for
--martha-chat-space2pxSpacing unit; every padding, gap and margin is a multiple
--martha-chat-radius8pxWidget, bubbles, inputs, buttons; small radii derive from it
--martha-chat-radius-pill999pxActivity pill, launcher button
--martha-chat-border-width1pxEvery border (quote bars are 3x)
--martha-chat-focus-width2pxFocus outline width
--martha-chat-focus-offset2pxFocus outline offset
--martha-chat-control-size40pxComposer input, Send and icon buttons
--martha-chat-control-size-sm28pxFeedback thumbs
--martha-chat-icon-size18pxIcons
--martha-chat-icon-size-sm14pxFeedback result icon
--martha-chat-icon-stroke2Icon stroke width
--martha-chat-min-width280pxWidget minimum width
--martha-chat-min-height420pxWidget (and launcher panel) minimum height
--martha-chat-bubble-max-width680pxMessage bubble maximum width
--martha-chat-bubble-max-share88%Message bubble maximum share of the list width
--martha-chat-feedback-submit-min-width72pxFeedback Submit minimum width

State and motion ​

TokenDefaultUsed for
--martha-chat-disabled-opacity0.6Disabled controls and fields
--martha-chat-duration120msHover and focus transitions
--martha-chat-easingeaseTransition easing
--martha-chat-pulse-duration1.2sThinking indicator cycle (faster while generating)

Launcher ​

TokenDefaultUsed for
--martha-chat-launcher-size56pxLauncher button diameter
--martha-chat-launcher-offset-x20pxDistance from the viewport side
--martha-chat-launcher-offset-y20pxDistance from the viewport bottom
--martha-chat-launcher-width390pxPanel width
--martha-chat-launcher-height640pxPanel height
--martha-chat-launcher-iconon-accentLauncher icon colour
--martha-chat-launcher-icon-size24pxLauncher icon size
--martha-chat-launcher-open-bgtextLauncher button while open
--martha-chat-launcher-shadow0 14px 32px ink at 22%Launcher button shadow
--martha-chat-launcher-duration160msPanel open/close transition

Two breakpoints cannot be tokens, because CSS does not allow custom properties in media queries: the chat goes edge-to-edge below 480px, the launcher panel goes full-screen below 560px.

Stable parts:

  • launcher
  • panel
  • shell
  • header
  • new-conversation-button
  • messages
  • message
  • message-body
  • interrupted
  • activity (was tool-status before 0.4.0)
  • feedback
  • feedback-submit
  • feedback-result
  • composer
  • send-button
  • stop-button
  • conversations-button (with sessionSwitching)
  • conversations (the panel)
  • upload-button

Use host-side CSS variables for simple visual integration. Use Martha-side theme settings when administrators need centralized per-client defaults across multiple host pages.

Local testing checklist ​

  1. Start Martha and the chat widget locally.
  2. Create or update a Client in Admin.
  3. Enable embeds for that Client.
  4. Add the host origin, for example http://localhost:5173.
  5. If testing embed-token mode, provision embed credentials from Admin or martha clients embed credentials provision.
  6. Copy the web component snippet into a local host page.
  7. Verify GET /api/embed/manifest/{client_key} succeeds with the host page's Origin header.
  8. Open the widget, send a chat message, refresh, and confirm the expected session behavior.
  9. If uploads are enabled, upload a file from the widget and confirm it is scoped to the embed session.

Production Checklist ​

Before handing a snippet to a third-party developer:

  1. Confirm https://martha.nomadriver.co/embed/v1/martha-chat.ce.js?v=0.3.0 returns HTTP 200 with Cache-Control: no-cache, must-revalidate, and that a referenced hashed chunk returns one-year immutable caching.
  2. Confirm MARTHA_EMBED_TOKEN_SECRET is present in the deployed backend environment if embed-token mode will be used.
  3. Configure the Client Embed tab in Admin.
  4. Add the exact production host origin.
  5. Provision embed-token backend credentials if the host does not use host-token mode.
  6. Store MARTHA_CLIENT_ID, MARTHA_CLIENT_SECRET, and the token endpoint in the host backend secret store.
  7. Smoke test the manifest from that origin.
  8. Smoke test the selected auth mode from the real host page.
  9. Verify only the intended features are enabled.

The third-party developer should receive:

  • Martha host URL
  • Client key
  • Allowed origin requirements
  • Chosen auth mode
  • Chosen install path: hosted script tag or @aiaiai-pt/martha-chat
  • Snippet for web component, React, Svelte, or imperative mount
  • For embed-token mode, backend-only credential names and token endpoint
  • Expected feature flags

Martha is built by aiaiai-pt.