> ## Documentation Index
> Fetch the complete documentation index at: https://docs.concatflow.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Per-user door

> Let each logged-in customer ask about their own data. Your server opens a session, your page shows the assistant.

On the per-user door, each answer is filtered on one customer. Your server says which customer is logged in, with your API key. Concatflow returns a short-lived token. Your page gives that token to the assistant, which receives it by `postMessage`.

```mermaid theme={"system"}
sequenceDiagram
    participant B as Your page
    participant S as Your server
    participant C as Concatflow
    B->>S: POST /api/concatflow/session
    S->>C: sessions.create (API key)
    C-->>S: token
    S-->>B: token
    B->>C: iframe gets the token
```

## Before you start

In the assistant's dashboard:

* **Isolation** is signed. It defines the partition keys, such as `customer_id`.
* In **Doors**, the **per-user door** is on, and **Allowed origins** lists your app's origin.
* The assistant is **published**.

You also need an [API key and the assistant ID](https://docs.concatflow.com/identifiers.md).

## 1. Install the server SDK

```bash theme={"system"}
npm install @concatflow/node
```

`@concatflow/node` has no dependencies and needs Node 18 or later.

## 2. Add a session route

Add one route that opens a session for the logged-in customer. The page calls it on load, and again when the token expires.

> [!WARNING]
>
> **Take the partition from your own session, never from the request.** `req.body.customerId` is whatever the visitor typed in their console. `req.user.customerId`, set by your login, is the truth.

**Express**

```ts server.ts theme={"system"}
import express from "express";
import { Concatflow, ConcatflowError } from "@concatflow/node";
import { requireLogin } from "./auth"; // your middleware, sets req.user

const app = express();
const concatflow = new Concatflow({ apiKey: process.env.CONCATFLOW_API_KEY! });

app.post("/api/concatflow/session", requireLogin, async (req, res) => {
  try {
    const session = await concatflow.sessions.create({
      assistantId: process.env.CONCATFLOW_ASSISTANT_ID!,
      partitions: { customer_id: req.user.customerId },
    });
    res.json(session); // { token, expiresAt }
  } catch (err) {
    if (!(err instanceof ConcatflowError)) throw err;
    console.error(err.status, err.code);
    res.status(502).json({ error: err.code ?? "concatflow_error" });
  }
});

app.listen(3000);
```

**Next.js (App Router)**

```ts lib/concatflow.ts theme={"system"}
import "server-only";
import { Concatflow } from "@concatflow/node";

const concatflow = new Concatflow({ apiKey: process.env.CONCATFLOW_API_KEY! });

export function openSession(customerId: string) {
  return concatflow.sessions.create({
    assistantId: process.env.CONCATFLOW_ASSISTANT_ID!,
    partitions: { customer_id: customerId },
  });
}
```

```ts app/api/concatflow/session/route.ts theme={"system"}
import { auth } from "@/lib/auth"; // your auth
import { openSession } from "@/lib/concatflow";

export async function POST() {
  const user = await auth();
  if (!user) return new Response(null, { status: 401 });
  return Response.json(await openSession(user.customerId));
}
```

The keys of `partitions` must be **exactly** the partition keys of the assistant's isolation. A missing or extra key fails with `403 partition_keys_mismatch`. Values are strings or numbers.

## 3. Show the assistant

**Script tag**

```html index.html theme={"system"}
<script src="https://app.concatflow.com/widget.js"></script>
<div id="assistant"></div>
<script>
  async function openSession() {
    const res = await fetch("/api/concatflow/session", { method: "POST" });
    if (!res.ok) throw new Error("Could not open a Concatflow session");
    return (await res.json()).token;
  }

  openSession().then((token) => {
    Concatflow.init({
      session: token,
      launcher: "bar",
      target: "#assistant",
      onSessionExpired: openSession,
    });
  });
</script>
```

**npm**

```ts assistant.ts theme={"system"}
import { loadConcatflow } from "@concatflow/embed";

async function openSession(): Promise<string> {
  const res = await fetch("/api/concatflow/session", { method: "POST" });
  if (!res.ok) throw new Error("Could not open a Concatflow session");
  return (await res.json()).token;
}

const Concatflow = await loadConcatflow();
export const assistant = Concatflow.init({
  session: await openSession(),
  launcher: "bar",
  target: "#assistant",
  onSessionExpired: openSession,
});
```

**Next.js**

The server component opens the first session. The client component renews it.

```tsx app/dashboard/page.tsx theme={"system"}
import { Suspense } from "react";
import { redirect } from "next/navigation";
import { auth } from "@/lib/auth";
import { openSession } from "@/lib/concatflow";
import { Assistant } from "./assistant";

export default function Page() {
  return (
    <Suspense>
      <Dashboard />
    </Suspense>
  );
}

async function Dashboard() {
  const user = await auth();
  if (!user) redirect("/login");
  const { token } = await openSession(user.customerId);
  return <Assistant customerId={user.customerId} initialToken={token} />;
}
```

`<Suspense>` is required when Cache Components are on, as in a new Next.js 16 app: `auth()` reads the request.

```tsx app/dashboard/assistant.tsx theme={"system"}
"use client";
import { ConcatflowAssistant } from "@concatflow/embed/react";

async function renew(): Promise<string> {
  const res = await fetch("/api/concatflow/session", { method: "POST" });
  if (!res.ok) throw new Error("Could not open a Concatflow session");
  return (await res.json()).token;
}

export function Assistant({ customerId, initialToken }: { customerId: string; initialToken: string }) {
  return (
    <ConcatflowAssistant
      key={customerId}
      session={initialToken}
      launcher="bubble"
      onSessionExpired={renew}
    />
  );
}
```

That's all. Each customer now sees answers on their own rows only.

For a complete app with a login, renewal and an isolation check, follow the [Next.js tutorial](https://docs.concatflow.com/tutorials/nextjs-per-customer.md).

## What the token is

* **Opaque.** It describes nothing. The partition stays on Concatflow's side.
* **Short-lived.** It expires 15 minutes after it was opened. `onSessionExpired` renews it without the customer noticing. See [Session renewal](https://docs.concatflow.com/guides/session-renewal.md).
* **Never in a URL.** The page hands it to the iframe by `postMessage`, so it stays out of logs and `Referer` headers.
* **Stored hashed.** Concatflow keeps a SHA-256 of the token, never the token.

The **conversation** outlives tokens. There is one conversation per assistant and per partition. When the customer comes back tomorrow, the assistant shows their last 50 questions.

## When the customer changes

When a customer logs out, call `destroy()`. When another customer logs in on the same page, `destroy()` the assistant and `init()` a new one with the new customer's token.

> [!WARNING]
>
> Use `setSession()` only to renew the **same** customer's token. The iframe loads a conversation's history once: a token for another customer would leave the previous customer's questions on screen. In React, give the component a `key` per customer, as above, so it remounts.

## Several partition keys

If the isolation signs two keys, send both:

```ts theme={"system"}
await concatflow.sessions.create({
  assistantId: process.env.CONCATFLOW_ASSISTANT_ID!,
  partitions: { customer_id: user.customerId, region: user.region },
});
```

## Errors

`sessions.create` throws a `ConcatflowError` on any non-2xx answer. The ones you will meet first:

| Status | Code | Fix |
| - | - | - |
| 401 | `invalid_api_key` | Check `CONCATFLOW_API_KEY`. The key may be revoked. |
| 403 | `partition_keys_mismatch` | Send exactly the keys of the isolation. `err.body.detail.expected` lists them. |
| 409 | `door_closed` | Turn the per-user door on in **Doors**. |
| 409 | `isolation_unsigned` | Sign the isolation in **Isolation**. |
| 409 | `not_published` | Publish the assistant. |

The full list is in [Errors](https://docs.concatflow.com/reference/node/errors.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.