AI Connect/How to create/Ein Multi-Tenant-KI-SaaS

Ein Multi-Tenant-KI-SaaS

Frag die KI über Vinkius

Liefern Sie KI mit Connectoren an jeden Kunden Ihrer B2B-Plattform: begrenzen Sie jede external_id auf Tenant und Benutzer, halten Sie jede Organisation mit einem einzigen Application-Key isoliert und fügen Sie Steuerung pro Tenant hinzu, ohne jemals ein Credential über die Grenze preiszugeben. Ihre Kunden bekommen eine Konnektivitätsplattform; das Plumbing überlassen Sie ihnen nie.

Liefern Sie das KI-SaaS aus, auf das Ihre Kategorie gewartet hat: Jeder Kunde (Tenant) erhält ein Team von Benutzern, jeder Benutzer erhält seine KI, die auf sein GitHub, Jira und Slack zugreift, jeder Tenant isoliert auf Ihrem einen Application Key, und jeder Benutzer jedes Tenants gestützt auf Tausende KI-Verbindungen ab Tag eins. Keine von Ihnen gebaute Integration, kein von Ihnen gespeicherter Token, keine Identität, die Ihre Datenbank verlässt.

Jede Integrationsplattform auf dem Markt wird Ihnen sagen, dass dieses Problem in einem Vertrag pro Tenant, einer Rechnung pro Tenant oder Monaten Ihrer Ingenieure endet, die die Isolation von Hand bauen. Die Antwort des AI Connect SDK ist die, die kein Wettbewerber kopieren kann, ohne sein Produkt neu zu architecten: Liefern Sie KI-Funktionen, keine Integrationsprojekte. Dieser Leitfaden zeigt Ihnen, wie ein ganzer Marktplatz aus Tenants sich einen einzigen Application-Key teilt, ohne jemals eine Grenze zu überschreiten.

Hier ist der „Benutzer" eine Person in Ihrem Kundenunternehmen, daher muss die external_id sowohl den Tenant als auch den Menschen tragen. Diese einzelne Entscheidung ist das Isolationsmodell. Wenn es funktioniert, tut Ihr Produkt das, wofür einer Plattformfirma normalerweise Jahre und ein Sicherheitsteam brauchen, um es zu versprechen: jede Organisation ist eine Insel, jeder Benutzer ist Bürger von genau einer Insel, und Sie verwalten das ganze Archipel von einem einzigen Schlüssel aus. Vinkius trifft Ihre echten Nutzer niemals. Ihre E-Mails, ihre Namen, ihre Profile verlassen nie Ihre Datenbank; die Plattform sieht nur die opake Id, die Ihr Backend ihr übergibt.

Vinkiusnever sees your usersYour application keyvk_app_*acmetenant · isolatedusersown toolsglobextenant · isolatedusersown tools404404initechtenant · isolatedusersown tools
Every tenant an island: users are citizens of exactly one island, a cross-tenant attempt is a 404, and your brand faces every customer while Vinkius stays invisible.

Der Isolationsvertrag

  • Eine Application = Ihr Produkt. Alle Tenants teilen sich in der Regel Ihre einzelne appId.
  • Die external_id kodiert die Adresse. cus_<tenant>_u_<user> ist die Grenze; Fähigkeiten werden nur innerhalb davon aufgelöst.
  • Tenant-Überschreitung ist ein 404, kein 500. Eine offengelegte oder fehlgeleitete id kann die Verbindung eines anderen Tenants nicht lesen, Vinkius lehnt sie außerhalb des Scopes ab.
  • Ihre Benutzer bleiben unter Ihrer Kontrolle. Weder E-Mail noch Profil erreichen Vinkius; Sie übergeben nur eine opake id. Ihre Kundenbeziehung bleibt unter Ihrer Kontrolle.

Die Isolation wird aus einer wohlgeformten external_id abgeleitet, behandeln Sie sie daher als sicherheitskritische Eingabe. Erstellen Sie sie immer aus authentifizierten Tenant- und Benutzer-Claims, niemals aus Rohdaten der Anfrage, und lassen Sie niemals zu, dass ein Tenant die id eines anderen liefert. Für das detaillierte Garantienmodell siehe Authentifizierung und Scope und Sicherheit.

1. Einen Benutzer innerhalb eines Tenants adressieren

Komponieren Sie eine deterministische, URL-sichere id aus den beiden ids, denen Ihre Authentifizierung bereits vertraut.

typescript
interface AuthedPrincipal { tenantId: string; userId: string } // aus Ihrem JWT/Sitzung

const externalIdFor = (p: AuthedPrincipal) =>
  `cus_${p.tenantId}_u_${p.userId}`; // "cus_acme_u_9f2c"

2. Ein gemeinsamer Client

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

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

