Skip to content

Credential cascade

IdentitySDKgetCredentialgetActivityIdentityMissingCredentialErrorlt_oauth_tokens

An activity asks for a provider credential by name and gets the principal's, then the initiator's, then the system's, with the source recorded.

The situation

A workflow running as a service account needs to read the requesting person’s calendar, and also call a model with the company key. The activity should not know where each credential lives.

The recipe

activities/schedule.ts
import { getActivityIdentity } from '@hotmeshio/long-tail';
export async function proposeSlots(input: { orderId: string }) {
const identity = getActivityIdentity();
if (!identity) throw new Error('getActivityIdentity runs inside an activity');
const google = await identity.getCredential('google');
const anthropic = await identity.getCredential('anthropic');
// ...call both APIs
}

Each call resolves through the same cascade:

  1. The executing principal’s stored credential (lt_oauth_tokens, refreshed automatically when expired).
  2. The initiating person’s credential, when execute_as is active and the principal lacks one.
  3. A well-known environment variable such as ANTHROPIC_API_KEY.

The guarantee

When nothing in the cascade resolves, getCredential() throws MissingCredentialError. The interceptor catches it and opens an escalation with category: 'missing_credential', so a person can supply the value through a masked field and the run continues instead of failing. The credential source (user, bot or system) is tracked for billing and audit. OAuth tokens are encrypted at rest with AES-256-GCM. See Identity.