CherryHQ/cherry-studio · error · Error

QQ API request failed ${endpoint}: HTTP ${response.status} -

Error message

QQ API request failed ${endpoint}: HTTP ${response.status} - ${errorText}

What it means

Generic gateway thrown by QqAdapter.apiRequest() after net.fetch to a QQ API endpoint returns non-2xx. Embeds endpoint, status, and the raw errorText (with a .catch fallback to ''). Used by gateway lookup, message send (sendToChat), and other QQ REST calls.

Source

Thrown at src/main/ai/channels/adapters/qq/QqAdapter.ts:205

  private async apiRequest(
    endpoint: string,
    options?: { method?: string; body?: Record<string, unknown> }
  ): Promise<Response> {
    const token = await this.getAccessToken()
    const response = await net.fetch(endpoint, {
      method: options?.method ?? 'GET',
      headers: {
        Authorization: `QQBot ${token}`,
        'Content-Type': 'application/json',
        'X-Union-Appid': this.appId
      },
      ...(options?.body ? { body: JSON.stringify(options.body) } : {})
    })

    if (!response.ok) {
      const errorText = await response.text().catch(() => '')
      throw new Error(`QQ API request failed ${endpoint}: HTTP ${response.status} - ${errorText}`)
    }

    return response
  }

  private async getGatewayUrl(): Promise<string> {
    const response = await this.apiRequest(`${QQ_API_BASE}/gateway`)
    const data = (await response.json()) as { url: string }
    return data.url
  }

  private async startGateway(): Promise<void> {
    if (this.isConnecting || this.shouldStop) return
    this.isConnecting = true

    try {
      this.cleanup()

View on GitHub (pinned to 726446b54c)

Solutions

  1. On 401, invalidate this.tokenCache and call fetchAccessToken() once, then retry the original request (the cache has expiresAt but a server-side revocation can preempt it).
  2. Read the embedded errorText for QQ's {code, message,...} to find the specific cause (e.g. permission scope missing).
  3. For 429, honor Retry-After / back off.
  4. Verify X-Union-Appid equals this.appId and the endpoint path matches the chat type.
Defensive patterns

Strategy: retry

Validate before calling

// Pre-send: ensure the token cache is fresh.
if (!qq.tokenFresh()) await qq.refreshToken()
await qq.sendToChat(chatId, text)

Type guard

function isQqApiHttpError(e: unknown): e is Error {
  return e instanceof Error && /^QQ API request failed .*: HTTP \d+/.test(e.message)
}

Try / catch

try {
  await qq.sendToChat(chatId, text)
} catch (e) {
  if (isQqApiHttpError(e)) {
    const status = Number(/HTTP (\d+)/.exec(e.message)?.[1] ?? 0)
    if (status === 401) { await qq.invalidateTokenAndRefresh(); return qq.sendToChat(chatId, text) }
    if (status === 429 || status >= 500) return backoffAndRetry()
  }
  throw e
}

Prevention

When it happens

Trigger: apiRequest() (line ~188) fetches an endpoint with Authorization: QQBot <token> and X-Union-Appid; response.ok is false; errorText is read and the message is thrown. Hit during getGatewayUrl, message POSTs, etc.

Common situations: Access token expired (QQ returns 401 — token cache TTL missed), insufficient bot permissions for the target user/group/channel, rate limited, or hitting an endpoint with the wrong X-Union-Appid.

Related errors


AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12). Data as JSON: /api/errors/2ee7f2f72052ac91. Report an issue: GitHub.