Browse documentation

Product user authentication

Caltra keeps platform administrators separate from the product users who run agents through your application. Each organization selects one upstream identity adapter while your existing identity system continues to own login, passwords, MFA, and account recovery.

Clerk OAuth

The Clerk option establishes organization-specific trust with your Clerk tenant. It does not require a custom identity adapter in your backend for activation.

  1. In Organization settings → Product user identity, select Clerk OAuth.
  2. Copy the Caltra callback URL and the openid profile email scopes.
  3. Open Clerk Dashboard, create an OAuth application, and add the copied callback URL.
  4. Paste the Clerk issuer URL, client ID, and one-time client secret into Caltra.
  5. Choose Connect and activate. Caltra opens a real Clerk login and marks the organization connection active only after the authorization-code flow succeeds.

The setup is organization-specific. Caltra encrypts the client secret at rest and does not display it again.

This activation configures the upstream identity boundary. Direct Clerk exchange for SDK sessions and interactive MCP authorization are not part of the current early-access surface yet.

Customer-managed backend

Choose this provider-neutral option when your application has its own login system or an identity provider other than Clerk. The browser never receives your Caltra server API key or a long-lived token.

Create the live server credential in Organization settings → API keys. Give it a descriptive name and save the csk_live_ plaintext key when Caltra displays it once. The client_tokens:create scope authorizes client-authorization creation, while agents:provision permits the backend to provision agents only for tenant users whose workspace role grants Create agents. Both scopes apply across every current and future workspace in the organization. You can rename keys without changing their access, rotate a key before permanently deleting the previous value, and expect deletion to invalidate the key and its issued client tokens immediately. Legacy csk_test_ keys remain usable with their assigned permissions until rotated.

Install the server SDK in your backend and initialize it once with that key:

npm install @caltra/server
import { CaltraServerClient } from "@caltra/server";
 
const caltra = new CaltraServerClient({ apiKey: config.caltraApiKey });

When your application creates a customer organization, resolve its Caltra workspace after the local database transaction commits:

const workspace = await caltra.workspaces.get({
  externalId: organization.id,
  createIfMissing: { name: organization.name },
});

The API key already identifies the owning Caltra organization, so this call does not need an application ID. Store the returned workspace ID if convenient, or repeat the idempotent lookup before creating a client authorization.

Before authorizing the browser, your backend can idempotently provision that user’s personal agent:

await caltra.agents.get({
  externalId: "personal-assistant",
  owner: { tenantUserExternalId: signedInUser.id },
  workspaceId: workspace.id,
  createIfMissing: {
    name: "Product Assistant",
    instructions: PRODUCT_ASSISTANT_INSTRUCTIONS,
  },
});

The agent external ID is scoped to that tenant user and workspace. A different signed-in user cannot resolve it as their own agent. If the agent is missing and the user’s assigned workspace role does not grant Create agents, provisioning fails without creating anything.

For Fastify, install the authorization endpoint directly. The callback is the only application-owned identity boundary: authenticate the request, verify that the user belongs to the requested customer organization, run any application-specific provisioning, and return the two stable external IDs.

import caltraFastify from "@caltra/server/fastify";
 
await app.register(caltraFastify, {
  client: caltra,
  resolveIdentity: async (request, { requestedWorkspaceExternalId }) => {
    const user = await authenticateRequest(request);
    if (!user || !await memberships.hasAccess(user.id, requestedWorkspaceExternalId)) return null;
 
    const workspace = await caltra.workspaces.get({ externalId: requestedWorkspaceExternalId });
    if (!workspace) return null;
    await caltra.agents.get({
      externalId: "personal-assistant",
      owner: { tenantUserExternalId: user.id },
      workspaceId: workspace.id,
      createIfMissing: {
        name: "Product Assistant",
        instructions: PRODUCT_ASSISTANT_INSTRUCTIONS,
      },
    });
 
    return {
      firstName: user.firstName,
      lastName: user.lastName,
      userExternalId: user.id,
      workspaceExternalId: requestedWorkspaceExternalId,
    };
  },
});

The plugin defaults to POST /api/caltra/authorize and returns only the short-lived, single-use authorization_code. The code is bound to the exact browser origin and can authenticate once. Optional structured first and last names synchronize the Caltra tenant identity whenever they are supplied; omitting either field preserves the value already stored in Caltra.

The browser client uses that route by default and can get or create the user’s durable session by external ID:

import { CaltraClient } from "@caltra/client";
 
const client = new CaltraClient({
  // Optional. Production defaults to https://api.caltra.dev.
  apiUrl: "https://api.caltra.dev",
  workspaceExternalId: customerOrganization.id,
});
 
const session = await client.sessions.get({
  externalId: "primary",
  createIfMissing: { agentExternalId: "personal-assistant" },
});

The returned object includes Caltra’s internal session id, which can be passed to @caltra/react to render its transcript and live responses. Session lookup is derived from the signed tenant-user principal; the browser never supplies a user ID. The SDK authenticates each code at POST /caltra/v1/auth/authenticate, keeps the short-lived token in memory, and requests a fresh authorization after the server-provided refresh time. Set authorizationRoute only when your host application uses a non-default path.

What this configures

This setup establishes the organization’s upstream product-user identity source. Interactive MCP authorization will build on the same organization identity boundary as that surface becomes available; configuring an adapter does not yet register an MCP OAuth client.