For the complete documentation index, see llms.txt. This page is also available as Markdown.

Serve an Agent From a Web App

Serve a Platform agent from a customer web application using External Integration credentials and external-user session tokens.

This guide shows how to embed an Amigo Platform agent in your own web application without exposing workspace API keys or external integration secrets to the browser.

The production pattern is a backend-for-frontend (BFF):

  1. Your backend stores an External Integration client_id and client_secret.

  2. Your backend exchanges those credentials for a parent JWT with the external_user_sessions:create scope.

  3. Your backend mints a short-lived external-user session JWT for the signed-in web user.

  4. Your browser uses only that external-user JWT to call scoped conversation endpoints.

  5. Your browser refreshes the external-user JWT through your backend when it is near expiry.

Prerequisites

Before you start, collect these values:

Value
Where it comes from
Notes

AMIGO_PLATFORM_API_URL

Amigo

Usually https://api.platform.amigo.ai. Token exchange is mounted at /token; workspace APIs are mounted under /v1/{workspace_id}.

AMIGO_WORKSPACE_ID

Developer Console Settings page for the customer workspace

The workspace that owns the agent, service, integration, and user data. Make sure you are viewing the customer's workspace, not an internal or staging workspace, before copying the ID.

AMIGO_SERVICE_ID

Developer Console Services page

Open the service your web application will expose and copy its service ID. It should point at the desired agent and version set.

Admin or owner API key

Amigo dashboard or existing provisioning

Needed once to create the External Integration and credential. The caller must be able to create/update external integrations and create/delete API-key-backed credentials. Do not use this from browser code.

External Integration client_id and client_secret

Created below

Stored only in your backend secret manager.

Agent Forge Go v0.1.3+

Amigo Agent Forge Go CLI

Required for forge platform external-integration and nested credential commands. Verify with forge --version.

TypeScript SDK @amigo-ai/platform-sdk v0.66.0+

npm package

Required for the External Integration, token exchange, external-user session, refresh, and conversation streaming helpers used below.

Customer subject key

Your application

A stable per-user key such as your internal user ID. Amigo stores only a keyed hash of this value.

consumer_entity_id

Optional Amigo world model entity

Not required for authenticated or anonymous web conversations. Pass it only when the web user is already linked to a patient, member, customer, or other materialized entity that the agent should use as conversation context.

You also need a customer backend that can authenticate the browser user before minting an authenticated Amigo token, or apply product-specific abuse controls before minting an anonymous Amigo token.

Authenticated and Anonymous Conversations

External-user sessions support both authenticated customer accounts and anonymous guest sessions. Choose the subject type when your backend mints the external-user session:

User mode

subject_type

external_subject_key

consumer_entity_id

Authenticated customer account

user

A stable internal user ID from your application. Do not use an email address or phone number that can change.

Optional. Pass it only if that app user is already linked to an Amigo world model entity.

Anonymous guest session

anonymous

A stable anonymous session or visitor key generated by your application.

Usually omitted. Pass it only if your product intentionally links the guest session to a materialized Amigo entity.

A pre-existing Amigo world model entity is not required to start agent conversations for authenticated customer accounts. When you omit consumer_entity_id, Amigo creates and uses a stable external subject derived from your external_subject_key and the conversation is still scoped to the selected service_id.

Pass consumer_entity_id when the agent needs to operate with an existing materialized entity, such as a patient, member, customer, lead, or account record. That lets the agent use the correct world model context and keeps the conversation associated with that entity. If the web user is not linked to a materialized entity yet, omit consumer_entity_id and create or link the entity later through your normal application flow.

Anonymous sessions use the same External Integration credential and conversation flow, but your backend remains responsible for abuse controls such as rate limits, CAPTCHA or bot checks, origin checks, and session expiration before minting or refreshing guest tokens. Do not expose External Integration credentials or Amigo refresh tokens to the browser for either mode.

System Design

Use this boundary model:

Component
Responsibilities

Customer browser

