Docs Guides Managing End Users This guide walks through the full lifecycle of managing end users in Weavz — from registering a user to executing actions with their connections.
End users live within workspaces. Start by creating a workspace and configuring its integrations.
curl TypeScript SDK Python SDK TypeScript Python
# Create a workspace
curl -X POST https://api.weavz.io/api/v1/workspaces \
-H "Authorization: Bearer wvz_your_api_key" \
-H "Content-Type: application/json" \
-d '{"name": "My App", "slug": "my-app"}'
# Add Slack with per-user connections
curl -X POST https://api.weavz.io/api/v1/workspaces/{workspaceId}/integrations \
-H "Authorization: Bearer wvz_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"integrationName": "slack",
"connectionStrategy": "per_user"
}' import { WeavzClient } from '@weavz/sdk'
const client = new WeavzClient ({ apiKey: 'wvz_your_api_key' })
// Create a workspace
const { workspace } = await client.workspaces. create ({
name: 'My App' ,
slug: 'my-app' ,
})
// Add Slack with per-user connections
await client.workspaces. addIntegration (workspace.id, {
integrationName: 'slack' ,
connectionStrategy: 'per_user' ,
}) from weavz_sdk import WeavzClient
client = WeavzClient( api_key = "wvz_your_api_key" )
# Create a workspace
result = client.workspaces.create( name = "My App" , slug = "my-app" )
workspace = result[ "workspace" ]
# Add Slack with per-user connections
client.workspaces.add_integration(workspace[ "id" ],
integration_name = "slack" ,
connection_strategy = "per_user" ,
) // Create a workspace
const wsRes = await fetch ( 'https://api.weavz.io/api/v1/workspaces' , {
method: 'POST' ,
headers: {
'Authorization' : 'Bearer wvz_your_api_key' ,
'Content-Type' : 'application/json' ,
},
body: JSON . stringify ({ name: 'My App' , slug: 'my-app' }),
})
const { workspace } = await wsRes. json ()
// Add Slack with per-user connections
await fetch ( `https://api.weavz.io/api/v1/workspaces/${ workspace . id }/integrations` , {
method: 'POST' ,
headers: {
'Authorization' : 'Bearer wvz_your_api_key' ,
'Content-Type' : 'application/json' ,
},
body: JSON . stringify ({
integrationName: 'slack' ,
connectionStrategy: 'per_user' ,
}),
}) import httpx
headers = { "Authorization" : "Bearer wvz_your_api_key" }
# Create a workspace
res = httpx.post(
"https://api.weavz.io/api/v1/workspaces" ,
headers = headers,
json = { "name" : "My App" , "slug" : "my-app" },
)
workspace = res.json()[ "workspace" ]
# Add Slack with per-user connections
httpx.post(
f "https://api.weavz.io/api/v1/workspaces/ { workspace[ 'id' ] } /integrations" ,
headers = headers,
json = {
"integrationName" : "slack" ,
"connectionStrategy" : "per_user" ,
},
)
When a new customer signs up for your product, register them as an end user. Use their ID from your system as the externalId.
curl TypeScript SDK Python SDK TypeScript Python
curl -X POST https://api.weavz.io/api/v1/end-users \
-H "Authorization: Bearer wvz_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"workspaceId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"externalId": "user_123",
"displayName": "Alice Johnson",
"email": "[email protected] "
}' const { endUser } = await client.endUsers. create ({
workspaceId: workspace.id,
externalId: 'user_123' ,
displayName: 'Alice Johnson' ,
email: '[email protected] ' ,
}) result = client.end_users.create(
workspace_id = workspace[ "id" ],
external_id = "user_123" ,
display_name = "Alice Johnson" ,
email = "[email protected] " ,
)
end_user = result[ "endUser" ] const res = await fetch ( 'https://api.weavz.io/api/v1/end-users' , {
method: 'POST' ,
headers: {
'Authorization' : 'Bearer wvz_your_api_key' ,
'Content-Type' : 'application/json' ,
},
body: JSON . stringify ({
workspaceId: workspace.id,
externalId: 'user_123' ,
displayName: 'Alice Johnson' ,
email: '[email protected] ' ,
}),
})
const { endUser } = await res. json () res = httpx.post(
"https://api.weavz.io/api/v1/end-users" ,
headers = headers,
json = {
"workspaceId" : workspace[ "id" ],
"externalId" : "user_123" ,
"displayName" : "Alice Johnson" ,
"email" : "[email protected] " ,
},
)
end_user = res.json()[ "endUser" ]
Create a connect token so the end user can connect their Slack account through the hosted connect portal.
curl TypeScript SDK Python SDK TypeScript Python
curl -X POST https://api.weavz.io/api/v1/end-users/{endUserId}/connect-token \
-H "Authorization: Bearer wvz_your_api_key" \
-H "Content-Type: application/json" \
-d '{"integrationName": "slack"}' const { connectUrl } = await client.endUsers. createConnectToken (
endUser.id,
{ integrationName: 'slack' }
)
// Send connectUrl to your frontend to open in a popup
console. log ( 'Connect URL:' , connectUrl) result = client.end_users.create_connect_token(
end_user[ "id" ],
integration_name = "slack" ,
)
connect_url = result[ "connectUrl" ]
# Send connect_url to your frontend to open in a popup
print ( "Connect URL:" , connect_url) const res = await fetch (
`https://api.weavz.io/api/v1/end-users/${ endUser . id }/connect-token` ,
{
method: 'POST' ,
headers: {
'Authorization' : 'Bearer wvz_your_api_key' ,
'Content-Type' : 'application/json' ,
},
body: JSON . stringify ({ integrationName: 'slack' }),
}
)
const { connectUrl } = await res. json () res = httpx.post(
f "https://api.weavz.io/api/v1/end-users/ { end_user[ 'id' ] } /connect-token" ,
headers = headers,
json = { "integrationName" : "slack" },
)
connect_url = res.json()[ "connectUrl" ]
Open connectUrl in a popup or redirect the end user to it. The hosted connect page guides them through the OAuth2 consent flow. Once they approve, the connection is automatically linked to the end user.
Once the end user has connected their Slack, you can execute actions on their behalf by passing endUserId:
curl TypeScript SDK Python SDK TypeScript Python
curl -X POST https://api.weavz.io/api/v1/actions/execute \
-H "Authorization: Bearer wvz_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"integrationName": "slack",
"actionName": "send_channel_message",
"input": {
"channel": "C0123456789",
"text": "Hello from Alice!"
},
"workspaceId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"endUserId": "user_123"
}' const result = await client.actions. execute ( 'slack' , 'send_channel_message' , {
workspaceId: workspace.id,
endUserId: 'user_123' ,
input: {
channel: 'C0123456789' ,
text: 'Hello from Alice!' ,
},
}) result = client.actions.execute(
"slack" ,
"send_channel_message" ,
workspace_id = workspace[ "id" ],
end_user_id = "user_123" ,
input = {
"channel" : "C0123456789" ,
"text" : "Hello from Alice!" ,
},
) const res = await fetch ( 'https://api.weavz.io/api/v1/actions/execute' , {
method: 'POST' ,
headers: {
'Authorization' : 'Bearer wvz_your_api_key' ,
'Content-Type' : 'application/json' ,
},
body: JSON . stringify ({
integrationName: 'slack' ,
actionName: 'send_channel_message' ,
input: { channel: 'C0123456789' , text: 'Hello from Alice!' },
workspaceId: workspace.id,
endUserId: 'user_123' ,
}),
})
const data = await res. json () res = httpx.post(
"https://api.weavz.io/api/v1/actions/execute" ,
headers = headers,
json = {
"integrationName" : "slack" ,
"actionName" : "send_channel_message" ,
"input" : { "channel" : "C0123456789" , "text" : "Hello from Alice!" },
"workspaceId" : workspace[ "id" ],
"endUserId" : "user_123" ,
},
)
data = res.json()
Weavz resolves Alice's Slack connection automatically based on the endUserId and the workspace's per_user connection strategy.
Instead of generating connect URLs programmatically, you can send email invitations directly:
curl TypeScript SDK Python SDK TypeScript Python
curl -X POST https://api.weavz.io/api/v1/end-users/{endUserId}/invite \
-H "Authorization: Bearer wvz_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected] ",
"integrationName": "slack"
}' await client.endUsers. invite (endUser.id, {
email: '[email protected] ' ,
integrationName: 'slack' ,
}) client.end_users.invite(
end_user[ "id" ],
email = "[email protected] " ,
integration_name = "slack" ,
) await fetch ( `https://api.weavz.io/api/v1/end-users/${ endUser . id }/invite` , {
method: 'POST' ,
headers: {
'Authorization' : 'Bearer wvz_your_api_key' ,
'Content-Type' : 'application/json' ,
},
body: JSON . stringify ({
email: '[email protected] ' ,
integrationName: 'slack' ,
}),
}) httpx.post(
f "https://api.weavz.io/api/v1/end-users/ { end_user[ 'id' ] } /invite" ,
headers = headers,
json = {
"email" : "[email protected] " ,
"integrationName" : "slack" ,
},
)
The email contains a link to the connect portal where Alice can authorize her Slack account.
Check which integrations an end user has connected:
curl TypeScript SDK Python SDK TypeScript Python
curl https://api.weavz.io/api/v1/end-users/{endUserId} \
-H "Authorization: Bearer wvz_your_api_key" const { endUser , connections } = await client.endUsers. get (endUser.id)
for ( const conn of connections) {
console. log ( `${ conn . integrationName }: ${ conn . status }` )
} result = client.end_users.get(end_user[ "id" ])
for conn in result[ "connections" ]:
print ( f " { conn[ 'integrationName' ] } : { conn[ 'status' ] } " ) const res = await fetch ( `https://api.weavz.io/api/v1/end-users/${ endUserId }` , {
headers: { 'Authorization' : 'Bearer wvz_your_api_key' },
})
const { endUser , connections } = await res. json () res = httpx.get(
f "https://api.weavz.io/api/v1/end-users/ { end_user[ 'id' ] } " ,
headers = headers,
)
data = res.json()
connections = data[ "connections" ]
You can create an MCP server scoped to an end user. The AI agent uses the server's bearer token and all tool calls resolve to that end user's connections.
Create an end user Register the end user as shown in step 2 above.
Create an MCP server with endUserId curl TypeScript SDK Python SDK TypeScript Python
curl -X POST https://api.weavz.io/api/v1/mcp/servers \
-H "Authorization: Bearer wvz_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Agent for Alice",
"workspaceId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"endUserId": "user_123",
"mode": "TOOLS"
}' const { server , bearerToken } = await client.mcpServers. create ({
name: 'Agent for Alice' ,
workspaceId: workspace.id,
endUserId: 'user_123' ,
mode: 'TOOLS' ,
}) result = client.mcp_servers.create(
name = "Agent for Alice" ,
workspace_id = workspace[ "id" ],
end_user_id = "user_123" ,
mode = "TOOLS" ,
)
bearer_token = result[ "bearerToken" ] const res = await fetch ( 'https://api.weavz.io/api/v1/mcp/servers' , {
method: 'POST' ,
headers: {
'Authorization' : 'Bearer wvz_your_api_key' ,
'Content-Type' : 'application/json' ,
},
body: JSON . stringify ({
name: 'Agent for Alice' ,
workspaceId: workspace.id,
endUserId: 'user_123' ,
mode: 'TOOLS' ,
}),
})
const { server , bearerToken } = await res. json () res = httpx.post(
"https://api.weavz.io/api/v1/mcp/servers" ,
headers = headers,
json = {
"name" : "Agent for Alice" ,
"workspaceId" : workspace[ "id" ],
"endUserId" : "user_123" ,
"mode" : "TOOLS" ,
},
)
bearer_token = res.json()[ "bearerToken" ] Give the bearer token to the AI agent Configure your AI agent with the MCP server's bearer token and endpoint.
Handle missing connections When a tool call fails because the end user hasn't connected the required integration, the error includes a setup URL. Share this URL with the end user so they can connect their account and the agent can retry.
Best for strict isolation. Each customer gets their own workspace with separate integrations, connections, and end users.
graph TD
Org[Organization: My SaaS]
Org --> A[Workspace: Customer A]
Org --> B[Workspace: Customer B]
A --> Alice[alice - alice_123]
A --> Bob[bob - bob_456]
Alice --> AC[Alice's Slack]
Bob --> BC[Bob's Slack]
B --> Charlie[charlie - charlie_789]
Charlie --> CC[Charlie's Gmail]
Best for simpler setups where all customers share the same integration configuration.
graph TD
Org[Organization: My SaaS]
Org --> WS[Workspace: Production]
WS --> S[slack - per_user]
WS --> G[gmail - per_user_with_fallback]
WS --> Alice[alice]
WS --> Bob[bob]
WS --> Charlie[charlie]
Alice --> AS[Slack conn.]
Alice --> AG[Gmail conn.]
Bob --> BS[Slack conn.]
Bob -.- BG[Gmail fallback]
Charlie --> CG[Gmail conn.]
Charlie -.- CS[no Slack yet]
style BG stroke-dasharray: 5 5
style CS stroke-dasharray: 5 5
With per_user_with_fallback, end users who haven't connected their own account still get functionality through the workspace's default connection.
When you delete an end user, all their connections are automatically cleaned up:
curl TypeScript SDK Python SDK TypeScript Python
curl -X DELETE https://api.weavz.io/api/v1/end-users/{endUserId} \
-H "Authorization: Bearer wvz_your_api_key" await client.endUsers. delete (endUser.id) client.end_users.delete(end_user[ "id" ]) await fetch ( `https://api.weavz.io/api/v1/end-users/${ endUserId }` , {
method: 'DELETE' ,
headers: { 'Authorization' : 'Bearer wvz_your_api_key' },
}) httpx.delete(
f "https://api.weavz.io/api/v1/end-users/ { end_user[ 'id' ] } " ,
headers = headers,
)
PreviousUsing Storage & KV Store Next Activity Logs