santifer/career-ops · error · Error
Apify did not return a run id
Error message
Apify did not return a run id: ${JSON.stringify(body).slice(0, 200)} What it means
startRun posts to the Apify API to launch an actor run and expects the response body to contain a run id at body.data.id. If the response parses but lacks that field, the library cannot track or poll the run, so it throws with a truncated JSON dump (first 200 chars) of the unexpected body. This treats the malformed/failed response as an explicit error instead of returning undefined.
Solutions
- Inspect the truncated body in the message — it usually contains Apify's own error field explaining the failure (e.g. invalid token).
- Verify APIFY_TOKEN is valid and has run permission: `curl -H "Authorization: Bearer $APIFY_TOKEN" https://api.apify.com/v2/users/me`.
- Confirm the actorId is runnable by your account and the plan allows concurrent runs.
- Retry on transient 5xx/429 (the wrapper already retries connects; a hard API error body will still fail) or check the Apify status page.
Example fix
// before: silent default token
const token = process.env.APIFY_TOKEN; // undefined → error body in message
// after: fail fast with a clear message
if (!process.env.APIFY_TOKEN) throw new Error('missing APIFY_TOKEN');
const token = process.env.APIFY_TOKEN; Defensive patterns
Strategy: try-catch
Validate before calling
if (!process.env.APIFY_TOKEN) throw new Error('missing APIFY_TOKEN before calling startRun'); Type guard
function hasRunId(body) {
return !!body && typeof body === 'object' &&
!!body.data && typeof body.data === 'object' &&
typeof body.data.id === 'string' && body.data.id.length > 0;
} Try / catch
try {
const runId = await startRun(actorId, input);
} catch (err) {
if (String(err.message).startsWith('Apify did not return a run id')) {
// message embeds the first 200 chars of the error body — log or surface it
console.error('Apify startRun failed:', err.message);
// typically invalid token / plan limit / actor not runnable
return null;
}
throw err;
} Prevention
- Verify APIFY_TOKEN with GET /v2/users/me before launching runs.
- Log the truncated body from the message to read Apify's own error field.
- Watch the Apify status page for incidents that change response envelopes.
When it happens
Trigger: Calling startRun when the Apify API returns a 4xx/5xx error envelope (e.g. invalid token, insufficient plan, actor not found) wrapped as parseable JSON without data.id, or an unexpected response shape after an Apify API change.
Common situations: Expired or revoked APIFY_TOKEN; wrong token scope or plan limits; actor ID that exists but the account cannot run; transient Apify API incidents returning HTML/error JSON; proxying through a gateway that rewrites responses.
Understand the failure class
Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.
Related errors
- API error: code=
- Apify actor finished with status
- Apify run returned non-array dataset payload
- getonbrd: unexpected API response for category
- glints: unexpected API response
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/83f52351c42822b3.
Report an issue: GitHub.
Appendix: 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 aac998c7ed)