MultiVibe Cloud

Developers

← Back to dashboard

Documentation / Quickstart

Add “Connect MultiVibe” to your app.

Your users bring their inference account. Your application receives scoped, short-lived tokens tied to the project they choose.

Add the branded connect button

Drop this link and script into your app. Set href to your own backend’s OAuth start route, which generates state and PKCE and redirects to MultiVibe. The link works even if the script is blocked.

<a href="/auth/multivibe/start" data-multivibe-connect>
  Connect with MultiVibe
</a>
<script src="https://app.multivibe.cloud/developers/connect/v1/button.js"
        defer crossorigin="anonymous"></script>

Preview buttons open app setup. Add data-theme="dark" for a dark button, or data-size="compact" for a smaller one. Keep the visible label descriptive; translate it for your users if needed.

The script adds the shared logo and namespaced styling. It does not read cookies, store tokens, open popups, track clicks, or start authorization on page load. It supports same-origin HTTPS start routes and local HTTP on localhost or a loopback address. No client ID, secret, token, or PKCE verifier belongs in the embed.

JavaScript-free option

Use a regular styled link if you prefer not to load a third-party script. This is also the simplest option for React, Next.js and other frameworks that own their rendered DOM.

<link rel="stylesheet"
  href="https://app.multivibe.cloud/developers/connect/v1/button.css"
  crossorigin="anonymous">
<a class="mv-connect-button" href="/auth/multivibe/start">
  <img class="mv-connect-icon"
    src="https://app.multivibe.cloud/developers/connect/v1/mark.svg"
    width="24" height="24" alt="" crossorigin="anonymous">
  Connect with MultiVibe
</a>

React

// Load button.css once in your document head or app layout.
export function ConnectMultiVibe() {
  return (
    <a className="mv-connect-button" href="/auth/multivibe/start">
      <img className="mv-connect-icon"
        src="https://app.multivibe.cloud/developers/connect/v1/mark.svg"
        width={24} height={24} alt="" crossOrigin="anonymous" />
      Connect with MultiVibe
    </a>
  );
}

The script also enhances links inserted later. For explicit DOM integrations, call window.MultiVibeConnect.enhance(container) after loading it; repeated calls do not duplicate icons or stylesheets.

Hosting and Content Security Policy

Allow https://app.multivibe.cloud in your script-src, style-src, and img-src policies for the scripted embed; the JavaScript-free option only needs style and image access. No inline styles, inline scripts or external fonts are required. The button uses Manrope when available, with system-font fallbacks.

You may self-host the script, stylesheet, and logo together in one directory. The script resolves its assets relative to its own URL. Keep the MultiVibe logo, name, contrast and focus indicator intact. Loading these public assets conveys no account access; users must still authorize your registered app.

1. Register your app

Open My apps, add a website and exact redirect URLs, then copy the client ID. Treat this as a public client: no client secret is issued. Keep tokens on your backend or in operating-system secure storage. The token endpoint is intended for backend and native clients; cross-origin browser token exchange is not supported.

2. Start Authorization Code with PKCE

Create a new cryptographically random state and verifier for each attempt. Store them in a short-lived, single-use session associated with your browser user. Never reuse a verifier.

import { randomBytes, createHash } from "node:crypto";
const state = randomBytes(32).toString("base64url");
const verifier = randomBytes(32).toString("base64url");
const challenge = createHash("sha256")
  .update(verifier).digest("base64url");
// Save state + verifier in the user's server-side session.
const authorize = new URL(
  "https://app.multivibe.cloud/developers/oauth/authorize"
);
authorize.search = new URLSearchParams({
  client_id: process.env.MULTIVIBE_CLIENT_ID,
  redirect_uri: "https://your-app.com/auth/multivibe/callback",
  response_type: "code",
  scope: "models:read chat:write responses:write",
  state, code_challenge: challenge,
  code_challenge_method: "S256"
}).toString();
// Redirect the user's browser to authorize.href.

The user signs in, reviews your app and permissions, and chooses a project. The callback contains code and your state, or error=access_denied. Validate state against the original single-use session before processing either response. Authorization codes expire after five minutes.

3. Exchange the code

const response = await fetch(
  "https://app.multivibe.cloud/developers/oauth/token",
  {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      grant_type: "authorization_code",
      client_id: process.env.MULTIVIBE_CLIENT_ID,
      code, code_verifier: verifier,
      redirect_uri: "https://your-app.com/auth/multivibe/callback"
    })
  }
);
if (!response.ok) throw new Error("MultiVibe authorization failed");
const tokens = await response.json();
// Encrypt tokens at rest; never log them or send them in URLs.

The response contains access_token, refresh_token, token_type: "Bearer", expires_in: 3600, and the approved scope. These are inference credentials, not OIDC identity tokens. Do not decode them or use them as proof of identity.

4. Discover models and run inference

const headers = { Authorization: `Bearer ${tokens.access_token}` };
const models = await fetch("https://api.multivibe.cloud/v1/models", { headers });
// Let the user choose a compatible model from the returned list.
const result = await fetch("https://api.multivibe.cloud/v1/chat/completions", {
  method: "POST",
  headers: { ...headers, "Content-Type": "application/json" },
  body: JSON.stringify({
    model: selectedModel,
    messages: [{ role: "user", content: "Hello!" }]
  })
});

Use /v1/responses with responses:write for the Responses API. Streaming uses stream: true. Model discovery avoids hard-coded provider catalogs; it does not guarantee that every model supports every API or is always available. Respect the selected model’s capabilities and handle removal by offering a new selection.

5. Refresh access without asking for a key

POST to the same token endpoint with grant_type=refresh_token, client_id, and refresh_token. Save both replacement tokens atomically. Serialize refreshes per connection: refresh tokens are single-use, and replay revokes the entire connection. After an uncertain refresh response, reconnect rather than replaying the old token. Connections expire after 90 days; refresh does not extend that deadline.

Billing, permissions and revocation

Each request is attributed to the user-selected project and uses its existing billing, credits and spending limits. Connecting creates no subscription and grants no credits. Insufficient credit or disabled inference can still reject a request. Your app’s own subscription, if any, is separate.

  • models:read: discover available models.
  • chat:write: call Chat Completions.
  • responses:write: call Responses.
  • embeddings:write, rerank:write, images:write, audio:write, realtime:write: request the corresponding modality when available.
  • usage:read: read authorized inference usage.

Users can disconnect at Connected applications. Developers can disable an app in My apps to revoke every connection. For programmatic disconnect, POST client_id and token (a refresh token) to /developers/oauth/revoke with form encoding. Revocation stops future requests; it does not undo inference already running or charges already incurred.

Existing connections keep their original scopes. Adding permissions requires a new authorization and consent; refreshing never expands a grant. A scope does not activate an unavailable model or route.

Errors and deployment

400 invalid_grant means reconnect; 401 on inference can mean expired or revoked access; 403 means insufficient permission. Handle credit, rate-limit and availability errors separately. Never retry a billable request blindly after a timeout. App configuration and OAuth require the developer migration and Identity runtime; registering an app does not activate provider inference or billing.

Create your first application →