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
- Use an account without relayAccountRef (a directly-connected account) for these endpoints
- Route the operation through the Relay flow using the relayAccountRef provided in the exception
- Check the environment/key pairing: cn accounts with aitoearn.cn, intl accounts with aitoearn.ai per project rules
- 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
- Check relayAccountRef before choosing direct vs relay code paths
- Match environment and API key (aitoearn.cn vs aitoearn.ai)
- Keep relay and direct account IDs clearly separated in your data model
- Handle RelayAccountException by switching transports, not by retrying the same call
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
- body.code
- AiCallFailed
- Relay API error [${body.code}]: ${body.message}
- Relay uploadSign returned no uploadUrl: ${JSON.stringify(sig
- InvalidModel
AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31).
Data as JSON: /api/errors/c1b607093280a562.
Report an issue: GitHub.