Renders chat UI, holds the external-user access token in memory, creates conversations, sends user messages, and streams agent responses.

Customer backend

Authenticates the user, stores external integration secrets and external-user refresh tokens, exchanges parent credentials, mints and refreshes external-user tokens, applies your app's authorization checks.

Amigo Identity

Issues parent JWTs, external-user JWTs, and rotating refresh tokens.

Amigo Platform API

Accepts the external-user JWT for scoped conversation access.

Amigo agent runtime

Runs the service's agent, context graph, tools, and safety behavior.

Create the External Integration

You can provision with forge, the TypeScript SDK, or direct HTTP. The examples below use Agent Forge Go v0.1.3+ for one-time setup because it includes both platform external-integration and platform external-integration credential commands and prints the one-time secret clearly.

Check your Forge version:

Configure Forge with a workspace-scoped admin or owner credential:

Create an integration record:

Save the returned id as AMIGO_EXTERNAL_INTEGRATION_ID.

Create a credential scoped to the service you will expose:

Save these response fields in your backend secret manager:

The client_secret is returned only on create and rotate. If you lose it, rotate the credential and store the replacement.

TypeScript SDK Snippets

The public SDK guide has a complete TypeScript walkthrough: External User Text Conversations. The snippets in this guide require @amigo-ai/platform-sdk v0.66.0+.

Install the SDK:

Create the integration credential from an admin or setup job:

Mint an external-user session from your backend:

Use the child access token with a separate SDK client:

Runtime Backend Flow

Your backend exposes a route such as POST /api/amigo/session. It should:

  1. Authenticate the web user with your normal app session.

  2. Decide whether the user may access this Amigo service.

  3. Choose subject_type:

    • user for authenticated customer accounts.

    • anonymous for guest sessions.

  4. Set external_subject_key to a stable app-side key. Use a durable user ID, not an email address that can change.

  5. Optionally set consumer_entity_id if the user is already linked to an Amigo world model entity.

  6. Mint or reuse a cached parent token from client_credentials.

  7. Mint an external-user session token, store the refresh token server-side, and return the access token metadata to the browser.

If you use @amigo-ai/platform-sdk v0.66.0+ server-side, the equivalent token resource methods are client.tokens.exchangeClientCredentials(), client.tokens.createExternalUserSession(), and client.tokens.refresh().

Browser Conversation Flow

After your backend returns an external-user token, create a conversation and send turns with that token. For streaming UI, call the turn endpoint with Accept: text/event-stream.

When rendering streamed turns:

  • Append user messages optimistically when the browser sends them.

  • Render token events only as provisional text if you want a typing effect.

  • Commit message events as the completed agent messages in the chat transcript.

  • External-user streams never include tool_call_started / tool_call_completed events - tool-call detail is workspace-credential-only, and requesting include_tool_calls=true with an external-user token returns 403. During tool-backed turns, expect a quiet gap after token/thinking activity and keep the response UI pending until the final message and done events arrive.

  • On page load, refresh, or reconnect, reload history with GET /conversations/{id} and render turns[].role / turns[].text.

  • Do not use /calls/{id} or agent_transcript to render web chat history. Those fields are for call-oriented transcript views, not customer web chat.

  • Handle deferred tool results. When a turn returns background_pending: true, the message you just rendered is only an acknowledgement - a slow tool is still running and the real answer arrives a few seconds later. Keep the turn in a "working…" state and drain the result: send the next user turn, or poll with POST /conversations/{id}/turns?poll=true and an empty request body (no message) until you receive a turn with background_pending: false. Combining poll=true with a non-empty message returns 422; the SDK wrapper pollTurn() handles this for you. Do not treat the acknowledgement as final, and do not subscribe the browser to /events/stream for this - that is a workspace-wide observer bus, not the per-chat delivery path. See Background (deferred) tool results.

