{"record":{"id":"d8bdd4cf63dbc836","repo":"yikart/AiToEarn","slug":"15070-d8bdd4","errorCode":"15070","errorMessage":"{{platform}} platform API request failed","messagePattern":"(.+?)\\} platform API request failed","errorType":"exception","errorClass":"FacebookPlatformException","httpStatus":null,"severity":"error","filePath":"project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/facebook/facebook.service.ts","lineNumber":46,"sourceCode":"\n@Injectable()\nexport class FacebookService {\n  private readonly logger = new Logger(FacebookService.name)\n  private readonly http: AxiosInstance\n\n  constructor(\n    private readonly cfg: FacebookConfig,\n    private readonly mediaService: MediaService,\n  ) {\n    this.http = this.createHttpClient()\n  }\n\n  private createHttpClient(): AxiosInstance {\n    const http = axios.create()\n    http.interceptors.response.use(\n      response => response,\n      (error: AxiosError<FacebookErrorBody>) => {\n        throw FacebookPlatformException.fromAxiosError(error)\n      },\n    )\n    return http\n  }\n\n  private get graphApiBaseUrl(): string {\n    return `https://graph.facebook.com/${this.cfg.graphApiVersion}`\n  }\n\n  private get facebookDialogBaseUrl(): string {\n    return `https://www.facebook.com/${this.cfg.graphApiVersion}`\n  }\n\n  generateAuthUrl(scopes: string[], state: string): string {\n    const params = new URLSearchParams({\n      client_id: this.cfg.clientId,\n      redirect_uri: this.cfg.redirectUri,\n      scope: scopes.join(','),","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/facebook/facebook.service.ts#L28-L64","documentation":"FacebookService's createHttpClient installs a single axios response-rejection interceptor that converts every AxiosError on calls to the Facebook Graph API into a FacebookPlatformException (ChannelPlatformException) with message '{{platform}} platform API request failed' (code 15070). Unlike Bilibili/Douyin, there is no fromPlatformResponse interceptor here — non-2xx Graph API responses (Graph signals errors via HTTP status) are caught by this interceptor and wrapped with httpStatus, endpoint, and a classified category/retryable flag.","triggerScenarios":"Graph API calls from this client (token exchange, page publishing, media upload to graph.facebook.com) returning 400/401/403/429/5xx; long-lived page token expired or user revoked permissions; app-level rate limiting (code 4, 17) or ads-rate-limit (code 613); network-level failures (timeout, DNS, proxy) where error.response is undefined.","commonSituations":"Expired Facebook access tokens after the 60-day sliding window without reconnect; missing publish_pages/pages_manage_posts permissions after app review changes; Meta platform outages (5xx); hitting Graph API rate limits during bulk publishing; wrong FACEBOOK_APP_ID/SECRET or redirect URI producing 400 on OAuth token exchange.","solutions":["Inspect exception.cause.httpStatus and platformCode from error.response.data.error: 190 = invalid/expired token → reconnect the Facebook account; 4/17/613 = rate limit → back off and retry later.","If e.retryable is true (5xx or network-level failure), retry with exponential backoff; for 401/403 mark the channel as needing reauthorization instead of retrying.","Use exception.context.endpoint (graphApiBaseUrl + path) to identify which Graph call failed and check required permissions/scopes for it.","Verify FACEBOOK app credentials and that the server can reach graph.facebook.com (proxy/DNS/firewall) — reproduce with curl from the container."],"exampleFix":"// before: generic catch hides the Graph error subcode\ntry {\n  await facebookService.publish(page, post)\n} catch (e) {\n  this.logger.error('fb publish failed', e)\n}\n\n// after: branch on the classified platform exception\ntry {\n  await facebookService.publish(page, post)\n} catch (e) {\n  if (e instanceof ChannelPlatformException) {\n    if (e.cause?.httpStatus === 401 || e.cause?.platformCode === 190) {\n      return this.channels.markNeedsReauth(page.accountId)\n    }\n    if (e.retryable) return this.retryWithBackoff(e)\n  }\n  throw e\n}","handlingStrategy":"try-catch","validationCode":"// pre-flight: verify token and required permission before Graph calls\nif (!account.facebookPageToken || account.facebookTokenExpiresAt < Date.now()) {\n  throw new Error('Facebook token missing or expired; reconnect required')\n}\nif (!grantedScopes.includes('pages_manage_posts')) {\n  throw new Error('Facebook account lacks pages_manage_posts permission')\n}","typeGuard":"import { ChannelPlatformException } from '../channels/platforms/platforms.exception'\n\nfunction isFacebookPlatformError(e: unknown): e is ChannelPlatformException {\n  return e instanceof ChannelPlatformException\n    && e.platform === 'facebook'\n    && (e.cause?.platformCode !== undefined || typeof e.cause?.httpStatus === 'number')\n}","tryCatchPattern":"try {\n  return await facebookService.publish(page, post)\n} catch (e) {\n  if (isFacebookPlatformError(e)) {\n    if (e.cause?.platformCode === 190 || e.cause?.httpStatus === 401) {\n      return markAccountNeedsReauth(page.accountId)\n    }\n    if ([4, 17, 613].includes(Number(e.cause?.platformCode)) || e.retryable) {\n      return retryWithBackoff(e)\n    }\n  }\n  throw e\n}","preventionTips":["Refresh long-lived Facebook tokens before their ~60-day expiry; schedule proactive re-auth for connected pages.","Respect Graph rate limits: throttle page publishing and back off on error codes 4/17/613.","Re-verify granted scopes after app review changes; missing permissions surface as 403 from this interceptor.","Health-check connectivity to graph.facebook.com (proxy/DNS) in deployment.","Log cause.httpStatus and cause.platformCode for every wrapped Graph error to keep debugging fast."],"tags":["facebook","graph-api","http","third-party","axios"],"backgroundTag":"platform-api-request-failed","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}