# ZCode → standalone Z.ai subscription access

Reverse engineered from `ZCode-3.10.1-linux-x64.AppImage` (Electron app; asar at
`resources/app.asar`, CLI engine at `resources/glm/zcode.cjs`).

Bottom line: **your ZCode subscription is usable from any client** via the
Anthropic-compatible endpoint `https://api.z.ai/api/anthropic/v1/messages` with a
standard Z.ai API key (`id.secret` format). The desktop app already stores that
key in plaintext on disk.

## TL;DR

```bash
node zcode-standalone-auth.mjs read    # key already on disk (plaintext)
node zcode-standalone-auth.mjs session # derive key from the encrypted OAuth session
node zcode-standalone-auth.mjs login   # full OAuth flow (any machine)
node zcode-standalone-auth.mjs test    # live API call

curl https://api.z.ai/api/anthropic/v1/messages \
  -H "Authorization: Bearer <apiKey>" -H "Content-Type: application/json" \
  -d '{"model":"glm-5.3","max_tokens":1024,"messages":[{"role":"user","content":"hi"}]}'
```

Works with any Anthropic-SDK client:

```
ANTHROPIC_BASE_URL=https://api.z.ai/api/anthropic
ANTHROPIC_AUTH_TOKEN=<apiKey>          # sent as Authorization: Bearer
# x-api-key: <apiKey> works too
```

Models on the coding plan: `glm-5.3`, `glm-5.1`, `glm-5.1-highspeed`, `glm-5`,
`glm-5-turbo`, `glm-4.7`, `glm-4.7-flash`, `glm-4.6`, `glm-4.5-air`, vision
variants (`glm-4.6v*`, `glm-5.3-flash`), etc. Context limits per model are in the
app config (`~/.zcode/cli/config.json` → `provider["builtin:zai-coding-plan"].models`).

## Where the key comes from

The desktop app's own chain (all verified against the live API):

1. **Login (browser):** `chat.z.ai/api/oauth/authorize` with
   `client_id=client_P8X5CMWmlaRO9gyO-KSqtg`, `response_type=code`,
   `redirect_uri=https://zcode.z.ai/app/oauth/login?redirect=zcode://oauth/callback`
   (also `zcode://oauth/callback` deep link), random `state`. No PKCE.
2. **Code → tokens:** `POST https://zcode.z.ai/api/v1/oauth/token`
   `{"provider":"zai","code":"...","redirect_uri":"...","state":"..."}`
   → `data.token` (zcode JWT) + `data.zai.access_token`.
3. **Business JWT:** `POST https://api.z.ai/api/auth/z/login` `{"token":"<zai access_token>"}`
   → `data.access_token` (HS512 JWT, long-lived, no `exp`).
4. **API key (name: `zcode-api-key`):**
   - `GET  https://api.z.ai/api/biz/customer/getCustomerInfo` → pick org + project
   - `GET/POST https://api.z.ai/api/biz/v1/organization/<org>/projects/<proj>/api_keys`
   - `GET  .../api_keys/copy/<apiKey>` → `secretKey`
   - final key = `<apiKey>.<secretKey>` (49 chars, `xxxxxxxx.yyyyyyyyyyyyyyyy`)
5. Requests go to `https://api.z.ai/api/anthropic/v1/messages`. The app sometimes
   proxies to `https://zcode.z.ai/api/v1/ultra-zai/anthropic/v1/messages`
   (server-driven mapping from `GET https://zcode.z.ai/api/v1/agent/configs`);
   direct works.

## Automatable login (what `login` uses)

Same as the app's "polling" flow — good for headless/CLI setups:

```bash
POLL_TOKEN=$(openssl rand -hex 32)

curl -s https://zcode.z.ai/api/v1/oauth/cli/init \
  -H "Authorization: Bearer $POLL_TOKEN" -H "Content-Type: application/json" \
  -d '{"provider":"zai"}'
# → {"code":0,"data":{"flow_id","authorize_url","expires_at","poll_interval_sec"}}

# open authorize_url in a browser, log in with your Z.ai account, then poll:

curl -s "https://zcode.z.ai/api/v1/oauth/cli/poll/<flow_id>" \
  -H "Authorization: Bearer $POLL_TOKEN"
# pending: {"code":0,"data":{"status":"pending"}}
# done:    {"code":0,"data":{"status":"ready","token":"<zcode jwt>","zai":{"access_token":"..."},"user":{...}}}
```

Then steps 3–4 above to get the API key. Flow expires after 300s.

## Local credential storage (backup path)

- `~/.zcode/cli/config.json` → `provider["builtin:zai-coding-plan"].options.apiKey`
  — **plaintext** API key + `baseURL`.
- `~/.zcode/v2/credentials.json` → JSON of `key → enc:v1:<iv>.<tag>.<ct>` (base64url),
  AES-256-GCM, key = `sha256($ZCODE_CREDENTIAL_SECRET || "zcode-credential-fallback:<platform>:<homedir>:<username>")`.
  Keys: `oauth:zai:access_token` (business JWT), `zcodejwttoken`, `oauth:zai:user_info`,
  `oauth:active_provider`. The CLI engine reads the same store (`ZCODE_DATA_BASE_DIR`
  overrides the dir).

## Other endpoints seen

- `GET https://api.z.ai/api/biz/subscription/list` — plan entitlements (Bearer business JWT)
- `GET https://zcode.z.ai/api/v1/client/configs` — feature flags; can enable
  request signing (`x-client-sig`/`x-client-pow`, HKDF+HMAC/Ed25519 code in the
  CLI bundle). Currently **absent/disabled** — plain key auth works. If Z.ai
  enables it later, a standalone client would need to replicate the signing.
- OpenAI-compatible alternative: `https://api.z.ai/api/coding/paas/v4/chat/completions`
  (same API key).

## App-image internals, for reference

- AppImage = ELF runtime + squashfs → `7zz x ZCode-3.10.1-linux-x64.AppImage -o extracted`
- `resources/app.asar` → `npx @electron/asar extract app.asar app_asar`
- OAuth flow: `app_asar/out/host/index.js` (beautify with `js-beautify`) —
  `ZaiProviderAdapter`, `OAuthService`, `ZaiBusinessTokenResolver`, `resolveBizApiKey`
- Headers the app sends to zcode.z.ai (advisory): `User-Agent: ZCode/<ver>`,
  `X-ZCode-App-Version`, `X-Platform`, `X-Release-Channel`, `X-Device-Mid`, etc.
