{"record":{"id":"e51739fe46876c89","repo":"yikart/AiToEarn","slug":"15070-e51739","errorCode":"15070","errorMessage":"{{platform}} platform API request failed","messagePattern":"(.+?)\\} platform API request failed","errorType":"exception","errorClass":"TwitterPlatformException","httpStatus":null,"severity":"error","filePath":"project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/twitter/twitter.service.ts","lineNumber":41,"sourceCode":"import { TwitterPlatformException, TwitterWorkLinkException, TwitterWorkNotFoundException } from './twitter.exception'\n\n@Injectable()\nexport class TwitterService {\n  private readonly logger = new Logger(TwitterService.name)\n  private readonly oauthHttp: AxiosInstance\n\n  constructor(\n    private readonly cfg: TwitterConfig,\n  ) {\n    this.oauthHttp = this.createOAuthHttpClient()\n  }\n\n  private createOAuthHttpClient(): AxiosInstance {\n    const http = axios.create()\n    http.interceptors.response.use(\n      response => response,\n      (error: AxiosError<TwitterOAuthErrorBody>) => {\n        throw TwitterPlatformException.fromAxiosError(error)\n      },\n    )\n    return http\n  }\n\n  private createOAuth2Client(scopes?: string[]) {\n    return new OAuth2({\n      clientId: this.cfg.clientId,\n      clientSecret: this.cfg.clientSecret || undefined,\n      redirectUri: this.cfg.redirectUri,\n      scope: scopes,\n    })\n  }\n\n  private createApiClient(accessToken: string): Client {\n    return new Client({\n      accessToken,\n      headers: { 'Content-Type': 'application/json' },","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/twitter/twitter.service.ts#L23-L59","documentation":"TwitterService.createOAuthHttpClient() builds a bare Axios instance for Twitter OAuth endpoints (token exchange/refresh at api.x.com / twitter.com). Its response interceptor converts any AxiosError carrying a TwitterOAuthErrorBody ({error, error_description}) into TwitterPlatformException with code ChannelPlatformApiFailed (15070). This surfaces OAuth-level failures such as invalid client credentials, bad grant, or invalid redirect URI.","triggerScenarios":"OAuth token requests fail: invalid or expired authorization code, mismatched client_id/client_secret, redirect_uri not matching the registered callback, unsupported grant_type, expired/revoked refresh token, or network failure reaching the Twitter OAuth endpoint.","commonSituations":"User takes too long between authorization and code exchange (code expires ~30s-30min); wrong callback URL registered in the X developer portal; X API credentials rotated or app suspended; environment misconfig between production/staging callback URLs; refresh token revoked by user.","solutions":["Compare exception.cause (error, error_description) with Twitter OAuth error values (invalid_grant, invalid_client, unauthorized_client)","If invalid_grant: the auth code was used/expired — restart the OAuth flow with a fresh state","Verify client_id/client_secret and redirect_uri exactly match the X developer portal app settings","For refresh failures, mark the channel disconnected and prompt the user to re-authorize","Check network access to the Twitter OAuth host if no HTTP status was returned"],"exampleFix":"// before\nconst token = await twitterService.exchangeCode(code) // throws 15070 on invalid_grant\n// after\ntry {\n  const token = await twitterService.exchangeCode(code)\n} catch (e) {\n  if (e instanceof ChannelPlatformException && e.cause?.platformMessage?.includes('invalid_grant')) {\n    // code expired/used — redirect user to twitterService.generateAuthUrl(...) again\n  }\n  throw e\n}","handlingStrategy":"try-catch","validationCode":"if (!clientId || !clientSecret || !redirectUri) throw new Error('Twitter OAuth credentials/redirect_uri are not configured')\n// also: use the code immediately — authorization codes are single-use and short-lived","typeGuard":"function isTwitterPlatformException(e: unknown): e is TwitterPlatformException {\n  return e instanceof TwitterPlatformException\n}","tryCatchPattern":"try {\n  const token = await twitterService.exchangeCode(code)\n} catch (e) {\n  if (e instanceof ChannelPlatformException) {\n    const desc = e.cause?.platformMessage ?? ''\n    if (desc.includes('invalid_grant')) return restartOAuthFlow() // code expired/used\n    if (desc.includes('invalid_client')) logger.error('Check TWITTER_CLIENT_ID/SECRET and redirect_uri')\n  }\n  throw e\n}","preventionTips":["Exchange authorization codes immediately; never reuse a code","Ensure the redirect_uri matches the X developer portal app exactly, including scheme and trailing slash","Distinguish OAuth errors via cause.platformMessage (error_description) for targeted handling","Detect refresh-token revocation and mark the channel as needing re-authorization","Keep separate credentials per environment and never mix staging callbacks with production apps"],"tags":["twitter","oauth","axios","x-api","platform-integration"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}