Gazebo
    ServicesAgentsDocsWritingPricing
    Log inSign up
    Log in

    Getting Started

    • Introduction
    • How Gazebo works
    • Core concepts
    • Quickstart

    Agent Access

    • Access profiles
    • Bearer tokens
    • Audit logs

    MCP

    • Overview
    • Cursor
    • Claude Desktop
    • Claude Code
    • OpenAI / Codex
    • Devin / Windsurf
    • Any MCP client
    • Get credential reference

    Authorization

    • Agent Consent Flow

    REST API

    • Overview
    • Authentication
    • Services
    • Agents
    • Access policies
    • Audit events
    • Consent administration
    • Agent credential access
    • Errors and limits

    Developer Tools

    • TypeScript SDK
    • CLI

    Security

    • Overview
    • Encryption model
    • Credential storage
    • Audit and compliance
    • Responsible disclosure
    Documentation

    Getting Started

    • Introduction
    • How Gazebo works
    • Core concepts
    • Quickstart

    Agent Access

    • Access profiles
    • Bearer tokens
    • Audit logs

    MCP

    • Overview
    • Cursor
    • Claude Desktop
    • Claude Code
    • OpenAI / Codex
    • Devin / Windsurf
    • Any MCP client
    • Get credential reference

    Authorization

    • Agent Consent Flow

    REST API

    • Overview
    • Authentication
    • Services
    • Agents
    • Access policies
    • Audit events
    • Consent administration
    • Agent credential access
    • Errors and limits

    Developer Tools

    • TypeScript SDK
    • CLI

    Security

    • Overview
    • Encryption model
    • Credential storage
    • Audit and compliance
    • Responsible disclosure
    DocsAuthorization

    Agent Consent Flow

    Connect an application's users to their own Gazebo accounts with Agent Consent Flow and the TypeScript SDK. Users approve specific service keys; your backend completes the callback and retrieves only the approved keys.

    Want an overview first? See how Agent Consent Flow works.

    The application's runtime and its host receive the selected provider keys. This is credential release, not a proxy that hides keys from the host. Disconnecting stops future retrieval and renewal; it does not invalidate a provider key already retrieved.

    Before you start

    • Use server-side Node.js and the TypeScript SDK. Never put signing keys, PKCE verifiers or issued tokens in a browser bundle.
    • Give your application its own authenticated user sessions. Gazebo sign-in is separate from sign-in to your application.
    • Provide encrypted, durable per-user connection storage and a shared lock across all application workers.
    • Ask users to connect the required service and named key in Gazebo first. Approval cannot connect a missing provider or supply an unavailable key.

    Manual SDK tokens, CLI setup and MCP remain separate supported options. This guide does not add automatic CLI browser login or consent-token access to MCP.

    1. Register your application

    Generate one ES256 P-256 key pair during application setup:

    import { generateConnectionKeys } from "@gazebohq/sdk/consent";
    
    const { publicKeyJwk, privateKeyJwk } = generateConnectionKeys();
    // Register JSON.stringify(publicKeyJwk) with Gazebo.
    // Store privateKeyJwk in your backend's secret manager; never log it.
    

    In Settings → Developer → Consent clients, register an application name, the public JWK, your exact callback URI and the allowed scope gazebo.credentials.read. Keep the returned client ID in your application configuration. You can also register through Consent administration using a trusted account-administration token; do not give that token to the agent runtime.

    Use an exact HTTPS callback such as https://your-application.example/integrations/gazebo/callback. HTTP loopback callbacks are permitted for local development. Wildcards and fragments are not allowed. Keep the callback identical during registration, connection start and code exchange.

    Do not generate a new signing key on every startup. Changes to a client's signing key, callback list or scope ceiling invalidate existing approvals; cosmetic name changes do not.

    2. Provide session-bound storage

    The backend recipe below is framework-neutral. These interfaces describe adapters you must implement using your application's authentication, database and secret management—not an in-memory fallback:

    import { Gazebo, GazeboError } from "@gazebohq/sdk";
    import {
      startConnection, completeConnection, storedConnection,
      createConnectionTokenProvider, disconnectStoredConnection,
      type ConnectionStore, type CredentialResource,
    } from "@gazebohq/sdk/consent";
    import type { webcrypto } from "node:crypto";
    
    export interface ApplicationSession {
      id: string;
      userId: string;
    }
    
    export interface PendingConnection {
      state: string;
      verifier: string;
      expiresAt: string;
      sessionId: string;
      userId: string;
    }
    
    export interface ApplicationStorage {
      savePending(
        sessionId: string, userId: string, pending: PendingConnection,
      ): Promise<void>;
      // Atomically remove-and-return once, checking BOTH session and user.
      consumePending(
        sessionId: string, userId: string,
      ): Promise<PendingConnection | null>;
      // Scope the encrypted store AND shared lock to this app user/client.
      connectionForUser(userId: string, clientId: string): ConnectionStore;
    }
    

    Derive ApplicationSession from trusted server-side authentication. Never accept its userId or session ID from callback query parameters. Pending state must expire with the authorization request, be bound to the initiating session and user, and be consumed atomically—even across workers.

    Each ConnectionStore must implement get, set, clear and withLock. Encrypt issued tokens at rest, save changes atomically, and serialize operations for the same user/client across every worker. A process-local mutex or global shared user token is not sufficient for a deployed multi-worker application. Do not delete the saved refresh credential immediately after a failed remote disconnect: the SDK needs it to retry revocation while local use stays blocked.

    3. Implement start, callback, runtime and disconnect

    Add this factory to the same backend module as the interfaces above:

    export function createGazeboConnection(config: {
      endpoint: string;
      clientId: string;
      callbackUri: string;
      privateKeyJwk: webcrypto.JsonWebKey;
      resources: CredentialResource[];
      storage: ApplicationStorage;
    }) {
      const storeFor = (session: ApplicationSession) =>
        config.storage.connectionForUser(session.userId, config.clientId);
    
      return {
        async connect(session: ApplicationSession) {
          const pending = await startConnection(config.endpoint, {
            clientId: config.clientId,
            redirectUri: config.callbackUri,
            resources: config.resources,
          }, config.privateKeyJwk);
    
          await config.storage.savePending(session.id, session.userId, {
            state: pending.state,
            verifier: pending.verifier,
            expiresAt: pending.expiresAt,
            sessionId: session.id,
            userId: session.userId,
          });
          return pending.authorizationUrl;
        },
    
        async callback(
          session: ApplicationSession,
          query: { state?: string; code?: string; error?: string },
        ) {
          const pending = await config.storage.consumePending(
            session.id, session.userId,
          );
          if (!pending || pending.sessionId !== session.id ||
              pending.userId !== session.userId ||
              !Number.isFinite(Date.parse(pending.expiresAt)) ||
              Date.parse(pending.expiresAt) <= Date.now()) {
            throw new GazeboError("reconnect_required");
          }
    
          // Validates returned state and denial, then exchanges the code with PKCE.
          const issued = await completeConnection(config.endpoint, {
            state: query.state,
            code: query.code,
            error: query.error,
            expectedState: pending.state,
            codeVerifier: pending.verifier,
            clientId: config.clientId,
            redirectUri: config.callbackUri,
          });
          const store = storeFor(session);
          await store.withLock(async () => {
            await store.set(storedConnection(issued));
          });
          return "/settings/connections"; // Fixed application route, not user input.
        },
    
        runtime(session: ApplicationSession) {
          return new Gazebo({
            endpoint: config.endpoint,
            auth: {
              type: "consent",
              tokenProvider: createConnectionTokenProvider({
                endpoint: config.endpoint,
                clientId: config.clientId,
                privateKeyJwk: config.privateKeyJwk,
                store: storeFor(session),
              }),
            },
          });
        },
    
        async disconnect(session: ApplicationSession) {
          await disconnectStoredConnection({
            endpoint: config.endpoint,
            clientId: config.clientId,
            store: storeFor(session),
          });
        },
      };
    }
    

    Configure the factory with endpoint: "https://app.gazebohq.com" and the registered client/callback. For example, request resources: [{ service: "github", keyName: "api_key" }] when that is the service/key pair your application needs. Use the actual Gazebo service slug and named key, not a provider URL or an arbitrary scope label.

    Wire the factory into your application's routes:

    Application routeRequired behavior
    POST /integrations/gazebo/connectRequire your application session and CSRF protection. Call connect(session) and redirect the browser to its returned URL.
    GET /integrations/gazebo/callbackRequire that same application session. Read single string code, state and error parameters, call callback(session, query), then redirect to the fixed application route.
    POST /integrations/gazebo/disconnectRequire your application session and CSRF protection. Call disconnect(session); report success only after remote revocation succeeds.

    If the initiating application session is missing, expired or changed when the callback arrives, ask the user to start again. Do not recover ownership using a userId in the URL. Configure your application and infrastructure not to log callback query strings, and immediately redirect to a clean URL after handling the callback.

    What the user sees

    The user signs into Gazebo using its normal sign-in methods, including magic links. Approval requires a real Gazebo browser session; account API tokens cannot approve it. If needed, complete sign-in before starting a fresh request—the approval request lasts five minutes.

    Gazebo shows the application's name, requested service/key pairs, the credential-release warning and the existing expiry choices: No expiry, 24 hours, 7 days, 30 days or 90 days. Unavailable keys are disabled. The user can approve a subset or deny the request. An expired/missing key must be added or updated in Gazebo before reconnecting.

    Approval creates a user-owned grant and linked agent, then returns a one-time code and state to your registered callback. Codes expire after one minute. Denial returns error=access_denied and state; it creates no new connection. Neither response should be treated as connected until the backend validates it and successfully stores the exchanged tokens.

    4. Retrieve an approved key

    In a server-side request or job, resolve the correct authenticated application user and use integration.runtime(session):

    // integration is your configured createGazeboConnection(...) instance.
    // session comes from trusted application authentication, not URL parameters.
    const agent = integration.runtime(session);
    const connection = await agent.connection.get();
    const approved = connection.resources.some(
      resource => resource.service === "github" && resource.keyName === "api_key",
    );
    if (!approved) {
      throw new Error("Reconnect Gazebo and approve the required key");
    }
    
    const result = await agent.credentials.get("github", { keyName: "api_key" });
    // Pass result.credentials.api_key only to your trusted server-side runtime.
    // Never log it or return it to the browser.
    

    Consent-mode operations are named-key retrieval, connection identity and disconnect. They do not include account administration, bulk retrieval or MCP access. Gazebo also applies current service/method policy and key availability on retrieval. A user-selected subset does not authorize your application to retrieve the unselected keys.

    5. Renewal and disconnect

    LifetimeBehavior
    User-selected agent expiryThe one user-facing connection lifetime. No expiry is the default, but does not remove refresh inactivity expiry.
    Access tokenAt most one hour, capped by grant/agent expiry.
    Refresh credentialRotates on renewal; expires after 30 days without successful renewal, capped by grant/agent expiry.

    The token provider renews access when it is within 60 seconds of expiry, on use—not through a browser timer. It persists a renewal operation ID before the request and atomically stores the rotated pair. The shared storage lock prevents different workers from rotating the same credential independently.

    An identical signed renewal retry can recover the same pair for 60 seconds, including a lost response. The helper retries a network failure once with the same operation; it does not silently retry every credential request. A different operation reusing a consumed refresh credential revokes the connection. Recovery outside the retry window requires a fresh connection.

    disconnectStoredConnection marks local use blocked before remote revocation. Success clears the local store. If Gazebo is unavailable, the blocked store retains the revocation payload: report disconnection as pending and retry disconnect(session) explicitly. Do not claim that remote access was revoked while the request failed.

    Revoking the grant or linked agent, disabling/changing the client's security configuration, or reaching the approval expiry stops future retrieval and renewal. Expanding approved service keys requires a new request and user approval. Reapproval replaces the previous grant/agent for that user/client; it does not silently expand existing grants.

    Errors and reconnection

    Catch GazeboError in your route/job layer and return a safe application message rather than logging callback URLs, tokens or credential responses:

    ErrorApplication response
    cancelled from the callbackShow “Connection not approved.” Offer to start again; do not mark connected.
    reconnect_requiredOffer a fresh Connect Gazebo flow. Covers expired/revoked connections and invalid state/code/PKCE. Do not reuse the callback code or verifier.
    Credential retrieval 401The access token or its connection is no longer valid. Offer reconnection rather than falling back to an account token.
    network, timeout or httpReport the failure. Preserve the persisted renewal/disconnect state for its bounded retry; do not claim success or erase needed revocation data.
    Credential retrieval 403The requested key/service/method is not permitted. Explain what access is needed and request new approval if appropriate.
    Credential retrieval 404The key/service is no longer available. Ask the user to fix the connection in Gazebo and reconnect as needed.
    invalid_responseFail closed and investigate the SDK/server response mismatch. Do not invent tokens or fall back to an account token.

    Check the returned approved resources rather than assuming every requested key was accepted. Keep unrelated application users' stores separate. An application-side storage failure must leave the UI disconnected or pending, not falsely report a working connection. If code exchange succeeded but saving the tokens failed, the remote grant may still exist: attempt revocation while the issued refresh credential is available, or tell the user to revoke the grant in Gazebo before retrying. Do not claim that a failed local write automatically revoked remote access.

    Security and verification boundaries

    This is a Gazebo product interface, not an OAuth-compatibility claim. Gazebo retains opaque token hashes and a bounded encrypted rotated-token reply for retry. Expired encrypted replies are physically cleared by maintenance; active-family hashes remain for replay detection.

    Audit records describe credential retrieval, not provider actions after delivery. The supplied method describes the requested credential-release context; it is not enforcement of provider operations once the key leaves Gazebo. Provider-key rotation may be necessary to invalidate a key already retrieved.

    Test your application's authenticated session binding, durable encrypted store, cross-worker lock, renewal and revocation before exposing the integration to users.

    Explore Gazebo

    Gazebo gives each agent an independent access boundary around your agent credentials, with controls for approval, auditing, and revocation.

    Scoped identities for AI agentsGive every agent only the credentials it needs.Credential security and audit controlsSee how access is protected, recorded, and revoked.
    Get credential referenceOverview
    Gazebo

    Credential management for AI agents. Scoped access, approval controls, and complete audit logs—without hardcoding or rotating shared API keys.

    Gazebo

    • Platform
    • Demo
    • MCP
    • Pricing

    Explore

    • Services
    • Agents
    • Vaults

    Content

    • Writing
    • Topics
    • Blog
    • Docs

    Company

    • About
    • [email protected]

    © 2026 Gazebo. All rights reserved.

    PrivacyTermsSecurity