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

# Accounts

> Isolated provider sign-in, accounts, API credentials, endpoint presets, and usage-limit handling in the ADE SDK.

The SDK owns the provider logins used by its embedded runtime. Each app can
manage its own accounts and credentials without reading keys from another
tool's config directory.

## Login isolation

SDK 0.7 defaults `useMachineLogin` to `false`. The runtime starts with isolated
provider logins, so a Claude or Codex login on the host machine is not silently
reused. **BREAKING from SDK 0.6:** to keep using machine logins, pass
`useMachineLogin: true` explicitly:

```ts theme={null}
const client = await createAdeChat({
  home: "./.ade-embed",
  useMachineLogin: true,
});
```

`doctor()` reports `logins.mode` (`isolated`, `machine`, or `attached`) and a
per-provider `logins.perProvider` map (`isolated`, `shared`, or `unknown`).
Check this before describing which credentials a thread can use.

Isolation covers the providers' sign-in folders. It does not cover API keys in
your app's own environment: the runtime inherits your process environment, so
an `ANTHROPIC_API_KEY`, `OPENAI_API_KEY` or `CURSOR_API_KEY` set there is still
visible to it. Pass an `env` to `createAdeChat` without them if
your users should only ever run on their own logins and keys.

## Provider accounts

```ts theme={null}
const accounts = await client.accounts.list("claude");
const account = await client.accounts.add("claude", { label: "Work" });
const login = await client.accounts.signIn(account.id, { deviceAuth: true });

login.on("update", (status) => {
  if (status.url) showLoginLink(status.url, status.deviceCode);
  if (status.awaitingCode) showCodeEntry();
});
const signedIn = await login.done;
```

`SignInHandle` also provides `submitCode(code)` and `cancel()`. `done` resolves
to the refreshed account or rejects when sign-in fails or is cancelled. Always
cancel a login the user abandons. The provider account API also has `remove`,
`rename`, `setDefault`, `signOut`, and `refresh`.

`AccountInfo` contains only the public account summary: `id`, `provider`,
`label`, `signedIn`, `isDefault`, and optional `email`, `plan`, `loginBroken`,
`accentColor`, `createdAt`, and `usage`. Runtime-only fields such as an
absolute provider config path are removed before the result reaches the host.
`list()` and `refresh()` include `usage` when the runtime has a matching quota
row for that account:

```ts theme={null}
{
  state: "ok" | "stale" | "expired" | "signed_out" | "no_reading" | "error",
  readAt: string | null,
  note: string | null,
  windows: Array<{
    type: string;
    label: string;
    percentLeft: number;
    resetsAt: string | null;
    unlimited?: true;
    countLeft?: number;
    countTotal?: number;
  }>;
}
```

Usage can be absent when the runtime has no usage tracker or the latest report
has no row for that account yet. `readAt` is the quota reading time; `note`
explains a stale, signed-out, failed, or not-yet-read state. Only accounts with
an `instanceId` in the usage report receive per-account usage; one-login
providers do not have one.

