yikart/AiToEarn · error · RelayAccountException

This account requires relay

Error message

This account requires relay

What it means

getPlatformAccount() rejects accounts that are configured as relay accounts. When account.relayAccountRef is set, a RelayAccountException carrying the relayAccountRef is thrown instead of returning the account, because direct platform API calls cannot be made through a non-relay-capable code path for that account.

Source

Thrown at project/aitoearn-backend/apps/aitoearn-server/src/core/channels/works/work.service.ts:229

    const credential = await this.authService.getValidCredential(accountId, userId)
    return {
      accessToken: credential.accessToken,
      refreshToken: credential.refreshToken,
      platformUid: account.uid,
      account: account.account,
    }
  }

  private async getPlatformAccount(userId: string, accountId: string, platform: AccountType) {
    const account = await this.accountRepository.getByIdAndUserId(accountId, userId)
    if (!account) {
      throw new AppException(ResponseCode.AccountNotFound)
    }
    if (account.type !== platform) {
      throw new AppException(ResponseCode.ChannelAuthPlatformMismatch)
    }
    if (account.relayAccountRef) {
      throw new RelayAccountException(account.relayAccountRef, accountId)
    }
    return account
  }

  private async callPlatformProvider<T>(accountId: string | undefined, action: () => Promise<T>): Promise<T> {
    try {
      return await action()
    }
    catch (error) {
      if (accountId) {
        await this.authService.markAccountOfflineForCredentialFailure(accountId, error, 'platform_auth_failed')
      }
      throw error
    }
  }

  private async saveWorkSnapshots(
    userId: string | undefined,

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Use an account without relayAccountRef (a directly-connected account) for these endpoints
  2. Route the operation through the Relay flow using the relayAccountRef provided in the exception
  3. Check the environment/key pairing: cn accounts with aitoearn.cn, intl accounts with aitoearn.ai per project rules
  4. Re-create or re-link the account in non-relay mode if direct access is required

Example fix

// before
const info = await workService.resolveLinkInfo(userId, relayAccountId) // throws
// after
const acct = await accountService.get(userId, id)
if (acct.relayAccountRef) {
  await relayClient.execute(acct.relayAccountRef, 'resolveLinkInfo', payload)
} else {
  const info = await workService.resolveLinkInfo(userId, acct.id)
}
Defensive patterns

Strategy: fallback

Validate before calling

const acct = await accountStore.get(accountId)
if (acct && acct.relayAccountRef) {
  // must go through relay
}

Type guard

function isDirectAccount(a: { relayAccountRef?: string | null }): boolean {
  return !a.relayAccountRef
}

Try / catch

try {
  const info = await workService.resolveLinkInfo(userId, accountId)
} catch (e) {
  if (e instanceof RelayAccountException) {
    result = await relayClient.execute(e.relayAccountRef, 'resolveLinkInfo', payload)
  } else throw e
}

Prevention

When it happens

Trigger: Calling resolveLinkInfo or account endpoints with an accountId whose stored account has relayAccountRef populated — i.e. the account is registered to operate via Relay, so direct provider access is refused.

Common situations: Passing a relay-managed account ID into APIs that expect a directly-connected platform account; environment mismatch where the account was created under relay mode; copying an accountId from a relay setup into a non-relay flow.

Related errors


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