# Session API

> The server-to-server call that creates a session for one of your users: endpoint, request and response fields, attribution, and errors.

In the browser, the SDK runs the conversation with Mindset, with your page tools and widgets. It gets its session from your server, which creates it with your org API key. Mindset holds the agent and runs its model and tools. Highlighted: Your server.

Your server makes this call to create a session for a user. What a session is and what it can reach is in [Sessions and trust](https://docs.mindset.ai/docs/sessions-and-trust).

## Endpoint

|                          |                                                                                                                                       |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
| Method and path          | `POST https://YOUR-MINDSET-HOST/api/v1/orgs/{orgSlug}/envs/{envSlug}/agent-sessions`                                                  |
| Authentication           | Your org API key in the `x-api-key` header, not in `Authorization`                                                                    |
| Key                      | Created in **Settings → API keys** for the same environment as `envSlug`. See [Environments](https://docs.mindset.ai/docs/environments#members-and-api-keys) |
| Where to find the values | The agent's **Embed** tab in the Studio shows the host, both slugs and the agent's handle                                             |
| Callers                  | Servers only. The route sends no CORS headers, so a browser can't call it                                                             |

## Request body

| Field                | Type    | Required       | Notes                                                                                         |
| -------------------- | ------- | -------------- | --------------------------------------------------------------------------------------------- |
| `user.email`         | string  | one of the two | Must contain `@` and no spaces. Matched without regard to case                                |
| `user.externalId`    | string  | one of the two | Your own ID. Letters, numbers, `_` and `-` only, 100 characters or fewer. Matched exactly     |
| `agent`              | string  | yes            | The agent's handle or its ID. The ID survives a rename of the handle                          |
| `createUserIfNeeded` | boolean | no             | `true` creates a user Mindset hasn't seen. Defaults to `false`, which refuses an unknown user |
| `attribution`        | object  | no             | Your own string labels for this session. See [Attribution](#attribution)                      |

Send exactly one of `user.email` and `user.externalId`. The body is strict: any field not in this table is a 400.

## Response

A 200 with a JSON body:

| 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, and missing means none |

Return the whole body to the browser unchanged, with `cache-control: no-store`, from the endpoint your page's `getSession` function calls. If you pass only `session`, the SDK still runs, but nothing that sits behind a release feature appears, and nothing reports it.

## Attribution

| Rule           | Limit                                 |
| -------------- | ------------------------------------- |
| Values         | Strings only                          |
| Keys           | Up to 16, each up to 64 characters    |
| Values' length | Up to 256 characters                  |
| Over a limit   | A 400. Mindset never trims the labels |

The labels reach three places:

| Where                      | What arrives                                                                                                                                                                           |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Your trace export          | Every span the session produces, as `mindset.tag.<key>` attributes                                                                                                                     |
| An MCP server you attached | Every call to it, including calls from a function the agent runs, in `_meta["mindset/attribution"]`. An admin turns this on per server with **Send attribution labels to this server** |
| Your functions and scripts | They read a label as `attribution.<key>`, so they can decide per plan, region or account                                                                                               |

They are set once, at creation. The page can't change them, and Mindset never decides anything on them: the session may do exactly the same with or without labels. To test a function or script that reads one, give the test run labels of its own. See [Functions](https://docs.mindset.ai/docs/functions) and [Scripts](https://docs.mindset.ai/docs/scripts).

## Errors

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` or `externalId` that breaks its format                             | 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, is archived, or is a personal agent | 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, environment, agent or key scope look the same on purpose, so nobody holding a key can use them to learn which orgs or environments exist. During setup, check each value against the Embed tab. Leaving out `createUserIfNeeded` gives a 404 that looks like a wrong agent.

## Example

```bash
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. [Put an agent on your page](https://docs.mindset.ai/docs/put-an-agent-on-your-page) has a complete server that makes this call.