`client.providers.capabilities(provider)` returns the runtime's choices for
permission modes, reasoning effort, fast mode, accounts, sign-in, API keys,
presets and MCP ([`ProviderCapabilities`](https://ade-app.dev/docs/sdk/reference#providercapabilities)). It throws `unsupported` if the runtime does not advertise
provider capabilities.

## Credentials and presets

Credentials are stored by the runtime, encrypted with the OS credential store
(Keychain on macOS, DPAPI on Windows; on Linux, a file only this user can
read). `credentials.list()` returns summaries with labels, provider, source,
timestamps and a masked tail; the API key value is never returned.

A key is filed under its **API vendor**, not the harness that uses it: a Claude
thread runs on an `anthropic` key, Codex on `openai`, Kimi on `moonshotai`,
Grok on `xai`. Other harnesses use their own name (`cursor`, `droid`, `pi`,
`opencode`, `qwen`, `copilot`).

```ts theme={null}
const saved = await client.credentials.add({ provider: "openai", key, label: "Build key" });
await client.credentials.verify("openai");
await client.threads.open("build", {
  provider: "codex",
  model: "gpt-5-codex",
  credential: saved.credentialId,
});
```

`credentials.remove(id, provider?)` removes a stored key. Custom endpoint
presets use `client.presets.list()`, `add(input)`, `update(id, patch)`, and
`remove(id)`. Preset results omit key material.

When a key lives in your own vault instead, pass its id to a thread and supply
the value with `resolveSecret`:

```ts theme={null}
const client = await createAdeChat({
  home,
  // Called with the thread's harness ("codex") and the credential id.
  resolveSecret: (provider, credentialId) => vault.get(provider, credentialId),
});

await client.threads.open("support", {
  provider: "codex",
  model: "gpt-5-codex",
  credential: "build-key",
});
```

The SDK pushes the value to the runtime on every open and resume. The runtime
holds it in memory only, under the thread's API vendor, so it is never saved in
the thread record, the runtime's key store or its account vault, and it is gone
when the runtime exits. `credentials.list()` shows it with `source: "host"`. If
`resolveSecret` returns nothing, the open fails with `invalid_option`.

Two harnesses read keys only from a config file: Droid (`settings.json`) and
OpenCode presets (`opencode.json`). For those, the runtime writes the key into a
private launch folder under the SDK home while the thread runs, exactly as it
does for a stored key.

## Accounts on a thread

Pass `account`, `credential`, or `preset` when opening a thread to select the
provider instance, stored credential, or endpoint preset. These choices are
saved with the thread and used again if the runtime loses the session and the
SDK recreates it. `thread.switchAccount(instanceId)` changes the account for a
live thread; check its result because the runtime can report `busy`,
`signed_out`, or `failed` without switching.

## Usage limits

New threads default to `onUsageLimit: "wait"`. Choose another policy when
creating the client:

```ts theme={null}
const client = await createAdeChat({ home, onUsageLimit: "failover" });
client.on("usageLimit", (event) => showLimitNotice(event));
```

`usageLimit` fires whenever a thread's limit state changes:
`{ key, sessionId, provider, status }`. `status` is null when the limit
clears; otherwise it carries `state` (`armed`, `resuming`, `paused`,
`opted_out` or `no_reset`), `resetAt`, `fireAt` (when the runtime will resume
on its own), and `alternateAccount` when another signed-in account still has
room.

`"failover"` continues the interrupted turn on that `alternateAccount`, once
per limited turn. The runtime picks the account, because it has the quota
reading. With no alternate, the thread waits for the reset. `"wait"` and
`"ask"` only emit the event. Threads expose `usageLimit.resumeNow()` and
`usageLimit.continueOnAlternate()` for an explicit choice.

## Who can manage accounts

Only the client that started the runtime can manage accounts, credentials and
presets. The SDK proves it with a secret it hands the runtime at launch, so the
agents running inside your chats, which can reach the same runtime, cannot
sign your users' accounts out or touch their keys. A client created with
`attach: true`, or one that found a runtime an earlier copy of your app left
running, cannot manage accounts and gets `unsupported`. Dispose it, stop that
runtime, and create the client again.

## Electron renderers

Account, credential, and preset methods are refused for renderers by default.
Enable them only for trusted frames by passing `allowAccountManagement` to
`registerAdeIpc`:

```ts theme={null}
registerAdeIpc(ipcMain, client, {
  authorize: (event) => isTrustedFrame(event.sender),
  allowAccountManagement: (event, method) => isTrustedFrame(event.sender) && method.startsWith("accounts."),
});
```

The gate covers `accounts.*`, `credentials.*`, and `presets.*`. Credential
values can cross into main only through `credentials.add`; list and verify
responses contain summaries, not secret values.


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