{"record":{"id":"83f52351c42822b3","repo":"santifer/career-ops","slug":"apify-did-not-return-a-run-id-json-stringify-bo","errorCode":null,"errorMessage":"Apify did not return a run id: ${JSON.stringify(body).slice(0, 200)}","messagePattern":"Apify did not return a run id: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"plugins/apify/_apify.mjs","lineNumber":126,"sourceCode":"  throw lastErr;\n}\n\nasync function startRun(actorId, input, token, deadline = null) {\n  const url = `${APIFY_API_BASE}/acts/${normalizeActorId(actorId)}/runs`;\n  const body = await fetchJson(\n    url,\n    {\n      method: 'POST',\n      headers: { 'content-type': 'application/json', ...authHeaders(token) },\n      body: JSON.stringify(input || {}),\n    },\n    PER_REQUEST_TIMEOUT_MS,\n    CONNECT_RETRY_ATTEMPTS,\n    deadline,\n  );\n  const runId = body?.data?.id;\n  if (!runId) {\n    throw new Error(`Apify did not return a run id: ${JSON.stringify(body).slice(0, 200)}`);\n  }\n  return runId;\n}\n\n// Best-effort — if we give up on a run, stop the actor so credits aren't wasted.\nasync function abortRun(runId, token) {\n  const url = `${APIFY_API_BASE}/actor-runs/${runId}/abort`;\n  const controller = new AbortController();\n  const timer = setTimeout(() => controller.abort(), 5_000);\n  try {\n    await fetch(url, { method: 'POST', headers: authHeaders(token), signal: controller.signal });\n  } catch {} finally {\n    clearTimeout(timer);\n  }\n}\n\nasync function waitForRun(runId, token, deadline, timeoutMs) {\n  const url = `${APIFY_API_BASE}/actor-runs/${runId}`;","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/plugins/apify/_apify.mjs#L108-L144","documentation":"Thrown by `startRun` (plugins/apify/_apify.mjs:126) after a successful POST to `/acts/<actor>/runs` when the JSON response body has no `data.id` field. Apify's start-run endpoint is expected to return `{ data: { id: '<runId>' } }`; an absent or malformed `data.id` means the run was not actually created (or the response shape changed), so the caller cannot poll for results. The first 200 chars of the body are included for diagnosis.","triggerScenarios":"Apify returns 200 but with an unexpected body (e.g. an error envelope without `data`, an HTML error page parsed as JSON, or an API-version mismatch where the id lives elsewhere). The check `const runId = body?.data?.id; if (!runId)` fires.","commonSituations":"Apify API version drift (response schema changed); hitting a deprecated actor endpoint; a transient Apify platform error returning a 200 with an error body; an actor that requires paid plan but returns a soft error; an intercepting proxy returning a captive-portal JSON.","solutions":["Inspect the 200-char body snippet in the message — it usually reveals whether it is an error envelope or a schema change.","Verify the actorId is valid and the actor still exists on the Apify store.","Check Apify status page / your account plan limits (a soft failure can omit the run id).","Retry once; if persistent, update the plugin if the Apify v2 response schema changed."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  const items = await runActor(actorId, input, opts);\n} catch (err) {\n  if (/did not return a run id/.test(err.message)) {\n    // Inspect the body snippet; likely API drift or a soft platform error.\n    console.error(`Apify start-run returned no id — check actor/account: ${err.message}`);\n    // retry once after a short delay\n  } else throw err;\n}","preventionTips":["Keep the apify plugin updated to track Apify v2 response schema.","Watch the 200-char body snippet in the message for early signs of API drift."],"tags":["apify","api","response-shape","network"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}