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

# Auth Component

> A drop-in React component for secure credential capture, source selection, and authentication.

<video controls playsInline preload="none" poster="/images/auth-component-video-poster.jpg" src="https://mintcdn.com/decksoftwareinc/03XZIojHijunkB0_/images/auth-component-video.mp4?fit=max&auto=format&n=03XZIojHijunkB0_&q=85&s=b436bbc0eba811e092baf10ab7927702" />

The Auth Component is a pre-built React component that collects end-user credentials, stores them in the [Credential Vault](https://docs.deck.co/platform/credential-management.md), and creates a [Credential](https://docs.deck.co/concepts/credentials.md). Credentials never touch your systems.

It handles source selection, username/password capture, [interactions](https://docs.deck.co/guides/interactions.md) (MFA, security questions), and error states out of the box. To show a run you've already started, such as one from a trigger, use the [Live View Component](https://docs.deck.co/components/live-view.md).

> [!TIP]
>
> Want full customization and control over the UX? See [Building your auth flow](https://docs.deck.co/guides/building-auth-ui.md) to build your own using the API directly.

## How it works

```mermaid theme={null}
%%{init: {'theme': 'base', 'themeVariables': {'darkMode': true, 'background': '#0D100F', 'primaryColor': '#1B1E1D', 'primaryTextColor': '#F6F6F6', 'primaryBorderColor': '#323534', 'lineColor': '#60917E', 'actorBkg': '#1B1E1D', 'actorBorder': '#323534', 'actorTextColor': '#F6F6F6', 'actorLineColor': '#323534', 'signalColor': '#60917E', 'signalTextColor': '#F6F6F6'}}}%%
sequenceDiagram
    participant Backend as Your Server
    participant Frontend as Your Frontend
    participant Component as Auth Component
    participant API as Deck API
    participant Vault as Credential Vault

    Backend->>API: POST /v2/token
    API-->>Backend: session token
    Backend-->>Frontend: pass token
    Frontend->>Component: Mount <DeckAuthComponent />
    Component->>Component: User selects source & enters credentials
    Component->>API: Credentials (encrypted)
    API->>Vault: Encrypt & store
    API-->>Component: Credential created
    Component-->>Frontend: onSuccess({ credentialId })
```

1. **Your server** creates a session token via `POST /v2/token`
2. **Your frontend** mounts `<DeckAuthComponent />` with the token and all configuration
3. **The component** renders a secure UI (hosted at `auth.components.deck.co`) where the user selects a source and enters credentials
4. **Credentials** go from the component directly to the Deck API and into the Credential Vault. They never touch your page
5. **On success**, the component returns the new Credential ID via `onSuccess`

## Restrictions

* Collects credentials via `username_password` (default), `source_fields`, `google_sso` or `apple_sso` (opt in per source through [`sourceConfig`](#source-fields)). No other auth method is supported.

## Installation

```bash theme={null}
npm install @deckco/auth
```

```tsx theme={null}
import { DeckAuthComponent } from '@deckco/auth';
```

React 18+ is a peer dependency.

## Quick example

Create a session token on your server:

```typescript Node theme={null}
const response = await fetch('https://api.deck.co/v2/token', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.DECK_SECRET_KEY}`,
  },
});

const { token } = await response.json();
```

```bash curl theme={null}
curl -X POST https://api.deck.co/v2/token \
  -H "Authorization: Bearer $DECK_SECRET_KEY"
```

```python Python theme={null}
import os, requests

response = requests.post(
    "https://api.deck.co/v2/token",
    headers={"Authorization": f"Bearer {os.environ['DECK_SECRET_KEY']}"},
)
token = response.json()["token"]
```

Mount the component in your React app:

```tsx Client theme={null}
<DeckAuthComponent
  token={token}
  sourceId="src_abc123"
  externalId="user_12345"
  taskId="task_x9y8z7..."
  onSuccess={({ credentialId, externalId, taskRunId, verified }) => {
    console.log('Credential created:', credentialId);
  }}
  onError={({ type, code, message }) => {
    console.error('Auth failed:', code, message);
  }}
  onClose={() => {
    // User dismissed the component. Unmount, navigate, etc.
  }}
/>
```

## Session token

Every auth component session starts with a server-side token. The token is authentication only. It proves the request came from your backend. All configuration (sourceId, appearance, etc.) is passed on the component, not the token.

### `POST /v2/token`

Creates a session token for the Auth Component.

**Authentication:** `Authorization: Bearer sk_live_...`

**Request body:** None

**Response (201):**

```json theme={null}
{
  "id": "tok_abc123...",
  "object": "token",
  "token": "tk_abc123...",
  "expires_at": "2026-04-10T12:30:00Z",
  "created_at": "2026-04-10T12:00:00Z",
  "request_id": "req_xyz789"
}
```

| Field | Type | Description |
| - | - | - |
| `id` | string | Token identifier, prefixed `tok_` |
| `object` | string | Always `token` |
| `token` | string | The token value to pass to the component, prefixed `tk_` |
| `expires_at` | ISO 8601 | 30 minutes from creation. This is the expiry if the token is never used. See [Token lifecycle](#token-lifecycle). |
| `created_at` | ISO 8601 | Creation timestamp |
| `request_id` | string | Request ID for support |

### Token lifecycle

* Tokens expire **30 minutes** after their last use. Each request the component makes extends the expiry by 30 minutes, up to **24 hours** after creation
* Expired tokens return a clear error to `onError`

## Component props

| Prop | Type | Description |
| - | - | - |
| `token` | `string` | **Required.** Session token from `POST /v2/token`. |
| `mode` | `"auth" \| "interaction"` | `"auth"` (default) runs the full flow. `"interaction"` skips source selection and credential capture and runs a task or workflow directly. See [Interaction mode](#interaction-mode). |
| `sourceId` | `string \| string[]` | Pre-select a single source (skips picker) or filter to multiple. |
| `sourceConfig` | `Record<string, SourceConfig>` | Per-source [source fields](#source-fields) and [interaction text](#interaction-text), keyed by source ID (or `"*"` as a default). |
| `credentialId` | `string` | If provided, enters [Update Mode](#update-mode). Re-collects credentials for an existing credential. |
| `externalId` | `string` | Your user ID. Passed back in `onSuccess` and persisted on the credential. |
| `taskId` | `string` | If provided, fires a verification [task run](#task-linking) after credential creation/update. |
| `taskInput` | `TaskInputConfig` | Supplies or customizes the [task input](#task-input) for the run fired by `taskId`. Requires `taskId`. |
| `workflowId` | `string` | Starts a [workflow](#workflows) after credential creation/update. Can't be combined with `taskId`. |
| `closeAfterStep` | `string` | The workflow step the end user waits for, or `"*"` for the whole run. Defaults to the first step. See [When the component closes](#when-the-component-closes). |
| `showStepProgress` | `boolean` | Shows "Step 2 of 3" while a workflow runs. Defaults to `false`. |
| `workflowInput` | `WorkflowInputConfig` | Supplies or customizes [workflow input](#workflow-input) per step. Requires `workflowId`. |
| `appearance` | `AppearanceConfig` | Full theming control. See [Appearance](#appearance). |
| `language` | `"en-US" \| "fr-CA"` | Language for all component-rendered copy. Defaults to `"en-US"`. See [Language](#language). |
| `taskRunView` | `"status" \| "live"` | How the task run is displayed while executing. Defaults to `"status"`. |
| `onSuccess` | `(data) => void` | **Required.** Called when credential creation/update succeeds. |
| `onCancel` | `(data) => void` | Called when the flow is canceled. See [`onCancel`](#oncancel). |
| `onError` | `(data) => void` | Called on failure. See [Error handling](#error-handling). |
| `onClose` | `() => void` | Called when the user dismisses the flow — the close (X) icon, Escape, or a Cancel/Close button. Its presence also shows the X unless `showCloseButton` overrides it. |
| `showCloseButton` | `boolean` | Show or hide the persistent close (X) icon, independently of `onClose`. Defaults to `onClose != null`. Pass `onClose` together with `showCloseButton={false}` to hide the X while keeping the Cancel/Close buttons working. |
| `credentialPersistence` | `boolean` | Opts the created credential into [credential persistence](https://docs.deck.co/concepts/credentials.md#credential-persistence). Defaults to `false`. Applies to credential creation only: in [Update Mode](#update-mode) it is ignored, so an existing credential's persistence setting is never changed. |

The component does not own its lifecycle. Your app does. When `onClose` fires, you decide what happens (unmount, navigate, show a confirmation, etc.).

## Callbacks

### `onSuccess`

```typescript theme={null}
onSuccess: (data: {
  credentialId: string;
  externalId: string | null;
  taskRunId: string | null;
  workflowRunId: string | null;
  verified: boolean | null;
  sessionId: string | null;
}) => void;
```

| Field | Type | Description |
| - | - | - |
| `credentialId` | `string` | The created or updated credential |
| `externalId` | `string \| null` | Your external ID, if provided |
| `taskRunId` | `string \| null` | Verification task run ID. `null` if no `taskId` was passed |
| `workflowRunId` | `string \| null` | Workflow run ID. `null` if no `workflowId` was passed |
| `verified` | `boolean \| null` | `true` if run succeeded, `false` if run failed, `null` if no `taskId` or `workflowId` was passed. For a workflow, reflects the [first step](#when-the-component-closes) |
| `sessionId` | `string \| null` | Session ID of the verification run. `null` if no `taskId` or `workflowId` was passed |

### `onCancel`

```typescript theme={null}
onCancel: (data: {
  credentialId: string | null;
  taskRunId: string | null;
  workflowRunId: string | null;
}) => void;
```

Fires when a verification task run or workflow run is canceled out-of-band (for example, you call the [cancel endpoint](https://docs.deck.co/api-reference/task-runs/cancel-a-task-run.md) while the run is in progress). This is not user dismissal of the component, which is [`onClose`](#onclose).

### `onError`

```typescript theme={null}
onError: (data: {
  type: string;
  code: string;
  message: string;
}) => void;
```

`type` is the error category. `code` is the machine-readable identifier. `message` is human-readable and may change. Switch on `type` and `code`, not `message`. See [Error handling](#error-handling) for all error codes.

### `onClose`

```typescript theme={null}
onClose: () => void;
```

`onClose` fires when the user dismisses the flow — via the persistent close (X) icon, the Escape key, or a Cancel/Close button on the canceled or error screen. You handle the teardown.

By default the X is shown whenever `onClose` is provided and hidden when it is omitted. To control the two independently — for example, to embed the component in your own modal that already has a close control — pass [`showCloseButton`](#showclosebutton):

```tsx theme={null}
// Hide the persistent X, but keep the Cancel/Close buttons working:
<DeckAuthComponent token={token} onClose={handleClose} showCloseButton={false} />
```

Omitting `onClose` entirely hides the X *and* leaves the Cancel/Close buttons with no handler, so prefer `showCloseButton={false}` when you still want those buttons to work.

If a task run is in progress when the user closes the component, the task run continues running in the background. To cancel it, use the [cancel endpoint](https://docs.deck.co/api-reference/task-runs/cancel-a-task-run.md) from your `onClose` handler.

### `showCloseButton`

```typescript theme={null}
showCloseButton?: boolean;
```

Controls whether the persistent close (X) icon is rendered, independently of `onClose`. Defaults to `onClose != null`, so existing integrations are unaffected. Pass `onClose` together with `showCloseButton={false}` to hide the X while keeping the Cancel/Close buttons (which share the same close handler) functional.

## Source selection

The `sourceId` prop controls what the user sees before credential capture.

| `sourceId` | Behavior |
| - | - |
| Not provided | Full source search. User searches across all sources in your organization. |
| Single ID (`"src_abc123"`) | Skips source selection. Goes straight to credential capture. |
| Array (`["src_abc", "src_def"]`) | Filtered selection. User picks from the provided sources in your organization. |

## Source fields

Some sources need values beyond username/password: an account number, a company ID, etc. The `sourceConfig` prop attaches **source fields** to a source's credential form, keyed by source ID (or `"*"` as a default for any source without its own entry). By default these fields are collected alongside username/password, but a source can also authenticate on source fields alone, or offer Google or Apple sign-in (see [Auth methods](#auth-methods)). Collected values are stored on the credential under [`auth_credentials.source_fields`](https://docs.deck.co/concepts/credentials#source-fields); fields marked `tokenized` are [vaulted](https://docs.deck.co/concepts/credentials.md#tokenizing-source-fields).

Only sources that need custom fields get an entry. Every other source renders the standard username/password form.

```tsx theme={null}
<DeckAuthComponent
  token={token}
  sourceId="src_toronto_hydro"
  language="en-US"
  sourceConfig={{
    src_toronto_hydro: {
      fields: {
        account_number: {
          label: { 'en-US': 'Account number', 'fr-CA': 'Numéro de compte' },
          placeholder: 'On your bill, e.g. 100 123 456 7',
        },
        account_type: {
          label: 'Account type',
          type: 'select',
          options: [
            { value: 'residential', label: 'Residential' },
            { value: 'commercial', label: 'Commercial' },
          ],
        },
        customer_number: { label: 'Customer number', tokenized: true },
        // Plain string = silent pass-through: stored, never shown to the user.
        integration_source: 'partner_x',
      },
    },
  }}
  onSuccess={handleSuccess}
/>
```

Each field is either a **config object** (presented to the user) or a **plain string** (a silent pass-through: stored on the credential, never shown).

### Entry config

Each entry in `sourceConfig` is keyed by source ID (or `"*"`) and takes:

| Key | Type | Description |
| - | - | - |
| `fields` | `Record<string, FieldConfig \| string>` | The source fields to collect. Required when `authMethods` includes `"source_fields"`. See [Field config](#field-config). |
| `authMethods` | `AuthMethod[]` | The methods the source offers, in display order. Defaults to `["username_password"]`. See [Auth methods](#auth-methods). |
| `authMethod` | `AuthMethod` | **Deprecated.** Read as a one-element `authMethods`. See [Auth methods](#auth-methods). |
| `interactionText` | `Record<string, InteractionPromptConfig>` | Text shown to the end user on the source's interaction steps. See [Interaction text](#interaction-text). |

### Auth methods

`authMethods` lists the methods a source offers, in display order. The end user picks one, and the credential is created with whichever they chose.

| Value | What the user enters |
| - | - |
| `"username_password"` | Default. The standard username and password for the source. |
| `"source_fields"` | Only the entry's [fields](#field-config). No username or password inputs appear; the source authenticates on the field values alone. |
| `"google_sso"` | Their **Google Account** email and password, for a source they sign in to with Google. |
| `"apple_sso"` | Their **Apple Account** email and password, for a source they sign in to with Apple. |

The non-SSO method renders as the form itself; each SSO method renders as a provider button beneath it. Picking a provider swaps the form to that provider's fields. A source may declare at most one non-SSO method, since two cannot both be the form.

| `authMethods` | What the user sees |
| - | - |
| `["username_password"]` | The username and password form. No buttons. |
| `["username_password", "google_sso"]` | The form, then a Google button. |
| `["google_sso"]` | Google's fields directly. No buttons; there is nothing to choose. |
| `["google_sso", "apple_sso"]` | Two buttons and no form until the user picks one. |

Source fields are collected alongside whichever method the user picks.

```tsx theme={null}
<DeckAuthComponent
  token={token}
  sourceId="src_acme_portal"
  sourceConfig={{
    src_acme_portal: {
      authMethods: ['username_password', 'google_sso'],
      fields: {
        account_number: { label: 'Account number' },
      },
    },
  }}
  onSuccess={handleSuccess}
/>
```

#### The SSO methods

`google_sso` and `apple_sso` are **not** a redirect into Google or Apple. The end user types their provider-account credentials into the component, exactly as they would a username and password; the agent then clicks the source's "Sign in with Google" or "Sign in with Apple" button and signs in on the provider's page on their behalf.

The form names the provider in its heading, its placeholders and its submit button, so the end user can tell this is their Google or Apple login and not the source's own. That wording is fixed and not configurable.

Both submit `auth_credentials: { email, password }`.

#### Validation

A `source_fields` entry must declare at least one field; one with none fails fast at mount with [`source_fields_auth_requires_fields`](#validation-errors). An unknown method fails with [`auth_method_invalid`](#validation-errors), and a malformed `authMethods` (not an array, empty, repeating a method, or declaring more than one non-SSO method) fails with [`auth_methods_invalid`](#validation-errors). If the source doesn't support the selected method, the flow fails at runtime with [`auth_method_not_supported`](#runtime-errors).

Reserved source-field keys follow the methods an entry declares: `email` and `password` once any SSO method is offered, `username` and `password` once `username_password` is (including by default), and `source_fields` always. `source_fields` as a method reserves nothing else.

#### `authMethod` (deprecated)

The older singular `authMethod` still works, read as a one-element `authMethods`. Using it logs a console warning naming the sources still on it, and it will be removed in a major version. Setting both keys on one entry is an error.

```diff theme={null}
- sourceConfig={{ src_acme_portal: { authMethod: 'google_sso' } }}
+ sourceConfig={{ src_acme_portal: { authMethods: ['google_sso'] } }}
```

### Field config

Every display string (`label`, `placeholder`, and each option `label`) is a `LocalizedString`: either a plain string, rendered as-is in every locale, or a `{ "en-US": string; "fr-CA"?: string }` map picked by [`language`](#language), falling back to `"en-US"`. The two forms can be mixed within one config.

| Key | Type | Description |
| - | - | - |
| `label` | `LocalizedString` | **Required.** Rendered exactly as provided; keys are never converted to labels. |
| `type` | `"string" \| "select" \| "boolean"` | Defaults to `"string"`. `string` → text input, `select` → dropdown, `boolean` → checkbox. |
| `options` | `Array<{ value: string; label: LocalizedString }>` | `select` only. The chosen `value` is stored. |
| `placeholder` | `LocalizedString` | `string` only. |
| `value` | `string \| boolean` | Prefill; the user can edit it. |
| `required` | `boolean` | Defaults to `true`. |
| `tokenized` | `boolean` | `string` only. Masks the input and vaults the value (never returned on read). |
| `validation` | `FieldValidation` | `string` only. Format constraints checked in the form before submit. See [Field validation](#field-validation). |

### Field validation

A `string` field can declare format constraints via `validation`. The component checks them in the form (on blur, then live as the user edits) and keeps the submit button disabled while a value violates them. The check is client-side, so it narrows what the user can submit but does not replace the source's own validation.

| Key | Type | Description |
| - | - | - |
| `pattern` | `string` | Regex (JS syntax, no flags) the whole value must match. Anchoring is applied for you, so `"\\d{9}"` accepts exactly nine digits. |
| `minLength` | `number` | Minimum value length. |
| `maxLength` | `number` | Maximum value length. |
| `min` | `number` | Inclusive minimum numeric **value** (not length). Checked on values that parse as a number; others are skipped. |
| `max` | `number` | Inclusive maximum numeric value. |
| `message` | `LocalizedString` | Shown under the field on a `pattern` violation. |

The keys combine: `{ pattern: '\\d+', max: 9 }` is a digits-only field capped at 9.

```tsx theme={null}
<DeckAuthComponent
  token={token}
  sourceId="src_toronto_hydro"
  sourceConfig={{
    src_toronto_hydro: {
      fields: {
        account_number: {
          label: 'Account number',
          validation: {
            pattern: '\\d{10}',
            message: {
              'en-US': 'Enter the 10 digits, no spaces',
              'fr-CA': 'Entrez les 10 chiffres, sans espaces',
            },
          },
        },
      },
    },
  }}
  onSuccess={handleSuccess}
/>
```

Length and value bounds show a built-in message naming the bound ("Must be at least 6 characters"), localized by [`language`](#language). A `pattern` violation shows your `message` instead, since only you know the format the source expects, so the component supplies no default. With no `message`, a pattern violation still marks the field invalid and blocks submit, it just renders no text.

* **Empty values** are never format-checked. `required` covers those, and an optional field left blank is omitted from the submission.
* **Prefills** (`value`, or a value restored on [reconnect](#update-mode)) are checked at mount, so an invalid prefill explains the blocked submit without waiting for a blur.
* **Malformed constraints** fail fast at mount with [`source_field_config_invalid`](#validation-errors): a `pattern` that isn't a valid regex, a negative or non-integer length bound, `minLength` above `maxLength`, `min` above `max`, or `validation` on a type other than `"string"`.

### Behavior

* **Scoping** is per source: the entry matching the selected source applies, and `"*"` covers any source without its own entry.
* **Stored values** are always strings: selects store the option `value`, booleans store `"true"` / `"false"`. Option values and stored values are data and are never localized.
* **Reconnect** ([Update Mode](#update-mode)) prefills non-tokenized fields from the stored credential and keeps them editable. Tokenized fields render empty (their values are never returned) and are treated as optional even when `required`: leave one blank to keep the stored value, or re-enter it to change it, so users aren't forced to re-enter vaulted values.

> [!NOTE]
>
> Invalid `sourceConfig` fails fast: the component fires `onError` at mount with a `source_field_*` code rather than erroring at credential creation. See [Error handling](#error-handling).

## Update mode

Pass a `credentialId` to re-collect credentials for an existing credential instead of creating a new one.

| `credentialId` | `sourceId` | Behavior |
| - | - | - |
| absent | absent | Create mode. Full source search. |
| absent | present | Create mode. Skip/filter source selection. |
| present | ignored | Update mode. Skip source selection, patch existing credential. |

A credential's auth method is fixed when it is created, so Update mode renders the method the credential already uses and never shows the [provider picker](#auth-methods), whatever `authMethods` lists.

```tsx theme={null}
<DeckAuthComponent
  token={token}
  credentialId="cred_existing_456"
  externalId="user_12345"
  onSuccess={({ credentialId, externalId }) => {
    console.log('Credentials updated for:', credentialId);
  }}
/>
```

## Task linking

Pass a `taskId` to verify credentials immediately after creation. The component fires a [task run](https://docs.deck.co/concepts/task-runs.md) using the new credential, and the task run view stays visible while it executes.

If the task declares fields in its `input_schema`, the component collects them from the user (or from you, silently) before firing the run. See [Task input](#task-input).

When the user picks a source, the component [prepares a session](https://docs.deck.co/concepts/sessions.md#preparing-a-session) for it, so the agent is already on the login page when the credentials arrive. The run uses that session. If the user goes back and picks a different source, the component ends the session and prepares a new one. If the component closes before the run starts, it ends the session. To do this, the session token can start sessions and end the ones it started. It can't list or end any other session in your organization.

| `taskId` | Auth outcome | Run outcome | Credential status | Callback |
| - | - | - | - | - |
| absent | n/a | n/a | `unverified` | `onSuccess` with `{ ..., taskRunId: null, verified: null }` |
| present | succeeds | run succeeds | `verified` | `onSuccess` with `{ ..., taskRunId, verified: true }` |
| present | succeeds | run fails (no error) | `verified` | `onSuccess` with `{ ..., taskRunId, verified: false }` |
| present | succeeds | run fails (with error) | `verified` | `onError` with the run's error |
| present | fails | n/a | `invalid` | `onError` with `auth_invalid` |

Credential status reflects whether the agent could authenticate, not whether the run finished successfully. As long as auth succeeds, the credential becomes `verified` even if the run later fails. A failed run does **not** delete or invalidate the credential. If auth itself fails, the credential becomes `invalid`.

> [!TIP]
>
> Do not rely on `onSuccess` with `verified: false` to detect a failed verification. When the run fails with an error (including an authentication failure, `auth_invalid`), the component fires `onError`, not `onSuccess`. `onSuccess` with `verified: false` only occurs for a run that failed without returning an error. Handle verification failures in both `onError` and the `verified` field of `onSuccess`.

> [!WARNING]
>
> The task you pass as `taskId` must actually authenticate with the source. A task that only visits a public page will not challenge the credential, and running it will not move the credential from `unverified` to `verified`. Pick (or create) a task whose first step is logging in.

For a task built only to sign in and confirm the credential, see [Verify credentials](https://docs.deck.co/guides/credential-verification.md).

### `taskRunView`

With `taskId` present, the task run view is shown while the run executes:

* `"status"`: spinner with status text (e.g., "Connecting...", "Verifying credentials...")
* `"live"`: live view of the task run

Without `taskId`, no task run view is shown.

## Task input

Some tasks declare input in their [`input_schema`](https://docs.deck.co/concepts/tasks#input-and-output-schemas): a date range, an account number, a file to upload. The component supports chaining to these tasks in both auth mode and [interaction mode](#interaction-mode).

Whenever a `taskId` is provided, the component fetches the task's `input_schema` at mount and presents its fields to the end user in a **task details** step: after credential submit in auth mode, before the run in interaction mode. The heading of the step is a generic "Additional details" (the end user doesn't know a task is involved). The step is skipped only when the schema declares no fields, or every field is supplied silently.

The optional `taskInput` prop refines that presentation. The schema stays the source of truth for structure: field types, required fields, enum values, and [tokenization](https://docs.deck.co/concepts/tasks.md#tokenizing-sensitive-fields) all come from the schema. `taskInput` supplies what the schema can't: values you already know (passed silently, nothing rendered), localized labels and placeholders, prefills, and the step heading.

```tsx theme={null}
<DeckAuthComponent
  token={token}
  sourceId="src_toronto_hydro"
  taskId="task_x9y8z7..."
  taskInput={{
    title: { 'en-US': 'Billing details', 'fr-CA': 'Détails de facturation' },
    fields: {
      // Raw scalar = silent pass-through: merged into the run's input, never shown.
      account_id: 'acct_8842',
      // Config object = presented to the user, with your labels.
      statement_period: {
        label: { 'en-US': 'Statement period', 'fr-CA': 'Période du relevé' },
        options: [
          { value: 'last_30_days', label: 'Last 30 days' },
          { value: 'year_to_date', label: 'Year to date' },
        ],
      },
      // Nested object fields are keyed by dot path.
      'delivery.email': { label: 'Email for delivery', value: 'user@example.com' },
      // hidden: true silently passes a value of any JSON shape.
      metadata: { hidden: true, value: { partner: 'partner_x' } },
    },
  }}
  onSuccess={handleSuccess}
/>
```

`taskInput` takes:

| Key | Type | Description |
| - | - | - |
| `title` | `LocalizedString` | Heading of the task details step. Defaults to "Additional details". |
| `fields` | `Record<string, string \| number \| boolean \| TaskInputFieldConfig>` | Per-field map, keyed by `input_schema` property name (dot path for nested fields, e.g. `"delivery.email"`). |

Each `fields` entry is either a **raw scalar** (a silent pass-through: merged into the run's `input`, never shown) or a **config object** (presented to the user unless `hidden`), the same pattern as [`sourceConfig`](#source-fields). Schema fields with no entry are presented with the schema property name as their label. `taskInput` requires a `taskId`; without one the component fires `onError` with `task_input_requires_task` at mount.

### Task input field config

| Key | Type | Description |
| - | - | - |
| `hidden` | `boolean` | `true` passes `value` silently and renders nothing. Requires `value`; cannot be combined with the display keys below. Use it to silently supply non-scalar values (objects, arrays, files). |
| `value` | `unknown` | With `hidden: true`: the passed value (any JSON shape). Presented: a prefill the user can edit. |
| `label` | `LocalizedString` | Rendered verbatim. Defaults to the schema property name. |
| `placeholder` | `LocalizedString` | String fields only. |
| `options` | `Array<{ value: string; label: LocalizedString }>` | Enum fields only: localized labels for the schema-defined values. Unlike source fields, the values themselves come from the schema's `enum`. |
| `validation` | `FieldValidation` | Schema `string` and `number` fields only. Format constraints checked before the run fires. Same keys and behavior as [source fields](#field-validation). |

Display strings (`title`, `label`, `placeholder`, option labels) are the same `LocalizedString` type as [source fields](#field-config): a plain string, or a `{ "en-US": string; "fr-CA"?: string }` map picked by [`language`](#language).

Unlike `sourceConfig`, there is no `type`, `required`, or `tokenized` key: those come from the schema. `validation` is yours to declare, because the schema states a field's type and required-ness but not its format:

```tsx theme={null}
<DeckAuthComponent
  token={token}
  taskId="task_x9y8z7..."
  taskInput={{
    fields: {
      account_number: {
        label: 'Account number',
        validation: { pattern: '\\d{10}', message: 'Enter the 10 digits' },
      },
      // Schema `number` field: value bounds, not lengths.
      months: { label: 'Months of history', validation: { min: 1, max: 24 } },
    },
  }}
  onSuccess={handleSuccess}
/>
```

The keys and in-form behavior are the same as [source field validation](#field-validation): `pattern` and the lengths check the typed text, `min`/`max` check it as a number. A malformed block fails fast at mount with [`task_input_config_invalid`](#validation-errors); set on a schema kind other than `string` or `number`, `validation` fires [`task_input_invalid`](#runtime-errors) at mount rather than being silently ignored. A value passed silently with `hidden: true` never reaches the form, so its constraints are checked once at mount instead.

### Field rendering

The schema property's type drives the input control:

| Schema type | Renders as |
| - | - |
| `string` | Text input |
| `number` / `integer` | Number input |
| `boolean` | Checkbox |
| `enum` | Dropdown; option values come from the schema |
| `object` | Grouped sub-fields, each configurable by dot path (`"delivery.email"`) |
| `array` | Repeatable rows |
| [File input](https://docs.deck.co/concepts/tasks.md#accepting-file-inputs) | File picker. The component base64-encodes the file and enforces a 30 MB limit client-side. |

Fields the schema marks `required` must be filled before the user can continue, and any field carrying a [`validation`](#field-validation) block must satisfy it; presented optional fields left empty are omitted from the run's `input`. Fields in the schema's `tokenized` array render masked and are handled like credentials: never logged and never put in URL parameters.

Bad task input configuration fails fast at mount, before a credential is created. See [Error handling](#error-handling).

## Workflows

Pass a `workflowId` instead of a `taskId` to start a [workflow](https://docs.deck.co/guides/workflows.md) after credential creation/update. Requires `@deckco/auth` 3.0.0 or later.

```tsx theme={null}
<DeckAuthComponent
  token={token}
  sourceId="src_hilton"
  workflowId="wflo_a1b2c3..."
  closeAfterStep="check_price"
  onSuccess={({ credentialId, workflowRunId, verified }) => {
    // check_price finished. Later steps keep running.
  }}
  onError={handleError}
/>
```

The component reads the workflow and every step's task at mount, so configuration problems surface before a credential is created. After the user enters their credentials, it creates or updates the credential, binds it to the steps, starts the run, and follows it step by step. [Interactions](#interactions) on any step up to the close step reach the end user, and `taskRunView: "live"` follows whichever step is running.

As with a task, the component [prepares a session](#task-linking) when the user picks a source, and every step of the run executes in it.

`workflowId` can't be combined with `taskId` or `taskInput`. It works in auth mode, [Update Mode](#update-mode), and [interaction mode](#interaction-mode).

### When the component closes

`closeAfterStep` names the step the end user waits for. It defaults to the workflow's first step. Pass `"*"` to wait for the whole run. A [loop](https://docs.deck.co/guides/workflows.md#loops) step can be the close step, which waits for every item.

The component closes once that step finishes, fails, is canceled, or is skipped, or once the whole run ends. Steps after it keep running in the background and report only through [`workflow_run.*` and `task_run.*` events](https://docs.deck.co/events/events-reference). The component never cancels the run.

| Outcome | Callback |
| - | - |
| The close step finishes | `onSuccess` |
| A step at or before the close step fails | `onError` with the step's error. The message is prefixed with the step name. |
| A step after the close step fails | No callback. Watch events. |
| The run is canceled | `onCancel` |

An `auth_invalid` error on any step fires `onError` immediately.

If the end user closes the component before the close step finishes, `onClose` fires and the run keeps going, the same as a task run. To stop it, [cancel the workflow run](https://docs.deck.co/api-reference/workflow-runs/cancel-a-workflow-run.md) from your server.

For a workflow, `onSuccess` returns `workflowRunId` and `taskRunId: null`. `verified` always reflects the workflow's **first** step, whatever `closeAfterStep` is: `true` if it completed, `false` if it failed, and `null` if it was skipped or canceled. `sessionId` is the run's [session](https://docs.deck.co/concepts/sessions.md).

### After the component closes

Steps after the close step can still pause for an MFA code or security question. The component can't answer those. Subscribe to `task_run.interaction_required`, which carries the run's `workflow_run_id` and `step`, match `workflow_run_id` to the `workflowRunId` from `onSuccess`, and collect the answer from your end user through your own UI. See [Interactions](https://docs.deck.co/guides/interactions.md).

> [!NOTE]
>
> Step names are part of your workflow setup. `closeAfterStep` and `workflowInput.steps` refer to steps by `name`, so renaming a step in the workflow breaks a component config that names it.

### Step progress

Set `showStepProgress` to show "Step 2 of 3" on the status screen. It defaults to `false`. The count runs up to the close step and never shows step names. Inside a [loop](https://docs.deck.co/guides/workflows.md#loops), it adds the item number ("Step 2 of 3 · Item 4"). Nothing is shown when the end user waits for only one step.

### Credential binding

The component collects one credential and uses it on every step, replacing any `credential_id` in the definition, except:

* Steps whose definition sets a `source_id`. They run unauthenticated as defined.
* For a loop, the rule applies to its inner step.
* Steps pinned with `credentialId` or `sourceId` in `workflowInput.steps`.

In interaction mode without a `credentialId`, steps keep the credential or source from the definition or their `workflowInput.steps` pin, and steps with no target use the component's `sourceId`. If a step ends up with neither, the component fails at mount with `workflow_step_unresolved`.

### Workflow input

`workflowInput` supplies or customizes step input, the same way [`taskInput`](#task-input) does for a single task. Fields from every step appear on one task details step.

```tsx theme={null}
<DeckAuthComponent
  token={token}
  workflowId="wflo_a1b2c3..."
  workflowInput={{
    title: 'Booking details',
    steps: {
      check_price: {
        fields: {
          confirmation_number: { label: 'Confirmation number' },
          currency: 'USD',
        },
      },
      // Keep this step on a fixed credential instead of the user's.
      notify: { credentialId: 'cred_ops_mailbox' },
    },
  }}
  onSuccess={handleSuccess}
/>
```

| Key | Type | Description |
| - | - | - |
| `title` | `LocalizedString` | Heading of the task details step. Defaults to "Additional details". |
| `steps` | `Record<string, WorkflowInputStepConfig>` | Per-step config, keyed by step `name`. |

Each step entry takes:

| Key | Type | Description |
| - | - | - |
| `fields` | `Record<string, string \| number \| boolean \| TaskInputFieldConfig>` | Same grammar as [`taskInput.fields`](#task-input-field-config), checked against this step's task `input_schema`. |
| `credentialId` | `string` | Run this step with this credential instead of the one the user entered. Can't be combined with `sourceId`. |
| `sourceId` | `string` | Run this step with no credential, against this public source. Can't be combined with `credentialId`. |

Values are sent as each step's [run-time `input` override](https://docs.deck.co/guides/workflows#run-time-overrides), shallow-merged onto the definition. Input the workflow definition already fills, with a literal or a `{{ }}` reference, isn't presented or required. The value comes from the definition at run time. Configuring one of those fields in `workflowInput` fails at mount with `workflow_input_invalid`.

### Retry

When a workflow fails, Retry returns the end user to the credential form. On resubmit, the component updates the credential and [reruns](https://docs.deck.co/api-reference/workflow-runs/rerun-a-workflow-run.md) the failed run with the same input, so the end user doesn't re-enter step details.

### Not supported

| Not supported | Instead |
| - | - |
| Letting the end user choose between steps | Close after the step that fetches the options, read its output, show your own picker, and start a second run with the chosen values. Steps that only need an earlier step's output already work through `{{ }}` references. |
| Collecting more than one credential | The user's credential runs every step except `source_id` steps. Pin other credentials per step with `workflowInput.steps`. |
| Hiding steps that use other credentials | Steps up to the close step all show in the live view. Put them after it. |
| Tokenized task input on any step | The component refuses the workflow at mount with `workflow_step_tokenized_input`. Tokenized [source fields](#source-fields) on the credential still work. |
| Presenting a field filled from an earlier step | The value comes from the earlier step at run time. |
| Reopening the component onto an existing run | The component always starts a new run. Answer pauses on later steps [through the API](#after-the-component-closes). |
| Restricting a token to one workflow | Session tokens are scoped to your organization. A token can start any workflow in it. |

## Interaction mode

Set `mode="interaction"` to collect interaction inputs (MFA code, security question, MFA method) from the user during a task run against an existing `credentialId` or `sourceId`. The component skips source selection and credential capture, triggers the task run, and renders any pending [interaction](#interactions) in place.

If the task declares fields in its `input_schema`, the component collects them in a [task details step](#task-input) before triggering the run.

```tsx theme={null}
<DeckAuthComponent
  token={token}
  mode="interaction"
  credentialId="cred_abc123"
  taskId="task_x9y8z7..."
  taskRunView="status"
  onSuccess={({ taskRunId, verified }) => {
    // The run completed. Any interaction was submitted along the way.
  }}
  onError={({ type, code, message }) => {
    // e.g. task_id_required, credential_or_source_required
  }}
/>
```

### Required props

Interaction mode needs `taskId` or `workflowId` **plus** one of:

| Provided | Run type |
| - | - |
| `credentialId` | Authenticated run against the existing credential. |
| `sourceId` (single ID) | No-auth, source-only run. An array `sourceId` does not identify one source and does not satisfy the requirement. |

Both are validated before the component mounts:

* Omitting both `taskId` and `workflowId` fires `onError` with `task_id_required`.
* Providing neither a `credentialId` nor a single `sourceId` fires `onError` with `credential_or_source_required`.

## Appearance

Customize the component's look via the `appearance` prop.

```tsx theme={null}
<DeckAuthComponent
  token={token}
  appearance={{
    theme: 'dark',
    variables: {
      colorPrimary: '#0066FF',
      borderRadius: 8,
    },
    layout: {
      logoUrl: 'https://content.cdn.deck.co/logos/your-org/logo.png',
    },
  }}
  onSuccess={handleSuccess}
/>
```

### Theme

| Value | Description |
| - | - |
| `"default"` | Light theme with neutral styling |
| `"dark"` | Dark theme |

### Variables

| Variable | Description |
| - | - |
| `colorPrimary` | Primary button and accent color |
| `colorBackground` | Card background |
| `colorForeground` | Main text color |
| `colorPrimaryForeground` | Text on primary buttons |
| `colorNeutral` | Neutral accent |
| `colorDanger` | Error states |
| `colorSuccess` | Success states |
| `colorMuted` | Muted backgrounds (hover, etc.) |
| `colorMutedForeground` | Secondary text |
| `colorBorder` | Border color |
| `colorInput` | Input border color |
| `colorRing` | Focus ring color |
| `fontFamily` | Font stack |
| `borderRadius` | Border radius in pixels (e.g., `8`) |
| `shadow` | Shadow token for card/elevated surfaces |

### Branding

| Variable | Description |
| - | - |
| `logoUrl` | Logo shown in the card header |
| `logoPlacement` | `"top"` or `"left"` |

Upload a logo in the Deck console under **Settings → Branding**. Deck hosts it and returns a CDN URL on `content.cdn.deck.co`. Pass that as `logoUrl`. Accepted formats are PNG, JPG, JPEG, GIF, and WebP (no SVG). The logo renders into a 40×40 box and is hidden if it fails to load.

> [!NOTE]
>
> For security, the component only loads images from Deck's content CDN, `data:` URIs, and `cdn.brandfetch.io`. Arbitrary external URLs (for example, `https://your-company.com/logo.png`) are blocked by its content security policy and will not render.

## Language

Set the language for all component-rendered copy via the `language` prop.

```tsx theme={null}
<DeckAuthComponent
  token={token}
  sourceId="src_abc123"
  language="fr-CA"
  onSuccess={handleSuccess}
/>
```

| Value | Description |
| - | - |
| `"en-US"` | US English. Default. |
| `"fr-CA"` | Canadian French. |

`language` applies to copy the component owns: source picker, credential capture labels and helper text, interaction prompts (generic labels and submit/continue buttons), in-UI error messages, the task run status view (`taskRunView: "status"`), and the close button and accessibility labels.

Content returned by the source itself (source names, interaction field labels, and other source-defined strings) is not translated by the component and is rendered as the source returns it. To translate interaction steps, set [interaction text](#interaction-text).

## Interactions

Sources may require additional input mid-flow: an MFA code, a security question, an account selection. The component handles these automatically as **interactions**: a generic pause-and-prompt primitive.

When an interaction is required:

1. The component transitions to an interaction step and renders the fields from the source's response
2. The user fills in the fields and submits
3. The source validates. On accept, the flow continues
4. Some sources require multiple sequential interactions (e.g., MFA code then a security question)

The component does not enforce its own interaction timeout. Timeout windows are set by the source and handled by the backend. The user cannot go back to credential entry from an interaction step (credentials have already been created).

See [Interactions](https://docs.deck.co/guides/interactions.md) for more on how Deck models interactions.

### Interaction text

The agent writes the message and field labels on each interaction step. To replace them for a source, set `interactionText` on its [`sourceConfig`](#source-fields) entry. Requires `@deckco/auth` 3.1.0 or later.

```tsx theme={null}
sourceConfig={{
  src_toronto_hydro: {
    interactionText: {
      mfa: {
        message: {
          'en-US': 'Enter the code from your authenticator app.',
          'fr-CA': "Entrez le code de votre application d'authentification.",
        },
        fields: { '*': { label: { 'en-US': 'Access code', 'fr-CA': "Code d'accès" } } },
      },
    },
  },
}}
```

`interactionText` is keyed by interaction type (`mfa`, `captcha`, `security_question`, `account_selection`, `credentials_required`) or `"*"` for any other type. Each entry takes:

| Key | Type | Description |
| - | - | - |
| `message` | `LocalizedString` | Replaces the message. |
| `fields` | `Record<string, { label?, placeholder? }>` | Replaces field labels and placeholders, keyed by field name or `"*"`. |

Use `"*"` for `fields`: field names depend on what the source asks for on each attempt. Name fields exactly only when an interaction step has several (`method` and `code`, or `answer_0`, `answer_1`, ...). Anything you don't override keeps the agent's wording.

Overrides change wording only. The field's name, type, and options stay the same, so the agent handles the answer as it would without the override. Keep your wording consistent with the agent's.

## Error handling

All errors surface through `onError` with a uniform `{ type, code, message }` shape. `code` falls into two groups by where it comes from.

### Validation errors

Raised by the component itself before any network request, from checking the token and the prop combination. These are deterministic: a given misconfiguration always produces the same code.

| Type | Code | Description |
| - | - | - |
| `request` | `token_invalid` | Token is missing, malformed, or not an embed token (`tk_`). Also fires if the API later rejects the token as expired. |
| `request` | `task_id_required` | Interaction mode without a `taskId` or `workflowId`. |
| `request` | `credential_or_source_required` | Interaction mode without a `credentialId` or a single `sourceId`. |
| `request` | `source_field_key_invalid` | A `sourceConfig` field key doesn't match `^[a-z][a-z0-9_]{0,63}$`. |
| `request` | `source_field_key_reserved` | A `sourceConfig` field key is reserved by one of the entry's [auth methods](#auth-methods) (`email` / `password` on the SSO methods, `username` / `password` on `username_password`, and `source_fields` always). |
| `request` | `source_fields_limit_exceeded` | A source declares more than 10 source fields. |
| `request` | `source_field_config_invalid` | A `sourceConfig` field is malformed (bad `options`, `tokenized` or `validation` on a non-string, a malformed [`validation`](#field-validation) block, or a `LocalizedString` map missing its `"en-US"` entry). |
| `request` | `interaction_text_invalid` | A `sourceConfig` `interactionText` entry is malformed (not an object keyed by interaction type, a non-object `fields` or field override, or a `LocalizedString` map missing its `"en-US"` entry). See [Interaction text](#interaction-text). |
| `request` | `auth_method_invalid` | A `sourceConfig` entry lists an unknown auth method. |
| `request` | `auth_methods_invalid` | A `sourceConfig` entry's `authMethods` is not a non-empty array, repeats a method, declares more than one non-SSO method, or is set alongside the deprecated `authMethod`. |
| `request` | `source_fields_auth_requires_fields` | A `sourceConfig` entry offers `"source_fields"` but declares no fields. |
| `request` | `task_input_requires_task` | `taskInput` without a `taskId`. See [Task input](#task-input). |
| `request` | `task_input_config_invalid` | A `taskInput` entry is malformed (`hidden: true` without a `value` or combined with display keys, a malformed [`validation`](#field-validation) block, a `LocalizedString` map missing its `"en-US"` entry, or an array as a field entry). |
| `request` | `workflow_id_conflict` | Both `workflowId` and `taskId` are set. See [Workflows](#workflows). |
| `request` | `workflow_input_requires_workflow` | `workflowInput` without a `workflowId`. |
| `request` | `workflow_input_config_invalid` | `workflowInput` is malformed (not keyed by step name, a non-object step entry or `fields`, an array as a field entry, a `LocalizedString` map missing its `"en-US"` entry, or a malformed field config). |
| `request` | `workflow_step_target_conflict` | A `workflowInput.steps` entry sets both `credentialId` and `sourceId`. |

### Runtime errors

Surfaced by the Deck API and the source as the flow runs. The component forwards the API's `type`, `code`, and `message` (mapping a couple of generic not-found codes to the specific ones below). Treat this as the set you are most likely to encounter, not an exhaustive list, and handle any unrecognized `code` as a generic failure.

| Type | Code | Description |
| - | - | - |
| `auth` | `auth_invalid` | Credentials rejected by the source. |
| `auth` | `auth_method_not_supported` | Source does not support the attempted auth method. |
| `auth` | `password_reset_required` | Source is forcing a password reset. |
| `auth` | `account_locked` | Account is locked at the source. |
| `request` | `credential_not_found` | Update mode: the `credentialId` does not exist. |
| `source` | `source_not_found` | The `sourceId` does not exist. |
| `source` | `source_not_available` | Source is temporarily unreachable. |
| `source` | `source_error` | Source returned an unexpected failure. |
| `interaction` | `interaction_timeout` | Interaction window expired (source-defined). |
| `request` | `interaction_input_invalid` | Submitted input didn't match the declared field schema. |
| `request` | `task_input_invalid` | A silently supplied [task input](#task-input) value doesn't match the task's `input_schema` or violates its declared [`validation`](#field-validation), or `validation` is set on a schema field that is neither `string` nor `number`. Checked at mount, after the schema fetch. |
| `api` | `task_read_failed` | The task behind `taskId` could not be read at mount. See [Task input](#task-input). |
| `request` | `workflow_id_required` | `workflowId` was passed but empty. |
| `request` | `workflow_not_found` | The `workflowId` does not exist. |
| `request` | `workflow_step_not_found` | `closeAfterStep` or a `workflowInput.steps` key doesn't name a step in the workflow. |
| `request` | `workflow_step_unresolved` | Interaction mode without a `credentialId`: a step has no credential or source. See [Credential binding](#credential-binding). |
| `request` | `workflow_step_tokenized_input` | A step's task declares tokenized input. The workflow can't be started from the component. |
| `request` | `workflow_input_invalid` | A [workflow input](#workflow-input) value doesn't match its step's `input_schema`, or configures a field the workflow definition already fills. |
| `api` | `workflow_read_failed` | The workflow could not be read at mount. |

### Error behavior

* **Validation errors** fire before the iframe mounts. Fix the token or props and remount.
* **Task and workflow mount errors** (`task_input_invalid`, `task_read_failed`, `workflow_not_found`, `workflow_step_*`, `workflow_input_invalid`, `workflow_read_failed`) fire at mount, after the task or workflow is fetched but before any credential is created. Fix the config (or the task or workflow) and remount.
* **Token expiry** (`token_invalid`) terminates the session. Create a new token and remount.
* **Credential and source errors** (`auth_invalid`, `account_locked`, etc.) terminate the session.
* **Interaction errors** (`interaction_timeout`) terminate the session. Wrong user input is not an error; the source emits another prompt and the user retries in place.

## Embed by URL

For clients that can't run the React SDK, load the component's URL in a `WebView` (for example, in a React Native app) or an `<iframe>` and pass configuration as query parameters:

```bash theme={null}
https://auth.components.deck.co/?token=tk_abc123&sourceId=src_abc123&externalId=user_12345
```

Each parameter does what its [prop](#component-props) does, and the flow is otherwise the same as the React component.

### URL parameters

| Parameter | Maps to prop | Notes |
| - | - | - |
| `token` | `token` | **Required.** Without it the component stays idle. |
| `mode` | `mode` | `auth` (default) or `interaction`. `interaction` also needs `taskId` plus a `credentialId` or `sourceId`. See [Interaction mode](#interaction-mode). |
| `sourceId` | `sourceId` | Pass multiple by repeating the param (`?sourceId=a&sourceId=b`) or comma-separating (`?sourceId=a,b`). |
| `sourceConfig` | `sourceConfig` | URL-encoded JSON. Subject to URL-length limits; for anything beyond a few small fields, send it over [postMessage](#configuring-via-postmessage) instead. |
| `credentialId` | `credentialId` | Enters [Update Mode](#update-mode). `sourceId` is ignored when set. |
| `externalId` | `externalId` | Echoed back in the result. |
| `taskId` | `taskId` | Fires a verification [task run](#task-linking) after capture. |
| `workflowId` | `workflowId` | Starts a [workflow](#workflows) after capture. |
| `closeAfterStep` | `closeAfterStep` | A step name, or `*` for the whole run. |
| `showStepProgress` | `showStepProgress` | `true` shows step progress. Anything else leaves it off. |
| `workflowInput` | `workflowInput` | URL-encoded JSON. Same limits as `taskInput`. |
| `taskInput` | `taskInput` | URL-encoded JSON. See [Task input](#task-input). Subject to URL-length limits, and tokenized values must never go in a URL; send anything sensitive or large over [postMessage](#configuring-via-postmessage) instead. |
| `appearance` | `appearance` | URL-encoded JSON. See below. |
| `theme` | `appearance.theme` | Shorthand for `appearance={{ theme }}`. Ignored if `appearance` is also provided. |
| `language` | `language` | `en-US` (default) or `fr-CA`. |
| `taskRunView` | `taskRunView` | `status` (default) or `live`. |
| `showCloseButton` | `showCloseButton` | `true` renders the persistent close (X) icon and enables Escape-to-close. Defaults to `false` when omitted — there is no `onClose` callback over the hosted URL, so unlike the React prop it has no `onClose`-derived default. Dismissal is delivered as a [`CLOSE`](#receiving-results) message either way. |
| `credentialPersistence` | `credentialPersistence` | `true` opts the created credential into [credential persistence](https://docs.deck.co/concepts/credentials.md#credential-persistence). Defaults to `false` when omitted. Creation only; ignored in [Update Mode](#update-mode). |

### Appearance parameter

`appearance` takes the same [`AppearanceConfig`](#appearance) object as the prop, encoded as a URL-safe JSON string:

```js theme={null}
const url =
  'https://auth.components.deck.co/?token=tk_abc123' +
  '&appearance=' +
  encodeURIComponent(JSON.stringify({
    theme: 'default',
    layout: { logoUrl: 'https://content.cdn.deck.co/logos/your-org/logo.png' },
  }));
```

Malformed JSON is ignored and theming falls back to the default. For a theme-only change, use the `theme` shorthand instead.

### Examples

```bash theme={null}
# Show the source picker
https://auth.components.deck.co/?token=tk_abc123

# Pre-select a source, verify after capture, live view, French, with a close button
https://auth.components.deck.co/?token=tk_abc123&sourceId=src_abc123&taskId=task_x9y8z7&taskRunView=live&language=fr-CA&showCloseButton=true

# Update mode (re-collect an existing credential)
https://auth.components.deck.co/?token=tk_abc123&credentialId=cred_abc123&externalId=user_12345

# Interaction mode (run a task against an existing credential)
https://auth.components.deck.co/?token=tk_abc123&mode=interaction&credentialId=cred_abc123&taskId=task_x9y8z7
```

### Configuring via postMessage

URL parameters are subject to URL-length limits, so a large `sourceConfig` or `taskInput` (or any object config you'd rather not URL-encode) is best delivered over `postMessage`, which has no encoding step and no length limit. Tokenized task input values must only be sent this way. The React component uses this same handshake internally.

Load the component with **no configuration in the URL** (just the bare origin), wait for the `READY` message, then post an `INIT` message with the full configuration object:

```js theme={null}
const AUTH_ORIGIN = 'https://auth.components.deck.co';
const iframe = document.getElementById('deck-auth'); // <iframe src="https://auth.components.deck.co/">

window.addEventListener('message', (e) => {
  if (e.origin !== AUTH_ORIGIN) return;
  if (e.data?.type === 'READY') {
    iframe.contentWindow.postMessage(
      {
        type: 'INIT',
        payload: {
          token: 'tk_abc123',
          sourceId: 'src_toronto_hydro',
          sourceConfig: {
            /* full object, no encoding, no length limit */
          },
          // Send the whole shape; use null for anything you're not setting.
          credentialId: null,
          externalId: null,
          taskId: null,
          taskInput: null,
          workflowId: null,
          closeAfterStep: null,
          showStepProgress: false,
          workflowInput: null,
          appearance: null,
          taskRunView: 'status',
          language: 'en-US',
          showCloseButton: false,
          credentialPersistence: false,
          mode: 'auth',
        },
      },
      AUTH_ORIGIN,
    );
  }
  // Handle SUCCESS / ERROR / CLOSE here. See Receiving results below.
});
```

In a React Native `WebView`, run the same `window.postMessage({ type: 'INIT', ... })` (via `injectJavaScript`) once you receive `READY` through `onMessage`.

> [!WARNING]
>
> Pick one channel. Don't put a `token` in the URL if you're going to `postMessage`: the component self-initializes from the URL first, and the two configurations would conflict. Configure via URL parameters **or** via `postMessage`, not both.

### Receiving results

Without React callbacks, the component delivers results as messages to the client:

* **React Native `WebView`:** messages arrive via `window.ReactNativeWebView.postMessage` as JSON strings. Read them with the `WebView`'s `onMessage` handler.
* **`<iframe>`:** the same messages arrive via `window.postMessage` to the parent frame.

Every message the component emits is forwarded, not only the terminal results. Switch on `type` and ignore any you don't handle:

| `type` | Payload | Meaning |
| - | - | - |
| `SUCCESS` | `{ credentialId, externalId, taskRunId, workflowRunId, verified, sessionId }` | Credential created or updated. |
| `CANCELED` | `{ credentialId, taskRunId, workflowRunId }` | Flow canceled. |
| `ERROR` | `{ type, code, message }` | Failure. See [Error handling](#error-handling). |
| `CLOSE` | None | User dismissed the component. |
| `READY` | None | Component loaded. |
| `STEP_CHANGED` | `{ step }` | User moved through the flow. |
| `RESIZE` | `{ height }` | Content height, for sizing the `WebView` or `<iframe>`. |

Each message is `{ type, payload }`:

```json theme={null}
{
  "type": "SUCCESS",
  "payload": {
    "credentialId": "cred_abc123",
    "externalId": "user_12345",
    "taskRunId": "run_xyz789",
    "workflowRunId": null,
    "verified": true,
    "sessionId": "sess_def456"
  }
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.