# Put 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](https://docs4.mindset.ai/docs/ams/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](https://docs4.mindset.ai/docs/ams/glossary) 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.

```javascript
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: true`** creates a Mindset user the first time one of your users shows up. Leave it out and every first-time user gets a 404.
- **`no-store`** stops 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](https://docs4.mindset.ai/docs/sdk/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.

```html
<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.

```tsx
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.

```html
<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](https://docs4.mindset.ai/docs/sdk/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-agent` fails, 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](https://docs4.mindset.ai/docs/sdk/the-mindset-agent-element#resume-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](https://docs4.mindset.ai/docs/sdk/create-a-session-for-your-users) for the full backend call.
- [The mindset-agent element](https://docs4.mindset.ai/docs/sdk/the-mindset-agent-element) for attributes, methods, events and theming.
