santifer/career-ops · error · Error

Apify did not return a run id: ${JSON.stringify(body).slice(

Error message

Apify did not return a run id: ${JSON.stringify(body).slice(0, 200)}

What it means

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.

Source

Thrown at plugins/apify/_apify.mjs:126

  throw lastErr;
}

async function startRun(actorId, input, token, deadline = null) {
  const url = `${APIFY_API_BASE}/acts/${normalizeActorId(actorId)}/runs`;
  const body = await fetchJson(
    url,
    {
      method: 'POST',
      headers: { 'content-type': 'application/json', ...authHeaders(token) },
      body: JSON.stringify(input || {}),
    },
    PER_REQUEST_TIMEOUT_MS,
    CONNECT_RETRY_ATTEMPTS,
    deadline,
  );
  const runId = body?.data?.id;
  if (!runId) {
    throw new Error(`Apify did not return a run id: ${JSON.stringify(body).slice(0, 200)}`);
  }
  return runId;
}

// Best-effort — if we give up on a run, stop the actor so credits aren't wasted.
async function abortRun(runId, token) {
  const url = `${APIFY_API_BASE}/actor-runs/${runId}/abort`;
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), 5_000);
  try {
    await fetch(url, { method: 'POST', headers: authHeaders(token), signal: controller.signal });
  } catch {} finally {
    clearTimeout(timer);
  }
}

async function waitForRun(runId, token, deadline, timeoutMs) {
  const url = `${APIFY_API_BASE}/actor-runs/${runId}`;

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Inspect the 200-char body snippet in the message — it usually reveals whether it is an error envelope or a schema change.
  2. Verify the actorId is valid and the actor still exists on the Apify store.
  3. Check Apify status page / your account plan limits (a soft failure can omit the run id).
  4. Retry once; if persistent, update the plugin if the Apify v2 response schema changed.
Defensive patterns

Strategy: try-catch

Try / catch

try {
  const items = await runActor(actorId, input, opts);
} catch (err) {
  if (/did not return a run id/.test(err.message)) {
    // Inspect the body snippet; likely API drift or a soft platform error.
    console.error(`Apify start-run returned no id — check actor/account: ${err.message}`);
    // retry once after a short delay
  } else throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13). Data as JSON: /api/errors/83f52351c42822b3. Report an issue: GitHub.