can1357/oh-my-pi · error · AIError.OAuthError

Z.ai business login returned no access token

Error message

Z.ai business login returned no access token

What it means

businessLogin exchanges the user's OAuth access token for a business token via the Z.ai business login endpoint. If the unwrapped envelope contains neither access_token nor accessToken (or they are empty/non-string), the flow throws OAuthError (kind=token-exchange, provider=zai). The API succeeded at HTTP level but the response shape lacked the expected credential.

Source

Thrown at packages/ai/src/registry/oauth/zai.ts:135

}

function trimmedString(value: unknown): string | undefined {
	return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
}

/**
 * Exchange the short-lived OAuth access token for a durable biz token via
 * ZCode's business-login endpoint. The biz APIs reject the raw OAuth token;
 * they require this token.
 */
async function businessLogin(oauthAccessToken: string, fetchImpl: FetchImpl): Promise<string> {
	const data = unwrapEnvelope(
		await postJson(BUSINESS_LOGIN_URL, { token: oauthAccessToken }, {}, fetchImpl),
		"business login",
	) as { access_token?: unknown; accessToken?: unknown } | undefined;
	const bizToken = trimmedString(data?.access_token) ?? trimmedString(data?.accessToken);
	if (!bizToken) {
		throw new AIError.OAuthError("Z.ai business login returned no access token", {
			kind: "token-exchange",
			provider: "zai",
		});
	}
	return bizToken;
}

interface ZaiProject {
	projectId?: unknown;
	isDefault?: unknown;
}
interface ZaiOrganization {
	organizationId?: unknown;
	isDefault?: unknown;
	projects?: ZaiProject[];
}

/**

View on GitHub (pinned to 9690622007)

Solutions

  1. Confirm the Z.ai account has business/API access enabled and complete any required consent in the Z.ai console.
  2. Re-run OAuth login to get a fresh token and retry — a partially valid token can yield a tokenless response.
  3. Log the raw business login response (via fetch interception) to check for renamed fields; upgrade the library if Z.ai changed its schema.
  4. Catch AIError.OAuthError and surface a clear message directing the user to enable Z.ai business API.
Defensive patterns

Strategy: type-guard

Validate before calling

if (!oauthAccessToken || oauthAccessToken.length < 20) throw new Error("OAuth access token missing/short; re-run Z.ai login first");

Type guard

function hasBizToken(d: unknown): d is { access_token: string } | { accessToken: string } {
  if (!d || typeof d !== "object") return false;
  const o = d as Record<string, unknown>;
  return typeof o.access_token === "string" && o.access_token.length > 0 || typeof o.accessToken === "string" && o.accessToken.length > 0;
}

Try / catch

try { const biz = await businessLogin(token, fetch); }
catch (e) { if (e instanceof AIError.OAuthError) { logger.error("Z.ai business login returned no token; check account business-API enablement"); return null; } throw e; }

Prevention

When it happens

Trigger: Z.ai returns a successful envelope whose data omits access_token — e.g. account not provisioned for business API, consent missing, or Z.ai changed the response field name/version.

Common situations: New Z.ai account without business API enablement; API contract drift on Z.ai side; token valid but scoped to an account type that cannot mint business tokens.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/9b1c3ad95736bb09. Report an issue: GitHub.