{"record":{"id":"b19952068f774fd8","repo":"jackwener/OpenCLI","slug":"flomo-api-returned-http-resp-status","errorCode":null,"errorMessage":"Flomo API returned HTTP ${resp.status}","messagePattern":"Flomo API returned HTTP (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/flomo/memos.js","lineNumber":162,"sourceCode":"\nasync function fetchFlomoJson(url, token) {\n  let resp;\n  try {\n    resp = await fetch(url, {\n      headers: {\n        Authorization: 'Bearer ' + token,\n        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',\n        Accept: 'application/json',\n      },\n    });\n  } catch (err) {\n    throw new CommandExecutionError(`Failed to fetch Flomo memos: ${err instanceof Error ? err.message : String(err)}`);\n  }\n  if (resp.status === 401 || resp.status === 403) {\n    throw new AuthRequiredError(FLOMO_API_DOMAIN, `Flomo API returned HTTP ${resp.status}; please refresh your Flomo login session`);\n  }\n  if (!resp.ok) {\n    throw new CommandExecutionError(`Flomo API returned HTTP ${resp.status}`);\n  }\n  try {\n    return await resp.json();\n  } catch (err) {\n    throw new CommandExecutionError(`Flomo API returned malformed JSON: ${err instanceof Error ? err.message : String(err)}`);\n  }\n}\n\nasync function readAccessToken(page) {\n  const token = unwrapBrowserResult(await page.evaluate(buildGetTokenJs()));\n  if (typeof token !== 'string' || !token.trim()) {\n    throw new AuthRequiredError(FLOMO_API_DOMAIN, 'Flomo memos requires an active signed-in Flomo browser session');\n  }\n  return token.trim();\n}\n\nconst command = cli({\n  site: 'flomo',","sourceCodeStart":144,"sourceCodeEnd":180,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/flomo/memos.js#L144-L180","documentation":"fetchFlomoJson throws this CommandExecutionError when the Flomo API responds with any non-OK HTTP status other than 401/403 (e.g. 404, 429, 500, 502, 503). The CLI surfaces the bare status code so callers know the request reached the server but was rejected or failed server-side. 401/403 are deliberately excluded and raised as AuthRequiredError instead.","triggerScenarios":"Calling `flomo memos` during Flomo server incidents (5xx); rate limiting (429) from too-frequent calls; 404 if the /api/v1/memo/updated/ endpoint path or the pinned app_version 4.0 sign parameters become invalid; 400 from an invalid signature/timestamp combination.","commonSituations":"Hammering the CLI in a loop and hitting Flomo rate limits; Flomo outage or maintenance window; API version drift breaking the MD5-signed request parameters; misconfigured system clock producing bad `timestamp`/`sign` values.","solutions":["If status is 429, wait and back off before retrying — reduce call frequency.","For 5xx, check Flomo's service status and retry later; it is a server-side issue.","Ensure the CLI/opencli package is up to date in case the signed endpoint or parameters changed.","Verify your system clock is accurate, since the request signature includes the current timestamp.","For persistent non-transient statuses (e.g. 404), add retry-with-backoff only around 429/5xx and surface others directly."],"exampleFix":"// before: no distinction between retryable and fatal statuses\nawait fetchFlomoJson(url, token);\n// after: caller retries only transient statuses\nconst retry = [429, 500, 502, 503, 504];\nfor (let i = 0; ; i++) {\n  try { return await fetchFlomoJson(url, token); }\n  catch (e) {\n    const m = /HTTP (\\d+)/.exec(e.message);\n    if (!m || !retry.includes(Number(m[1])) || i >= 3) throw e;\n    await new Promise((r) => setTimeout(r, 2 ** i * 1000));\n  }\n}","handlingStrategy":"retry","validationCode":"// no caller-side check can prevent server-side statuses; optionally pre-check service health\nconst health = await fetch('https://flomoapp.com', { method: 'HEAD' }).then((r) => r.status).catch(() => 0);\nif (health >= 500) console.warn('Flomo appears to be having server issues; expect HTTP 5xx');","typeGuard":"null","tryCatchPattern":"const RETRYABLE = new Set([429, 500, 502, 503, 504]);\ntry {\n  body = await fetchFlomoJson(url, token);\n} catch (err) {\n  const m = /HTTP (\\d+)/.exec(err.message || '');\n  if (m && RETRYABLE.has(Number(m[1]))) {\n    for (let i = 1; i <= 3; i++) {\n      await new Promise((r) => setTimeout(r, 2 ** i * 1000));\n      try { body = await fetchFlomoJson(url, token); break; } catch {}\n    }\n  }\n  if (!body) throw err;\n}","preventionTips":["Throttle CLI invocations to avoid 429 rate limiting from Flomo.","Retry only transient statuses (429/5xx) with exponential backoff; fail fast on 4xx.","Keep the opencli package updated in case the signed endpoint/parameters change.","Keep the system clock accurate — request signatures embed the current timestamp.","Watch Flomo's service status before bulk runs."],"tags":["http-error","rate-limit","server-error","flomo","network"],"backgroundTag":"http-5xx-server-error","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}