Skip to content

Z.ai: quota probe returns nothing when baseUrl is the Anthropic-compatible coding endpoint (https://api.z.ai/api/anthropic) #4154

Description

@nordz0r

Client or integration

Other — OpenCodex provider UI / provider quota probe (zai provider)

Provider or upstream service

Z.ai — GLM Coding Plan

OpenCodex version

2.49.0

Endpoint or capability

Provider quota probe (GET https://api.z.ai/api/monitor/usage/quota/limit) vs. Anthropic-compatible messages endpoint POST https://api.z.ai/api/anthropic/v1/messages

Current behaviour

fetchZaiQuota() only runs when isCanonicalZaiBaseUrl() matches. The canonical list accepts https://api.z.ai and https://api.z.ai/api/coding/paas/v4, but not the documented Anthropic-compatible coding endpoint https://api.z.ai/api/anthropic.

Z.ai's GLM Coding Plan serves the Anthropic wire format only at https://api.z.ai/api/anthropic:

  • POST https://api.z.ai/api/anthropic/v1/messages → 200 (works)
  • POST https://api.z.ai/v1/messages → 404
  • POST https://api.z.ai/api/coding/paas/v4/v1/messages → 404
  • POST https://api.z.ai/api/coding/paas/v4/messages → 404

Since the Anthropic adapter builds the messages URL as {baseUrl}/v1/messages, users face a mutually exclusive choice:

  • baseUrl = https://api.z.ai/api/anthropic → messages work, provider quota view is empty
  • baseUrl = https://api.z.ai/api/coding/paas/v4 → quota view works, all message requests 404

Expected behaviour

The quota probe should also treat https://api.z.ai/api/anthropic as a canonical Z.ai Coding Plan base URL, so a provider configured for the Anthropic wire format still gets its quota/usage view populated.

Minimal redacted request or reproduction

# 1. Configure a provider with:
#    adapter  = anthropic
#    baseUrl  = https://api.z.ai/api/anthropic
#    authMode = key (x-api-key)
#
# 2. Verify messages work through the proxy — 200 OK.
# 3. Open the provider "Usage"/quota view — it stays empty.

# The probe's guard rejects the URL:
#   src/providers/quota.ts :: isCanonicalZaiBaseUrl("https://api.z.ai/api/anthropic") === false
#   → fetchZaiQuota() returns null before any request is made.

# Messages endpoint itself is healthy (key redacted):
curl -s -o /dev/null -w '%{http_code}\n' https://api.z.ai/api/anthropic/v1/messages \
  -H 'x-api-key: <REDACTED>' -H 'anthropic-version: 2023-06-01' \
  -H 'Content-Type: application/json' \
  -d '{"model":"glm-5.3-flash","max_tokens":8,"messages":[{"role":"user","content":"hi"}]}'
# → 200

Actual response or error

No error is surfaced — the quota view silently stays empty, because fetchZaiQuota bails out at the canonical-URL guard before requesting https://api.z.ai/api/monitor/usage/quota/limit.

For reference, the quota endpoint itself works fine for Coding Plan keys (verified with Authorization: Bearer <coding-plan-key> → 200 with a data.limits[] payload), it is only the canonical-URL gate that excludes the Anthropic base URL.

Upstream documentation

  • Z.ai GLM Coding Plan / DevPack docs: https://docs.z.ai/devpack/overview — the Anthropic-compatible base URL for coding tools is https://api.z.ai/api/anthropic (e.g. used as ANTHROPIC_BASE_URL for Claude Code).
  • Quota monitor endpoint used by the probe: GET https://api.z.ai/api/monitor/usage/quota/limit (no public spec; verified empirically, returns {code, data:{limits:[{type,unit,number,usage,currentValue,remaining,percentage,nextResetTime}]}}).

Suggested mapping or implementation notes

One-line addition in isCanonicalZaiBaseUrl (src/providers/quota.ts):

|| normalized === `${ZAI_BASE_URL}/api/anthropic`

fetchZaiQuota already derives the monitor host correctly for the international host and sends Authorization: Bearer <key>, which the monitor endpoint accepts for Coding Plan keys.

Additional data point from a controlled A/B test (30 identical requests ≈ 75k tokens per leg, GLM-5.3-Flash, inside the September 2026 Flash campaign window, neutral user-agent): the OpenAI-compatible path (/api/coding/paas/v4/chat/completions) reduced the plan's remaining budget by ~31 units per window vs ~6 units for the Anthropic path (/api/anthropic/v1/messages) — i.e. the two paths account quota noticeably differently, which makes a working quota view for the Anthropic base URL even more useful.

Additional context and attachments

Configured provider (redacted):

{
  "name": "zai",
  "adapter": "anthropic",
  "baseUrl": "https://api.z.ai/api/anthropic",
  "defaultModel": "glm-5.3-flash",
  "authMode": "key",
  "apiKeyTransport": "x-api-key"
}

Happy to test a fix against 2.49.x.

Checks

  • I searched existing provider and compatibility issues.
  • The request and response were redacted.
  • The expected behaviour is based on an upstream specification or a concrete client requirement.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    account-poolOAuth, credentials, Codex pool, quota, failover, plansproviderProvider adapters, OpenAI-compat presets, upstream API quirksprovider-compatibilityProvider compatibility reports

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions