yikart/AiToEarn · error · YouTubePlatformException

15070

15070

Error message

{{platform}} platform API request failed

What it means

YouTubeService installs a response interceptor in createHttpClient that converts every failed Axios response into YouTubePlatformException.fromAxiosError. Because the client is created in the constructor, any YouTube API call made through this instance that returns an HTTP error (or network failure) surfaces as code ChannelPlatformApiFailed (15070) with message '{{platform}} platform API request failed'. The underlying status, endpoint, and YouTube error body are preserved in the exception's cause.

Source

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

import { YoutubeOAuthGrantType, YoutubeSearchOrder, YoutubeSearchType } from './youtube.schema'

@Injectable()
export class YoutubeService {
  private readonly http: AxiosInstance

  constructor(
    private readonly cfg: YoutubeConfig,
    private readonly mediaService: MediaService,
  ) {
    this.http = this.createHttpClient()
  }

  private createHttpClient(): AxiosInstance {
    const http = axios.create()
    http.interceptors.response.use(
      response => response,
      (error: AxiosError<YouTubeErrorBody>) => {
        throw YouTubePlatformException.fromAxiosError(error)
      },
    )
    return http
  }

  private createOAuth2Client() {
    return new google.auth.OAuth2(
      this.cfg.clientId,
      this.cfg.clientSecret,
      this.cfg.redirectUri,
    )
  }

  private createYouTubeClient(accessToken: string) {
    const auth = new google.auth.OAuth2()
    auth.setCredentials({ access_token: accessToken })
    return google.youtube({ version: 'v3', auth })
  }

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Read the exception cause (httpStatus, platformCode like quotaExceeded/invalidGrant) to identify the real failure.
  2. On 401, refresh the access token and retry once; refresh proactively before expiry based on expiresAt.
  3. On 403 quotaExceeded, back off and schedule work outside quota-heavy windows or request a quota increase.
  4. On 404, re-fetch the resource list and treat the item as gone rather than retrying.
  5. Retry only retryable statuses (429, 5xx) with exponential backoff; never retry 400/401/403 non-quota errors.

Example fix

// before
const res = await youtubeHttp.get('/youtube/v3/videos', { params })
// after: handle auth/quota explicitly
try {
  const res = await youtubeHttp.get('/youtube/v3/videos', { params })
} catch (e) {
  if (e.httpStatus === 401) return this.withRefreshedToken(() => fetchVideos(params))
  if (e.cause?.platformCode === 'quotaExceeded') throw new QuotaExceededException()
  throw e
}
Defensive patterns

Strategy: type-guard

Validate before calling

function isYouTubeRequestSafe(accessToken: string, expiresAt?: Date): boolean {
  return accessToken.length > 0 && (!expiresAt || expiresAt.getTime() > Date.now() + 60_000)
}

Type guard

function isYouTubePlatformError(e: unknown): e is ChannelPlatformException & { cause: { httpStatus?: number; platformCode?: string } } {
  return e instanceof ChannelPlatformException
    && e.code === ResponseCode.ChannelPlatformApiFailed
    && e.cause !== undefined
}

Try / catch

try {
  const res = await youtubeHttp.get('/youtube/v3/videos', { params })
  return res.data
} catch (e) {
  if (!isYouTubePlatformError(e)) throw e
  const { httpStatus, platformCode } = e.cause
  if (httpStatus === 401 || platformCode === 'invalidGrant') return refreshAndRetryOnce()
  if (platformCode === 'quotaExceeded' || httpStatus === 429) throw new QuotaBackoffException(e)
  if (httpStatus === 404) return null // resource gone
  throw e
}

Prevention

When it happens

Trigger: Any YouTube Data API request through this HttpClient returning non-2xx: expired/insufficient-scope access tokens (401/403 quotaExceeded/forbidden), quota exceeded (403), invalid video/resource IDs (404), malformed requests (400), rate limiting (429), or network-level Axios failures.

Common situations: YouTube OAuth tokens expiring without proactive refresh (401); daily API quota exhausted after heavy uploads (403 quotaExceeded); uploading videos longer than allowed for unverified apps; channel removed or video set private between listing and fetching; calling APIs without the right scope for private resources.

Related errors


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