AI Connect/Integration/Multi-tenant patterns

Multi-tenant patterns

Ask AI about Vinkius

One application-scoped client, scope resolved before request input, capabilities returned only for the authenticated user, and an isolation boundary you can test.

A single Vinkius client serves every user of your application. Isolation comes from how you derive the externalId, not from client instances. This page is the security checklist for wiring the SDK into a multi-tenant backend.

Vinkius never meets your real users. The platform knows your Application and the opaque identifier your backend passes, nothing else: no emails, no names, no cross-application profile. Thousands of users under one Application, each with their own connectors, credentials and state, while your user base stays entirely on your side.

Create one application-scoped client

typescript
// lib/vinkius.ts — server only
import { Vinkius } from '@vinkius/connect';

export const vinkius = new Vinkius({
  appId: process.env.VINKIUS_APP_ID!,
  apiKey: process.env.VINKIUS_APP_KEY!,
});

The client holds application credentials, never per-user state. Do not build one client per user; the user scope travels in the route.

Resolve scope before reading request input

Bind the externalId to your authenticated session before anything else. The authorization boundary is your session lookup; an App ID and key authorize the application, never a browser user:

typescript
async function handleCapabilities(request: Request) {
  const session = await requireSession(request); // your auth code
  const capabilities = await vinkius.user(session.userId).capabilities();
  // ...
}

Return only the authenticated user's capabilities

Project the fields your client needs and nothing else. The capability objects are already scoped to the user the session resolved:

typescript
return Response.json(
  capabilities.map(({ name, description, inputSchema }) => ({
    name,
    description,
    inputSchema,
  })),
);

Authorize connector setup separately

Writing credentials is a privileged operation. Verify that the session is allowed to administer the connector for that user before calling connect() and credentials.set():

typescript
const github = vinkius.user(session.userId).connector('github');
await github.connect();
await github.credentials.set({ GITHUB_TOKEN: submittedToken });

Execute only capabilities loaded in the same scope

Capabilities carry the connection route that produced them. Never accept a capability name from the client and execute it against a handle resolved from a different scope; load the capability set inside the authenticated request and dispatch from it:

typescript
const capabilities = await vinkius.user(session.userId).capabilities();
const capability = capabilities.findCapability(requestedName);
if (!capability) {
  return Response.json({ error: 'not available for this user' }, { status: 404 });
}
const result = await capability.execute(args, { idempotencyKey });

Use metadata only for non-secret application context

user.ensure(metadata) upserts the user and stores application context such as plan tier or labels. It is not a credential store; keep secrets in connector credentials, which are write-only.

Test the isolation boundary

Two users connecting the same connector must never see each other's state. A minimal test shape:

typescript
const alice = vinkius.user('alice_123');
const bob = vinkius.user('bob_456');

await alice.connector('github').connect();
const bobConnectors = await bob.connectors();

// bob's list must not contain alice's connection

Run this against a staging environment with its own App ID and key. See Authentication and scope for the environment separation rules.

Next steps