3. Den Akteur aus einer verifizierten Session auflösen

Leiten Sie den Principal aus Ihrem Token ab und übergeben Sie ihn an das SDK. Alles Nachgelagerte ist ebenfalls scoped, weil externalIdFor es ist.

typescript
import { vinkius } from './vinkius';

export async function actorFor(req: Request) {
  const claims = await verifySession(req); // Ihre Authentifizierung/Autorisierung
  if (!claims) throw new Error('unauthenticated');
  return vinkius.user(externalIdFor(claims));
}

4. Jeden Benutzer seine eigenen Tools verbinden lassen

Zwei Personen in zwei Unternehmen verbinden die gleiche GitHub-Integration und erhalten zwei völlig getrennte Verbindungen, Credentials und Fähigkeiten, automatisch.

typescript
// POST /connect  { connector, values }
async function connectForUser(claims: AuthedPrincipal, connector: string, values: Record<string, string>) {
  const handle = vinkius.user(externalIdFor(claims)).connector(connector);
  await handle.connect();
  await handle.credentials.set(values);
  return handle.status();
}

5. Tenant-spezifische Richtlinien auf einem gemeinsamen Key

In der Regel werden Sie unterschiedliche Connectoren pro Plan oder pro Tenant anbieten müssen. Weil der Akteur über einen Namespace verfügt, fügt sich die Konfiguration auf Tenant-Ebene ohne ein spezielles SDK-Konzept in die Verbindung auf Benutzer-Ebene ein: Halten Sie die Allowlist pro Tenant in Ihrer Datenbank und übergeben Sie sie als include.

typescript
// server/policy.ts
export async function allowedConnectors(tenantId: string): Promise<string[]> {
  // z. B. erhalten Enterprise-Tenants 'salesforce' und 'snowflake'
  return await billing.planAllows(tenantId);
}

async function capabilitiesFor(claims: AuthedPrincipal) {
  const allowed = await allowedConnectors(claims.tenantId);
  return vinkius
    .user(externalIdFor(claims))
    .capabilities({ include: allowed }); // beschränkt den Fan-out auf die erlaubten Connectoren
}

include beschneidet den Fan-out vor jedem Runtime-Aufruf: Ein Connector, den der Plan des Tenants verbietet, wird nie abgefragt, sodass der Benutzer ihn schlicht niemals sieht, selbst wenn diese Verbindung existiert. Richtlinie und Konnektivität fügen sich sauber zusammen.

6. Der passende Adapter für heterogene Modell-Runtimes

Unterschiedliche Tenants (oder unterschiedliche Funktionen) können auf unterschiedlichen Modellen laufen. Da CapabilitySet framework-agnostisch ist, bedient ein einziger Code-Pfad sie alle: Konvertieren Sie in der letzten Zeile.

typescript
import { toOpenAITools } from '@vinkius/connect/openai';
import { toAnthropicTools } from '@vinkius/connect/anthropic';
import { toGeminiTools } from '@vinkius/connect/gemini';

const capabilities = await capabilitiesFor(claims);

const toolSpec =
  model === 'openai' ? toOpenAITools(capabilities)
  : model === 'anthropic' ? toAnthropicTools(capabilities)
  : model === 'gemini' ? toGeminiTools(capabilities)
  : capabilities; // behalten Sie die Rohmenge zur Ausführung

Behalten Sie das ursprüngliche capabilities für die Ausführung, nur die konvertierten Definitionen werden an das Modell übergeben. Die Dispatch-Helfer des Adapters routen das gewählte Tool zurück auf die richtige Benutzer-Verbindung.

7. Enterprise-Tenants, die eine eigene Anwendung verlangen

Manche Enterprise-Kunden verlangen einen dedizierten Tenant-Key, statt Ihren zu teilen. Das ist schlicht eine weitere Vinkius-Instanz, pro Anfrage ausgewählt, Ihre externalIdFor-Logik ändert sich nicht.

typescript
import { Vinkius } from '@vinkius/connect';

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

const dedicated = new Map<string, Vinkius>(); // tenantId -> eigene App

function clientFor(tenantId: string): Vinkius {
  return dedicated.get(tenantId) ?? shared;
}

8. Den Fall „falscher Tenant" explizit behandeln

Verteidigung in die Tiefe: Verweist eine Anfrage auf eine id, die Sie nicht autorisieren können, behandeln Sie einen NotFoundError als Scope-Fehler, nicht als generischen 404.

typescript
import { NotFoundError, AuthError } from '@vinkius/connect';

try {
  await capabilitiesFor(claims);
} catch (error) {
  if (error instanceof AuthError) return respond(401);
  if (error instanceof NotFoundError) return respond(403, 'out of scope'); // tenantübergreifend
  throw error;
}

