> ## Documentation Index
> Fetch the complete documentation index at: https://www.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# createAuthClient

> Creates a headless auth client that mints a Dynamic user JWT via email OTP or SIWE for use with authenticateJwt

## Function Signature

```typescript theme={"system"}
createAuthClient(options: {
  environmentId: string;
  baseUrl?: string;
  fetchApi?: FetchAPI;
  appOrigin?: string;
}): DynamicAuthClient
```

Exported from `@dynamic-labs-wallet/node`.

## Description

Creates a headless auth client — the server-side counterpart to the JS client SDK's sign-in flows. It mints a Dynamic user JWT via email OTP or Sign-In With Ethereum (SIWE) so a server-side agent can obtain a JWT without a browser, then hand it to [authenticateJwt](/node/reference/auth/authenticate-jwt).

The client holds configuration only — no secrets, no persistence. Sign-in prompting (collecting the OTP code, getting the sign-in message signed) and storage of the resulting JWT stay with you.

## Parameters

### Required Parameters

* **`environmentId`** (`string`) - Your Dynamic environment ID.

### Optional Parameters

* **`appOrigin`** (`string`) - The origin SIWE sign-ins present as (for example, `https://yourapp.example.com`). It must be on the environment's allowlisted CORS origins. `wallet.signIn` derives its `domain` and `uri` from it; per-call params override. Throws `"createAuthClient appOrigin is not a valid URL"` at sign-in time if it does not parse as a URL.
* **`baseUrl`** (`string`) - Override the Dynamic API base URL. Defaults to the production API.
* **`fetchApi`** (`FetchAPI`) - Custom fetch implementation (tests, proxies).

## Returns

* **`DynamicAuthClient`** - An object with two sign-in flows:

```typescript theme={"system"}
interface DynamicAuthClient {
  email: {
    sendOtp(email: string): Promise<{ verificationUUID: string }>;
    verifyOtp(params: {
      verificationUUID: string;
      code: string;
      sessionPublicKey?: string;
    }): Promise<AuthResult>;
  };
  wallet: {
    signIn(params: {
      address: string;
      signMessage: (message: string) => Promise<string>;
      sessionPublicKey?: string;
      domain?: string;
      uri?: string;
      chainId?: number;
      statement?: string;
      walletName?: string;
      walletProvider?: WalletProviderEnum;
    }): Promise<AuthResult>;
    getNonce(): Promise<{ nonce: string }>;
    createMessage(params: {
      domain: string;
      address: string;
      uri: string;
      chainId: number;
      nonce: string;
      statement?: string;
      issuedAt?: string;
    }): string;
    verify(params: {
      message: string;
      signature: string;
      walletAddress: string;
      sessionPublicKey?: string;
      walletName?: string;
      walletProvider?: WalletProviderEnum;
      chain?: ChainEnum;
    }): Promise<AuthResult>;
  };
}
```

Each sign-in resolves to an `AuthResult`:

```typescript theme={"system"}
interface AuthResult {
  jwt: string; // Pass to authenticateJwt. Custody is yours; never log it.
  expiresAt: number;
  userId?: string;
}
```

See [Email OTP sign-in](/node/reference/auth/email-otp-sign-in) and [Wallet sign-in](/node/reference/auth/wallet-sign-in) for per-method parameters, returns, and errors.

## Example

```typescript theme={"system"}
import { createAuthClient } from '@dynamic-labs-wallet/node';

const auth = createAuthClient({
  environmentId: 'your-environment-id',
  appOrigin: 'https://yourapp.example.com',
});

const { verificationUUID } = await auth.email.sendOtp('user@example.com');
const { jwt } = await auth.email.verifyOtp({
  verificationUUID,
  code: otpCodeFromUser,
  sessionPublicKey: publicKeyHex,
});
```

## Error Handling

All methods throw `Error` with a descriptive message:

* **401 responses** → `"<operation> failed: the code or signature is invalid or expired"`.
* **Other HTTP errors** → the server's error message and status code (codes and messages only — never tokens).
* **MFA-required accounts** → `"account requires MFA, which the headless auth client does not support"`.
* **Cookie-auth environments** → sign-in responses carry no JWT in the body; the client throws explaining that headless clients cannot consume cookie auth.
* **`wallet.signIn` input validation** → thrown before any network call; see [Wallet sign-in](/node/reference/auth/wallet-sign-in).

## Related Functions

* [`authenticateJwt()`](/node/reference/auth/authenticate-jwt) - Use the minted JWT
* [`generateSessionKeyPair()`](/node/reference/auth/generate-session-key-pair) - Generate the session key to bind at sign-in
