{"record":{"id":"456c8c6fc48cb270","repo":"jackwener/OpenCLI","slug":"search-failed-http-res-status","errorCode":null,"errorMessage":"Search failed: HTTP ${res.status}","messagePattern":"Search failed: HTTP (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"clis/tiktok/search.js","lineNumber":19,"sourceCode":"import { cli } from '@jackwener/opencli/registry';\ncli({\n    site: 'tiktok',\n    name: 'search',\n    access: 'read',\n    description: 'Search TikTok videos',\n    domain: 'www.tiktok.com',\n    args: [\n        { name: 'query', required: true, positional: true, help: 'Search query' },\n        { name: 'limit', type: 'int', default: 10, help: 'Number of results' },\n    ],\n    columns: ['rank', 'desc', 'author', 'url', 'plays', 'likes', 'comments', 'shares'],\n    pipeline: [\n        { navigate: { url: 'https://www.tiktok.com/explore', settleMs: 5000 } },\n        { evaluate: `(async () => {\n  const query = \\${{ args.query | json }};\n  const limit = \\${{ args.limit }};\n  const res = await fetch('/api/search/general/full/?keyword=' + encodeURIComponent(query) + '&offset=0&count=' + limit + '&aid=1988', { credentials: 'include' });\n  if (!res.ok) throw new Error('Search failed: HTTP ' + res.status);\n  const data = await res.json();\n  const items = (data.data || []).filter(function(i) { return i.type === 1 && i.item; });\n  return items.slice(0, limit).map(function(i, idx) {\n    var v = i.item;\n    var a = v.author || {};\n    var s = v.stats || {};\n    return {\n      rank: idx + 1,\n      desc: (v.desc || '').replace(/\\\\n/g, ' ').substring(0, 100),\n      author: a.uniqueId || '',\n      url: (a.uniqueId && v.id) ? 'https://www.tiktok.com/@' + a.uniqueId + '/video/' + v.id : '',\n      plays: s.playCount || 0,\n      likes: s.diggCount || 0,\n      comments: s.commentCount || 0,\n      shares: s.shareCount || 0,\n    };\n  });\n})()","sourceCodeStart":1,"sourceCodeEnd":37,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/tiktok/search.js#L1-L37","documentation":"The search script fetches TikTok's internal /api/search/general/full/ endpoint from the explore page and throws 'Search failed: HTTP <status>' when res.ok is false. This surfaces the raw HTTP status of TikTok's search API, most commonly 403 from bot detection or an unauthenticated request.","triggerScenarios":"fetch to /api/search/general/full/?keyword=... returns non-2xx: 403 (bot detection/missing cookies), 429 (rate limited), 400 (bad query encoding), or 5xx (TikTok-side failure).","commonSituations":"Too many searches in a row triggering rate limits; no valid session cookie in the automation browser; special characters or very long queries; TikTok changing/protecting the internal search endpoint.","solutions":["Retry after a delay with backoff — 429/403 are often transient rate-limit responses","Ensure the automation browser has a logged-in TikTok session with valid cookies","Slow down request rate and randomize queries/offsets to avoid bot detection","Check the status code in the message: 4xx (auth/query problem) vs 5xx (TikTok outage) and act accordingly"],"exampleFix":"// before\nfor (const q of queries) await run('tiktok search', { query: q, limit: 50 });\n// after\nfor (const q of queries) {\n  try {\n    await run('tiktok search', { query: q, limit: 50 });\n  } catch (e) {\n    if (/Search failed: HTTP (403|429)/.test(e.message)) await sleep(30000); // backoff\n    else throw e;\n  }\n}","handlingStrategy":"retry","validationCode":"const probe = await fetch('/api/search/general/full/?keyword=test&offset=0&count=1&aid=1988', { credentials: 'include' });\nif (!probe.ok) throw new Error(`TikTok search API unavailable (HTTP ${probe.status}) — check session/rate limits`);","typeGuard":"function isSearchOk(res) {\n  return res && typeof res.status === 'number' && res.status >= 200 && res.status < 300;\n}","tryCatchPattern":"async function searchWithBackoff(query, limit, attempts = 3) {\n  for (let i = 0; i < attempts; i++) {\n    try {\n      return await run('tiktok search', { query, limit });\n    } catch (e) {\n      const m = e.message.match(/HTTP (\\d+)/);\n      if (!m || ![403, 429, 500, 502, 503].includes(+m[1]) || i === attempts - 1) throw e;\n      await new Promise(r => setTimeout(r, 15000 * 2 ** i));\n    }\n  }\n}","preventionTips":["Throttle search frequency and add jittered delays between queries","Keep an authenticated session so the internal API does not 403","Read the HTTP status in the message to distinguish rate limits (429) from auth (403) vs outage (5xx)","Cache query results to reduce repeated API hits"],"tags":["tiktok","http","rate-limit","scraping","auth"],"backgroundTag":"http-request-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}