Skip to Content
Client SDKAuthentication

Authentication

A CustomerClient acts for one customer. Customer sessions work in Live and Sandbox.

A client gets its session in one of three ways:

  • Pass authentication: { type: 'identity-token-provider', getIdentityToken } to createCustomerClient(). The client exchanges the token for a session.
  • Pass authentication: { type: 'session', session } with a session your app saved earlier.
  • Create the client without authentication and call client.session.set(session) later, for example in an OAuth callback.

Read the current session

const session = client.session.current if (session) { saveCustomerSession(session) }

client.session.current is null when the client holds no session.

Restore or replace a session

client.session.set({ accessToken: 'customer-access-token', refreshToken: 'customer-refresh-token', customerId: 'customer-id', projectId: 'project-id', sandboxMode: false })

Replacing or clearing the session ends the requests and streams of the previous session. They fail with CustomerSessionChangedError, so a late result never overwrites the new session. Call client.session.clear() to sign out.

Establish a session from an identity provider

Supply a token from the identity provider configured for the project:

import { createCustomerClient } from '@preternatural-js/client-sdk' const client = createCustomerClient({ environment: 'production', projectId: 'your-project-id', authentication: { type: 'identity-token-provider', getIdentityToken: async () => getIdentityTokenForCurrentUser() } }) await client.session.establish()

getIdentityToken returns the token, or null when nobody is signed in. Without establish(), the first customer request establishes the session. Either way, the client exchanges the identity-provider token for Modem customer tokens.

Synthetic customers

A synthetic customer exists only in a Sandbox project. Create one in the dashboard or with modem-cli customers create --project-id <id>, and keep its customer ID and secret. See Create a sandbox customer.

Synthetic customers use the gateway-only client:

import { createGatewayProviderClient, ServiceProvider } from '@preternatural-js/client-sdk' const gateway = createGatewayProviderClient({ environment: 'production', auth: { type: 'synthetic', projectId: 'sandbox-project-id', customerId: 'synthetic-customer-id', sandboxSecret: 'synthetic-customer-secret' }, productId: 'your-product-id', provider: ServiceProvider.openai, modelIdentifier: 'gpt-4o-mini' }) const completion = await gateway.chat.completions.create({ model: 'gpt-4o-mini', messages: [{ role: 'user', content: 'Hello' }] })

The secret authenticates gateway requests directly, with no refreshable session. Do not pass a customer JWT as sandboxSecret. Synthetic requests still use the product’s provider accounts, usage limits, and rate limits.

The gateway logs customer content only for synthetic requests in Sandbox mode.

Use a customer access token without a session

With a customer access token, you can create a gateway client without a refresh token or the app API:

import { createGatewayProviderClient, ServiceProvider } from '@preternatural-js/client-sdk' const gateway = createGatewayProviderClient({ auth: { type: 'customer', accessToken: 'customer-access-token', customerId: 'customer-id', projectId: 'project-id' }, environment: 'production', productId: 'your-product-id', provider: ServiceProvider.openai, modelIdentifier: 'gpt-4o-mini' })

Refresh the access token

await client.session.refresh() const updatedSession = client.session.current

client.session.refresh() sends the stored refresh token to the app’s token endpoint, POST /api/client/refresh, and stores the new customer access token it returns. The refresh token stays the same. The SDK refreshes only when you call this method.

Observe access changes

Read client.session.state, or subscribe with client.session.onStateChange(listener), which returns an unsubscribe function.

The state is one of these values:

StateMeaning
signed_outThe client holds no session.
authenticatedThe client holds a session.
customer_access_revokedThe gateway refused the customer with a 401 because the customer’s access was revoked.
customer_deletedThe gateway refused the customer with a 401 because the customer was deleted.
customer_suspendedThe gateway refused the customer with a 403 because the customer is suspended.

For the last three, the client throws CustomerAccessInvalidatedError with the reason, as the subclass CustomerAccessRevokedError, CustomerDeletedError, or CustomerSuspendedError. It clears the session, ends the session’s other streams, and sets session.state to the reason. Other errors do not change the session.