yikart/AiToEarn · critical · AppException

ChannelAccessTokenFailed

ChannelAccessTokenFailed

Error message

ChannelAccessTokenFailed

What it means

ChannelAccessTokenFailed is thrown by DouyinService.getClientToken when POST /oauth/client_token/ does not return data.access_token. This token (client_credentials grant using client_key/client_secret) is required for all app-level Douyin API calls (share-id, ticket). The error means the credential exchange itself failed, typically because the platform returned an error envelope.

Source

Thrown at project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/douyin/douyin.service.ts:370

        }
        return cached.access_token
      }
    }

    const response = await this.http.post<DouyinOAuthEnvelope<DouyinClientTokenResponse>>(
      '/oauth/client_token/',
      {
        grant_type: DouyinOAuthGrantType.ClientCredential,
        client_key: this.cfg.clientId,
        client_secret: this.cfg.clientSecret,
      },
      {
        headers: { 'Content-Type': 'application/json' },
      },
    )
    const result = response.data.data
    if (!result?.access_token) {
      throw new AppException(ResponseCode.ChannelAccessTokenFailed, { platform: AccountType.Douyin, field: 'access_token', reasonCode: 'missing_platform_field' })
    }
    const expiresIn = Number(result.expires_in)
    const expiresAt = now + expiresIn * 1000

    this.clientTokenCache = {
      accessToken: result.access_token,
      expiresAt,
    }
    await this.redis.saveDouyinClientToken({
      access_token: result.access_token,
      expires_in: expiresIn,
      expiresAt,
    })
    return result.access_token
  }

  private async getOpenTicket(): Promise<string> {
    const clientToken = await this.getClientToken()

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Check the response body of /oauth/client_token/ for the platform error_code (e.g. 40015 invalid client_secret) and correct DOUYIN_CLIENT_KEY/DOUYIN_CLIENT_SECRET accordingly.
  2. Confirm the Douyin Open Platform app is approved and not suspended, and that the client_key matches an app with the required scopes.
  3. Clear the stale Redis douyin client-token cache key if it holds an expired/rejected token, then retry.
  4. If the secret was rotated, redeploy servers with the new secret before the old one is revoked.

Example fix

// before
DOUYIN_CLIENT_KEY=aw9x0000000000
DOUYIN_CLIENT_SECRET=             // empty -> /oauth/client_token/ fails -> throws
// after
DOUYIN_CLIENT_KEY=<real client_key>
DOUYIN_CLIENT_SECRET=<matching client_secret> // response now has data.access_token
Defensive patterns

Strategy: validation

Validate before calling

function assertDouyinCredentials(cfg: { clientId?: string; clientSecret?: string }) {
  if (!cfg.clientId || !cfg.clientSecret) throw new Error('DOUYIN_CLIENT_KEY/DOUYIN_CLIENT_SECRET are required')
}
assertDouyinCredentials(douyinConfig)

Type guard

const hasClientToken = (r: unknown): r is { data: { data: { access_token: string; expires_in: number } } } =>
  !!r && typeof r === 'object' && typeof (r as any).data?.data?.access_token === 'string'

Try / catch

try {
  const token = await douyinService.clientToken()
} catch (err) {
  if (err instanceof AppException && err.code === ResponseCode.ChannelAccessTokenFailed) {
    // reload config, clear Redis token cache, alert ops about credential failure
  } else throw err
}

Prevention

When it happens

Trigger: Any first call or cache-miss call to getClientToken (directly or via clientToken/refreshedClientToken after cache invalidation) where Douyin's response lacks data.access_token — e.g. wrong client_key, invalid client_secret, app suspended, or platform error payload.

Common situations: DOUYIN_CLIENT_KEY/DOUYIN_CLIENT_SECRET env vars missing, swapped, or rotated on the platform but not on the server; both in-memory and Redis token caches empty on a fresh deployment; Douyin app disabled or under review; clock/network issues producing a malformed response.

Related errors


AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31). Data as JSON: /api/errors/d582ed9ecdd2fb65. Report an issue: GitHub.