> ## Documentation Index
> Fetch the complete documentation index at: https://auth0.com/ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Call Others' APIs on Users' Behalf

> Learn how Auth0 for AI Agents enables AI agents to call other's APIs on the user's behalf.

export const DownloadQuickstartButton = ({category, framework, label = "Download Sample"}) => {
  const [isDownloading, setIsDownloading] = useState(false);
  const githubRepo = 'auth0-samples/auth0-ai-samples';
  const filename = `${category}-${framework}-sample.zip`;
  const downloadSample = () => {
    setIsDownloading(true);
    const downloadUrl = `https://github.com/${githubRepo}/releases/latest/download/${filename}`;
    window.open(downloadUrl, '_blank');
    setTimeout(() => setIsDownloading(false), 1000);
  };
  return <div className="download-button-container">
      <button onClick={downloadSample} disabled={isDownloading} className={`download-button ${isDownloading ? 'downloading' : ''}`} aria-label={`Download ${category} ${framework} sample code`}>
        {isDownloading ? <>
            <svg className="download-spinner" width="16" height="16" viewBox="0 0 24 24" fill="none">
              <circle cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="2" fill="none" strokeDasharray="31.416" strokeDashoffset="31.416">
                <animate attributeName="stroke-dasharray" dur="2s" values="0 31.416;15.708 15.708;0 31.416" repeatCount="indefinite" />
                <animate attributeName="stroke-dashoffset" dur="2s" values="0;-15.708;-31.416" repeatCount="indefinite" />
              </circle>
            </svg>
            Downloading...
          </> : <>
            <Icon icon="download" color="white" />
            {label}
          </>}
      </button>
    </div>;
};

export const AccountAndAppSteps = ({applicationType = "Regular Web Applications", callbackUrl = "http://localhost:3000/auth/callback", logoutUrl = "http://localhost:3000", appCreation = true, allowedWebOrigins, copyDomain, enableTokenVaultGrant = false, enableRefreshTokenGrant = false, enableAllowRefreshTokenRotation = undefined}) => {
  const steps = [<Step title="Create an Auth0 Account">
      To continue with this quickstart, you need to have an{" "}
      <a href="https://auth0.com/signup?onboard_app=auth_for_aa&amp;ocid=701KZ000000cXXxYAM-aPA4z0000008OZeGAM" target="_blank">
        Auth0 account.
      </a>
    </Step>];
  if (appCreation) {
    steps.push(<Step title="Create an Auth0 Application">
        Go to your{" "}
        <a href="https://manage.auth0.com/dashboard" target="_blank">
          Auth0 Dashboard
        </a>{" "}
        to create a new Auth0 Application.
        <ul>
          <li>
            Navigate to <a href="https://manage.auth0.com/#/applications" target="_blank"><strong>Applications {">"} Applications</strong></a> in the
            left sidebar.
          </li>
          <li>
            Click the <strong>Create Application</strong> button in the top
            right.
          </li>
          <li>
            In the pop-up select <strong>{applicationType}</strong> and click{" "}
            <strong>Create</strong>.
          </li>
          <li>
            Once the Application is created, switch to the{" "}
            <strong>Settings</strong> tab.
          </li>
          {copyDomain && <li>
              Copy the <strong>Domain</strong> from the{" "}
              <strong>Basic Information</strong> section to your clipboard.
            </li>}
          <li>
            Scroll down to the <strong>Application URIs</strong> section.
          </li>
          <li>
            Set Allowed Callback URLs as: <code>{callbackUrl}</code>
          </li>
          {logoutUrl && <li>
              Set Allowed Logout URLs as: <code>{logoutUrl}</code>
            </li>}
          {allowedWebOrigins && <li>
              Set Allowed Web Origins as: <code>{allowedWebOrigins}</code>
            </li>}
          {enableAllowRefreshTokenRotation !== undefined && <li>
              Scroll down to the <strong>Refresh Token Rotation</strong> section and {enableAllowRefreshTokenRotation === true ? "enable" : "disable"} the <strong>Allow Refresh Token Rotation</strong> option.
            </li>}
          {enableTokenVaultGrant && !enableRefreshTokenGrant && <li>
              Scroll down and expand the <strong>Advanced</strong> section. Switch to the <strong>Grant Types</strong> tab and enable the <strong>Token Vault</strong> grant type.
            </li>}
          {enableTokenVaultGrant && enableRefreshTokenGrant && <li>
              Scroll down and expand the <strong>Advanced</strong> section. Switch to the <strong>Grant Types</strong> tab and enable the <strong>Token Vault</strong> and <strong>Refresh Token</strong> grant types.
            </li>}
          <li>
            Click <strong>Save</strong> in the bottom right to save your
            changes.
          </li>
        </ul>
        To learn more about Auth0 applications, read{" "}
        <a href="https://auth0.com/docs/get-started/applications" target="_blank">
          Applications
        </a>
        .
      </Step>);
  }
  return steps;
};

export const Prerequisites = ({createAuth0ApplicationStep = {
  applicationType: "Regular Web Applications",
  callbackUrl: "http://localhost:3000/auth/callback",
  logoutUrl: "http://localhost:3000",
  allowedWebOrigins: undefined,
  enableTokenVaultGrant: undefined,
  enableRefreshTokenGrant: undefined,
  enableAllowRefreshTokenRotation: undefined
}, createAuth0ApiStep = undefined, createResourceServerClientStep = undefined}) => {
  const steps = [];
  steps.push(...AccountAndAppSteps({
    applicationType: createAuth0ApplicationStep.applicationType,
    callbackUrl: createAuth0ApplicationStep.callbackUrl,
    logoutUrl: createAuth0ApplicationStep.logoutUrl,
    appCreation: !!createAuth0ApplicationStep,
    allowedWebOrigins: createAuth0ApplicationStep.allowedWebOrigins,
    enableTokenVaultGrant: createAuth0ApplicationStep.enableTokenVaultGrant,
    enableRefreshTokenGrant: createAuth0ApplicationStep.enableRefreshTokenGrant,
    enableAllowRefreshTokenRotation: createAuth0ApplicationStep.enableAllowRefreshTokenRotation
  }));
  if (createAuth0ApiStep) {
    steps.push(<Step key="auth0-api" title="Create a Custom API">
        A Custom API is an API you own that you want to secure using Auth0.
        <ul>
          <li>
            In your Auth0 Dashboard, go to{" "}
            <a href="https://manage.auth0.com/#/apis" target="_blank"><strong>Applications &gt; APIs</strong></a>.
          </li>
          <li>Create a new API with an identifier (audience), for example, <code>https://my-custom-api.com</code>.</li>
          <li>
            Once API is created, go to the APIs{" "}
            <strong>Settings &gt; Access Settings</strong> and enable{" "}
            <strong>Allow Offline Access</strong>.
          </li>
          <li>Note down the API identifier for your environment variables. You will use the API identifier as the audience parameter in token requests (<code>AUTH0_AUDIENCE</code>).</li>
        </ul>
      </Step>);
  }
  if (createResourceServerClientStep) {
    steps.push(<Step key="resource-server" title="Create a Custom API Client">
        The Custom API Client allows your API server to perform token
        exchanges using{" "}
        <strong>
          <i>access tokens</i>
        </strong>{" "}
        instead of{" "}
        <strong>
          <i>refresh tokens</i>
        </strong>
        . This client enables Token Vault to exchange an access token for an
        external API access token (e.g., Google Calendar API).
        <br />
        <ul>
              <li>
               From the <strong>Settings</strong> page of the API that you just created, click the <strong>Add Application</strong> button in the right top corner. This will open a modal to create a new Custom API Client.
              </li>
              <li>
                Give your Custom API Client a name in the Application Name field and click the <strong>Add</strong> button to create a new Custom API Client.
              </li>
          <li>
            After creation is successful, you should be redirected to the settings page for your newly created Custom API Client application. Note down the <code>client id</code> and <code>client secret</code>{" "}for your environment variables.
          </li>
        </ul>
      </Step>);
  }
  steps.push(<Step key="my-account-api" title="Configure My Account API">
      The Connected Accounts flow uses the <a href="https://auth0.com/docs/manage-users/my-account-api" target="_blank">My Account API</a> to create and manage connected accounts for a user across supported external providers.<br /><br />
      In the Auth0 Dashboard, configure the My Account API:
      <ul>
        <li>Navigate to <a href="https://manage.auth0.com/#/apis" target="_blank"><strong>Applications &gt; APIs</strong></a>, locate the My Account API banner, and select <strong>Activate</strong> to activate the Auth0 My Account API.</li>
        <li>Once activated, select <strong>Auth0 My Account API</strong> and then select the <strong>Application Access</strong> tab.
          <ul>
            <li>Find your client application and select <strong>Edit</strong> to configure its <a href="https://www.auth0.com//docs/get-started/apis/api-access-policies-for-applications" target="_blank">application access policies</a>.</li>
            <li>Select <strong>User Access</strong> and under <strong>Authorization</strong>, select <strong>Authorized</strong>.</li>
            <li>For the permissions, select <strong>All</strong> the <a href="https://www.auth0.com/docs/manage-users/my-account-api#scope" target="_blank">Connected Accounts scopes</a> for the application.</li>
            <li>Select <strong>Save</strong>. This creates a <a href="https://www.auth0.com/docs/get-started/applications/application-access-to-apis-client-grants" target="_blank">client grant</a> that allows your client application to access the My Account API with the Connected Accounts scopes on the user’s behalf.</li>
          </ul>
        </li>
        <li>Next, navigate to the <strong>Settings</strong> tab. Under <strong>Access Settings</strong>, select <strong>Allow Skipping User Consent</strong>.</li>
      </ul>
    </Step>);
  steps.push(<Step key="mrrt-policy" title="Define a Multi-Resource Refresh Token policy for your Application">
      After your web application has been granted access to the My Account API, you will also need to leverage the <a href="https://auth0.com/docs/secure/tokens/refresh-tokens/multi-resource-refresh-token" target="_blank">Multi-Resource Refresh Token</a> feature, which enables the refresh token delivered to your application to also obtain an access token to call the My Account API. <br /><br />
      You can quickly define a <a href="https://auth0.com/docs/secure/tokens/refresh-tokens/multi-resource-refresh-token/configure-and-implement-multi-resource-refresh-token" target="_blank">refresh token policy</a> for your application to use when requesting access tokens for the My Account API by doing the following:
      <ul>
        <li>Navigate to <a href="https://manage.auth0.com/#/applications" target="_blank"><strong>Applications &gt; Applications</strong></a> and select your client application.</li>
        <li>On the <strong>Settings</strong> tab, scroll down to the <strong>Multi-Resource Refresh Token</strong> section.</li>
        <li>Select <strong>Edit Configuration</strong> and then enable the MRRT toggle for the <strong>Auth0 My Account API</strong>.</li>
      </ul>
  </Step>);
  steps.push(<Step key="google-connection" title="Configure Google Social Integration">
      Set up a Google Cloud account that allows for external API calls by
      following the <a href="/integrations/google">Google Social Integration</a>{" "}
      instructions.
    </Step>);
  steps.push(<Step key="openai" title="OpenAI Platform">
      Set up an{" "}
      <a href="https://platform.openai.com/docs/libraries#create-and-export-an-api-key" target="_blank">
        OpenAI account and API key
      </a>
      .
    </Step>);
  return <>
      <Heading level={3} id="prerequisites">
        Prerequisites
      </Heading>
      Before getting started, make sure you have completed the following steps:
      <Steps>{steps}</Steps>
    </>;
};

