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

Z.ai key provisioning returned no apiKey

Error message

Z.ai key provisioning returned no apiKey

What it means

After creating the API key, mintZaiApiKey unwraps the create response and requires a non-empty apiKey string field. If the record lacks it (or it's non-string/empty), OAuthError is thrown. The create call succeeded at HTTP/envelope level but did not return the expected key identifier.

Source

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

		throw new AIError.OAuthError("Z.ai key provisioning failed: no organization/project on account", {
			kind: "token-exchange",
			provider: "zai",
		});
	}

	const keysUrl = `${BIZ_BASE}/api/biz/v1/organization/${organizationId}/projects/${projectId}/api_keys`;
	const existing = asKeyArray(unwrapEnvelope(await getJson(keysUrl, auth, fetchImpl), "api key list")).find(
		key => key.name === KEY_NAME,
	);
	const keyRecord =
		existing ??
		(unwrapEnvelope(await postJson(keysUrl, { name: KEY_NAME }, auth, fetchImpl), "api key create") as
			| Record<string, unknown>
			| undefined);

	const apiKey = trimmedString(keyRecord?.apiKey);
	if (!apiKey) {
		throw new AIError.OAuthError("Z.ai key provisioning returned no apiKey", {
			kind: "token-exchange",
			provider: "zai",
		});
	}

	// Always fetch the secret via the copy endpoint: list entries mask it
	// (`*****abcd`) and the create response's inline secret is not reliable
	// across account states, whereas copy always returns the full secret.
	const copied = unwrapEnvelope(
		await getJson(`${keysUrl}/copy/${encodeURIComponent(apiKey)}`, auth, fetchImpl),
		"api key copy",
	) as { secretKey?: unknown } | undefined;
	const secretKey = trimmedString(copied?.secretKey);
	if (!secretKey) {
		throw new AIError.OAuthError("Z.ai key provisioning returned no secretKey", {
			kind: "token-exchange",
			provider: "zai",
		});

View on GitHub (pinned to 9690622007)

Solutions

  1. Log the raw create response to see the actual field names; update the library or parse the alternative field if Z.ai renamed apiKey.
  2. Retry provisioning — transient partial responses can occur; delete orphaned keys afterward to stay under quota.
  3. Verify the token used has full api_keys read/write scope.
  4. Catch AIError.OAuthError and report provisioning as incomplete rather than treating login as failed auth.
Defensive patterns

Strategy: try-catch

Type guard

function hasApiKeyField(r: unknown): r is { apiKey: string } { return !!r && typeof r === "object" && typeof (r as Record<string, unknown>).apiKey === "string" && (r as { apiKey: string }).apiKey.length > 0; }

Try / catch

try { return await mintZaiApiKey(token, fetch); }
catch (e) {
  if (e instanceof AIError.OAuthError && e.message.includes("no apiKey")) {
    logger.error("Z.ai key create succeeded but response lacked apiKey — check API schema/token scope");
  }
  throw e;
}

Prevention

When it happens

Trigger: Z.ai key-create endpoint returns data without apiKey (name rejected silently, async creation pending, or response schema changed so the field is named id/key instead).

Common situations: Z.ai API version drift renaming the field; creation endpoint returning a job/pending object; permission-limited token that can create but not read key material.

Related errors


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