Auth & sessions
Embedded users authenticate through a short-lived embed token your backend mints — there is no second login and your app secret never reaches the browser.
How it works
An embed token is a normal tenant access token plus embed/embed_app_id/scopes claims. Its audience stays ivorycom-api, so the CRM, the agent and row-level security accept it unchanged. Tokens live 15 minutes (expires_in: 900); refresh by re-calling the mint endpoint from your backend.
secret is server-side only. Mint from your backend and pass just the resulting token to the browser. Never ship the secret in client code.Mint a session
POST https://api.ivorycomcrm.com/api/auth/v1/embed/sessions — headers X-Embed-Key (client_id) + X-Embed-Secret; body describes the user to sign in.
curl -X POST https://api.ivorycomcrm.com/api/auth/v1/embed/sessions \
-H "X-Embed-Key: $CLIENT_ID" -H "X-Embed-Secret: $SECRET" \
-H "Content-Type: application/json" \
-d '{"sub":"user-42","email":"[email protected]","name":"Jo","scopes":["leads","pipeline","agent"]}'
# → { "embed_token": "eyJ…", "expires_in": 900 }import { createEmbedClient } from "@ivorycom/embed-node";
const client = createEmbedClient({ clientId: process.env.CLIENT_ID, secret: process.env.SECRET });
const { embed_token } = await client.mintSession({
sub: "user-42", email: "[email protected]", name: "Jo",
scopes: ["leads", "pipeline", "agent"],
});import requests
r = requests.post("https://api.ivorycomcrm.com/api/auth/v1/embed/sessions",
headers={"X-Embed-Key": CLIENT_ID, "X-Embed-Secret": SECRET},
json={"sub":"user-42","email":"[email protected]","name":"Jo","scopes":["leads","pipeline","agent"]})
embed_token = r.json()["embed_token"]body, _ := json.Marshal(map[string]any{"sub":"user-42","email":"[email protected]","name":"Jo","scopes":[]string{"leads","pipeline","agent"}})
req, _ := http.NewRequest("POST", "https://api.ivorycomcrm.com/api/auth/v1/embed/sessions", bytes.NewReader(body))
req.Header.Set("X-Embed-Key", clientID); req.Header.Set("X-Embed-Secret", secret)
res, _ := http.DefaultClient.Do(req) // res.Body → { embed_token, expires_in }Users: JIT vs mapped
The sub you pass identifies the host user. A new sub is provisioned just-in-time as a member of your tenant; a sub mapped to a real Ivorycom user inherits that user's role. A sub+email that collides with a different tenant's user is rejected — the cross-tenant guard.
Origins
An embed app declares its allowed origins; the embed host sets frame-ancestors from that list (fail-closed to none for unknown apps), so only your domains may frame the CRM.
