Skip to content
Indiana
Dashboard
Docs
Browse docs / Quickstart

Quickstart

NEXT.JS APP ROUTER · TYPESCRIPT · SANDBOX

Install the SDK, identify a customer and read their entitlements.

Download integration example
SDK 0.0.1Updated

Before you start

Connect an AI creation app to customer identity and a generations entitlement. The host example uses Next.js App Router; the SDK itself is framework-independent.

  • Run the backend on Node.js 22 or later with ESM. Pin the current organizational package with the command below; its version history is independent of the old personal package.
  • A Web app with existing server-side authentication. Your auth system supplies getSignedInUser, returning a stable user.id or null.
  • An Indiana App in Sandbox. Save its base URL, App ID and server key from SDK setup, and configure your website origin.
  • Grant at least customers:write, sessions:create and entitlements:read. Customer tokens inherit the issuing key’s customer permissions.
  • Save and activate a generations metered feature and a product that includes it, then claim or purchase through a Funnel. Default free plans or automatic trials are assigned only when configured and eligible; empty holdings are valid without them.

Related:Published SDK and current-source scope·Server API key authentication·See the key permissions walkthrough·Create features and plans

Install and configure the SDK

This guide uses the published npm 0.0.1 beta. Run the install command below; no source build is required. The ZIP contains host integration files. Create a Git-ignored .env.local in your app root, copy the values from SDK setup, then restart your dev server.

Use the same App ID, base URL and Sandbox on both sides. GROOVE_SECRET_KEY belongs only in server modules; never prefix it with NEXT_PUBLIC_.

Connect the current App and Sandbox

Integrations → SDK setup

Isolated fixture capture of current components; no persistence or live-service calls.

  1. Prepare trusted identity and origins

    An Owner, Admin or Developer with App access and apiKeys:write configures a server key and the complete HTTPS origin for app.example.com. The backend verifies its user; the browser receives only a customer token.

Check the resultActual progress comes from persisted App facts. This empty fixture does not prove a live connection.

Check your result

The dependency is installed, configuration is filled in, and the secret key is absent from browser code.

TerminalShell
# Install npm 0.0.1 beta, the version used in this guidenpm install @tinkerlandai/indiana-sdk@0.0.1

Create the server client

Create lib/groove-server.ts. Keep credentials and scope in this module, shared by your session endpoint and protected business operations.

Your login system is the authority for user identity. The same App, environment and externalUserId return the same customer, without merging by email or overwriting existing profile data.

Check your result

Missing configuration fails explicitly. The key is read only on the server. Next, identify the customer from your authenticated session.

lib/groove-server.tsTypeScript
import "server-only";import { createGrooveServer } from "@tinkerlandai/indiana-sdk/server"; function required(name: string): string {  const value = process.env[name];  if (!value) throw new Error(`Missing ${name}`);  return value;} export const groove = createGrooveServer({  baseUrl: required("GROOVE_BASE_URL"),  apiKey: required("GROOVE_SECRET_KEY"),  scope: {    projectId: required("GROOVE_PROJECT_ID"),    environment: "sandbox",  },});

Issue a session for the signed-in user

Add GET /api/groove-session to your own app. Authenticate the user, identify the customer, and return that customer’s short-lived token. The browser supplies neither customerId nor externalUserId.

Replace @/lib/auth with your existing server auth module. The second tab documents its type contract; you must supply the actual implementation.

Check your result

Authenticated requests return customerToken and expiresAt with no-store. Anonymous requests return 401. Tokens expire after 10 minutes by default; permissions are rechecked on every request.

app/api/groove-session/route.tsTypeScript
import { groove } from "@/lib/groove-server";import { getSignedInUser } from "@/lib/auth"; export async function GET(request: Request) {  // Use your existing server-side auth. Never take identity from query parameters.  const user = await getSignedInUser(request);  if (!user) return Response.json({ error: "Unauthenticated" }, { status: 401 });   const { customer } = await groove.getOrCreateCustomer({    externalUserId: user.id,  });  const { customerToken, expiresAt } =    await groove.createCustomerSession(customer.id);   return Response.json(    { customerToken, expiresAt },    { headers: { "Cache-Control": "no-store" } },  );}

Read and display entitlements in the browser

Create lib/groove-client.ts and mount GenerationAccess on your signed-in page. Obtain the session, call identify, and read the current customer’s entitlements.

Holdings use server time and the actual ledger. Use accessible, unlimited and available for presentation. Your trusted backend must still authorize a generation job and atomically consume or reserve credits.

Check your result

The page shows available or unavailable generations. Browser requests carry a customer token and no secret key. Call grow.reset() before sign-out.

lib/groove-client.tsTypeScript
import { createGroove } from "@tinkerlandai/indiana-sdk"; export const grow = createGroove({  baseUrl: process.env.NEXT_PUBLIC_GROOVE_BASE_URL!,  scope: {    projectId: process.env.NEXT_PUBLIC_GROOVE_PROJECT_ID!,    environment: "sandbox",  },}); export async function connectGroove(signal?: AbortSignal) {  const response = await fetch("/api/groove-session", {    credentials: "same-origin",    cache: "no-store",    signal,  });  if (!response.ok) throw new Error(`Session failed: ${response.status}`);  const { customerToken } = await response.json();  if (typeof customerToken !== "string") throw new Error("Invalid session");  signal?.throwIfAborted();  grow.identify(customerToken);  return grow.readHoldings();} // Call before sign-out or switching users. Do not persist the token in localStorage.export function disconnectGroove() {  grow.reset();}

Verify the integration and troubleshoot

Check the network panel and console
SymptomWhat to check
UNAUTHENTICATEDConfirm the host session endpoint returns 200. Refresh an expired token once, then retry; do not loop indefinitely.
FORBIDDENDoes the key have entitlements:read? Has it been revoked, expired, or had permissions reduced?
SCOPE_MISMATCHDo the browser and backend use the same App ID and environment? Headers cannot switch a key’s scope.
unassigned / allowed: falseHas the published Product been granted or purchased through a Funnel? Is the entitlement key generations? Existing customers retain their original contract version.
Browser CORS failureDoes the configured origin exactly match your website’s scheme, hostname and port?

Check the network panel and console

  • UNAUTHENTICATED

    What to check
    Confirm the host session endpoint returns 200. Refresh an expired token once, then retry; do not loop indefinitely.
  • FORBIDDEN

    What to check
    Does the key have entitlements:read? Has it been revoked, expired, or had permissions reduced?
  • SCOPE_MISMATCH

    What to check
    Do the browser and backend use the same App ID and environment? Headers cannot switch a key’s scope.
  • unassigned / allowed: false

    What to check
    Has the published Product been granted or purchased through a Funnel? Is the entitlement key generations? Existing customers retain their original contract version.
  • Browser CORS failure

    What to check
    Does the configured origin exactly match your website’s scheme, hostname and port?

Related:Complete code examples·Consume credits·HTTP parameters and responses