Authentication & Tenancy
This page covers the two things every embed call depends on: how your customers map to Monad teams, and how your backend turns a long-lived API key into the short-lived session token the browser uses. Understand the tenancy model first — the team id shows up in almost every API call.
Tenancy and teams
Vocabulary
| Term | What it means |
|---|---|
| Team (organization) | A Monad sub-org, nested underneath your (the vendor's) root org, that owns a customer's connectors and pipelines. Identified by an organizationId. |
| Tenant | Your customer. Your backend maps a tenant to its Monad team. |
| Session token | A short-lived (about 30 minutes), team-scoped credential minted from your API key. The browser passes it into the iframe. |
The recommended model: one team per account
Give each account on your side its own Monad team. "Account" here means whatever your isolation boundary is — usually one of your customers. Several users inside the same customer still share that one customer's team.
You hold a single Monad API key. Under it you create one team per account. Your backend owns the tenant-to-team mapping, which in the simplest case is a column on your accounts table. The browser only ever names a tenant; your backend resolves it to the Monad team id and never sends that id to the client.
:::warning Resolve, don't trust Don't accept an org id from the browser as authority. Resolve it server-side from the tenant. The same rule, applied to pipeline and connector ids, comes back as "resolve-then-act" in Connector Lifecycle. :::
Why one team per account
The team is Monad's isolation boundary between your accounts, and the team id is load-bearing:
- The session token is scoped to one team. The API rejects any call outside its
orgclaim, so a token minted for one account can't touch another's data. - Every catalog and pipeline call is org-scoped:
/v1/{org}/inputs,/v2/{org}/pipelines/, and so on. Listing connectors returns everything in that team and nothing outside it.
One team per account means each customer's connectors, pipelines, and secrets are
isolated by the API itself, and the short-lived token you hand the browser can only ever
reach that one customer's resources. Wherever you see org or organizationId in these
docs, read it as "the team this tenant maps to."
The risk of sharing a team across accounts
You can put multiple accounts on one team, but that erases the boundary the model depends on:
- No isolation. A token minted for account A can read and act on account B's connectors and pipelines — to the API they're the same org.
- Listing leaks.
GET /v1/{org}/{kind}sreturns every connector in the team, exposing one account's names and ids to another. - Mutations can hit the wrong account. Build, pause, and delete act on ids in the shared team. Resolve-then-act helps, but can't restore a boundary a separate team would give you.
- Wider blast radius. One leaked token or bad deploy affects every account on the team, not one.
- Messy offboarding. Deleting an account means hand-picking its resources out of a shared team instead of just deleting the team.
Sharing is only fine when the accounts aren't separate security principals (say, environments of one internal app). Distinct customers get distinct teams.
Provisioning teams
Create a team per account with POST /v3/{yourOrgId}/organizations (body
{ name, friendly_name }), and store the returned id against your tenant when you
onboard them. If you're embedding for a single account, this collapses to one team you
resolve to server-side — no mapping table needed.
Authentication
Authentication is a chain: API key on your server → session token in the browser → into the iframe.
The API key
A long-lived JWT you issue in Monad (Settings → API Keys, the eyJ… value). It
authenticates backend calls as Authorization: ApiKey <key> and stays on your server,
for the reason in the architecture overview. See the
API Keys guide for issuing and rotating keys.
Minting a session token
Your backend trades the key for a token scoped to one team, then hands it to the browser.
It's a plain POST /v3/sessions:
Code
Response:
Code
Passing it to the iframe
The frontend fetches a token from your endpoint, not Monad directly, and passes it to
createConnectorFrame. Watch the casing: the mint response is snake_case
(session_token) but createConnectorFrame wants camelCase (sessionToken). Remap at
the call site:
Code
Scope, lifetime, and reuse
- Scope is one team. The API rejects any call outside the token's
orgclaim. - Lifetime is whatever
ttl_secondsyou set. Keep it short; about 30 minutes is typical. - A token is scoped to a team and a time window, not to a single iframe. You can reuse one token across several mounts while it's valid. Mint per tenant, not per frame.
- The form reads the token once at mount. An expired token means minting a fresh one
and remounting. Re-mint on expiry or on a
401.
Next
- Connector Lifecycle — build pipelines and manage integrations.
- Best Practices — default inputs/outputs and deployment.
Support
For additional support contact support@monad.com