yikart/AiToEarn · error · AppException

ResponseCode.ChannelAccountNotAuthorized

ResponseCode.ChannelAccountNotAuthorized

Error message

ChannelAccountNotAuthorized

What it means

getCredentialAccount throws ChannelAccountNotAuthorized (AppException, ResponseCode.ChannelAccountNotAuthorized) when the account exists and is not a Relay account but its status is AccountStatus.ABNORMAL. ABNORMAL status is set after non-retryable credential failures (e.g. markAccountOfflineForCredentialFailure), meaning the stored token is known-bad and the account must be re-authorized before use.

Source

Thrown at project/aitoearn-backend/apps/aitoearn-server/src/core/channels/auth/auth.service.ts:629

      accessToken: refreshed.accessToken,
      refreshToken: refreshed.refreshToken,
      expiresAt: refreshed.expiresAt,
      scope: refreshed.scope,
    }
  }

  private async getCredentialAccount(accountId: string, userId?: string) {
    const account = userId
      ? await this.accountRepo.getByIdAndUserId(accountId, userId)
      : await this.accountRepo.getAccountById(accountId)
    if (!account) {
      throw new AppException(ResponseCode.AccountNotFound)
    }
    if (account.relayAccountRef) {
      throw new RelayAccountException(account.relayAccountRef, accountId)
    }
    if (account.status === AccountStatus.ABNORMAL) {
      throw new AppException(ResponseCode.ChannelAccountNotAuthorized)
    }
    return account
  }

  async markAccountOfflineForCredentialFailure(
    accountId: string,
    error: unknown,
    reason = 'platform_auth_failed',
  ): Promise<boolean> {
    if (!this.isCredentialFailure(error)) {
      return false
    }
    try {
      await this.markAccountOffline(accountId, reason)
      return true
    }
    catch (markError) {
      this.logger.warn(error, `Credential failure for account ${accountId}`)

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Re-run the platform OAuth flow to reconnect the account; this resets status to NORMAL with fresh tokens.
  2. Check account.status before credential calls and short-circuit with a 'reconnect required' user prompt instead of invoking the service.
  3. Filter scheduled/batch jobs to exclude ABNORMAL accounts to avoid systematic failures.
  4. Investigate the original failure (logs of markAccountOfflineForCredentialFailure) to confirm why the account went ABNORMAL before reconnecting.

Example fix

// before
await authService.refreshCredential(accountId, userId)
// after
const account = await accountRepo.getAccountById(accountId)
if (account?.status === AccountStatus.ABNORMAL) {
  throw new AppException(ResponseCode.ChannelAccountNotAuthorized, { accountId })
}
await authService.refreshCredential(accountId, userId)
Defensive patterns

Strategy: validation

Validate before calling

const account = await accountRepo.getAccountById(accountId)
if (account?.status === AccountStatus.ABNORMAL) {
  throw new AppException(ResponseCode.ChannelAccountNotAuthorized, { accountId })
}

Type guard

function isUsableAccount(a: { status: AccountStatus } | null): a is { status: AccountStatus } {
  return !!a && a.status === AccountStatus.NORMAL
}

Try / catch

try {
  await authService.refreshCredential(accountId, userId)
} catch (e) {
  if (e instanceof AppException && e.code === ResponseCode.ChannelAccountNotAuthorized) {
    return res.status(403).json({ message: 'Reconnect this channel to continue', accountId })
  }
  throw e
}

Prevention

When it happens

Trigger: Calling refreshCredential or any credential-consuming flow on an account that was previously marked offline/ABNORMAL after a platform auth failure; using an account after the user revoked access or the refresh token expired and the failure handler flagged it.

Common situations: Retry loops re-processing an account already marked offline; schedulers not filtering by account status; users trying to publish content with a disconnected channel without re-linking it.

Related errors


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