santifer/career-ops · error · Error
apify: invalid actorId
Error message
apify: invalid actorId ${JSON.stringify(actorId)}. Expected "owner/actor" or "owner~actor" with letters, digits, "_", ".", or "-" only. What it means
normalizeActorId validates the Apify actor identifier against ACTOR_ID_RE, which requires exactly two segments separated by '~' or '/', each starting with an alphanumeric and containing only letters, digits, '_', '.', '-'. The validation exists so the ID can be safely interpolated into API paths without enabling path traversal or token leakage to unintended api.apify.com endpoints. A malformed actorId is rejected before any request is made.
Solutions
- Convert any full actor URL to its ID: take the path segments after apify.com → 'owner/actor'.
- Trim whitespace and remove query strings/fragments; ensure exactly one '~' or '/' separator between owner and actor name.
- Confirm each segment starts with a letter or digit and contains only [A-Za-z0-9_.-].
- If the ID comes from config or an env var, log JSON.stringify(actorId) to reveal hidden characters before calling.
Example fix
// before
await startRun('https://apify.com/some-owner/some-actor?foo=1');
// after
await startRun('some-owner/some-actor'); Defensive patterns
Strategy: validation
Validate before calling
const ACTOR_ID_RE = /^[A-Za-z0-9][A-Za-z0-9_.-]*[~/][A-Za-z0-9][A-Za-z0-9_.-]*$/;
function assertValidActorId(id) {
if (typeof id !== 'string' || !ACTOR_ID_RE.test(id)) {
throw new Error(`invalid actorId: ${JSON.stringify(id)}`);
}
}
// or derive from a URL first:
function actorIdFromUrl(input) {
const m = String(input).match(/apify\.com\/([^/?#]+)\/([^/?#]+)/);
return m ? `${m[1]}/${m[2]}` : input;
} Type guard
function isActorId(v) {
return typeof v === 'string' &&
/^[A-Za-z0-9][A-Za-z0-9_.-]*[~/][A-Za-z0-9][A-Za-z0-9_.-]*$/.test(v);
} Try / catch
try {
const runId = await startRun(actorId, input);
} catch (err) {
if (String(err.message).startsWith('apify: invalid actorId')) {
throw new Error(`Check the actor ID/config value: ${err.message}`);
}
throw err;
} Prevention
- Pass 'owner/actor' IDs, never full apify.com URLs.
- Trim and sanitize values read from env/config before use.
- Sanity-check IDs with the same regex in a unit test.
When it happens
Trigger: Calling an apify plugin function (startRun, etc.) with an actorId that is not a string, is empty, lacks the 'owner/actor' separator, has extra segments ('a/b/c'), contains '/', '..', '?', '#', whitespace, or starts with a non-alphanumeric character.
Common situations: Passing a full Apify URL ('https://apify.com/owner/actor') instead of the ID; pasting an actor name with a trailing slash or newline from docs; config/env values with stray quotes or spaces; IDs copied with the '~run' or run-id suffix appended.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- apify: entry has invalid field_map. Each of title, url…
- apify: entry missing 'actor' (e.g. misceres/indeed-scraper)
- apify: invalid timeoutMs
- reportNum must be a numeric report number
- 4dayweek: invalid URL
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/7ae355a3a5002fdd.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/apify/_apify.mjs:39
const DEFAULT_RUN_TIMEOUT_MS = 180_000;
const POLL_INTERVAL_MS = 3_000;
const PER_REQUEST_TIMEOUT_MS = 15_000;
const CONNECT_RETRY_ATTEMPTS = 3;
const TERMINAL_STATUSES = new Set(['SUCCEEDED', 'FAILED', 'ABORTED', 'TIMED-OUT']);
export function hasToken(token = process.env.APIFY_TOKEN) {
return Boolean(token);
}
// Apify accepts both "user/actor" and "user~actor" in URLs; normalize to `~`.
// Validate strictly so a malformed config can't escape the intended
// /acts/<actor>/runs path with extra `/`, `..`, `?`, or `#` characters and
// send our bearer token to an unintended endpoint on api.apify.com.
const ACTOR_ID_RE = /^[A-Za-z0-9][A-Za-z0-9_.-]*[~/][A-Za-z0-9][A-Za-z0-9_.-]*$/;
export function normalizeActorId(actorId) {
if (typeof actorId !== 'string' || !ACTOR_ID_RE.test(actorId)) {
throw new Error(
`apify: invalid actorId ${JSON.stringify(actorId)}. ` +
`Expected "owner/actor" or "owner~actor" with letters, digits, "_", ".", or "-" only.`
);
}
const [owner, name] = actorId.split(/[~/]/, 2);
return `${encodeURIComponent(owner)}~${encodeURIComponent(name)}`;
}
// Apify supports auth via ?token= or Authorization: Bearer. The query-string
// form leaks the token into HTTP access logs and any error/log line that
// includes the URL, so always use the header.
function authHeaders(token) {
return { authorization: `Bearer ${token}` };
}
function sleep(ms) {
return new Promise(r => setTimeout(r, ms));
}View on GitHub (pinned to aac998c7ed)