> ## 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.

# Errors

> Every error the SDK reports to your page, what it means, and what to do about it.

The SDK reports problems in three ways, depending on how far it got:

| Where | When | Who tells the founder |
| - | - | - |
| `loadDoola()` rejects | The SDK could not start at all | Your page |
| `onAuthError` | A session could not be created or renewed | The iframe. Your page also handles `partner_session_expired` |
| `onLoadError` | The iframe hit a problem while running | The iframe, except for `render_error` |

Both callbacks can fire more than once for the same problem, so keep them idempotent.

## loadDoola() rejects

| Cause | Fix |
| - | - |
| Called outside a browser, for example during server rendering | Call it from a client-only path such as `useEffect` |
| The loader script could not load: a CSP, an ad blocker or the network | Allow `https://js.doola.com` in `script-src`, and show your own fallback |
| An invalid option, such as a key without the `pk_test_` or `pk_live_` prefix | The message names the option |
| A different publishable key than the live instance's | Call `destroy()` first |
| Trusted Types refused the loader URL | Allow the `doola-js` policy, as the message says |

## Auth errors

`onAuthError` receives `{ type, message }`.

| `type` | What happened | What to do |
| - | - | - |
| `partner_session_expired` | Your session route answered 401: your own user is no longer signed in | Send them to your sign-in. Retrying cannot succeed |
| `email_in_use` | A 409 with `E_EMAIL_IN_USE`: the email belongs to a doola account outside your partner account, to a Partner Portal user, or to another account your customer would be renamed onto. Also any first 409 your route sent without a code | Offer a different email or your support. Never retried. On a renewal this is `renewal_failed` |
| `external_id_conflict` | A 409 with `E_RESOURCE_CONFLICT`: your `externalCustomerId` and doola's record disagree, or an older customer cannot take a new email | Fix your mapping between users and `externalCustomerId`, or contact doola. The founder cannot fix it |
| `customer_revoked` | A 409 with `E_CUSTOMER_REVOKED`: doola deactivated this customer | Offer your support. Never retried |
| `mint_failed` | The first session could not be fetched for another reason: a 5xx or 502 from your route, the network, an invalid response, or a 409 with an `E_` code the SDK does not know | Log it. The iframe offers the founder **Try again**, up to three times, and each failed try fires `mint_failed` again. Mounting again also fetches again |
| `renewal_failed` | A renewal failed for a reason other than 401, `E_RESOURCE_CONFLICT` or `E_CUSTOMER_REVOKED`. The current session keeps working until it expires | Usually nothing. When the iframe next needs a session, the SDK asks your route again. If the SDK mounts again after the session went stale, the iframe shows **Try again** |

The iframe shows the founder a matching message: "This session has ended", "This email already has a doola account", "We could not open your account" or "This account is no longer active". `mint_failed` shows "We could not start your session" with **Try again**, and a session that went stale before the SDK mounted again shows "We lost your session" with **Try again**. After three failed tries, both read "Please contact the team you signed up with." A 409 your route sent without a code shows "We could not start your session" with that line and no **Try again**, because the email may not be the cause and retrying will not help.

## Load errors

`onLoadError` receives `{ type, message }`.

| `type` | What happened |
| - | - |
| `api_connection_error` | The iframe could not reach doola |
| `authentication_error` | doola rejected the session, and a fresh one did not help |
| `invalid_request_error` | A request was refused because of your configuration. Not retryable |
| `api_error` | Anything else, including a doola outage |
| `render_error` | The iframe could not render, or never started at all |

In every case but one, the iframe is running and shows its own error screen, so `onLoadError` is for your logging. Today the SDK reports only `render_error`. Log the other types too, so a release that starts sending them needs no change on your side.

### render\_error

When the iframe never starts, nothing inside it can show an error, and **your page is the only place to tell the founder.** That happens when:

* your CSP's `frame-src` refuses `sdk.doola.com`;
* a browser extension or a network filter blocks the iframe;
* the iframe's page fails to load.

The SDK reports `render_error` within 20 seconds of mounting, or 5 seconds after the iframe's page loads without starting. If it was full screen, it releases the overlay and your page's scroll first. Keep the `message`, which says which case it was:

| `message` | Meaning |
| - | - |
| `The doola frame did not load.` | The page never finished loading: DNS, TLS or a stalled connection |
| `The doola frame loaded but never started.` | Something loaded but never started: often a CSP placeholder or a blocked page |
| `The doola frame spoke a protocol version this loader does not support.` | A version mismatch during a doola deploy. Reloading fixes it |

```ts theme={null}
onLoadError: (error) => {
  logError('doola', error);
  if (error.type === 'render_error') showFormationUnavailable();
},
```

## Partner API errors

Errors from your server's calls to doola use the Partner API's `{ payload, error }` envelope and codes. The ones specific to the SDK flow:

| HTTP | Code | Returned by | Meaning |
| - | - | - | - |
| 409 | `E_EMAIL_IN_USE` | Create a customer session | The email belongs to a doola account outside your partner account, to a Partner Portal user, or to another account your customer would be renamed onto |
| 409 | `E_RESOURCE_CONFLICT` | Create a customer session | The email's customer is bound to a different `externalCustomerId`, this `externalCustomerId` belongs to another customer, or an older customer cannot take a new email |
| 409 | `E_CUSTOMER_REVOKED` | Create a customer session | doola deactivated this customer |
| 404 | `E_NOT_FOUND` | Confirm payment | No such company in your account |
| 409 | `E_COMPANY_CANCELLED` | Confirm payment | doola cancelled this formation before payment. Refund the founder |

See [Errors](/api/errors) for the envelope and the codes every endpoint can return.


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