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

# Add Vana to your own app

> Wire Vana into your own app: the backend controller, API routes, and React button, using the SDK directly.

<Tip>
  The [example app](/build-a-vana-app/example-app) is the fastest way to see the flow and stays in sync with the SDK. Follow these steps to build the same integration directly into your own app — the routes and hook below are the ones the example uses.
</Tip>

## Install

The snippets below use **Next.js App Router**, but `@opendatalabs/vana-sdk` isn't tied to Next — the server controller runs in any Node backend and the hook works in any React app. Use an existing Next.js project (or adapt the routes to your framework). To start fresh:

```shell theme={null}
npx create-next-app@latest vana-direct-demo --ts --app --eslint --src-dir --import-alias "@/*"
cd vana-direct-demo
```

Install the Vana SDK:

```shell theme={null}
npm install @opendatalabs/vana-sdk viem
```

## Configure the backend

Set server-side environment variables in `.env.local`:

```shell .env.local theme={null}
VANA_APP_PRIVATE_KEY=0x...
VANA_APP_URL=http://localhost:3000
VANA_ENV=production
VANA_NETWORK=moksha
```

Use `VANA_ENV=production` for the current Direct app flow. Use `VANA_NETWORK=moksha` (testnet) — mainnet deployment is being finalized, so build on testnet for now.

Create `lib/vana.ts`:

```typescript lib/vana.ts theme={null}
import { createDirectDataController } from "@opendatalabs/vana-sdk/server";

const network = process.env.VANA_NETWORK === "mainnet" ? "mainnet" : "moksha";

export const vana = createDirectDataController({
  env: "production",
  network,
  appPrivateKey: process.env.VANA_APP_PRIVATE_KEY!,
  app: {
    id: "spotify-taste",
    name: "Spotify Taste",
    homepageUrl: process.env.VANA_APP_URL!,
  },
  source: "spotify",
  scopes: ["spotify.profile"],
});

export const appAddress = vana.getAppAddress();
```

The SDK resolves the escrow contract and escrow gateway from the selected `network` — you don't pass an address. Just [fund escrow](/build-a-vana-app/escrow-and-fees) for your app on that network. (Pass an `escrow` config only to override the defaults for a custom deployment.) Replace `source` and `scopes` with values from the selected source detail or connector schema.

## Create API routes

Create an access request route:

```typescript app/api/vana/request/route.ts theme={null}
import { vana } from "@/lib/vana";

export async function POST() {
  const request = await vana.createAccessRequest({
    returnUrl: `${process.env.VANA_APP_URL}/connect/return`,
  });

  return Response.json(request);
  // {
  //   requestId: "dcr_123",
  //   approvalUrl: "https://app.vana.org/...",
  //   appAddress: "0x1234..."
  // }
}
```

Create a return page:

```tsx app/connect/return/page.tsx theme={null}
export default function ConnectReturnPage() {
  return (
    <main>
      <h1>Approval complete</h1>
      <p>You can close this tab and return to the app.</p>
    </main>
  );
}
```

Vana redirects the approval tab to this page after approval. The original app tab continues polling status and reads the approved data.

Create a status route:

```typescript app/api/vana/status/route.ts theme={null}
import { vana } from "@/lib/vana";

export async function GET(request: Request) {
  const requestId = new URL(request.url).searchParams.get("requestId");

  if (!requestId) {
    return Response.json({ error: "Missing requestId" }, { status: 400 });
  }

  const status = await vana.getAccessRequestStatus(requestId);
  return Response.json(status);
  // Approved:
  // {
  //   status: "approved",
  //   personalServerUrl: "https://...",
  //   grantId: "0xabc...",
  //   scope: "spotify.profile"
  // }
}
```

Create a read route:

```typescript app/api/vana/data/route.ts theme={null}
import { vana } from "@/lib/vana";

export async function GET(request: Request) {
  const requestId = new URL(request.url).searchParams.get("requestId");

  if (!requestId) {
    return Response.json({ error: "Missing requestId" }, { status: 400 });
  }

  const result = await vana.readApprovedData({ requestId });
  return Response.json(result);
}
```

`readApprovedData` reads from the user's Personal Server. If payment is required, the SDK signs the protocol challenge with your app key, pays from your app's escrow using the network's escrow contract and gateway, retries with `X-PAYMENT`, and returns the paid read result. If your app's escrow balance is unfunded, the read fails with `Insufficient finalized balance` — [fund escrow](/build-a-vana-app/escrow-and-fees) and retry.

## Add React

The frontend calls your backend, opens Vana approval, polls status, asks your backend to read approved data, and renders the returned result.

```tsx app/components/ConnectSpotifyButton.tsx theme={null}
"use client";

import { useDirectVanaConnect } from "@opendatalabs/vana-sdk/react";

async function jsonFetch(path: string, init?: RequestInit) {
  const res = await fetch(path, init);
  if (!res.ok) throw new Error(`${res.status} from ${path}`);
  return res.json();
}

export function ConnectSpotifyButton() {
  const connect = useDirectVanaConnect({
    createRequest: () => jsonFetch("/api/vana/request", { method: "POST" }),
    getStatus: (requestId: string) =>
      jsonFetch(`/api/vana/status?requestId=${encodeURIComponent(requestId)}`),
    readResult: (requestId: string) =>
      jsonFetch(`/api/vana/data?requestId=${encodeURIComponent(requestId)}`),
  });

  return (
    <div>
      <button
        disabled={connect.state.type !== "idle"}
        onClick={() => connect.start()}
        type="button"
      >
        {connect.state.type === "idle" ? "Connect Spotify" : "Connecting..."}
      </button>

      {connect.state.type === "done" ? (
        <pre>{JSON.stringify(connect.state.result, null, 2)}</pre>
      ) : null}

      {connect.state.type === "error" ? (
        <p role="alert">{connect.state.error.message}</p>
      ) : null}
    </div>
  );
}
```

Render `<ConnectSpotifyButton />` from a page in your app.