For a production UI:

  • Keep the external-user access_token in memory when possible.

  • Refresh before expires_in elapses, with a buffer such as 60 seconds.

  • Refresh by calling your backend with the user's normal app session cookie; do not pass a refresh token from the browser.

  • Use the latest access token for every conversation request.

  • If refresh fails with invalid_grant, discard the current access token, clear the server-side refresh token, and mint a new external-user session after revalidating the app user.

The sessions/connect WebSocket endpoint is for materialized world-model entities and requires an entity_id query parameter. For External Integration web chat, prefer the Conversations REST/SSE endpoints shown above because they derive the correct entity binding from the external-user JWT and enforce the token's service_id and external-user scopes.

Refresh Endpoint in Your Backend

Expose a backend route such as POST /api/amigo/session/refresh. It should verify the app session, load the current server-side refresh token for that app user/session, and call Amigo's refresh grant with workspace_id.

Return the new access token to the browser and replace the stored refresh token after every successful refresh.

JWT, TTL, and Refresh Lifecycle

There are two JWT levels in this flow.

Token
Holder
How it is minted
TTL
Purpose

Parent JWT

Customer backend only

grant_type=client_credentials with External Integration client_id and client_secret

expires_in=900 seconds

Delegation token that can mint external-user sessions only.

External-user JWT

Browser

grant_type=external_user_session using the parent JWT

Default 3600 seconds. ttl_seconds may be 900 to 3600.

Runtime token for the signed-in or anonymous web subject.

External-user refresh token

Customer backend only

Returned with the external-user JWT

Absolute session limit is 7 days; idle timeout is the external-user token TTL selected at mint time.

Rotates the browser session without re-exchanging parent credentials.

External-user JWT claims include:

Claim
Meaning

principal_type

Always external_user.

sub

The Amigo consumer_subject_id.

workspace_id

Workspace the token is bound to.

credential_id

Parent External Integration credential ID.

session_id

External-user session ID.

consumer_subject_id

Stable Amigo subject created from your external_subject_key hash.

subject_type

user or anonymous.

consumer_entity_id

Present only when you pass a materialized Amigo entity.

service_id

The only service this token can use.

scopes

Subset of external-user scopes granted for this session.

External-user scopes are restricted to:

  • conversations:create

  • conversations:turn

  • conversations:read_own

  • conversations:close_own

  • conversations:approve_own - enables the subject of care to approve or reject a parked, approval-gated write in their own conversation (the self-approval path)

A mint request with no explicit scope parameter grants all five.

Refresh lifecycle details:

  • The parent JWT is not refreshed. When it expires, your backend repeats the client_credentials exchange.

  • The browser must send refresh requests through your backend using its normal app authentication; it should not hold the Amigo refresh token.

  • The refresh grant must include workspace_id for external-user refresh tokens.

  • Each successful refresh returns a new access token and a new refresh token.

  • The previous refresh token is rotated away. Replace the server-side stored token immediately.

  • A short 30-second grace window makes retrying the same refresh request idempotent if the client loses the response.

  • Reusing an old refresh token after the grace window is treated as token reuse and revokes the token family.

  • Refresh fails if the External Integration credential is revoked, expired, inactive, or no longer allows the session's service_id.

  • Refresh fails if the external-user session is revoked, the workspace is inactive, the session has been idle longer than its selected TTL, or the 7-day absolute session lifetime has elapsed.

Operational Guidance

  • Use one External Integration credential per deployed web application and environment.

  • Scope each credential to the smallest service allowlist possible.

  • Rotate External Integration credentials on a schedule and immediately after suspected exposure.

  • Cache the parent JWT server-side only until expires_in minus a safety buffer.

  • Handle 429 responses and honor Retry-After on token and conversation calls.

  • Log token mint and refresh failures by status and error code, but never log token values.

  • Treat external_subject_key as stable identity material. Use an internal immutable ID rather than email or phone number.

  • For linked users, prefer passing consumer_entity_id so the agent can use the correct world model context.

  • For guest users, use subject_type=anonymous and a stable anonymous session key.

Last updated

Was this helpful?