This case study integrates AgentChat into a multi-tenant project SaaS called ProjectDesk. Every customer uses the same project and task APIs, but user Alice may act only in workspace_a and user Bob only in workspace_b. We will reuse one API document while creating two sessions with different write-only request headers.
The security boundary is not a prompt that asks the model to stay inside a workspace. ProjectDesk authenticates and authorizes every HTTP tool request. AgentChat selects operations and injects the correct session credential; ProjectDesk decides whether each operation is allowed.
Step 1: make the business API tenant-aware
GET /v1/projects?status=active
→ { "items": [{ "project_id": "proj_12", "name": "Website launch", "role": "editor" }] }
POST /v1/projects/proj_12/tasks
{ "title": "Review launch checklist", "due_at": "2026-08-28" }
→ { "task_id": "task_91", "project_id": "proj_12", "status": "open" } ProjectDesk verifies the bearer token, derives its user identity, confirms membership in the X-Workspace-ID workspace, and adds that workspace to every database query. A project ID alone never bypasses the tenant filter. Read and write scopes are checked separately.
Step 2: register one reusable ProjectDesk document
POST $AGENTCHAT_SITE_URL/api/api-documents
Authorization: Bearer ac_live_AGENTCHAT_KEY
Content-Type: application/json
{
"title": "ProjectDesk Projects and Tasks",
"description": "List authorized projects and create tasks inside them.",
"content": "# ProjectDesk API\nBase URL: https://projects.example.com/v1\n\nGET /projects?status=active returns only projects visible to the authenticated workspace member. Response items contain project_id, name, and role.\n\nPOST /projects/{project_id}/tasks body: title required, due_at optional ISO date. Create only after resolving a project through GET /projects. Response contains task_id, project_id, and status.\n\n401 means the session credential is missing or expired. 403 means the current member lacks permission. On either response, do not retry and tell the user that the chat credential or role must be updated."
} This document describes capability, not identity. ProjectDesk saves the returned document UUID once and attaches it to chats for every tenant. No tenant ID, user token, or secret is embedded in the document.
Step 3: create Alice’s AgentChat session
POST $AGENTCHAT_SITE_URL/api/agent/sessions
Authorization: Bearer ac_live_AGENTCHAT_KEY
Content-Type: application/json
{
"title": "ProjectDesk assistant — Alice",
"llm_config_id": "llm_config_uuid",
"api_document_ids": ["projectdesk_document_uuid"],
"system_prompt": "Help the current member manage projects. Never infer access from names; rely on API results.",
"max_turns": 10,
"host_headers": [
{ "host": "projects.example.com", "header_key": "Authorization", "header_value": "Bearer alice_short_lived_token" },
{ "host": "projects.example.com", "header_key": "X-Workspace-ID", "header_value": "workspace_a" }
]
} The session response includes ordinary configuration but never returns header_value. AgentChat can inject Alice’s token into a matching HTTP request, while the model can see neither the token nor the stored authorization header.
Step 4: create Bob’s session from the same document
POST $AGENTCHAT_SITE_URL/api/agent/sessions
Authorization: Bearer ac_live_AGENTCHAT_KEY
Content-Type: application/json
{
"title": "ProjectDesk assistant — Bob",
"llm_config_id": "llm_config_uuid",
"api_document_ids": ["projectdesk_document_uuid"],
"system_prompt": "Help the current member manage projects. Never infer access from names; rely on API results.",
"max_turns": 10,
"host_headers": [
{ "host": "projects.example.com", "header_key": "Authorization", "header_value": "Bearer bob_short_lived_token" },
{ "host": "projects.example.com", "header_key": "X-Workspace-ID", "header_value": "workspace_b" }
]
} The API contract and model configuration can be shared, while session headers define the caller for each conversation. This avoids generating duplicate documents for every customer and keeps credential rotation independent from capability documentation.
Step 5: run the same instruction in both sessions
POST /api/agent/sessions/alice_session_uuid/chat
{ "message": "Create a task called Review launch checklist in the Website launch project, due August 28." }
POST /api/agent/sessions/bob_session_uuid/chat
{ "message": "Create a task called Review launch checklist in the Website launch project, due August 28." } For Alice, http_request sends Alice’s token and workspace_a only to projects.example.com. ProjectDesk returns projects visible to Alice; the agent resolves the requested project from that result and posts the task. Bob’s run follows the same plan, but ProjectDesk filters against workspace_b. If Bob cannot see that project, the agent receives no matching project and must not invent or reuse Alice’s project ID.
How host matching prevents credential leakage
AgentChat injects a session header only when the HTTP destination matches its configured hostname and optional port. Redirects are matched again at every hop. A credential for projects.example.com is therefore not carried to files.example.net, an image host, or an unexpected redirect destination.
What a permission failure looks like
HTTP/1.1 403 Forbidden
Content-Type: application/json
{ "error": "insufficient_scope", "required_scope": "tasks:write" } AgentChat’s instructions treat 401 and 403 as credential configuration problems. The agent should stop the blocked operation and tell the user what permission needs attention. It should not request the secret in chat, reveal stored header values, or repeatedly retry a denial.
ProjectDesk should keep denial responses useful but non-sensitive. It can name the required scope without confirming whether a cross-tenant record exists. Audit logs should record the verified actor and attempted resource, while redacting bearer tokens and other header values.
Step 6: consume AgentChat state and ProjectDesk state separately
GET /api/agent/sessions/alice_session_uuid/messages?after_id=&after_revision=0
GET /api/agent/sessions/alice_session_uuid/state
GET https://projects.example.com/v1/projects/proj_12/tasks The ProjectDesk chat UI polls AgentChat messages and merges assistant revisions while processing. After the run finishes, the project board reloads tasks from the ProjectDesk API or database. The assistant response explains the action, but the saved task record—not the prose—is the authoritative product state.
Use separate sessions for separate privilege levels
A read-only assistant can attach search and reporting documents with a token limited to read scopes. A project editor can attach task-writing documents with a write-scoped token. High-risk operations such as billing changes, account deletion, or public publishing should use a narrower session or a product-side pending-approval endpoint.
For approval flows, the agent creates a pending action and returns its ID. ProjectDesk shows the exact change to the user, records explicit approval, and performs the irreversible operation in its own backend. The final authorization decision never depends on the model interpreting a confirmation phrase.
Production isolation tests
- Use Alice’s session to request a known workspace_b project ID and verify ProjectDesk returns no cross-tenant data.
- Run identical prompts for Alice and Bob and verify their HTTP calls receive different authorized results.
- Expire, revoke, omit, and corrupt each session token and verify the correct 401 behavior.
- Give a member read-only scope and verify every task write returns 403 without changing the database.
- Redirect a request to another hostname and verify AgentChat does not forward configured credentials.
- Inspect session API responses, tool results, application logs, and error messages for secret leakage.
- Stop and continue a run, then verify the resumed work still uses only that session’s headers and document bindings.
The reusable multi-tenant pattern
Register the business capability once. Create one AgentChat session per user conversation. Attach the relevant document UUIDs, inject short-lived credentials for the exact API host, and enforce identity and policy inside your SaaS endpoints. Then let AgentChat manage reasoning and durable conversation state while your own database remains the source of truth.