Zwei Garantien, denen jeder Tenant vertraut

Ein offengelegter Key überschreitet keine Grenze

Ihre Plattform hält ein einziges vk_app_sk_*-Secret. Dessen Auswirkungsradius ist konstruktionsbedingt begrenzt: Ein offengelegter Key kompromittiert eine Anwendung, und jeder Versuch, auf eine Ressource außerhalb dieser Anwendung zuzugreifen, liefert 404, niemals Daten eines anderen Tenants, niemals ein 500, das die Existenz einer Ressource bestätigt. Eine fehlgeleitete external_id schlägt daher sicher fehl, genau das, was ein Enterprise-Sicherheitsreview erwartet.

Ein Namespace-Name, viele Modellregeln

Der Standard-Namespace connector__name ist nicht für jeden Runtime gültig, auf den Sie einen Tenant routen könnten. toGeminiTools lehnt Bindestriche sofort ab (ein Connector-Slug wie google-calendar verletzt die ^[a-zA-Z_][a-zA-Z0-9_]*$-Regel von Gemini), und toOpenAITools begrenzt Namen auf 64 Zeichen, beide werfen einen ConfigError zur Konvertierungszeit, nicht eine 400 des Providers zur Inferenzzeit. Normalisieren Sie einmal, damit ein Tenant auf Gemini genauso sauber läuft wie einer auf OpenAI:

typescript
new Vinkius({
  appId,
  apiKey,
  // nur Unterstriche, längenbegrenzt: gültig für OpenAI, Anthropic und Gemini
  namespaceCapability: (connector, name) =>
    `${connector}_${name}`.replace(/[^a-zA-Z0-9_]/g, '_').slice(0, 64),
});

Produktions-Checkliste

  • [ ] Erstellen Sie die external_id immer aus authentifizierten Tenant- und Benutzer-Claims, niemals aus Rohdaten.
  • [ ] Behalten Sie einen Application-Key für die Plattform; fügen Sie dedizierte Vinkius-Instanzen nur für Tenants hinzu, die sie verlangen.
  • [ ] Erzwingen Sie Tenant-Pläne mit capabilities({ include }), gestützt auf Ihre Billing-Tabelle.
  • [ ] Verlassen Sie sich auf 404-als-außerhalb-des-Scopes: versuchen Sie niemals, Ressourcen eines anderen Tenants zu rekonstruieren.
  • [ ] Konvertieren Sie mit Adaptern beim Modellauf; führen Sie auf dem behaltenen CapabilitySet aus.
  • [ ] Verwenden Sie pro Operation einen idempotencyKey, damit Tenant-Wiederholungen sich nie kreuzen oder duplizieren.

Sie betreiben nun eine einzige KI-Plattform, die jeden Kunden und jeden Benutzer in seinem Inneren bedient, jeder auf isolierten Verbindungen und Credentials, mit Ihrem eigenen Application-Key und einer Policy-Schicht pro Tenant, und die Identitäten Ihrer Kunden verlassen niemals Ihre Kontrolle. Wettbewerber müssen diese Fähigkeit kaufen oder jahrelang selbst bauen. Sie haben sie an dem Tag erhalten, an dem Sie Ihre Application erstellt haben, und dieser Vorsprung wächst mit jedem Tenant, den Sie gewinnen.

What you just got

Not a pitch: the properties this build inherits automatically.

Isolation by construction

Connections and capabilities resolve only inside one external_id. No cross-actor leakage is possible, and you wrote none of that enforcement.

Write-only credentials

Your server stores secrets and can read back which fields are configured, never the values. Not your code, the model, or a dashboard can exfiltrate them.

Metered, revocable spend

Every connection owns a vk_live_* token, so cost and revocation are per connection. One call to disconnect() is a complete, auditable stop.

Any model runtime

One CapabilitySet converts to OpenAI, Anthropic, Gemini, Vercel AI SDK, LangChain, LlamaIndex, Workers AI or neutral JSON Schema. Only the last line changes.

Production safety built in

idempotencyKey, timeoutMs and AbortSignal per call; automatic full-jitter retries on transient failures; typed VinkiusError branches. No bespoke harness.

Enterprise-grade tenancy

One app key, every customer isolated by address; a cross-tenant attempt is a 404. A customer can even get their own Vinkius instance, same code.

Give it to your AI agent

An Agent Skill (SKILL.md) for this build. Preview the first lines below, then copy or download it into your repo under .claude/skills/: Claude Code, Cursor or any Agent-Skills-compatible agent follows it to implement this pattern correctly.

Download SKILL.md6 · Available in your language
What a multi-tenant SaaS inherits, plus the SKILL.md, in your language, for your coding agent.

Nächste Schritte