Docs / SDK / Start / Put an agent on your page
View as MarkdownPut an agent on your page
Add one backend endpoint and two HTML tags, and your signed-in users can talk to a Mindset agent in your web app.
By the end of this page your users can talk to a Mindset agent inside your web app. You add one endpoint to your backend and two tags to a page.
What you need first
- An agent you can embed. It needs a configuration version in force and Embedded in your own product ticked under its availability. New agents start with that box unticked. See Publish and share an agent.
- An org API key. An org admin creates it in Settings → API keys. A key works only in the environment it was created in, so create it in the environment you are embedding from.
- The agent's Embed tab. Open the agent in AMS and choose Embed. It shows your Mindset host, your org slug, the current environment's slug and the agent's handle. Copy them from there.
The examples below use the agent handle billing-help.
Step 1: add a session endpoint to your backend
Your page can't call Mindset directly, because that would put the org API key in a browser. Your backend makes the call and passes the result on.
This is a complete Express server for Node 18 or later. Any server stack works the same way.
import express from "express";
const MINDSET_HOST = "https://eu.mindset.ai"; // the host your Embed tab shows
const ORG_SLUG = process.env.MINDSET_ORG_SLUG;
const ENV_SLUG = process.env.MINDSET_ENV_SLUG;
const ORG_API_KEY = process.env.MINDSET_ORG_API_KEY;
const app = express();
// Your own sign-in middleware goes here. This example expects it to set req.user.
app.get("/api/session", async (req, res) => {
if (!req.user) return res.status(401).json({ error: "Not signed in" });
const r = await fetch(
`${MINDSET_HOST}/api/v1/orgs/${ORG_SLUG}/envs/${ENV_SLUG}/agent-sessions`,
{
method: "POST",
headers: {
"x-api-key": ORG_API_KEY,
"content-type": "application/json",
},
body: JSON.stringify({
user: { email: req.user.email },
agent: "billing-help",
createUserIfNeeded: true,
}),
},
);
if (!r.ok) {
console.error("Mindset session create failed", r.status, await r.text());
return res.status(502).json({ error: "Could not start a session" });
}
// Forward the whole body, unchanged.
res.set("cache-control", "no-store").json(await r.json());
});
app.listen(3000);Four things matter here.
- The org API key stays on the server. It never goes in a response body, a page or a log line.
createUserIfNeeded: truecreates a Mindset user the first time one of your users shows up. Leave it out and every first-time user gets a 404.no-storestops a shared cache handing one user's session to another.- Forward the response body whole. It holds the credential plus a list of switched-on release features. Rebuilding it as
{ session }drops that list without any error.
Create a session for your users covers every field, including identifying users by your own ID instead of email.
Step 2: put the agent on your page
Load the script, write the tag, and give the element a function that fetches a session.
<script src="https://eu.mindset.ai/sdk/mindset-agent.js"></script>
<mindset-agent agent="billing-help"></mindset-agent>
<script>
const agent = document.querySelector("mindset-agent");
agent.addEventListener("mindset:error", (e) => {
console.error("Mindset error:", e.detail.code, e.detail.message);
});
agent.configure({
getSession: async () => {
const r = await fetch("/api/session");
if (!r.ok) throw new Error("Could not start a session");
return r.json();
},
});
</script>The script registers the mindset-agent tag. The element draws nothing until you call configure().
getSession is a function that returns your endpoint's response body. The SDK calls it as soon as you call configure(), and again whenever it needs a fresh session, so you write no refresh logic. It must be a function. A session pasted in as a fixed value can't be renewed, and an org API key is never sent from a browser.
The element renders inside a shadow root with its own styles. Your page's CSS won't change it, and its CSS won't leak into your page.
Step 3: check it works
Load the page. A chat panel appears and you can send a message and get a reply.
If nothing appears, open the browser console. The element logs every problem to the console and also raises it as a mindset:error event with a stable code, which the listener above prints. The troubleshooting table below lists the codes.
Using React
React 19 renders custom elements directly, so there is no wrapper package. Load the same script once, in your index.html, and use the tag in JSX.
import { useEffect, useRef, type HTMLAttributes, type Ref } from "react";
type MindsetAgentElement = HTMLElement & {
configure(config: { getSession: () => Promise<unknown> }): void;
};
declare module "react" {
namespace JSX {
interface IntrinsicElements {
"mindset-agent": HTMLAttributes<HTMLElement> & {
agent?: string;
ref?: Ref<HTMLElement>;
};
}
}
}
export function BillingAgent() {
const ref = useRef<HTMLElement>(null);
useEffect(() => {
(ref.current as MindsetAgentElement | null)?.configure({
getSession: async () => {
const r = await fetch("/api/session");
if (!r.ok) throw new Error("Could not start a session");
return r.json();
},
});
}, []);
return <mindset-agent ref={ref} agent="billing-help" />;
}The declare module "react" block tells TypeScript about the tag. Plain JavaScript projects can drop it and the type.
Vue 3 also uses the element directly, with no wrapper.
If you want your own interface
The UI-less client gives you the same conversation with no Mindset UI. Your backend endpoint from step 1 stays the same.
<script type="module">
import { createAgentConversation } from "https://eu.mindset.ai/sdk/mindset-agent-uiless.js";
const chat = createAgentConversation({
agent: "billing-help",
getSession: async () => {
const r = await fetch("/api/session");
if (!r.ok) throw new Error("Could not start a session");
return r.json();
},
});
let reply = "";
chat.on((event) => {
if (event.type === "text_delta") reply += event.content;
if (event.type === "complete") console.log("Agent:", reply);
});
await chat.send("What does my last invoice cover?");
</script>The UI-less client covers the whole API.
Before you ship
- Load one Mindset SDK per page. A custom element tag can be registered once per page. Loading this bundle twice is harmless. Loading it next to an older Mindset embed that also uses
mindset-agentfails, and the console says so by name. - Every page load starts a fresh conversation unless you hand back a conversation ID. See resuming a conversation.
- Each element creates a session when you configure it, not when the user first types. Configure it when the user can see it.
Troubleshooting
| What you see | Cause and fix |
|---|---|
| The console says the script failed to load | The host in the script src is wrong. Use the host the Embed tab shows |
mindset:error with missing-agent | No agent. Set the agent attribute to a handle from your org |
mindset:error with invalid-auth | configure() got no getSession function, or the function returned something the SDK can't read. Return the parsed JSON body (r.json()), not the Response and not a field from inside it |
mindset:error with turn-failed | A turn failed. Check your backend logs for a failed session create, then the message on the event |
mindset:error with not-configured | Your code called send() before configure() |
| A 404 from the create call | One of four things: a wrong org slug, a wrong environment slug, an agent handle not in your org, or an API key without admin scope. Mindset gives the same 404 for each. A 404 with the message "no such user in this org" means you left out createUserIfNeeded |
A 403 agent_not_available_here | Embedded in your own product isn't ticked for this agent |
A 409 no_version_in_force | The agent has configuration versions but none is active. Activate one in AMS |
A 409 user_not_active | That user exists in your org but is invited and not yet active, or was removed |
Next steps
- Create a session for your users for the full backend call.
- The mindset-agent element for attributes, methods, events and theming.