Docs / SDK / Start / Create a session for your users
View as MarkdownCreate a session for your users
Make the one server-to-server call that creates an agent session for a user, and hand the result to the browser safely.
After this page your backend can create an agent session for any of your users and hand it to the browser. This one call is the whole server-side integration. Everything after it happens in the browser.
What this is
Your backend holds your org API key and never lets it near a browser. The browser holds a session credential that your backend gets from Mindset. That credential runs one agent for one user, and nothing else.
- A user opens a page in your app that has an agent on it.
- The page asks your backend for a session.
- Your backend calls Mindset with the org API key and gets back a JSON body.
- Your backend returns that body to the page, unchanged.
- The SDK runs the agent with it.
You don't read the credential, store it, or renew it.
Before you start
You need an org API key. An org admin creates it in AMS under Settings → API keys.
A key works only in the environment it was created in. A key from your test environment can't create sessions in production, so you need one key for each environment you integrate with.
Make the call
POST https://YOUR-MINDSET-HOST/api/v1/orgs/{orgSlug}/envs/{envSlug}/agent-sessionsThe org slug and environment slug go in the path. The agent's Embed tab in AMS shows both, plus the host. Send the key in the x-api-key header, not in Authorization.
This route is for servers only. It has no CORS headers, so a browser can't call it.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
user.email | string | one of the two | Must look like an email address (contains @, no spaces). The same person can also sign in to Mindset directly with this email, once they prove they own it |
user.externalId | string | one of the two | Your own opaque ID. Letters, numbers, _ and - only, 100 characters or fewer. Used only for embedding. It never signs in to Mindset |
agent | string | yes | The agent's handle, or its ID. Both are on the Embed tab. The ID survives a rename of the handle |
createUserIfNeeded | boolean | no | When true, a user Mindset hasn't seen is created. Defaults to false, which refuses an unknown user |
attribution | object | no | Your own string labels for this session. Up to 16 keys, keys up to 64 characters, values up to 256 characters. See attribution |
Send exactly one of user.email and user.externalId. Both, or neither, is a 400.
The body is strict. A field not in this table is a 400, so a typo fails at once.
Example
curl -sS -X POST \
"https://eu.mindset.ai/api/v1/orgs/acme/envs/production/agent-sessions" \
-H "x-api-key: $MINDSET_ORG_API_KEY" \
-H "content-type: application/json" \
-d '{
"user": { "email": "alice@acme.com" },
"agent": "billing-help",
"createUserIfNeeded": true,
"attribution": { "plan": "enterprise", "region": "emea" }
}'Replace eu.mindset.ai, acme and production with the host and slugs from your Embed tab.
What you get back
A 200 with a JSON body:
{
"session": "eyJhY2Nlc3NUb2tlbiI6...",
"enabledFeatures": []
}| Field | What it is |
|---|---|
session | The opaque credential. Its contents can change between releases, so don't decode, rebuild or store it |
enabledFeatures | Release features switched on for this deployment that the browser SDK can act on. Optional. Missing means none |
Pass the whole body to the browser unchanged, and have your page's getSession function return it. If you pass only session, the SDK still runs, but the agent shows nothing that sits behind a release feature, and nothing reports it.
In one call, Mindset finds or creates the user in your org, checks that the agent can be embedded in this environment, and returns the credential. Holding the org key and making the call is the authorization. The agent doesn't have to be published to the user in AMS.
The embed runs the agent's configuration version in force. Activate a new version and users get it on their next turn. A turn already running finishes on the version it started with. Which connections, widgets and functions the agent may use is fixed when the session is created, so a change there reaches a user with their next session.
Choose an identity
Use email when the person in your app might also use Mindset directly. One email is one person in your org, whichever way they arrive.
Use externalId when your users exist only in your app, or when you'd rather Mindset didn't hold their email. It never merges with an email identity.
Mindset checks only that an email has the right shape. It doesn't check that the mailbox exists.
Pick one form per user and keep it. Switching a user between the two gives you two separate users with two separate conversation histories.
Hand the session to the browser
- Create it when the user opens the page, and use it straight away. A session that isn't used within about 20 minutes of being created expires.
- Keep it in memory. Don't put it in local storage or a cookie, and don't render it into HTML that a cache could serve to someone else. Send the response with
cache-control: no-store. - Give the SDK a function, not the value. The page passes a
getSessionfunction that calls your endpoint, so the SDK can ask again when it needs to. - One session per agent. Two agents on one page need two calls and get two independent sessions.
How long a session lasts
You don't write renewal code, and there is no renewal endpoint for you to call.
While the page is open, the SDK renews its own short-lived access token in the background. A session that sits idle for 7 days ends, and every session ends 30 days after it was created. A page reload also loses the session, because the SDK keeps it only in memory.
When the SDK can't continue, it calls your getSession function again. Your backend creates a new session, the same way as the first time.
End someone's access
All of these are done in AMS, and each takes effect on the user's next turn:
- Untick Embedded in your own product on the agent. Nobody can run it from an embed.
- Switch the agent off (archive it). Nobody can reach it.
- Remove the user from your org. That person can't run anything.
What the credential can and can't do
The credential is accepted only where the agent runs: inference, tool calls and run events. It is refused on every management and admin surface.
That holds whatever the user's role. An org admin's session still runs one agent and nothing more. There is no way to widen it.
The credential works from any page that holds it. Treat it like a short-lived password for that user.
Your org API key is admin-grade. Keep it on your server, keep it out of logs, and revoke it in Settings → API keys if you think it has leaked.
Tag sessions with attribution
attribution labels travel with the session. Mindset adds them to every trace span the session produces, as mindset.tag.<key> attributes in your own trace export, so you can break down usage by your own dimensions.
"attribution": { "plan": "enterprise", "region": "emea", "team": "support" }- Values must be strings. Up to 16 keys, keys up to 64 characters, values up to 256 characters.
- A body over these limits is a 400. Mindset never trims it.
- You set them once, when the session is created. The page can't change them.
- They never affect what the session is allowed to do.
- An MCP server connection can also receive them, if an admin turns that on for the connection. It is off by default.
What can go wrong
Every error body is JSON with a code and a message.
| What happened | Status | code |
|---|---|---|
| A body field not in the table above | 400 | validation_error |
Both email and externalId, or neither | 400 | validation_error |
An email that isn't email-shaped | 400 | validation_error |
An externalId that breaks the format or length rule | 400 | validation_error |
attribution over the limits | 400 | validation_error |
| The agent has no published version to embed | 400 | validation_error |
| Embedded in your own product isn't ticked for the agent | 403 | agent_not_available_here |
An unknown user, without createUserIfNeeded | 404 | not_found |
| The agent isn't one of your org's agents, or is archived | 404 | not_found |
The key's org doesn't match orgSlug | 404 | not_found |
envSlug isn't the key's environment, or doesn't exist | 404 | not_found |
| The key doesn't carry admin scope | 404 | not_found |
| The user exists but isn't active (invited and not onboarded, or removed) | 409 | user_not_active |
| The agent has configuration versions but none is active | 409 | no_version_in_force |
The 404s for a wrong org, a wrong environment, a wrong agent and a key without admin scope look the same on purpose. Nobody holding a key can use them to find out which orgs or environments exist. During setup, check all four against the Embed tab.
Things that catch people out
- Query parameters are ignored, extra body fields are rejected. Everything that addresses the call is in the path.
createUserIfNeededdefaults tofalse. Most integrations wanttrue. Leaving it out gives a 404 that looks like a wrong agent.- The session is always end-user level. There is no admin session.