yikart/AiToEarn · error · FacebookPlatformException
15070
15070
Error message
{{platform}} platform API request failed What it means
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.
Source
Thrown at project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/facebook/facebook.service.ts:46
@Injectable()
export class FacebookService {
private readonly logger = new Logger(FacebookService.name)
private readonly http: AxiosInstance
constructor(
private readonly cfg: FacebookConfig,
private readonly mediaService: MediaService,
) {
this.http = this.createHttpClient()
}
private createHttpClient(): AxiosInstance {
const http = axios.create()
http.interceptors.response.use(
response => response,
(error: AxiosError<FacebookErrorBody>) => {
throw FacebookPlatformException.fromAxiosError(error)
},
)
return http
}
private get graphApiBaseUrl(): string {
return `https://graph.facebook.com/${this.cfg.graphApiVersion}`
}
private get facebookDialogBaseUrl(): string {
return `https://www.facebook.com/${this.cfg.graphApiVersion}`
}
generateAuthUrl(scopes: string[], state: string): string {
const params = new URLSearchParams({
client_id: this.cfg.clientId,
redirect_uri: this.cfg.redirectUri,
scope: scopes.join(','),View on GitHub (pinned to d3aa8bea5b)
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.
Example fix
// before: generic catch hides the Graph error subcode
try {
await facebookService.publish(page, post)
} catch (e) {
this.logger.error('fb publish failed', e)
}
// after: branch on the classified platform exception
try {
await facebookService.publish(page, post)
} catch (e) {
if (e instanceof ChannelPlatformException) {
if (e.cause?.httpStatus === 401 || e.cause?.platformCode === 190) {
return this.channels.markNeedsReauth(page.accountId)
}
if (e.retryable) return this.retryWithBackoff(e)
}
throw e
} Defensive patterns
Strategy: try-catch
Validate before calling
// pre-flight: verify token and required permission before Graph calls
if (!account.facebookPageToken || account.facebookTokenExpiresAt < Date.now()) {
throw new Error('Facebook token missing or expired; reconnect required')
}
if (!grantedScopes.includes('pages_manage_posts')) {
throw new Error('Facebook account lacks pages_manage_posts permission')
} Type guard
import { ChannelPlatformException } from '../channels/platforms/platforms.exception'
function isFacebookPlatformError(e: unknown): e is ChannelPlatformException {
return e instanceof ChannelPlatformException
&& e.platform === 'facebook'
&& (e.cause?.platformCode !== undefined || typeof e.cause?.httpStatus === 'number')
} Try / catch
try {
return await facebookService.publish(page, post)
} catch (e) {
if (isFacebookPlatformError(e)) {
if (e.cause?.platformCode === 190 || e.cause?.httpStatus === 401) {
return markAccountNeedsReauth(page.accountId)
}
if ([4, 17, 613].includes(Number(e.cause?.platformCode)) || e.retryable) {
return retryWithBackoff(e)
}
}
throw e
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31).
Data as JSON: /api/errors/d8bdd4cf63dbc836.
Report an issue: GitHub.