Create server keys, configure permissions, and handle revocation and rotation.
API v1Updated
Start with three identities
Server API calls use a gg_sk_ API key. The key is bound to one App/environment, and Indiana checks its permissions on every request. Server reads and writes do not require issuing a customer token first.
Who holds each credential
Caller
Credential
Purpose
Your backend
INDIANA_SECRET_KEY / gg_sk_
Call Indiana within the App/environment and permissions
Your client
customerToken / gg_ct_
Call allowed customer endpoints directly; issued by your backend
Indiana console member
Supabase Auth
Sign in to the console and create Apps and keys
Who holds each credential
Your backend
Credential
INDIANA_SECRET_KEY / gg_sk_
Purpose
Call Indiana within the App/environment and permissions
Your client
Credential
customerToken / gg_ct_
Purpose
Call allowed customer endpoints directly; issued by your backend
Indiana console member
Credential
Supabase Auth
Purpose
Sign in to the console and create Apps and keys
A Customer is an Indiana business record mapped to your product's user by App, environment and externalUserId. It does not share console login credentials. Your authentication system verifies end users; an Indiana key does not prove user login. Complete the server-key-only flow below before considering client sessions.
1. Create a Sandbox server key
Sign in as an Owner, Admin or Developer with App access and apiKeys:write, then select your App and Sandbox in SDK setup.
Create a key with customers:write, the only permission required for the first request below. Plaintext is shown once; save it in your backend configuration.
Copy the base URL and App ID from the same page. Use the site root as base URL; do not append /api/v1. Server-only requests do not require a browser origin.
A key is bound to one App/environment and expires in 1–365 days. Create a replacement to change permissions or expiry. Each App/environment supports up to 20 active keys.
2. Configure and verify your Key
Use Node.js 22+ and native fetch to verify the Key without an SDK. This verifies server authentication; the quickstart continues with Customer sessions and Web reads.
Save a Git-ignored .env.local in your app root and replace the site root, App ID and key below with the values from SDK setup. Read INDIANA_SECRET_KEY on the backend only; never prefix it with NEXT_PUBLIC_.
Create indiana-server-check.mjs in the same directory and copy the complete script below. It creates and retains one Sandbox test customer. It does not issue a customer token. Eligible free default Products can initialize; paid Products/trials require independent billing.
First request complete
Success includes requestId, Sandbox scope and customerId. Rerunning returns the same customerId with created: false, indicating an existing Customer was found.
In your application, replace the fixed test externalUserId with a stable user ID verified by your backend or authorized for your business task. A browser-supplied customerId or user ID is not authorization.
.env.localShell
1# Copy Sandbox values from SDK setup; ignore this file in Git.2INDIANA_BASE_URL=https://your-indiana-host.example3INDIANA_PROJECT_ID=your-app-id4INDIANA_SECRET_KEY=your-sandbox-server-key
HTTP uses the same key and permission as the SDK. Set INDIANA_BASE_URL, INDIANA_PROJECT_ID and INDIANA_SECRET_KEY in your terminal environment before this command; Node's --env-file does not export variables to your shell.
Success returns HTTP 200 with requestId, scope, created and customer. Repeating the same App, environment and externalUserId returns the existing customer.
SDK scope must match the key's App/environment. X-Indiana-Project and X-Indiana-Environment assert the expected scope; changing them cannot authorize another App. Direct HTTP may omit them, but must send both when used.
Refunds additionally require credits:manage alongside credits:consume. Funnel start requires sessions:create and funnels:run. Check each endpoint for additional conditions.
If your application only calls Indiana from its backend, the flow above is sufficient. Add customer sessions when the browser needs current-customer data or Funnel presentation.
Your backend verifies login through your authentication system, then maps the user's Customer.
Add sessions:create to the key plus the client's required permissions, such as entitlements:read.
Your backend calls POST /api/v1/customer-sessions with { customerId }, returns customerToken/expiresAt only to the verified user, and sets no-store.
The client sends Authorization: Bearer <customerToken> to Indiana. Add the browser origin to the current App/environment allowlist.
Customer tokens normally last 10 minutes, capped by key expiry. Only entitlements:read, funnels:run, billing:read, billing:write, offers:resolve and credits:read are delegated. Every request rechecks the issuing key and permissions. Guest sessions receive funnels:run only.
There is no Public SDK Key plus arbitrary user-ID bootstrap. Ordinary SDK handlers reject server-key requests carrying Origin; keep secret keys out of browsers. See the Funnel reference for its separate gateway's Origin rules.
Check that the full key was copied and loaded by your backend and is not revoked or expired. Refreshing a customer token cannot fix a server-key failure.
403 / FORBIDDEN
The first request requires customers:write. Ordinary server requests must not carry a browser Origin.
403 / SCOPE_MISMATCH
Check the App ID and sandbox/live against the key's scope.
400 / INVALID_REQUEST
Check the DTO, paired scope headers and JSON body.
500 / INTERNAL_ERROR; 503 / SERVICE_UNAVAILABLE
Record code, status and requestId to investigate dependencies/network failures. Preserve the original identity and input for write retries.
501 / NOT_CONFIGURED
A required Indiana service is unconfigured; its operator must resolve it.
Common failures and next steps
401 / UNAUTHENTICATED
Check first
Check that the full key was copied and loaded by your backend and is not revoked or expired. Refreshing a customer token cannot fix a server-key failure.
403 / FORBIDDEN
Check first
The first request requires customers:write. Ordinary server requests must not carry a browser Origin.
403 / SCOPE_MISMATCH
Check first
Check the App ID and sandbox/live against the key's scope.
400 / INVALID_REQUEST
Check first
Check the DTO, paired scope headers and JSON body.
500 / INTERNAL_ERROR; 503 / SERVICE_UNAVAILABLE
Check first
Record code, status and requestId to investigate dependencies/network failures. Preserve the original identity and input for write retries.
501 / NOT_CONFIGURED
Check first
A required Indiana service is unconfigured; its operator must resolve it.
Record code, status and requestId, then follow retryable/recovery. Recover the original business operation after a timeout or lost response. See the error reference for fields and version requirements. Never log keys or tokens.
Server-only rotation: create a replacement key for the same App/environment with required permissions, update backend configuration, confirm requests, then revoke the old key.
For client integration, also issue customer sessions with the new key and update clients before revoking the old key. renewCustomerSession requires the same issuer; it cannot renew across keys.
After revocation or expiry, the key and customer tokens it issued fail subsequent authentication. Committed operations are not rolled back.
The SDK does not renew automatically. Reauthenticate through the host before issuing or renewing customer tokens. grow.reset clears memory only; revoke sessions explicitly through the backend's revokeCustomerSessions.