# Embed SDK Embed Monad's connector-configuration UI directly inside your own product with the public [`@monad-inc/embed`](https://www.npmjs.com/package/@monad-inc/embed) package. Your users connect their own third-party data sources and destinations — an AWS account, an Okta org, a Splunk endpoint — from screens you control, and you never build or maintain the connector forms, secrets handling, or OAuth flows behind them. The form runs inside a Monad-hosted iframe. Secrets a user types (API keys, OAuth tokens) stay on Monad's origin and never reach your application's JavaScript. Your code gets back a connector `id` — a safe reference, like Stripe's `pm_…`. If you've used Stripe Elements or Plaid Link, this is the same isolation model. ## When to embed Reach for embedding when your customers need to bring their own integrations and you'd rather not build the long tail of connector forms, secrets handling, and OAuth. If you only move data between systems you already control, you may not need the embedded UI at all — you can call the Monad API directly (see the [Go SDK](/sdks/go) and [Python SDK](/sdks/python)). ## The two pieces you write Getting running takes two pieces of your own code: 1. **A session-mint endpoint on your backend** that trades your long-lived Monad API key for a short-lived, team-scoped session token. 2. **A `createConnectorFrame()` call on your frontend** that mounts the iframe with that token. Theming, the connector catalog, and starting data flowing after a connector is saved all build on those two. This overview gets you a working iframe; [Authentication & Tenancy](/sdks/embed/auth-tenancy), [Connector Lifecycle](/sdks/embed/lifecycle), and the [API Reference](/sdks/embed/reference) go deeper. ## Architecture Four parts, one boundary between your systems and Monad's. ```text YOUR FRONTEND MONAD +-----------------------+ +------------------------+ | Your app UI | | Monad iframe | | | postMsg| (connector form; | | createConnectorFrame ========>| secrets live here) | | ^ session tok | | | +--------+--------------+ +-----------+------------+ | | | /api/session (your route) | writes connector v v +-----------------------+ +------------------------+ | YOUR BACKEND | ApiKey| Monad API | | mints tokens, |=======>| POST /v3/sessions | | reads catalog, | | GET /v1/... | | builds pipelines | | POST /v2/{org}/... | | (API key lives here) | | | +-----------------------+ +------------------------+ ``` The boundary holds two guarantees: - **Your long-lived API key never leaves your backend.** The browser holds only a short-lived session token minted from it. Losing a session token is bounded (one team, a few minutes); losing the API key is not, which is why it stays on the server. - **The secrets a user types never leave Monad's iframe.** The iframe runs on Monad's origin, so the browser's same-origin policy stops your JavaScript from reading them. The session token reaches the iframe over `postMessage`, not the URL, so it stays out of browser history and the `Referer` header. The SDK checks the origin on every message in both directions. ## Install The SDK is a public npm package with TypeScript types and no runtime dependencies. ```bash npm install @monad-inc/embed ``` `pnpm add` and `yarn add` work the same. Import the host-side API from the `/connect` subpath: ```ts import { createConnectorFrame } from '@monad-inc/embed/connect'; ``` ## Quickstart ### Step 1 — mint a session token from your backend Your backend exchanges its long-lived Monad API key for a short-lived, team-scoped session token, then hands that token to your frontend. It's a plain `POST /v3/sessions`. Shown here as an Express handler — any HTTP framework works the same way: ```ts // An endpoint on YOUR backend, e.g. POST /api/session app.post('/api/session', async (req, res) => { const { tenantId } = req.body; const org = resolveTenantToTeam(tenantId); // your server-side tenant → team mapping const r = await fetch('https://app.monad.com/api/v3/sessions', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `ApiKey ${process.env.MONAD_API_KEY}`, // key stays server-side }, body: JSON.stringify({ organization_id: org, ttl_seconds: 1800 }), // 30 min }); const session = await r.json(); // { session_token, expires_at } res.json({ ...session, organizationId: org }); // merge org id for the frontend }); ``` The response: ```json { "session_token": "eyJhbGc…", "expires_at": "2026-05-26T18:30:00Z" } ``` :::warning Never put your long-lived `MONAD_API_KEY` in browser code. The frontend fetches a token from **your** endpoint, not from Monad directly. ::: ### Step 2 — mount the iframe on your frontend ```ts import { createConnectorFrame } from '@monad-inc/embed/connect'; // Fetch a fresh session from your own backend. const s = await fetch('/api/session', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ tenantId: currentUser.tenantId }), }).then((r) => r.json()); const frame = createConnectorFrame({ container: '#monad-mount', sessionToken: s.session_token, // remap snake_case → camelCase organizationId: s.organizationId, kind: 'input', // 'input' (a data source) or 'output' (a destination) typeId: 'aws-cloudtrail', // connector type slug onSave: ({ id }) => { // You now have a connector id — build a pipeline next (see Connector Lifecycle). frame.destroy(); }, onCancel: () => frame.destroy(), }); ``` That's it. The form renders inside the iframe; the user fills it in and clicks Save; you get a connector `id` back. The iframe owns its own Save / Test connection / Cancel buttons — your page controls only the surrounding chrome. :::info Watch the casing The mint response is snake_case (`session_token`), but `createConnectorFrame` expects camelCase (`sessionToken`). Remap at the call site, as shown above. ::: ## Creating a connector is not moving data Saving a connector **creates** it. Data does not move until you build a **pipeline** that wires the connector to another node. `createConnectorFrame` gives you the connector `id`; [Connector Lifecycle](/sdks/embed/lifecycle) covers turning that into flowing data, including the zero-setup default sink for a freshly connected source. ## Where to go next - [Authentication & Tenancy](/sdks/embed/auth-tenancy) — the one-team-per-account model, session tokens, and how a tenant maps to a Monad team. - [Connector Lifecycle](/sdks/embed/lifecycle) — build pipelines, ingress vs. egress, pause/resume, and delete/cleanup. - [Best Practices](/sdks/embed/best-practices) — default inputs and outputs, catalog caching, and deployment. - [API Reference](/sdks/embed/reference) — the full `createConnectorFrame` options, theming, catalog reads, connector icons, and the backend route contract. ## Support For additional support contact support@monad.com