# Use app tokens

Learn how to generate and use app tokens to call Hygraph APIs from your app.

**Action required for existing app installations:**
  If your app makes API calls using a token injected directly by the App SDK, you need to update it to use the App Token exchange flow described on this page. If your app already uses this flow or does not make API calls, no action is needed.

  Once you've updated and redeployed your app, reinstall it in your project and [notify our team](https://hygraph.com/support) so we can apply the patch on our end.

App tokens let your app call Hygraph APIs on behalf of the environment it's installed in. You only need to generate an app token if your app accesses the Content API, Management API, or webhooks.

The token is generated during installation by exchanging a setup code for an app token. The setup code is passed as a `code` query parameter and is only available while installation is in progress.

## Prerequisites

Before you start, make sure you have:

- Registered your app and saved the [`Client ID` and `Client Secret` from the **Permissions** tab](https://hygraph.com/docs/app-framework/register-an-app#permissions).
- Created your app using [<code>create-next-app</code>](https://nextjs.org/docs/api-reference/create-next-app) with the App Router.

## Step 1: Add the token exchange API route

Create `app/api/exchangeSetupCode/route.ts`. This route receives the setup code from your setup page, calls the Hygraph token exchange endpoint with your app credentials, and returns the app token.

**Warning:**

Store `CLIENT_ID` and `CLIENT_SECRET` as environment variables. Never commit them to your repository.


```ts
import { NextResponse } from "next/server";

type ExchangeSetupCodePayload = {
  code?: string;
  environmentId?: string;
  managementApiBaseUrl?: string;
};

export async function POST(request: Request) {
  let payload: ExchangeSetupCodePayload;

  try {
    payload = (await request.json()) as ExchangeSetupCodePayload;
  } catch {
    return NextResponse.json(
      { message: "Invalid request body. JSON payload is required." },
      { status: 400 },
    );
  }

  const { code, environmentId, managementApiBaseUrl } = payload;

  if (!code || !environmentId || !managementApiBaseUrl) {
    return NextResponse.json(
      {
        message:
          "Missing required fields: code, environmentId, and managementApiBaseUrl are required.",
      },
      { status: 400 },
    );
  }

  if (!process.env.CLIENT_ID || !process.env.CLIENT_SECRET) {
    return NextResponse.json(
      { message: "Server is missing CLIENT_ID or CLIENT_SECRET." },
      { status: 500 },
    );
  }

  try {
    const response = await fetch(`${managementApiBaseUrl}/app-exchange-token`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        clientId: process.env.CLIENT_ID,
        clientSecret: process.env.CLIENT_SECRET,
        exchangeCode: code,
      }),
    });

    const data = (await response.json()) as {
      appToken?: string;
      message?: string;
    };

    if (!response.ok) {
      return NextResponse.json(
        {
          message:
            data.message ?? "Failed to exchange setup code for an app token.",
        },
        { status: response.status },
      );
    }

    if (!data.appToken) {
      return NextResponse.json(
        { message: "Hygraph did not return an app token." },
        { status: 502 },
      );
    }

    return NextResponse.json({ appToken: data.appToken });
  } catch {
    return NextResponse.json(
      { message: "Could not reach Hygraph token exchange endpoint." },
      { status: 502 },
    );
  }
}
```

## Step 2: Add the setup page

Create `app/setup/page.tsx`. This page reads the setup code from the URL query parameter, calls your exchange route, and marks the installation as completed.

```ts
"use client";

import { useApp, Wrapper } from "@hygraph/app-sdk-react";
import { useSearchParams } from "next/navigation";
import { useState } from "react";

function Setup({ code }: { code: string }) {
  const { context, updateInstallation } = useApp();
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const managementApiBaseUrl = context.project.mgmtApi
    .split("/")
    .slice(0, -1)
    .join("/");

  const handleConnectApp = async () => {
    setError(null);
    setIsSubmitting(true);

    try {
      if (!code) {
        throw new Error(
          "Missing setup code. Please restart the app installation.",
        );
      }

      const response = await fetch(`/api/exchangeSetupCode`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          code,
          environmentId: context.environment.id,
          managementApiBaseUrl,
        }),
      });

      if (!response.ok) {
        let message = "Failed to connect your app. Please try again.";

        try {
          const errorData = await response.json();
          if (errorData?.message) {
            message = errorData.message;
          }
        } catch {
          // Use default message when response body is not JSON
        }

        throw new Error(message);
      }

      await updateInstallation({ status: "COMPLETED", config: {} });
    } catch (caughtError) {
      setError(
        caughtError instanceof Error
          ? caughtError.message
          : "Something went wrong while connecting your app.",
      );
    } finally {
      setIsSubmitting(false);
    }
  };

  return (
    <>
      <button onClick={handleConnectApp} disabled={isSubmitting}>
        {isSubmitting ? "Connecting..." : "Connect your app"}
      </button>
      {error ? <p role="alert">{error}</p> : null}
    </>
  );
}

function SetupContent({ code }: { code: string }) {
  const { installation } = useApp();

  if (!installation) {
    return <div>Loading installation...</div>;
  }

  if (installation.status === "COMPLETED") {
    return <div>Installation completed</div>;
  }

  return <Setup code={code} />;
}

export default function SetupPage() {
  const searchParams = useSearchParams();
  const code = searchParams.get("code") ?? "";

  return (
    <Wrapper>
      <SetupContent code={code} />
    </Wrapper>
  );
}
```

## Step 3: Make authenticated API calls

Once installation is complete, retrieve the app token from `context.environment.authToken` and pass it as a `Bearer` token in your API requests.

The example below shows an API route that creates a model using the Management API and a component that calls it.

1. In `app/api/createHygraphModel/route.ts`, create an API route that creates a model using the Management API:

    ```ts
    import { NextResponse } from "next/server";

    const createModelMutation = `
      mutation CreateModelMutation($input: CreateModelInput!) {
        createModel(data: $input) {
          migration {
            id
          }
        }
      }
    `;

    type CreateHygraphModelPayload = {
      appToken?: string;
      environmentId?: string;
      managementApiUrl?: string;
    };

    export async function POST(request: Request) {
      let body: CreateHygraphModelPayload;

      try {
        body = (await request.json()) as CreateHygraphModelPayload;
      } catch {
        return NextResponse.json(
          { message: "Invalid request body. JSON payload is required." },
          { status: 400 },
        );
      }

      if (!body.appToken || !body.environmentId || !body.managementApiUrl) {
        return NextResponse.json(
          {
            message:
              "Missing required fields: appToken, environmentId, and managementApiUrl are required.",
          },
          { status: 400 },
        );
      }

      try {
        const response = await fetch(body.managementApiUrl, {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            Authorization: `Bearer ${body.appToken}`,
          },
          body: JSON.stringify({
            query: createModelMutation,
            variables: {
              input: {
                environmentId: body.environmentId,
                apiId: "DemoModel",
                apiIdPlural: "DemoModels",
                displayName: "Demo Model",
                description: "Demo Model Description",
              },
            },
          }),
        });

        const data = (await response.json()) as Record<string, unknown>;

        if (!response.ok) {
          return NextResponse.json(
            { message: "Failed to create Hygraph model.", data },
            { status: response.status },
          );
        }

        return NextResponse.json(data, { status: 200 });
      } catch {
        return NextResponse.json(
          { message: "Could not reach Hygraph management API endpoint." },
          { status: 502 },
        );
      }
    }
    ```

2. In `app/page.tsx`, create a component that calls the route:

    ```ts
    "use client";

    import { Wrapper, useApp } from "@hygraph/app-sdk-react";
    import { useState } from "react";

    function CreateHygraphModel() {
      const { context } = useApp();
      const [isSubmitting, setIsSubmitting] = useState(false);
      const [message, setMessage] = useState<string | null>(null);

      const appToken = context.environment.authToken;

      const handleCreateModel = async () => {
        setMessage(null);
        setIsSubmitting(true);

        const response = await fetch("/api/createHygraphModel", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            appToken,
            environmentId: context.environment.id,
            managementApiUrl: context.project.mgmtApi,
          }),
        });

        const data = (await response.json()) as { message?: string };

        setMessage("🎉 Created Hygraph Model");
        setIsSubmitting(false);
      };

      return (
        <>
          <button onClick={handleCreateModel} disabled={isSubmitting}>
            {isSubmitting ? "Creating..." : "Create Hygraph Model"}
          </button>
          {message ? <p>{message}</p> : null}
        </>
      );
    }

    export default function PageRoute() {
      return (
        <Wrapper>
          <CreateHygraphModel />
        </Wrapper>
      );
    }
    ```

## What's next

- [Register an app](https://hygraph.com/docs/app-framework/register-an-app): How to configure app permissions and retrieve your Client ID and Client Secret.
- [Install an app](https://hygraph.com/docs/app-framework/app-installation): How the installation flow works from the user's perspective.
- [First steps](https://hygraph.com/docs/app-framework/first-steps/overview): Step-by-step tutorial for building an app with field and sidebar elements.
- [API Reference](https://hygraph.com/docs/app-framework/api-reference/api-reference-common): Full reference for common props, including `context.environment.authToken`.
