---
title: "Account re-login over HTTP — `POST /accounts/login-capture`"
---

# Account re-login over HTTP — `POST /accounts/login-capture`

## What it is

How an agent completes an Anthropic account **re-login end to end over the CLI control server**, without a human driving the desktop UI. This is the HTTP twin of the account panel's general **"Log In"** button.

## Where to find it

The thing being driven here is the account panel's general **"Log In"** action, which sits in the
footer of the account indicator — the small account strip in the app's chrome — and not the
per-account orange re-auth buttons beside it. Everything else on this page is addressed to an
agent working over HTTP rather than to someone clicking in the desktop UI.

## How it behaves

### What the "Log In" button actually does (and which browser it signs into)

The account panel's general **"Log In"** action — the account-agnostic one in the `AccountIndicator` footer, NOT the per-account orange re-auth buttons — starts a **PKCE OAuth flow** via `authService.login()`:

- It opens the **system default browser** (through `shell.openExternal`) to Anthropic's authorize URL, and starts a **loopback HTTP server** to catch the OAuth redirect.
- It is **account-agnostic**: whichever Anthropic account finishes the sign-in is the one captured — Omniscio identifies the account from the tokens the callback returns.
- It signs into the **system default browser** — **not** a managed login window, and **not** the Real Chrome bridge / the user's real Chrome over CDP. So step 2's magic link must be opened in that same system-default-browser OAuth flow.
- It imports a **completed OAuth callback**, never browser cookies. There is no "paste cookies to sign in" path.

That last point is why the endpoint below must be **started before** the magic link is opened — the OAuth callback can only complete a flow that is already running.

## For agents

### The endpoint

`POST /accounts/login-capture`

- **Auth**: bearer, **global `amc-cli` token only** (`cliTokenOnly`). A scoped agent-session token is refused `401` — this route mutates credentials.
- **Body**: `{ "loginHint"?: "<email>" }` — an optional email prefilled into the OAuth page. The route captures whichever account actually signs in, regardless of the hint.
- **Behaviour**: invokes the same `authService.login()` capture the button uses, then the same post-login side-effects (other windows refreshed, CLI caches cleared, refresh cooldown reset, usage primed, rate-limited sessions auto-restored).
- **NON-BLOCKING (fire-and-forget)**: it starts the OAuth flow and returns **immediately** — it does NOT wait for the callback. Start it **before** opening the sign-in / magic link, then **poll** for the result.
- **Response** `202`: `{ ok: true, data: { started: true } }` — that is the WHOLE body. There is no `accountId`, `account`, `health`, or `accountSwitched` here; the OAuth outcome is written only to the app log.

### The poll target

`GET /accounts/login-capture/status`

- **Auth**: bearer (read preamble).
- **Response** `200`: `{ ok: true, data: { inProgress, activeAccount } }`
  - `inProgress` — `true` while a sign-in is still in flight; it flips to `false` when the flow finishes (captured, timed out, or cancelled).
  - `activeAccount` — `{ id, email }` of the app-wide active account, or `null`. After a successful capture this is the account that was captured, which is how you confirm **which** account landed.
- It reports **liveness + the active account**, not success: `inProgress: false` alone does not mean the login worked. Confirm with `GET /accounts` (below).

### The full headless re-login sequence

1. **Start the capture** — `POST /accounts/login-capture` (optionally `{ "loginHint": "<email>" }`). This opens the system browser to the OAuth page and returns `202 { started: true }` **right away**. Do NOT hold the connection open, and do NOT try to read an account off this response.
2. **User enters their email** in the login browser (human step — may include a CAPTCHA).
3. **Agent opens the magic link** from the account's Gmail so that same browser/flow completes the OAuth redirect → the loopback callback fires.
4. **Poll** `GET /accounts/login-capture/status` until `data.inProgress` is `false`. The OAuth flow allows up to ~5 minutes, so poll on that budget and treat exhausting it as a failure.
5. **Confirm**: `GET /accounts` shows the expected account at `health: "ok"`. This is the authoritative check — `inProgress: false` only means the flow ended, not that it succeeded. Cross-check `data.activeAccount.email` from step 4 to see WHICH account was captured.

### Errors

| Status | Meaning |
| ------ | ------- |
| `202` | **Success** — the capture STARTED (not "completed"). Body is `{ started: true }`; poll `/accounts/login-capture/status`. |
| `403` | **Managed pathway unavailable on this install** (F038 fail-closed owner gate). On a public build with no managed accounts, no tier floor and no dev override, the route refuses **before** opening any browser. Not retryable — add your own Anthropic API key, or use your own `claude login`, instead. |
| `409` | A login is already in progress, or the user cancelled it — a caller-state conflict (humanized message), not a server fault. |
| `400` | Malformed body. |
| `401` | Missing/invalid token, or an in-app session token (this route is global-token-only). |
| `5xx` | Token-exchange / network failure — humanized for the caller, raw logged server-side. |

### Safety notes

- Desktop-only capability surfaced over loopback HTTP; because it mutates credentials it requires the **global** `amc-cli` token, never a spawned agent's scoped token.
- The route **returns no account data at all** — the `202` body is just `{ started: true }`, so no credential can leak through it. The stripped account view is served by `GET /accounts`.
- **Fail-closed managed-pathway gate (F038).** Before anything else the route checks `isManagedPathwayAvailableForSpawn()`; when the managed pathway is unavailable it answers `403` and never opens a browser. This mirrors the `ACCOUNT_ADD_LOGIN` IPC handler and exists because this route is reachable with a bearer token that lives on the customer's own disk — without the gate, a public build could bootstrap the managed account pool. Owner / dev installs are unaffected. See [auth-pathways-contract.md](../../.claude/memory/contracts/auth-pathways-contract.md).
- The capture path is identical to the desktop button's, so a CLI capture and a button login leave the app in the same state.

## Related

The wider account and sign-in picture — the account indicator, switching accounts, and what the
orange per-account re-auth buttons do — is on the [account indicator](account-indicator.md) and
[account re-login](account-relogin.md) pages. How a session's credential actually gets chosen, and
what the managed-versus-own-key gate means, is on the [auth pathways](auth-pathways.md) page. For
driving Omniscio over HTTP in general, rather than this one route, start from
[CLI control](cli-control.md).