Use Auth0 SDKs to fetch access tokens for social and enterprise identity providers from Auth0's [Token Vault](https://auth0.com/docs/secure/tokens/token-vault). Using these access tokens, AI agents integrated with the application can call third-party APIs to perform tasks on the user's behalf.

<Callout icon="file-lines" color="#0EA5E9" iconType="regular">
  If your application uses [Organizations](https://auth0.com/docs/manage-users/organizations), authenticate the user with the target organization before initiating the Connected Accounts flow. Each organization member connects and authorizes their own external account.
</Callout>

By the end of this quickstart, you should have an AI application integrated with Auth0 that can:

1. Initiate a Connected Accounts flow that will allow the user to connect their Google account and grant access to the AI agent.
2. Retrieve access tokens for a Google social connection.
3. Integrate with an AI agent to call Google APIs.

## Pick your tech stack

<Tabs>
  <Tab title="LangGraph.js + Next.js" icon="https://mintlify-assets.b-cdn.net/auth0/langchain.svg">
    <Prerequisites
      createAuth0ApplicationStep={{
enableTokenVaultGrant: true,
enableAllowRefreshTokenRotation: false,
}}
      createAuth0ApiStep={{}}
      createResourceServerClientStep={{}}
    />

    <Tabs>
      <Tab title="Use sample app (recommended)">
        ### Download sample app

        Start by downloading and extracting the sample app. Then open in your preferred IDE.

        <DownloadQuickstartButton category="call-others-apis-on-users-behalf" framework="langchain-next-js" />

        ### Create your environment file

        In the root directory of your project, create a new `.env.local` file and add the following content:

        ```bash .env.local wrap lines theme={null}
        # Auth0 configuration
        APP_BASE_URL='http://localhost:3000'
        AUTH0_SECRET='random 32 byte value'
        AUTH0_DOMAIN='<your-auth0-domain>'
        AUTH0_CLIENT_ID='<your-auth0-application-client-id>'
        AUTH0_CLIENT_SECRET='<your-auth0-application-client-secret>'

        AUTH0_AUDIENCE="https://your.domain.us.langgraph.app"
        AUTH0_SCOPE="openid profile email offline_access"
        AUTH0_CUSTOM_API_CLIENT_ID="{yourCustomApiClientId}"
        AUTH0_CUSTOM_API_CLIENT_SECRET="{yourCustomApiClientSecret}"

        # OpenAI API Key or any provider supported by the Vercel AI SDK
        OPENAI_API_KEY="YOUR_API_KEY"

        # LANGGRAPH
        LANGGRAPH_API_URL=http://localhost:54367
        LANGCHAIN_CALLBACKS_BACKGROUND=false
        ```

        To get your Auth0 application's `AUTH0_DOMAIN`, `AUTH0_CLIENT_ID`, and `AUTH0_CLIENT_SECRET`, navigate to <strong>[Applications > Applications](https://manage.auth0.com/#/applications)</strong> in the Auth0 Dashboard and select your Regular Web application. You'll find these values in the **Basic Information** section at the top.
        Copy each value to the matching setting.

        To get your API's `AUTH0_AUDIENCE`, navigate to <strong>[Applications > APIs](https://manage.auth0.com/#/apis)</strong> in the Auth0 Dashboard and select your API. You'll find the identifier in the **General Settings** section at the top.

        To get your Custom API Client's `AUTH0_CUSTOM_API_CLIENT_ID`, and `AUTH0_CUSTOM_API_CLIENT_SECRET` navigate to <strong>[Applications > Applications](https://manage.auth0.com/#/applications)</strong> in the Auth0 Dashboard and select your Custom API Client application. You'll find these values in the **Basic Information** section at the top.
        Copy each value to the matching setting.

        Next, run this command to generate a random 32 byte value and copy it to the `AUTH0_SECRET` field:

        ```bash generate random 32 byte value theme={null}
        openssl rand -hex 32
        ```

        Lastly, set your OpenAI API key or use any provider supported by the Vercel AI SDK:

        ```bash .env.local wrap lines theme={null}
        OPENAI_API_KEY="YOUR_API_KEY"
        ```

        ### Install packages

        Ensure you have `npm` installed or follow the instructions to [install npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) in its documentation.
        In the root directory of your project, run the following command to install the required packages:

        ```bash wrap lines theme={null}
        npm install
        ```

        ### Test your application

        Start the application with `npm run all:dev`. Then, navigate to `http://localhost:3000`.

        <Note>
          This opens LangGraph Studio in a new tab. You can safely close it.
        </Note>

        If you are already logged in, make sure to log out and log back in using Google. Then, ask your AI agent to fetch emails from your Gmail account!

        That's it! You successfully called a third-party API using Token Vault.
      </Tab>

      <Tab title="Integrate into your app">
        ### Install packages

        In the root directory of your project, install the following packages:

        * `@auth0/ai-langchain`: [Auth0 AI SDK for LangChain](https://github.com/auth0/auth0-ai-js/tree/main/packages/ai-langchain) built for AI agents powered by LangChain.
        * `@langchain/langgraph`: For building stateful, multi-actor applications with LLMs.
        * `langchain`: The LangChain library.
        * `@langchain/core`: LangChain core libraries.
        * `@langchain/openai`: OpenAI provider for LangChain.
        * `@langchain/community`: LangChain community integrations.
        * `langgraph-nextjs-api-passthrough`: API passthrough for LangGraph.

        ```bash wrap lines theme={null}
        npm install @auth0/ai-langchain@4 @langchain/community@0.3.53 @langchain/core@0.3.77 @langchain/langgraph@0.4.4 @langchain/openai@0.6.13 langchain@0.3.12 langgraph-nextjs-api-passthrough@0.1.4
        ```

        ### Update your environment file

        Add to your environment file the following variables:

        ```bash .env.local wrap lines theme={null}
        # Auth0 configuration
        APP_BASE_URL='http://localhost:3000'
        AUTH0_SECRET='random 32 byte value'
        AUTH0_DOMAIN='<your-auth0-domain>'
        AUTH0_CLIENT_ID='<your-auth0-application-client-id>'
        AUTH0_CLIENT_SECRET='<your-auth0-application-client-secret>'

        AUTH0_AUDIENCE="https://your.domain.us.langgraph.app"
        AUTH0_CUSTOM_API_CLIENT_ID="{yourCustomApiClientId}"
        AUTH0_CUSTOM_API_CLIENT_SECRET="{yourCustomApiClientSecret}"

        # OpenAI API Key or any provider supported by the Vercel AI SDK
        OPENAI_API_KEY="YOUR_API_KEY"

        # LANGGRAPH
        LANGGRAPH_API_URL=http://localhost:54367
        LANGCHAIN_CALLBACKS_BACKGROUND=false
        ```

        To get your Auth0 application's `AUTH0_DOMAIN`, `AUTH0_CLIENT_ID`, and `AUTH0_CLIENT_SECRET`, navigate to <strong>[Applications > Applications](https://manage.auth0.com/#/applications)</strong> in the Auth0 Dashboard and select your Regular Web application. You'll find these values in the **Basic Information** section at the top.
        Copy each value to the matching setting.

        To get your API's `AUTH0_AUDIENCE`, navigate to <strong>[Applications > APIs](https://manage.auth0.com/#/apis)</strong> in the Auth0 Dashboard and select your API. You'll find the identifier in the **General Settings** section at the top.

        To get your Custom API Client's `AUTH0_CUSTOM_API_CLIENT_ID`, and `AUTH0_CUSTOM_API_CLIENT_SECRET` navigate to <strong>[Applications > Applications](https://manage.auth0.com/#/applications)</strong> in the Auth0 Dashboard and select your Custom API Client application. You'll find these values in the **Basic Information** section at the top.
        Copy each value to the matching setting.

        Next, run this command to generate a random 32 byte value and copy it to the `AUTH0_SECRET` field:

        ```bash generate random 32 byte value theme={null}
        openssl rand -hex 32
        ```

        Lastly, set your OpenAI API key or use any provider supported by the Vercel AI SDK:

        ```bash .env.local wrap lines theme={null}
        OPENAI_API_KEY="YOUR_API_KEY"
        ```

        ### Set up Token Vault for Google social connection

        Use the [Auth0 AI SDK for LangChain](https://github.com/auth0/auth0-ai-js/tree/main/packages/ai-langchain) to get an access token for the [Google Social Connection](/integrations/google) using [Token Vault](https://auth0.com/docs/secure/tokens/token-vault):

        * `connection`: pass in the name of the connection you want to access.
        * `scopes`: pass in the scopes to be authorized for this connection.

        Create a file `src/lib/auth0-ai.ts` to instantiate the Auth0 AI SDK client:

        ```tsx src/lib/auth0-ai.ts wrap lines highlight={17} theme={null}
        import { Auth0AI } from '@auth0/ai-langchain';
        import { SUBJECT_TOKEN_TYPES } from "@auth0/ai";

        const auth0AI = new Auth0AI({
          auth0: {
            domain: process.env.AUTH0_DOMAIN!,
            clientId: process.env.AUTH0_CUSTOM_API_CLIENT_ID!,
            clientSecret: process.env.AUTH0_CUSTOM_API_CLIENT_SECRET!,
          },
        });

        const withAccessTokenForConnection = (connection: string, scopes: string[]) =>
          auth0AI.withTokenVault({
            connection,
            scopes,
            accessToken: async (_, config) => {
              return config.configurable?.langgraph_auth_user?.getRawAccessToken();
            },
            subjectTokenType: SUBJECT_TOKEN_TYPES.SUBJECT_TYPE_ACCESS_TOKEN,
          });

        // Connection for Google services
        export const withGmailSearch = withAccessTokenForConnection(
          'google-oauth2',
          ['openid', 'https://www.googleapis.com/auth/gmail.readonly'],
        );
        ```

        ### Pass credentials to the tools

        Create a file `/src/lib/auth0.ts` file with the following code:

        ```tsx src/lib/auth0.ts wrap lines theme={null}
        import { Auth0Client } from '@auth0/nextjs-auth0/server';

        export const auth0 = new Auth0Client({
          authorizationParameters: {
            scope: process.env.AUTH0_SCOPE,
            audience: process.env.AUTH0_AUDIENCE,
          },
          enableConnectAccountEndpoint: true,
        });

        export const getAccessToken = async () => {
          const tokenResult = await auth0.getAccessToken();

          if(!tokenResult || !tokenResult.token) {
            throw new Error("No access token found in Auth0 session");
          }

          return tokenResult.token;
        };
        ```

        Update your chat route, typically at `/src/app/api/chat/[..._path]/route.ts` to pass the `accessToken` to your LangGraph agent to use the Auth0 AI SDK to get the Google access token from the server.

        ```ts src/app/api/chat/[..._path]/route.ts wrap lines highlight={3,10} theme={null}
        import { initApiPassthrough } from "langgraph-nextjs-api-passthrough";

        import { getAccessToken } from "@/lib/auth0";

        export const { GET, POST, PUT, PATCH, DELETE, OPTIONS, runtime } =
          initApiPassthrough({
            apiUrl: process.env.LANGGRAPH_API_URL,
            baseRoute: "chat/",
            headers: async () => {
              const accessToken = await getAccessToken();
              return {
                Authorization: `Bearer ${accessToken}`,
              };
          });
        ```

        ### Add Custom Authentication

        <Info>
          For more information on how to add custom authentication for your LangGraph Platform application, read the [Custom Auth](https://langchain-ai.github.io/langgraphjs/how-tos/auth/custom_auth/) guide.
        </Info>

        In your `langgraph.json`, add the path to your auth file:

        ```typescript langgraph.json wrap lines highlight={8} theme={null}
        {
          "node_version": "20",
          "graphs": {
            "agent": "./src/lib/agent.ts:agent"
          },
          "env": ".env",
          "auth": {
            "path": "./src/lib/auth.ts:authHandler"
          }
        }
        ```

        Then, in your `auth.ts` file, add your auth logic:

        ```typescript src/lib/auth.ts wrap lines theme={null}
        import { createRemoteJWKSet, jwtVerify } from "jose";

        const { Auth, HTTPException } = require("@langchain/langgraph-sdk/auth");

        const AUTH0_DOMAIN = process.env.AUTH0_DOMAIN;
        const AUTH0_AUDIENCE = process.env.AUTH0_AUDIENCE;

        // JWKS endpoint for Auth0
        const JWKS = createRemoteJWKSet(
          new URL(`https://${AUTH0_DOMAIN}/.well-known/jwks.json`)
        );

        // Create the Auth instance
        const auth = new Auth();
        // Register the authentication handler
        auth.authenticate(async (request: Request) => {
          const authHeader = request.headers.get("Authorization");
          const xApiKeyHeader = request.headers.get("x-api-key");
            /**
             * LangGraph Platform will convert the `Authorization` header from the client to an `x-api-key` header automatically
             * as of now: https://docs.langchain.com/langgraph-platform/custom-auth
             *
             * We can still leverage the `Authorization` header when served in other infrastructure w/ langgraph-cli
             * or when running locally.
             */
            // This header is required in Langgraph Cloud.
            if (!authHeader && !xApiKeyHeader) {
              throw new HTTPException(401, {
                message: "Invalid auth header provided.",
              });
            }

            // prefer the xApiKeyHeader first
            let token = xApiKeyHeader || authHeader;

            // Remove "Bearer " prefix if present
            if (token && token.startsWith("Bearer ")) {
              token = token.substring(7);
            }

            // Validate Auth0 Access Token using common JWKS endpoint
            if (!token) {
              throw new HTTPException(401, {
                message:
                  "Authorization header format must be of the form: Bearer <token>",
              });
            }

            if (token) {
              try {
                // Verify the JWT using Auth0 JWKS
                const { payload } = await jwtVerify(token, JWKS, {
                  issuer: `https://${AUTH0_DOMAIN}/`,
                  audience: AUTH0_AUDIENCE,
                });

                console.log("✅ Auth0 JWT payload resolved!", payload);

                // Return the verified payload - this becomes available in graph nodes
                return {
                  identity: payload.sub!,
                  email: payload.email as string,
                  permissions:
                    typeof payload.scope === "string" ? payload.scope.split(" ") : [],
                  auth_type: "auth0",
                  // include the access token for use with Auth0 Token Vault exchanges by tools
                  getRawAccessToken: () => token,
                  // Add any other claims you need
                  ...payload,
                };
              } catch (jwtError) {
                console.log(
                  "Auth0 JWT validation failed:",
                  jwtError instanceof Error ? jwtError.message : "Unknown error"
                );
                throw new HTTPException(401, {
                  message: "Invalid Authorization token provided.",
                });
              }
            }
        });

        export { auth as authHandler };
        ```

        ### Use access token to call APIs from a tool

        Once the user is authenticated, you can fetch an access token from Token Vault using the Auth0 AI SDK. In this example, we fetch an access token for a Google social connection.
        Once you've obtained the access token for a connection, you can use it with an AI agent to fetch data during a tool call and provide contextual data in its response.
        This example uses `GmailSearch` from the `@langchain/community` tools. This tool will use the access token provided by Token Vault to query for emails.

        ```ts src/lib/tools/gmail-seatch.ts wrap lines highlight={10} theme={null}
        //...
        import { getAccessTokenFromTokenVault } from "@auth0/ai-langchain";
        import { GmailSearch } from "@langchain/community/tools/gmail";

        import { withGmailSearch } from "../../lib/auth0-ai";

        export const gmailSearchTool = withGmailSearch(
          new GmailSearch({
            credentials: {
              accessToken: async () => getAccessTokenFromTokenVault(),
            },
          })
        );
        ```

        Update your tool call to request an access token, as shown in the following example:

        ```ts src/lib/agent.ts wrap lines highlight={2,10-13} theme={null}
        //...
        import { gmailSearchTool } from './tools/gmail-search';

        //... existing code

        const tools = [gmailSearchTool];
        //... existing code
        export const agent = createReactAgent({
          llm,
          tools: new ToolNode(tools, {
            // Error handler must be disabled in order to trigger interruptions from within tools.
            handleToolErrors: false,
          }),
          // Modify the stock prompt in the prebuilt agent.
          prompt: AGENT_SYSTEM_TEMPLATE,
          store,
          checkpointer,
        });
        ```

        ### Add step-up authorization

        When you try to use the tool, the application requests any additional Google scopes that are required but not yet authorized. This process is called step-up authorization.

        To implement, install the Auth0 AI Components for Next.js SDK to get the required UI components:

        ```bash wrap lines theme={null}
        npx @auth0/ai-components add TokenVault
        ```

        Add a new file, `src/components/TokenVaultInterruptHandler.tsx`, with the following code:

        ```tsx src/components/TokenVaultInterruptHandler.tsx wrap lines theme={null}
        import { TokenVaultInterrupt } from "@auth0/ai/interrupts";
        import type { Interrupt } from "@langchain/langgraph-sdk";

        import { TokenVaultConsent } from "@/components/auth0-ai/TokenVault";

        interface TokenVaultInterruptHandlerProps {
          interrupt: Interrupt | undefined | null;
          onFinish: () => void;
        }

        export function TokenVaultInterruptHandler({
          interrupt,
          onFinish,
        }: TokenVaultInterruptHandlerProps) {
          if (
            !interrupt ||
            !TokenVaultInterrupt.isInterrupt(interrupt.value)
          ) {
            return null;
          }

          return (
            <div key={interrupt.ns?.join("")} className="whitespace-pre-wrap">
              <TokenVaultConsent
                mode="popup"
                interrupt={interrupt.value}
                onFinish={onFinish}
                connectWidget={{
                  title: "Authorization Required.",
                  description: interrupt.value.message,
                  action: { label: "Authorize" },
                }}
              />
            </div>
          );
        }
        ```

        Now, update your chat window code to include the `TokenVaultInterruptHandler` component, for example:

        ```tsx src/components/chat-window.tsx wrap lines highlight={2,3,8-19,36-38} theme={null}
        //...
        import { TokenVaultInterruptHandler } from '@/components/TokenVaultInterruptHandler';

        //... existing code
        export function ChatWindow(props: {
          //... existing code
        }) {
          const [threadId, setThreadId] = useQueryState('threadId');
          const [input, setInput] = useState('');
          const chat = useStream({
            apiUrl: props.endpoint,
            assistantId: 'agent',
            threadId,
            onThreadId: setThreadId,
            onError: (e: any) => {
              console.error('Error: ', e);
              toast.error(`Error while processing your request`, { description: e.message });
            },
          });
          //... existing code
          return (
            <StickToBottom>
              <StickyToBottomContent
                className="absolute inset-0"
                contentClassName="py-8 px-2"
                content={
                  chat.messages.length === 0 ? (
                    <div>{props.emptyStateComponent}</div>
                  ) : (
                    <>
                      <ChatMessages
                        aiEmoji={props.emoji}
                        messages={chat.messages}
                        emptyStateComponent={props.emptyStateComponent}
                      />
                      <div className="flex flex-col max-w-[768px] mx-auto pb-12 w-full">
                        <TokenVaultInterruptHandler interrupt={chat.interrupt} onFinish={() => chat.submit(null)} />
                      </div>
                    </>
                  )
                }
                {/* ... existing code */}
              ></StickyToBottomContent>
            </StickToBottom>
          );
        }
        ```

        Now when step-up authorization is required, the user will see a prompt in the chat window asking them to authorize.

        ### Test your application

        Start your application. If you are already logged in, make sure to log out and log back in using Google. Then, ask your AI agent to fetch emails from your Gmail account!

        That's it! You successfully integrated third-party API access using Token Vault into your app.

        ### View a complete example

        Want to see how it all comes together? Explore or clone the fully implemented sample application on [GitHub](https://github.com/auth0-samples/auth0-ai-samples/tree/main/call-apis-on-users-behalf/others-api/langchain-next-js).
      </Tab>
    </Tabs>
  </Tab>

  <Tab title="Vercel AI + Next.js" icon="https://mintlify-assets.b-cdn.net/auth0/vercel.svg">
    <Prerequisites
      createAuth0ApplicationStep={{
enableTokenVaultGrant: true,
enableAllowRefreshTokenRotation: false,
}}
    />

    <Tabs>
      <Tab title="Use sample app (recommended)">
        ### Download sample app

        Start by downloading and extracting the sample app. Then open in your preferred IDE.

        <DownloadQuickstartButton category="call-others-apis-on-users-behalf" framework="vercel-ai-next-js" />

        ### Create your environment file

        In the root directory of your project, create an `.env.local` file and add the following content:

        ```bash .env.local wrap lines theme={null}
        # Auth0 configuration
        APP_BASE_URL='http://localhost:3000'
        AUTH0_SECRET='random 32 byte value'
        AUTH0_DOMAIN='<your-auth0-domain>'
        AUTH0_CLIENT_ID='<your-auth0-application-client-id>'
        AUTH0_CLIENT_SECRET='<your-auth0-application-client-secret>'

        # OpenAI API Key or any provider supported by the Vercel AI SDK
        OPENAI_API_KEY="YOUR_API_KEY"
        ```

        To get your Auth0 application's `AUTH0_DOMAIN`, `AUTH0_CLIENT_ID`, and `AUTH0_CLIENT_SECRET`, navigate to <strong>[Applications > Applications](https://manage.auth0.com/#/applications) </strong> in the Auth0 Dashboard and select your client application. You'll find these values in the **Basic Information** section at the top.
        Copy each value to the matching setting.

        Next, run this command to generate a random 32 byte value and copy it to the `AUTH0_SECRET` field:

        ```bash generate random 32 byte value theme={null}
        openssl rand -hex 32
        ```

        Lastly, set your OpenAI API key or use any provider supported by the Vercel AI SDK:

        ```bash .env.local wrap lines theme={null}
        OPENAI_API_KEY="YOUR_API_KEY"
        ```

        ### Install packages

        Ensure you have `npm` installed or follow the instructions here to [install npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm).
        In the root directory of your project, run the following command to install the required packages:

        ```bash wrap lines theme={null}
        npm install
        ```

        ### Test your application

        Start the application with `npm run dev`. Then, navigate to `http://localhost:3000`.

        If you are already logged in, make sure to log out and log back in using Google. Then, ask your AI agent about your Google calendar.

        That's it! You successfully called a third-party API using Token Vault.
      </Tab>

      <Tab title="Integrate into your app">
        ### Install packages

        In the root directory of your project, install the following packages:

        * `@auth0/ai-vercel`: [Auth0 AI SDK for Vercel AI](https://github.com/auth0/auth0-ai-js/tree/main/packages/ai-vercel) built for AI agents powered by the Vercel AI SDK.
        * `ai`: Core [Vercel AI SDK](https://sdk.vercel.ai/docs) module that interacts with various AI model providers.
        * `@ai-sdk/openai`: [OpenAI](https://sdk.vercel.ai/providers/ai-sdk-providers/openai) provider for the Vercel AI SDK.
        * `@ai-sdk/react`: [React](https://react.dev/) UI components for the Vercel AI SDK.
        * `zod`: TypeScript-first schema validation library.
        * `googleapis`: Node.js client for Google APIs that supports authentication and authorization with OAuth 2.0.

        ```bash wrap lines theme={null}
        npm install @auth0/ai-vercel@5 ai@6 @ai-sdk/openai@3 @ai-sdk/react@3 zod@3.25.76 googleapis@161
        ```

        ### Update your environment file

        Add the following variables to your environment file:

        ```bash .env.local wrap lines theme={null}
        # Auth0 configuration
        APP_BASE_URL='http://localhost:3000'
        AUTH0_SECRET='random 32 byte value'
        AUTH0_DOMAIN='<your-auth0-domain>'
        AUTH0_CLIENT_ID='<your-auth0-application-client-id>'
        AUTH0_CLIENT_SECRET='<your-auth0-application-client-secret>'

        # OpenAI API Key or any provider supported by the Vercel AI SDK
        OPENAI_API_KEY="YOUR_API_KEY"
        ```

        To get your Auth0 application's `AUTH0_DOMAIN`, `AUTH0_CLIENT_ID`, and `AUTH0_CLIENT_SECRET`, navigate to <strong>[Applications > Applications](https://manage.auth0.com/#/applications)</strong> in the Auth0 Dashboard and select your client application. You'll find these values in the **Basic Information** section at the top.
        Copy each value to the matching setting.

        Next, run this command to generate a random 32 byte value and copy it to the `AUTH0_SECRET` field:

        ```bash generate random 32 byte value theme={null}
        openssl rand -hex 32
        ```

        Lastly, set your OpenAI API key or use any provider supported by the Vercel AI SDK:

        ### Set up Token Vault for Google social connection

        Use the [Auth0 AI SDK for Vercel AI](https://github.com/auth0/auth0-ai-js/tree/main/packages/ai-vercel) to get an access token for the [Google Social Connection](/integrations/google) using [Token Vault](https://auth0.com/docs/secure/tokens/token-vault):

        * `connection`: pass in the name of the connection you want to access.
        * `scopes`: pass in the scopes to be authorized for this connection.
        * `refreshToken`: pass in the function to get the refresh token from the current session.

        Create a file at `src/lib/auth0-ai.ts` to instantiate the Auth0 AI SDK client:

        ```tsx src/lib/auth0-ai.ts wrap lines theme={null}
        import { Auth0AI, getAccessTokenFromTokenVault } from "@auth0/ai-vercel";
        import { getRefreshToken } from "./auth0";

        // Get the access token for a connection via Auth0
        export const getAccessToken = async () => getAccessTokenFromTokenVault();

        const auth0AI = new Auth0AI();

        // Connection for Google services
        export const withGoogleConnection = auth0AI.withTokenVault({
          connection: "google-oauth2",
          scopes: ["openid", "https://www.googleapis.com/auth/calendar.events"],
          refreshToken: getRefreshToken,
        });
        ```

        ### Pass credentials to the tools

        Create a file at `/src/lib/auth0.ts` file with the following code:

        ```tsx src/lib/auth0.ts wrap lines theme={null}
        import { Auth0Client } from '@auth0/nextjs-auth0/server';

        // Create an Auth0 Client.
        export const auth0 = new Auth0Client({
          enableConnectAccountEndpoint: true,
        });

        // Get the refresh token from Auth0 session
        export const getRefreshToken = async () => {
          const session = await auth0.getSession();
          return session?.tokenSet?.refreshToken;
        };
        ```

        #### Use access token to call APIs from a tool

        Once the user is authenticated, you can fetch an access token from the Token Vault using the Auth0 AI SDK. In this example, we fetch an access token for a Google social connection.
        Once you've obtained the access token for a social connection, you can use it with an AI agent to fetch data during a tool call and provide contextual data in its response.

        In this example, we define a tool call, `getCalendarEventsTool`, that uses the access token with the Google Calendar API to query for calendar events on a specific date.
        Update your tool call to request an access token similar to this example:

        ```ts src/lib/tools/google-calendar.ts wrap lines theme={null}
        import { tool } from 'ai';
        import { endOfDay, formatISO, startOfDay } from 'date-fns';
        import { GaxiosError } from 'gaxios';
        import { google } from 'googleapis';
        import { z } from 'zod';
        import { TokenVaultError } from '@auth0/ai/interrupts';

        import { getAccessToken, withGoogleConnection } from '../auth0-ai';

        export const getCalendarEventsTool = withGoogleConnection(
          tool({
            description: `Get calendar events for a given date from the user's Google Calendar`,
            inputSchema: z.object({
              date: z.coerce.date(),
            }),
            execute: async ({ date }) => {
              // Get the access token from Auth0 AI
              const accessToken = await getAccessToken();

              // Google SDK
              try {
                const calendar = google.calendar('v3');
                const auth = new google.auth.OAuth2();

                auth.setCredentials({
                  access_token: accessToken,
                });

                // Get events for the entire day
                const response = await calendar.events.list({
                  auth,
                  calendarId: 'primary',
                  timeMin: formatISO(startOfDay(date)),
                  timeMax: formatISO(endOfDay(date)),
                  singleEvents: true,
                  orderBy: 'startTime',
                  maxResults: 50,
                });

                const events = response.data.items || [];

                return {
                  date: formatISO(date, { representation: 'date' }),
                  eventsCount: events.length,
                  events: events.map((event) => ({
                    id: event.id,
                    summary: event.summary || 'No title',
                    description: event.description,
                    startTime: event.start?.dateTime || event.start?.date,
                    endTime: event.end?.dateTime || event.end?.date,
                    location: event.location,
                    attendees:
                      event.attendees?.map((attendee) => ({
                        email: attendee.email,
                        name: attendee.displayName,
                        responseStatus: attendee.responseStatus,
                      })) || [],
                    status: event.status,
                    htmlLink: event.htmlLink,
                  })),
                };
              } catch (error) {
                if (error instanceof GaxiosError) {
                  if (error.status === 401) {
                    throw new TokenVaultError(`Authorization required to access the Token Vault connection`);
                  }
                }

                throw error;
              }
            },
          }),
        );
        ```

        ### Add step up authorization

        When you try to use the tool, the application requests any additional Google scopes that are required but not yet authorized. This process is called step-up authorization.

        To implement, install the Auth0 AI Components for Next.js SDK to get the required UI components:

        ```bash wrap lines theme={null}
        npx @auth0/ai-components add TokenVault
        ```

        Add a new file, `src/components/TokenVaultInterruptHandler.tsx` with the following code:

        ```tsx src/components/TokenVaultInterruptHandler.tsx wrap lines theme={null}
        import { TokenVaultInterrupt } from "@auth0/ai/interrupts";
        import type { Auth0InterruptionUI } from "@auth0/ai-vercel/react";

        import { TokenVaultConsent } from "@/components/auth0-ai/TokenVault";

        interface TokenVaultInterruptHandlerProps {
          interrupt: Auth0InterruptionUI | null;
          onFinish?: () => void;
        }

        export function TokenVaultInterruptHandler({
          interrupt,
          onFinish,
        }: TokenVaultInterruptHandlerProps) {
          if (!TokenVaultInterrupt.isInterrupt(interrupt)) {
            return null;
          }

          return (
            <div key={interrupt.name} className="whitespace-pre-wrap">
              <TokenVaultConsent
                mode="popup"
                interrupt={interrupt}
                onFinish={onFinish}
                connectWidget={{
                  title: "Authorization Required.",
                  description: interrupt.message,
                  action: { label: "Authorize" },
                }}
              />
            </div>
          );
        }
        ```

        Now, update your chat window code to include the `TokenVaultInterruptHandler` component, for example:

        ```tsx src/components/chat-window.tsx wrap lines highlight={2,3,10-18,36-38} theme={null}
        //...
        import { useInterruptions } from '@auth0/ai-vercel/react';
        import { TokenVaultInterruptHandler } from '@/components/TokenVaultInterruptHandler';


        //... existing code
        export function ChatWindow(props: {
          //... existing code
        }) {
          const chat = useInterruptions((handler) =>
            useChat({
              api: props.endpoint,
              onError: handler((e: Error) => {
                console.error('Error: ', e);
                toast.error(`Error while processing your request`, { description: e.message });
              }),
            }),
          );

          //... existing code
          return (
            <StickToBottom>
              <StickyToBottomContent
                className="absolute inset-0"
                contentClassName="py-8 px-2"
                content={
                  chat.messages.length === 0 ? (
                    <div>{props.emptyStateComponent}</div>
                  ) : (
                    <>
                      <ChatMessages
                        aiEmoji={props.emoji}
                        messages={chat.messages}
                        emptyStateComponent={props.emptyStateComponent}
                      />
                      <div className="flex flex-col max-w-[768px] mx-auto pb-12 w-full">
                        <TokenVaultInterruptHandler interrupt={chat.toolInterrupt} />
                      </div>
                    </>
                  )
                }
                {/* ... existing code */}
              ></StickyToBottomContent>
            </StickToBottom>
          );
        }
        ```

        Now when step-up authorization is required, the user will see a prompt in the chat window asking them to authorize.

        ### Add the tool to the AI agent

        Handle the interrupts from the AI agent and call the tool from your AI app to get calendar events. Update your chat route, typically at `/src/app/api/chat/route.ts`, as shown in the following example:

        ```ts src/app/api/chat/route.ts wrap lines theme={null}
        //...
        import { setAIContext } from "@auth0/ai-vercel";
        import {
          errorSerializer,
          withInterruptions,
        } from "@auth0/ai-vercel/interrupts";
        import { getCalendarEventsTool } from "@/lib/tools/google-calendar";
        //... existing code

        export async function POST(req: NextRequest) {
          const request = await req.json();
          const messages = sanitizeMessages(request.messages);

          setAIContext({ threadID: request.id });

          const tools = {
            getCalendarEventsTool,
          };

           const modelMessages = await convertToModelMessages(messages);

          const stream = createUIMessageStream({
            originalMessages: messages,
            execute: withInterruptions(
              async ({ writer }) => {
                const result = streamText({
                  model: openai('gpt-4o-mini'),
                  system: AGENT_SYSTEM_TEMPLATE,
                  messages: modelMessages,
                  tools,

                  onFinish: (output) => {
                    if (output.finishReason === 'tool-calls') {
                      const lastMessage = output.content[output.content.length - 1];
                      if (lastMessage?.type === 'tool-error') {
                        const { toolName, toolCallId, error, input } = lastMessage;
                        const serializableError = {
                          cause: error,
                          toolCallId: toolCallId,
                          toolName: toolName,
                          toolArgs: input,
                        };

                        throw serializableError;
                      }
                    }
                  },
                });
                writer.merge(
                  result.toUIMessageStream({
                    sendReasoning: true,
                  }),
                );
              },
              {
                messages: messages,
                tools,
              },
            ),
            onError: errorSerializer((err) => {
              console.error('ai-sdk route: stream error', err);
              return 'Oops, an error occured!';
            }),
          });

          return createUIMessageStreamResponse({ stream });
        }
        ```

        ### Test your application

        Start your application. If you are already logged in, make sure to log out and log back in using Google. Then, ask your AI agent about your Google calendar!

        That's it! You successfully integrated third-party API access using Token Vault into your app.

        ### View a complete example

        Want to see how it all comes together? Explore or clone the fully implemented sample application [here on GitHub](https://github.com/auth0-samples/auth0-ai-samples/tree/main/call-apis-on-users-behalf/others-api/vercel-ai-next-js).
      </Tab>
    </Tabs>
  </Tab>

  <Tab title="LangGraph + FastAPI" icon="https://mintlify-assets.b-cdn.net/auth0/langchain.svg">
    <Prerequisites
      createAuth0ApplicationStep={{
applicationType: "Regular Web Applications",
callbackUrl: "http://localhost:5173/api/auth/callback",
logoutUrl: "http://localhost:5173",
enableTokenVaultGrant: true,
enableAllowRefreshTokenRotation: false,
}}
    />

    <Tabs>
      <Tab title="Use sample app (recommended)">
        ### Download sample app

        Start by downloading and extracting the sample app. Then open in your preferred IDE.

        <DownloadQuickstartButton category="call-others-apis-on-users-behalf" framework="langchain-fastapi-py" />

        The project is divided into two parts:

        * `backend/`: contains the backend code for the web app, an API written in Python using FastAPI and the LangGraph agent.
        * `frontend/`: contains the frontend code for the web app, written in React as a Vite SPA.

        ### Install backend packages

        In the `backend` directory of your project, install the required packages using your preferred package manager, such as `uv`. You can find the instructions on how to [install uv](https://docs.astral.sh/uv/getting-started/installation/) in its documentation.

        ```bash wrap lines theme={null}
        cd backend
        uv sync
        ```

        ### Create your environment file

        In the `backend` directory of your project, create a new file and name it `.env` and add the following content:

        ```bash .env wrap lines theme={null}
        # Auth0 configuration
        APP_BASE_URL='http://localhost:5173'
        AUTH0_SECRET='random 32 byte value'
        AUTH0_DOMAIN='<your-auth0-domain>'
        AUTH0_CLIENT_ID='<your-auth0-application-client-id>'
        AUTH0_CLIENT_SECRET='<your-auth0-application-client-secret>'

        # OpenAI API Key or any provider supported by the LangGraph SDK
        OPENAI_API_KEY="YOUR_API_KEY"

        # LANGGRAPH
        LANGGRAPH_API_URL=http://localhost:54367
        ```

        To get your Auth0 application's `AUTH0_DOMAIN`, `AUTH0_CLIENT_ID`, and `AUTH0_CLIENT_SECRET`, navigate to <strong>[Applications > Applications](https://manage.auth0.com/#/applications)</strong> in the Auth0 Dashboard and select your client application. You'll find these values in the **Basic Information** section at the top.
        Copy each value to the matching setting.

        Next, run this command to generate a random 32 byte value and copy it to the `AUTH0_SECRET` field:

        ```bash generate random 32 byte value theme={null}
        openssl rand -hex 32
        ```

        Lastly, set your OpenAI API key or use any provider supported by the LangGraph SDK:

        ```bash .env wrap lines theme={null}
        OPENAI_API_KEY="YOUR_API_KEY"
        ```

        In the `frontend` directory of your project, copy the `.env.example` file to `.env`. No further customization required.

        ### Install frontend packages

        Ensure you have `npm` installed or follow the instructions to [install npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) in its documentation.
        Navigate to the `frontend` directory of your project and install the required packages:

        ```bash wrap lines theme={null}
        cd frontend
        npm install
        ```

        ### Run your application

        To run your application, start the FastAPI backend, LangGraph server, and the frontend in three terminals:

        1. In one terminal, start the FastAPI backend:

        ```bash wrap lines theme={null}
        cd backend
        source .venv/bin/activate
        uv pip install auth0_fastapi
        fastapi dev app/main.py
        ```

        2. In a second new terminal, start the LangGraph server:

        ```bash wrap lines theme={null}
        cd backend
        source .venv/bin/activate
        uv pip install -U langgraph-api
        langgraph dev --port 54367 --allow-blocking
        ```

        <Note>
          This opens LangGraph Studio in a new tab. You can safely close it.
        </Note>

        3. In a third new terminal, start the frontend:

        ```bash wrap lines theme={null}
        cd frontend
        npm run dev
        ```

        Visit the URL `http://localhost:5173` in your browser. If you are already logged in, make sure to log out and log back in using Google. Now ask your AI agent to list the upcoming events in your Google Calendar!

        That's it! You successfully called a third-party API using Token Vault.
      </Tab>

      <Tab title="Integrate into your app">
        ### Install packages

        In the `backend` directory of your project, install the following dependencies:

        * `auth0-ai-langchain`: [Auth0 AI SDK for LangChain](https://github.com/auth0/auth0-ai-python/tree/main/packages/auth0-ai-langchain) built for AI agents powered by LangChain.
        * `langgraph`: [LangGraph](https://pypi.org/project/langgraph/) for building stateful, multi-actor applications with LLMs.
        * `langchain-openai`: LangChain integrations for OpenAI.
        * `langgraph-cli`: LangGraph CLI for running a local LangGraph server.
        * `google-api-python-client`: Google API client library for Python.

        Make sure you have [uv](https://docs.astral.sh/uv/) installed and run the following command to install the dependencies:

        ```bash wrap lines theme={null}
        cd backend
        uv sync
        uv add "auth0-ai-langchain>=1.0.1" "langgraph>=0.5.4" langchain-openai "langgraph-cli[inmem]>=0.3.6" google-api-python-client --prerelease=allow
        ```

        ### Update your environment file

        In the `backend` directory of your project, add the following variables to your environment file:

        ```bash .env wrap lines theme={null}
        # Auth0 configuration
        APP_BASE_URL='http://localhost:5173'
        AUTH0_SECRET='random 32 byte value'
        AUTH0_DOMAIN='<your-auth0-domain>'
        AUTH0_CLIENT_ID='<your-auth0-application-client-id>'
        AUTH0_CLIENT_SECRET='<your-auth0-application-client-secret>'

        # OpenAI API Key or any provider supported by the Vercel AI SDK
        OPENAI_API_KEY="YOUR_API_KEY"

        # LANGGRAPH
        LANGGRAPH_API_URL=http://localhost:54367
        ```

        To get your Auth0 application's `AUTH0_DOMAIN`, `AUTH0_CLIENT_ID`, and `AUTH0_CLIENT_SECRET`, navigate to <strong>[Applications > Applications](https://manage.auth0.com/#/applications)</strong> in the Auth0 Dashboard and select your client application. You'll find these values in the **Basic Information** section at the top.
        Copy each value to the matching setting.

        Next, run this command to generate a random 32 byte value and copy it to the `AUTH0_SECRET` field.

        ```bash generate random 32 byte value theme={null}
        openssl rand -hex 32
        ```

        Lastly, set your OpenAI API key or use any provider supported by the LangGraph SDK.

        ### Set up Token Vault for Google social connection

        Use the [Auth0 AI SDK for LangChain](https://github.com/auth0/auth0-ai-js/tree/main/packages/ai-langchain) to get an access token for the [Google Social Connection](/integrations/google) using [Token Vault](https://auth0.com/docs/secure/tokens/token-vault):

        * `connection`: pass in the name of the connection you want to access.
        * `scopes`: pass in the scopes to be authorized for this connection.

        Create a file at `app/core/auth0_ai.py` to instantiate the Auth0 AI SDK client:

        ```python app/core/auth0_ai.py wrap lines theme={null}
        from auth0_ai.authorizers.types import Auth0ClientParams
        from auth0_ai_langchain.auth0_ai import Auth0AI

        from app.core.config import settings

        auth0_ai = Auth0AI(
            Auth0ClientParams(
                {
                    "domain": settings.AUTH0_DOMAIN,
                    "client_id": settings.AUTH0_CLIENT_ID,
                    "client_secret": settings.AUTH0_CLIENT_SECRET,
                }
            )
        )

        with_calendar_access = auth0_ai.with_token_vault(
            connection="google-oauth2",
            scopes=["openid", "https://www.googleapis.com/auth/calendar.events"],
            # Optional: authorization_params={"login_hint": "user@example.com", "ui_locales": "en"}
        )
        ```

        ### Pass credentials to the AI agent

        Update your API route to pass the user session data and an external provider's access token to the AI agent, for example, in `app/api/routes/chat.py`:

        ```python app/api/routes/chat.py wrap lines highlight={2,9,21} theme={null}
        # ...
        from app.core.auth import auth_client
        # ...

        @agent_router.api_route(
            "/{full_path:path}", methods=["GET", "POST", "DELETE", "PATCH", "PUT", "OPTIONS"]
        )
        async def api_route(
            request: Request, full_path: str, auth_session=Depends(auth_client.require_session)
        ):
            try:
                # ... existing code

                # Prepare body
                body = await request.body()
                if request.method in ("POST", "PUT", "PATCH") and body:
                    content = await request.json()
                    content["config"] = {
                        "configurable": {
                            "_credentials": {
                                "refresh_token": auth_session.get("refresh_token"),
                            }
                        }
                    }
                    body = json.dumps(content).encode("utf-8")

                    # ... existing code
        ```

        ### Use access token to call APIs from a tool

        Once the user is authenticated, you can fetch an access token from Token Vault using the Auth0 AI SDK. In this example, we fetch an access token for a Google social connection.
        Once you've obtained the access token for a connection, you can use it with an AI agent to fetch data during a tool call and provide contextual data in its response.

        Create or update a tool similar to this example that creates a Google Calendar query tool:

        ```python app/agents/tools/google_calendar.py wrap lines theme={null}
        from langchain_core.tools import StructuredTool
        from google.oauth2.credentials import Credentials
        from googleapiclient.discovery import build
        from pydantic import BaseModel
        from auth0_ai_langchain.token_vault import (
            get_access_token_from_token_vault,
        )
        import datetime
        import json

        from app.core.auth0_ai import with_calendar_access

        async def list_upcoming_events_fn():
            """List upcoming events from the user's Google Calendar"""
            google_access_token = get_access_token_from_token_vault()
            if not google_access_token:
                raise ValueError(
                    "Authorization required to access the Google Calendar API"
                )

            calendar_service = build(
                "calendar",
                "v3",
                credentials=Credentials(google_access_token),
            )

            events = (
                calendar_service.events()
                .list(
                    calendarId="primary",
                    timeMin=datetime.datetime.now().isoformat() + "Z",
                    timeMax=(datetime.datetime.now() + datetime.timedelta(days=7)).isoformat()
                    + "Z",
                    maxResults=5,
                    singleEvents=True,
                    orderBy="startTime",
                )
                .execute()
                .get("items", [])
            )

            return json.dumps(
                [
                    {
                        "summary": event["summary"],
                        "start": event["start"].get("dateTime", event["start"].get("date")),
                    }
                    for event in events
                ]
            )

        list_upcoming_events = with_calendar_access(
            StructuredTool(
                name="list_upcoming_events",
                description="List upcoming events from the user's Google Calendar",
                args_schema=BaseModel,
                coroutine=list_upcoming_events_fn,
            )
        )
        ```

        ### Add the tool to the AI agent

        If you created a new tool, ensure it's added to the list of tools used by your AI agent. For example:

        ```python app/agents/assistant0.py wrap lines highlight={2,4} theme={null}
        # ...
        from app.agents.tools.google_calendar import list_upcoming_events

        tools = [list_upcoming_events]

        llm = ChatOpenAI(model="gpt-4.1-mini")

        # ... existing code
        agent = create_react_agent(
            llm,
            tools=ToolNode(tools, handle_tool_errors=False),
            prompt=get_prompt(),
        )
        ```

        ### Add step-up authorization

        When you try to use the tool, the application requests any additional Google scopes that are required but not yet authorized. This process is called step-up authorization.

        To implement, install the Auth0 AI Components for React SDK to get the required UI components:

        ```bash wrap lines theme={null}
        cd frontend
        npm install @auth0/ai @langchain/langgraph-sdk
        npx @auth0/ai-components add TokenVault
        ```

        Add a new file, `src/components/TokenVaultInterruptHandler.tsx`, with the following code:

        ```tsx src/components/TokenVaultInterruptHandler.tsx wrap lines theme={null}
        import { TokenVaultInterrupt } from "@auth0/ai/interrupts";
        import type { Interrupt } from "@langchain/langgraph-sdk";

        import { TokenVaultConsent } from "@/components/auth0-ai/TokenVault";
        import type { TokenVaultAuthProps } from "./auth0-ai/TokenVault/TokenVaultAuthProps";

        export interface TokenVaultInterruptHandlerProps {
          interrupt: Interrupt | undefined | null;
          onFinish: () => void;
          auth?: TokenVaultAuthProps['auth'];
        }

        export function TokenVaultInterruptHandler({
          interrupt,
          onFinish,
          auth,
        }: TokenVaultInterruptHandlerProps) {
          if (!interrupt) {
            return null;
          }

          if (!TokenVaultInterrupt.isInterrupt(interrupt.value)) {
            return null;
          }

          return (
            <div key={interrupt.ns?.join("")} className="whitespace-pre-wrap">
              <TokenVaultConsent
                mode="popup"
                interrupt={interrupt.value}
                onFinish={onFinish}
                auth={auth}
                connectWidget={{
                  title: "Authorization Required.",
                  description: interrupt.value.message,
                  action: { label: "Authorize" },
                }}
              />
            </div>
          );
        }
        ```

        Now, update your chat window code to include the `TokenVaultInterruptHandler` component, for example:

        ```tsx src/components/chat-window.tsx wrap lines highlight={2,3,25-50} theme={null}
        //...
        import { TokenVaultInterruptHandler } from '@/components/TokenVaultInterruptHandler';
        import { getConnectUrl } from "@/lib/use-auth";

        //... existing code
        export function ChatWindow(props: {
          //... existing code
        }) {
          //... existing code
          return (
            <StickToBottom>
              <StickyToBottomContent
                className="absolute inset-0"
                contentClassName="py-8 px-2"
                content={
                  chat.messages.length === 0 ? (
                    <div>{props.emptyStateComponent}</div>
                  ) : (
                    <>
                      <ChatMessages
                        aiEmoji={props.emoji}
                        messages={chat.messages}
                        emptyStateComponent={props.emptyStateComponent}
                      />
                      <div className="flex flex-col max-w-[768px] mx-auto pb-12 w-full">
                        {!!chat.interrupt?.value && (
                          <TokenVaultInterruptHandler
                            auth={{
                              connectPath: getConnectUrl(),
                              returnTo: new URL(
                                "/close",
                                window.location.origin
                              ).toString(),
                            }}
                            interrupt={{
                              ...chat.interrupt,
                              value: {
                                ...chat.interrupt.value,
                                requiredScopes:
                                  (
                                    chat.interrupt.value as {
                                      required_scopes: [string];
                                    }
                                  ).required_scopes || [],
                                authorizationParams:
                                  (
                                    chat.interrupt.value as {
                                      authorization_params: Record<string, string>;
                                    }
                                  ).authorization_params || {},
                              },
                            }}
                            onFinish={() => chat.submit(null)}
                          />
                        )}
                      </div>
                    </>
                  )
                }
                {/* ... existing code */}
              ></StickyToBottomContent>
            </StickToBottom>
          );
        }
        ```

        Now when step-up authorization is required, the user will see a prompt in the chat window asking them to authorize.

        ### Test your application

        Start your application. If you are already logged in, make sure to log out and log back in using Google. Then, ask your AI agent to list the upcoming events in your Google Calendar!

        That's it! You successfully integrated third-party API access using Token Vault into your app.

        ### View a complete example

        Want to see how it all comes together? Explore or clone the fully implemented sample application on [GitHub](https://github.com/auth0-samples/auth0-ai-samples/tree/main/call-apis-on-users-behalf/others-api/langchain-fastapi-py).
      </Tab>
    </Tabs>
  </Tab>

  <Tab title="Vercel AI + React SPA" icon="https://mintlify-assets.b-cdn.net/auth0/vercel.svg">
    <Prerequisites
      createAuth0ApplicationStep={{
applicationType: "Single Page Web Applications",
callbackUrl: "http://localhost:5173/auth/callback",
logoutUrl: "http://localhost:5173",
allowedWebOrigins: "http://localhost:5173",
enableTokenVaultGrant: true,
enableRefreshTokenGrant: true,
enableAllowRefreshTokenRotation: true,
}}
      createAuth0ApiStep={{}}
      createResourceServerClientStep={{}}
      tokenExchangeGrantStep={{
applicationName: "Single Page Web Application",
}}
    />

    ### Key differences from Next.js approach

    This React SPA implementation differs from the <a href="/get-started/call-others-apis-on-users-behalf#vercel-ai-%2B-next-js" target="_blank">Next.js example</a> in a few important ways:

    1. **Token Vault Access Token Exchange**: Instead of using refresh tokens, the React SPA implementation exchanges the SPA's access token for a third-party access token.
    2. **Client-Side Authorization**: Client login and step-up authorization are handled client-side using `@auth0/auth0-spa-js`.
    3. **Resource Server Client**: Requires a special Resource Server Client configured for token exchange with Token Vault.
    4. **Interrupt Handling**: The React client manages tool access errors and step-up authorization using interrupts that trigger a redirect for re-authorization.

    <Tabs>
      <Tab title="Use sample app (recommended)">
        ### Download sample app

        Start by downloading and extracting the sample app. Then open in your preferred IDE.

        <DownloadQuickstartButton category="call-others-apis-on-users-behalf" framework="langchain-react-spa-js" />

        ### Create your environment files

        Add separate `.env` files with environment variables for the client and server.

        #### Client (client/.env)

        ```bash .env wrap lines theme={null}
        VITE_AUTH0_DOMAIN=your-auth0-domain
        VITE_AUTH0_CLIENT_ID=your-spa-client-id
        VITE_AUTH0_AUDIENCE=your-api-identifier
        VITE_API_URL=http://localhost:3001
        ```

        To get your SPA application's `VITE_AUTH0_DOMAIN`, and `VITE_AUTH0_CLIENT_ID`, navigate to <strong>[Applications > Applications](https://manage.auth0.com/#/applications)</strong> in the Auth0 Dashboard and select your SPA application. You'll find these values in the **Basic Information** section at the top.
        Copy each value to the matching setting.

        To get your API's `AUTH0_AUDIENCE`, navigate to <strong>[Applications > APIs](https://manage.auth0.com/#/apis)</strong> in the Auth0 Dashboard and select your API. You'll find the identifier in the **General Settings** section at the top.

        #### Server (server/.env)

        ```bash .env wrap lines theme={null}
        AUTH0_DOMAIN=your-auth0-domain
        AUTH0_AUDIENCE=your-api-identifier
        AUTH0_CUSTOM_API_CLIENT_ID=your-custom-api-client-id
        AUTH0_CUSTOM_API_CLIENT_SECRET=your-custom-api-client-secret
        OPENAI_API_KEY=your-openai-api-key
        PORT=3001
        ```

        To get your Custom API Client's `AUTH0_DOMAIN`, `AUTH0_AUDIENCE`, and `AUTH0_CUSTOM_API_CLIENT_ID`, and `AUTH0_CUSTOM_API_CLIENT_SECRET` navigate to <strong>[Applications > Applications](https://manage.auth0.com/#/applications)</strong> in the Auth0 Dashboard and select your Custom API Client application. You'll find these values in the **Basic Information** section at the top.
        Copy each value to the matching setting.

        The `AUTH0_AUDIENCE` should match the identifier of the API referenced in the `client/.env` file above.

        Lastly, set your OpenAI API key or use any provider supported by the Vercel AI SDK:

        ```bash .env.local wrap lines theme={null}
        OPENAI_API_KEY="YOUR_API_KEY"
        ```

        ### Install packages

        Ensure you have `npm` installed or follow the instructions to [install npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) in its documentation.
        In the root directory of your project, run the following command to install the required packages:

        ```bash wrap lines theme={null}
        npm install
        ```

        ### Test your application

        1. Start the client and server using Turbo: `npm run dev`.
        2. Navigate to `http://localhost:5173`.
        3. Log in with Google and ask your AI agent about your calendar.

        The application will automatically prompt for additional calendar permissions when needed using Auth0's step-up authorization flow.

        That's it! You've successfully called a third-party API using Token Vault in your React SPA + Vercel AI application.
      </Tab>

      <Tab title="Integrate into your app">
        ### Install dependencies

        In the root directory of your project, ensure you have the following client and server dependencies:

        **Client dependencies:**

        * `@auth0/auth0-spa-js`: Auth0 SPA SDK for client-side authentication
        * `@auth0/ai-vercel`: [Auth0 AI SDK for Vercel AI](https://github.com/auth0/auth0-ai-js/tree/main/packages/ai-vercel) built for AI agents
        * `ai`: Core [Vercel AI SDK](https://sdk.vercel.ai/docs) module

        **Server dependencies:**

        * `@hono/node-server`: Node.js server adapter for Hono
        * `hono`: Lightweight web framework
        * `ai`: Core [Vercel AI SDK](https://sdk.vercel.ai/docs) module
        * `@ai-sdk/openai`: [OpenAI](https://sdk.vercel.ai/providers/ai-sdk-providers/openai) provider
        * `googleapis`: Node.js client for Google APIs
        * `jose`: JavaScript Object Signing and Encryption library for JWT verification
          To install all the client and server dependencies, navigate to the root directory of your project and run the following command:

        ```bash wrap lines theme={null}
        # Install all client & server dependencies from the root directory of the project.
        npm install
        ```

        ### Update the environment files

        Add separate `.env` files with environment variables for the client and server.

        #### Client (client/.env)

        ```bash .env wrap lines theme={null}
        VITE_AUTH0_DOMAIN=your-auth0-domain
        VITE_AUTH0_CLIENT_ID=your-spa-client-id
        VITE_AUTH0_AUDIENCE=your-api-identifier
        VITE_API_URL=http://localhost:3001
        ```

        To get your SPA application's `VITE_AUTH0_DOMAIN`, and `VITE_AUTH0_CLIENT_ID`, navigate to <strong>[Applications > Applications](https://manage.auth0.com/#/applications)</strong> in the Auth0 Dashboard and select your SPA application. You'll find these values in the **Basic Information** section at the top.
        Copy each value to the matching setting.

        To get your API's `AUTH0_AUDIENCE`, navigate to <strong>[Applications > APIs](https://manage.auth0.com/#/apis)</strong> in the Auth0 Dashboard and select your API. You'll find the identifier in the **General Settings** section at the top.

        #### Server (server/.env)

        ```bash .env wrap lines theme={null}
        AUTH0_DOMAIN=your-auth0-domain
        AUTH0_AUDIENCE=your-api-identifier
        AUTH0_CUSTOM_API_CLIENT_ID=your-custom-api-client-id
        AUTH0_CUSTOM_API_CLIENT_SECRET=your-custom-api-client-secret
        OPENAI_API_KEY=your-openai-api-key
        PORT=3001
        ```

        To get your Custom API Client's `AUTH0_DOMAIN`, `AUTH0_AUDIENCE`, and `AUTH0_CUSTOM_API_CLIENT_ID`, and `AUTH0_CUSTOM_API_CLIENT_SECRET` navigate to <strong>[Applications > Applications](https://manage.auth0.com/#/applications)</strong> in the Auth0 Dashboard and select your Custom API Client application. You'll find these values in the **Basic Information** section at the top.
        Copy each value to the matching setting.

        The `AUTH0_AUDIENCE` should match the identifier of the API referenced in the `client/.env` file above.

        Lastly, set your OpenAI API key or use any provider supported by the Vercel AI SDK:

        ```bash .env.local wrap lines theme={null}
        OPENAI_API_KEY="YOUR_API_KEY"
        ```

        ### Create an Auth0 Provider and initialize the Auth0 SPA SDK

        Create `client/src/lib/auth0.ts` and initialize the Auth0 SPA SDK with your Auth0 application details, along with configuration for token storage and refresh tokens:

        ```ts client/src/lib/auth0.ts wrap lines theme={null}
        import { Auth0Client, createAuth0Client } from "@auth0/auth0-spa-js";

        // Auth0 configuration
        const AUTH0_DOMAIN = import.meta.env.VITE_AUTH0_DOMAIN;
        const AUTH0_CLIENT_ID = import.meta.env.VITE_AUTH0_CLIENT_ID;
        const AUTH0_AUDIENCE = import.meta.env.VITE_AUTH0_AUDIENCE;

        export const initAuth0 = async (): Promise<Auth0Client> => {
          if (auth0Client) {
            return auth0Client;
          }

          auth0Client = await createAuth0Client({
            domain: AUTH0_DOMAIN,
            clientId: AUTH0_CLIENT_ID,
            authorizationParams: {
              redirect_uri: window.location.origin,
              audience: AUTH0_AUDIENCE,
              scope: "openid profile email offline_access",
            },
            // Store tokens in localstorage to allow restoring the user session after following redirects.
            // Redirects are necessary to connect an account for the user.
            cacheLocation: 'localstorage',
            useRefreshTokens: true,
            useMrrt: true,
            useDpop: true,
          });

          return auth0Client;
        };
        ```

        Create a Provider component to wrap your application with Auth0 context in `client/src/components/Auth0Provider.tsx`:

        ```tsx client/src/components/Auth0Provider.tsx wrap lines theme={null}
        import React, { ReactNode, useEffect, useRef, useState } from "react";

        import { User } from "@auth0/auth0-ai-js-examples-react-hono-ai-sdk-shared";

        import {
          getToken,
          getUser,
          initAuth0,
          isAuthenticated,
          login,
          logout,
        } from "../lib/auth0";
        import { Auth0Context, Auth0ContextType } from "./auth0-context";

        interface Auth0ProviderProps {
          children: ReactNode;
        }

        export const Auth0Provider: React.FC<Auth0ProviderProps> = ({ children }) => {
          const [isLoading, setIsLoading] = useState(true);
          const [isAuthenticatedState, setIsAuthenticatedState] = useState(false);
          const [user, setUser] = useState<User | null>(null);
          const [error, setError] = useState<string | null>(null);
          const initRef = useRef(false);

          useEffect(() => {
            // Prevent double execution in React Strict Mode
            if (initRef.current) return;
            initRef.current = true;

            const initializeAuth0 = async () => {
              try {
                setIsLoading(true);
                const client = await initAuth0();

                // Check if user is returning from login redirect or connect flow
                if (
                  (window.location.search.includes("code=") ||
                    window.location.search.includes("connect_code=")) &&
                  window.location.search.includes("state=")
                ) {
                  await client.handleRedirectCallback();
                  window.history.replaceState({}, document.title, window.location.pathname);
                }

                const authenticated = await isAuthenticated();
                setIsAuthenticatedState(authenticated);

                if (authenticated) {
                  const userData = await getUser();
                  setUser(userData as User);
                }
              } catch (err) {
                setError(err instanceof Error ? err.message : "Authentication error");
              } finally {
                setIsLoading(false);
              }
            };

            initializeAuth0();
          }, []);

          const handleLogin = async (targetUrl?: string) => {
            try {
              setError(null);
              await login(targetUrl);
            } catch (err) {
              setError(err instanceof Error ? err.message : "Login failed");
            }
          };

          const handleLogout = async () => {
            try {
              setError(null);
              await logout();
              setIsAuthenticatedState(false);
              setUser(null);
            } catch (err) {
              setError(err instanceof Error ? err.message : "Logout failed");
            }
          };

          const contextValue: Auth0ContextType = {
            isLoading,
            isAuthenticated: isAuthenticatedState,
            user,
            error,
            login: handleLogin,
            logout: handleLogout,
            getToken,
          };

          return (
            <Auth0Context.Provider value={contextValue}>
              {children}
            </Auth0Context.Provider>
          );
        };
        ```

        Finally, wrap your application with the `Auth0Provider` in `client/src/main.tsx`:

        ```tsx client/src/main.tsx wrap lines theme={null}
        import "./index.css";

        import { StrictMode } from "react";
        import { createRoot } from "react-dom/client";

        import App from "./App.tsx";
        import { Auth0Provider } from "./contexts/Auth0Context.tsx";

        createRoot(document.getElementById("root")!).render(
          <StrictMode>
            <Auth0Provider>
              <App />
            </Auth0Provider>
          </StrictMode>
        );
        ```

        ### Configure the SPA for step-up authorization

        Unlike the <a href="/get-started/call-others-apis-on-users-behalf#vercel-ai-%2B-next-js" target="_blank">Next.js example</a>, which uses refresh tokens, this React SPA approach uses **access tokens** for token exchange with Token Vault. The SPA handles step-up authorization using the `loginWithPopup()` method from the Auth0 SPA SDK to display the consent screen and allow the user to grant additional permissions.

        Create `client/src/components/TokenVaultConsentPopup.tsx`:

        ```tsx client/src/components/TokenVaultConsentPopup.tsx wrap lines theme={null}
        import { useCallback, useState } from "react";
        import type { Auth0InterruptionUI } from "@auth0/ai-vercel/react";

        import { getAuth0Client } from "../lib/auth0";
        import { Button } from "./ui/button";
        import { Card, CardContent, CardHeader, CardTitle } from "./ui/card";

        /**
         * Component for handling connection authorization popups.
         * This component manages the connect account flow for Token Vault, allowing the
         * user to authorize access to third-party providers.
         */

        interface TokenVaultConsentPopupProps {
          interrupt: Auth0InterruptionUI;
        }

        export function TokenVaultConsentPopup({
          interrupt,
        }: TokenVaultConsentPopupProps) {
          const [isLoading, setIsLoading] = useState(false);

          const { connection, requiredScopes, authorizationParams, resume } = interrupt;

          // Use Auth0 SPA SDK to connect an external account
          const startConnectAccountFlow = useCallback(async () => {
            try {
              setIsLoading(true);

              // Filter out empty scopes
              const validScopes = requiredScopes.filter(
                (scope: string) => scope && scope.trim() !== "",
              );

              const auth0Client = getAuth0Client();

              // Use the connect account flow to request authorization+consent for the external API.
              // This will redirect the browser away from the SPA.
              await auth0Client.connectAccountWithRedirect({
                connection,
                scopes: validScopes,
                ...(authorizationParams
                  ? { authorization_params: authorizationParams }
                  : {}),
              });

              setIsLoading(false);

              // Resume the interrupted tool after successful authorization
              if (typeof resume === "function") {
                resume();
              }
            } catch (error) {
              console.error("Connect account flow failed:", error);
              setIsLoading(false);

              // Even if login fails, we should clear the interrupt
              if (typeof resume === "function") {
                resume();
              }
            }
          }, [connection, requiredScopes, authorizationParams, resume]);

          if (isLoading) {
            return (
              <Card className="w-full">
                <CardContent className="flex items-center justify-center p-6">
                  <div className="text-center">
                    <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-2"></div>
                    <p className="text-sm text-muted-foreground">
                      Connecting to {connection.replace("-", " ")}...
                    </p>
                  </div>
                </CardContent>
              </Card>
            );
          }

          return (
            <Card className="w-full border-yellow-200 bg-yellow-50">
              <CardHeader>
                <CardTitle className="text-lg text-yellow-800">
                  Authorization Required
                </CardTitle>
              </CardHeader>
              <CardContent className="space-y-4">
                <p className="text-sm text-yellow-700">
                  To access your {connection.replace("-", " ")} data, you need to
                  connect your account and authorize this application.
                </p>
                <p className="text-xs text-yellow-600">
                  Required permissions:{" "}
                  {requiredScopes
                    .filter((scope: string) => scope && scope.trim() !== "")
                    .join(", ")}
                </p>
                <Button onClick={startConnectAccountFlow} className="w-full">
                  Connect &amp; Authorize {connection.replace("-", " ")}
                </Button>
              </CardContent>
            </Card>
          );
        }
        ```

        #### Create tools with integrated Token Vault support for retrieving third-party access tokens

        Next, create a tool that accesses Token Vault to fetch a Google access token to list all the Google Calendars a user has access to.

        Create `server/src/lib/tools/listUserCalendars.ts`:

        ```ts server/src/lib/tools/listUserCalendars.ts wrap lines theme={null}
        import { tool } from "ai";
        import { google } from "googleapis";
        import { z } from "zod";

        import { getAccessTokenFromTokenVault } from "@auth0/ai-vercel";

        import type { ToolWrapper } from "@auth0/ai-vercel";

        /**
         * Tool: listUserCalendars
         * Lists all calendars the user has access to.
         * Uses the enhanced @auth0/ai SDK for token exchange with Token Vault.
         */
        export const createListUserCalendarsTool = (
          googleCalendarWrapper: ToolWrapper
        ) =>
          googleCalendarWrapper(
            tool({
              description: "List all calendars the user has access to",
              inputSchema: z.object({}),
              execute: async () => {
                // Get the access token from Token Vault using the enhanced SDK
                const token = getAccessTokenFromTokenVault();

                const calendar = google.calendar("v3");
                const auth = new google.auth.OAuth2();
                auth.setCredentials({ access_token: token });

                const res = await calendar.calendarList.list({ auth });

                const calendars =
                  res.data.items?.map((cal) => ({
                    id: cal.id,
                    name: cal.summary,
                    accessRole: cal.accessRole,
                  })) ?? [];

                return calendars;
              },
            })
          );
        ```

        #### Configure the API server with Google connection wrapper for calendar tools

        Instantiate an instance of `Auth0AI` with a pre-configured resource client. Then, create a wrapper for the Google Calendar Tool that sets up the token exchange. This allows you to directly exchange an Auth0 access token for a Google access token from Token Vault with the necessary Calendar scopes.

        Create `server/src/lib/auth0.ts`:

        ```ts server/src/lib/auth0.ts wrap lines theme={null}
        import { SUBJECT_TOKEN_TYPES } from "@auth0/ai";
        import { Auth0AI } from "@auth0/ai-vercel";

        import type { Context } from "hono";

        import type { ToolWrapper } from "@auth0/ai-vercel";
        // Create an Auth0AI instance configured with reserver client credentials
        const auth0AI = new Auth0AI({
          auth0: {
            domain: process.env.AUTH0_DOMAIN!,
            clientId: process.env.AUTH0_CUSTOM_API_CLIENT_ID!, // Resource server client ID for token exchange
            clientSecret: process.env.AUTH0_CUSTOM_API_CLIENT_SECRET!, // Resource server client secret
          },
        });

        // Enhanced token exchange with Token Vault, setup with access token support
        // This demonstrates the new API pattern where access tokens can be used directly
        export const createGoogleCalendarTool = (c: Context): ToolWrapper => {
          const accessToken = c.get("auth")?.token;
          if (!accessToken) {
            throw new Error("Access token not available in auth context");
          }
          return auth0AI.withTokenVault({
            accessToken: async () => accessToken,
            subjectTokenType: SUBJECT_TOKEN_TYPES.SUBJECT_TYPE_ACCESS_TOKEN,
            connection: process.env.GOOGLE_CONNECTION_NAME || "google-oauth2",
            scopes: [
              "openid",
              "https://www.googleapis.com/auth/calendar.calendarlist.readonly", // Read-only access to calendar list
              "https://www.googleapis.com/auth/calendar.events.readonly", // Read-only access to events
            ],
          });
        };
        ```

        ### Create Hono API Chat API server with interrupt handling

        Create an AI-powered chat server using Hono, the Auth0 AI SDK, and the Vercel AI SDK that allows a user to chat with an AI assistant that can access their Google Calendar.

        The `withInterruptions()` function in the Auth0 AI SDK wraps the Vercel AI SDK `streamText()` function, enabling the Hono server to handle interrupts, which are special responses from Token Vault. An interrupt is sent if a tool call requires a new or updated access token, for example, if a user needs to re-authenticate or a new permission is needed.

        Create `server/src/index.ts`:

        ```ts server/src/index.ts wrap lines theme={null}
        import {
          convertToModelMessages,
          createUIMessageStream,
          createUIMessageStreamResponse,
          generateId,
          streamText,
        } from "ai";
        import { Hono } from "hono";
        import { cors } from "hono/cors";
        import { decodeJwt } from "jose";

        import { openai } from "@ai-sdk/openai";
        import { setAIContext } from "@auth0/ai-vercel";
        import { errorSerializer, withInterruptions } from "@auth0/ai-vercel/interrupts";
        import { serve } from "@hono/node-server";

        import { createGoogleCalendarTool } from "./lib/auth";
        import { createListUserCalendarsTool } from "./lib/tools/listUserCalendars";
        import { jwtAuthMiddleware } from "./middleware/auth";

        import type { ApiResponse } from "shared/dist";

        export const app = new Hono().post("/chat", jwtAuthMiddleware(), async (c) => {
            // auth middleware adds the auth context to the request
            const auth = c.get("auth");

            const { messages: requestMessages } = await c.req.json();

            // Generate a thread ID for this conversation
            const threadID = generateId();

            // Set AI context for the tools to access
            setAIContext({ threadID });

            // Create the Google Calendar wrapper with auth context
            const googleCalendarWrapper = createGoogleCalendarTool(c);

            // Create tools with the auth context
            const listUserCalendars = createListUserCalendarsTool(googleCalendarWrapper);

            // Use the messages from the request directly
            const tools = { listUserCalendars };

            // note: you can see more examples of Hono API consumption with AI SDK here:
            // https://ai-sdk.dev/cookbook/api-servers/hono?utm_source=chatgpt.com#hono

            const modelMessages = await convertToModelMessages(requestMessages);
            const date = new Date().toISOString();

            const stream = createUIMessageStream({
              originalMessages: requestMessages,
              execute: withInterruptions(
                async ({ writer }) => {
                  const result = streamText({
                    model: openai("gpt-4o-mini"),
                    system:
                      `You are a helpful calendar assistant! You can help users with their calendar events and schedules. Keep your responses concise and helpful. Always format your responses as plain text. Do not use markdown formatting like **bold**, ##headers, or -bullet points. Use simple text formatting with line breaks and indentation only. The current date and time is ${date}.`,
                    messages: modelMessages,
                    tools,

                    onFinish: (output) => {
                      if (output.finishReason === "tool-calls") {
                        const lastMessage = output.content[output.content.length - 1];
                        if (lastMessage?.type === "tool-error") {
                          const { toolName, toolCallId, error, input } = lastMessage;
                          const serializableError = {
                            cause: error,
                            toolCallId: toolCallId,
                            toolName: toolName,
                            toolArgs: input,
                          };

                          throw serializableError;
                        }
                      }
                    },
                  });
                  writer.merge(
                    result.toUIMessageStream({
                      sendReasoning: true,
                    })
                  );
                },
                {
                  messages: requestMessages,
                  tools,
                }
              ),
              onError: errorSerializer((err) => {
                console.error("react-hono-ai-sdk route: stream error", err);
                return "Oops, an error occurred!";
              }),
            });

            return createUIMessageStreamResponse({ stream });
          });

        // Start the server for Node.js
        const port = Number(process.env.PORT) || 3000;

        console.log(`🚀 Server starting on port ${port}`);
        serve({ fetch: app.fetch, port });

        console.log(`✅ Server running on http://localhost:${port}`);
        ```

        ### Implement interrupt handling in React

        Update your chat component to handle step-up auth interrupts:

        ```tsx client/src/components/Chat.tsx wrap lines highlight={9,24,100-104,106-109} theme={null}
        import {
          DefaultChatTransport,
          lastAssistantMessageIsCompleteWithToolCalls,
        } from "ai";
        import { Loader2, Send, Trash2 } from "lucide-react";
        import { useState } from "react";

        import { useChat } from "@ai-sdk/react";
        import { useInterruptions } from "@auth0/ai-vercel/react";
        import { TokenVaultInterrupt } from "@auth0/ai/interrupts";

        import { useAuth0 } from "../hooks/useAuth0";
        import { TokenVaultConsentPopup } from "./TokenVaultConsentPopup";
        import { MarkdownText } from "./MarkdownText";
        import { Button } from "./ui/button";
        import { Card, CardContent, CardHeader, CardTitle } from "./ui/card";
        import { Input } from "./ui/input";

        import type { TextUIPart, UIMessage } from "ai";
        const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000";

        export function Chat() {
          const { getToken } = useAuth0();
          const [input, setInput] = useState<string>("");
          const chatHelpers = useInterruptions((errorHandler) =>
            useChat({
              transport: new DefaultChatTransport({
                api: `${SERVER_URL}/chat`,
                fetch: (async (url: string | URL | Request, init?: RequestInit) => {
                  const token = await getToken();
                  return fetch(url, {
                    ...init,
                    headers: {
                      "Content-Type": "application/json",
                      ...init?.headers,
                      Authorization: `Bearer ${token}`,
                    },
                  });
                }) as typeof fetch,
              }),
              onError: errorHandler((error) => {
                console.error("Chat error:", error);
              }),
              sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
            })
          );

          const { messages, sendMessage, status, error, setMessages, toolInterrupt } =
            chatHelpers;

          const clearMessages = () => {
            setMessages([]);
          };

          return (
            <Card className="w-full max-w-2xl mx-auto">
              <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
                <CardTitle className="text-lg font-semibold">
                  Calendar Assistant
                </CardTitle>
                {messages.length > 0 && (
                  <Button
                    variant="outline"
                    size="sm"
                    onClick={clearMessages}
                    className="h-8 w-8 p-0"
                  >
                    <Trash2 className="h-4 w-4" />
                  </Button>
                )}
              </CardHeader>
              <CardContent className="space-y-4">
                {/* Messages */}
                <div className="space-y-4 max-h-96 overflow-y-auto">
                  {messages.length === 0 ? (
                    <div className="text-center text-muted-foreground py-8">
                      <p className="text-sm">Ask me about your calendar events!</p>
                      <p className="text-xs mt-1">
                        Try: "What meetings do I have today?" or "Show me my upcoming
                        events"
                      </p>
                    </div>
                  ) : (
                    messages.map((message) => (
                      <MessageBubble key={message.id} message={message} />
                    ))
                  )}
                  {status === "streaming" && (
                    <div className="flex justify-start">
                      <div className="bg-muted rounded-lg px-3 py-2 max-w-[80%] flex items-center gap-2">
                        <Loader2 className="h-4 w-4 animate-spin" />
                        <span className="text-sm text-muted-foreground">
                          Thinking...
                        </span>
                      </div>
                    </div>
                  )}
                </div>

                {/* Error message - hide if it's an Auth0 interrupt (we show the popup instead) */}
                {error && !TokenVaultInterrupt.isInterrupt(toolInterrupt) && (
                  <div className="bg-destructive/10 text-destructive text-sm p-3 rounded-lg">
                    Error: {error.message}
                  </div>
                )}

                {/* Step-Up Auth Interrupt Handling */}
                {TokenVaultInterrupt.isInterrupt(toolInterrupt) && (
                  <TokenVaultConsentPopup interrupt={toolInterrupt} />
                )}

                {/* Input form */}
                <form
                  onSubmit={(e) => {
                    e.preventDefault();
                    sendMessage({ text: input });
                    setInput("");
                  }}
                  className="flex gap-2"
                >
                  <Input
                    value={input}
                    onChange={(e) => setInput(e.target.value)}
                    placeholder="Ask about your calendar..."
                    disabled={status === "streaming"}
                    className="flex-1"
                  />
                  <Button
                    className="h-10"
                    type="submit"
                    disabled={status === "streaming" || !input.trim()}
                  >
                    <Send className="h-4 w-4" />
                  </Button>
                </form>
              </CardContent>
            </Card>
          );
        }

        function MessageBubble({ message }: { message: UIMessage }) {
          const isUser = message.role === "user";

          // Get all text content from the message parts
          const textContent = message.parts
            .filter((part) => part.type === "text")
            .map((part) => (part as TextUIPart).text)
            .join("");

          return (
            <div className={`flex ${isUser ? "justify-end" : "justify-start"}`}>
              <div
                className={`rounded-lg px-3 py-2 max-w-[80%] ${
                  isUser ? "bg-primary text-primary-foreground" : "bg-muted"
                }`}
              >
                {isUser ? (
                  <p className="text-sm whitespace-pre-wrap">{textContent}</p>
                ) : (
                  <div className="text-sm">
                    <MarkdownText>{textContent}</MarkdownText>
                  </div>
                )}
              </div>
            </div>
          );
        }
        ```

        ### Test your application

        1. Start the client and server using Turbo: `npm run dev`.
        2. Navigate to `http://localhost:5173`.
        3. Log in with Google and ask your AI agent about your calendar.

        The application will automatically prompt for additional calendar permissions when needed using Auth0's step-up authorization flow.

        That's it! You've successfully integrated Token Vault with access tokens into your React SPA + Vercel AI application.

        Explore [the example app on GitHub](https://github.com/auth0/auth0-ai-js/tree/main/examples/calling-apis/spa-with-backend-api/react-hono-ai-sdk).
      </Tab>
    </Tabs>
  </Tab>
</Tabs>

## Next steps

You have successfully added the ability to get access tokens for tool calling to your application. For next steps:

* [Call your APIs on user's behalf docs](/intro/call-your-apis-on-users-behalf).
* Learn more about how Auth0's [Token Vault](https://auth0.com/docs/secure/tokens/token-vault) manages the tokens of supported identity providers.
