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

> ## Agent Instructions
> Authenticate with the raw API key in the Authorization header. Never add a Bearer prefix.
> Use the sandbox base URL https://api.test.doola.com and a dk_test_ key in examples unless the user asks for production.
> Send an Idempotency-Key header on POST /v1/partner/customers and POST /v1/partner/companies. No other endpoint takes one.
> Every response is wrapped as {payload, error}. Read fields from payload and branch on error.code, never on the message.
> Drive formation progress from webhooks plus a reconciliation sweep, never a polling loop.
> Embedded SDK (@doola/js): the dk_ key stays on the server and mints customer sessions; the browser gets only a pk_ key and the session. onFormed carries only companyId and can fire more than once. Before charging, read the company with the dk_ key, require formationSubmissionStatus AWAITING_PAYMENT, check the owner's email, and price on the server. Then POST payment-confirmed, which answers 200 with an empty body, and replace the SDK element with a new doola.create() (never append a second one).
> The doola Formation MCP server at https://mcp.doola.com is for founders forming their own company with their own doola account. Partners integrate the Partner API or the Embedded SDK. The /mcp endpoint on this docs site only searches these docs.
> For a full capability summary with the rules above, read https://docs.doola.com/skill.md.

# Client reference

> Every option, method and callback of @doola/js in the browser.

```ts theme={null}
import { loadDoola } from '@doola/js';
```

The package ships full TypeScript definitions with these descriptions, so your editor shows them too.

## loadDoola(options)

```ts theme={null}
function loadDoola(options: DoolaOptions): Promise<Doola>;
```

Loads doola's loader, validates the options and resolves with the SDK instance for this page.

* **One instance per page.** A second call with the same `publishableKey` resolves with the live instance and ignores the new options. A call with a different key rejects until you call `destroy()`.
* **Browser only.** It rejects outside a browser, so call it from a client-only path.
* **Rejects** when the loader cannot load (a CSP, an ad blocker, the network) or an option is invalid. The message names the problem.

## Options

<ParamField path="publishableKey" type="string" required>
  Your `pk_test_...` or `pk_live_...` key. Its prefix selects the environment. Any other value rejects.
</ParamField>

<ParamField path="fetchAccessToken" type="() => Promise<CustomerSession>" required>
  Called when the SDK mounts, before each session expires, and each time the iframe asks again, such as when the founder presses **Try again**. Calls at the same moment are merged into one. Fetch a fresh session from your [session route](/sdk/sessions) every time, and resolve with `{ accessToken, expiresIn }`. An `expiresAt` string is ignored; any other `expiresAt` value fails the session, so leave it out.

  On failure, reject with an object that carries the HTTP `status` of your route, and the `code` from its JSON body when it sent one: `401` becomes `partner_session_expired`, and a `409` is named by its code (`E_EMAIL_IN_USE`, `E_RESOURCE_CONFLICT` or `E_CUSTOMER_REVOKED`). A `409` with no code is `email_in_use` on the first session and `renewal_failed` on a renewal. Anything else becomes `mint_failed` or `renewal_failed`. See [Errors](/sdk/reference/errors#auth-errors).
</ParamField>

<ParamField path="onAuthError" type="(error: DoolaAuthError) => void" required>
  A session could not be created or renewed. Fires once for each failed call to `fetchAccessToken`, including each **Try again**, so keep it idempotent. See [Errors](/sdk/reference/errors#auth-errors).
</ParamField>

<ParamField path="onFormed" type="(event: { companyId: string }) => void" required>
  The founder submitted the wizard, or asked to pay again for a formation that waits on payment. Open your checkout for `companyId`. It can fire more than once for the same company, so keep it idempotent. See [Take payment](/sdk/payments).
</ParamField>

<ParamField path="onLoadError" type="(error: DoolaLoadError) => void">
  The SDK failed to load or run. The iframe shows its own error screen in most cases, so this is mainly for your logging, except for `render_error`. See [Errors](/sdk/reference/errors#load-errors).
</ParamField>

<ParamField path="onLoaderStart" type="() => void">
  The first time anything, including a loading state, is visible in the iframe. Use it to remove your own placeholder.
</ParamField>

<ParamField path="presentation" type="{ mode?: 'auto' | 'fullScreen' }" default="{ mode: 'auto' }">
  `auto` shows the SDK inline and switches to a full-screen overlay on viewports 640px wide or narrower. `fullScreen` always uses the overlay. See [Presentation and mobile](/sdk/presentation).
</ParamField>

<ParamField path="locale" type="string">
  A BCP 47 language tag. The SDK is in English (`en`) today.
</ParamField>

<ParamField path="origin" type="string">
  Your own domain for the iframe, set up with doola in advance. Keep it a constant. See [Your own domain](/sdk/presentation#your-own-domain).
</ParamField>

## The Doola instance

### create()

```ts theme={null}
create(): DoolaComponent;
```

Returns a new `<doola-embed>` element. Append it to mount the SDK, remove it to unmount. It takes no arguments: the iframe shows the wizard, the payment screen or the founder's company depending on their state. To refresh, for example after you confirm payment, replace the old element with a new one (`element.replaceWith(doola.create())`). Each call creates a separate iframe, so never leave the old one on the page.

### update(options)

```ts theme={null}
update(options: { locale?: string }): void;
```

Changes options after load. Only `locale` can change. Leaving a key out keeps its value, and `locale: undefined` clears it.

### destroy()

```ts theme={null}
destroy(): void;
```

Drops the customer's session from the page, removes every element and stops renewing. Call it when your user signs out of your product, never on an ordinary unmount. The instance cannot be used again: `create()` and `update()` throw afterwards. Calling `destroy()` twice is safe.

## Types

```ts theme={null}
interface CustomerSession {
  accessToken: string;
  expiresIn: number; // seconds
  expiresAt?: string;
}

interface DoolaAuthError {
  type:
    | 'partner_session_expired'
    | 'email_in_use'
    | 'external_id_conflict'
    | 'customer_revoked'
    | 'mint_failed'
    | 'renewal_failed';
  message: string;
}

interface DoolaLoadError {
  type:
    | 'api_connection_error'
    | 'authentication_error'
    | 'invalid_request_error'
    | 'render_error'
    | 'api_error';
  message: string;
}
```

Also exported: `Doola`, `DoolaComponent`, `DoolaOptions`, `FetchAccessToken`, `Presentation` and `DoolaGlobal`.

## The element

`<doola-embed>` is a block element. Its iframe takes the full width of the container, reserves 160px until the first screen renders, then follows the height of its content, and keeps it while hidden with `display: none`. In full-screen mode it covers the viewport and locks your page's scroll until you remove it.

## Versions

`@doola/js` follows semantic versioning. It loads doola's loader from `https://js.doola.com/v1/doola.js`, which updates in place and stays compatible with every `@doola/js` release in that major version, so fixes reach your site without an upgrade. See the [changelog](/sdk/changelog).


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