{"record":{"id":"7ae355a3a5002fdd","repo":"santifer/career-ops","slug":"apify-invalid-actorid-json-stringify-actorid","errorCode":null,"errorMessage":"apify: invalid actorId ${JSON.stringify(actorId)}. Expected \"owner/actor\" or \"owner~actor\" with letters, digits, \"_\", \".\", or \"-\" only.","messagePattern":"apify: invalid actorId (.+?)\\. Expected \"owner/actor\" or \"owner~actor\" with letters, digits, \"_\", \"\\.\", or \"-\" only\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"plugins/apify/_apify.mjs","lineNumber":39,"sourceCode":"const DEFAULT_RUN_TIMEOUT_MS = 180_000;\nconst POLL_INTERVAL_MS = 3_000;\nconst PER_REQUEST_TIMEOUT_MS = 15_000;\nconst CONNECT_RETRY_ATTEMPTS = 3;\nconst TERMINAL_STATUSES = new Set(['SUCCEEDED', 'FAILED', 'ABORTED', 'TIMED-OUT']);\n\nexport function hasToken(token = process.env.APIFY_TOKEN) {\n  return Boolean(token);\n}\n\n// Apify accepts both \"user/actor\" and \"user~actor\" in URLs; normalize to `~`.\n// Validate strictly so a malformed config can't escape the intended\n// /acts/<actor>/runs path with extra `/`, `..`, `?`, or `#` characters and\n// send our bearer token to an unintended endpoint on api.apify.com.\nconst ACTOR_ID_RE = /^[A-Za-z0-9][A-Za-z0-9_.-]*[~/][A-Za-z0-9][A-Za-z0-9_.-]*$/;\n\nexport function normalizeActorId(actorId) {\n  if (typeof actorId !== 'string' || !ACTOR_ID_RE.test(actorId)) {\n    throw new Error(\n      `apify: invalid actorId ${JSON.stringify(actorId)}. ` +\n      `Expected \"owner/actor\" or \"owner~actor\" with letters, digits, \"_\", \".\", or \"-\" only.`\n    );\n  }\n  const [owner, name] = actorId.split(/[~/]/, 2);\n  return `${encodeURIComponent(owner)}~${encodeURIComponent(name)}`;\n}\n\n// Apify supports auth via ?token= or Authorization: Bearer. The query-string\n// form leaks the token into HTTP access logs and any error/log line that\n// includes the URL, so always use the header.\nfunction authHeaders(token) {\n  return { authorization: `Bearer ${token}` };\n}\n\nfunction sleep(ms) {\n  return new Promise(r => setTimeout(r, ms));\n}","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/plugins/apify/_apify.mjs#L21-L57","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nawait startRun('https://apify.com/some-owner/some-actor?foo=1');\n// after\nawait startRun('some-owner/some-actor');","handlingStrategy":"validation","validationCode":"const ACTOR_ID_RE = /^[A-Za-z0-9][A-Za-z0-9_.-]*[~/][A-Za-z0-9][A-Za-z0-9_.-]*$/;\nfunction assertValidActorId(id) {\n  if (typeof id !== 'string' || !ACTOR_ID_RE.test(id)) {\n    throw new Error(`invalid actorId: ${JSON.stringify(id)}`);\n  }\n}\n// or derive from a URL first:\nfunction actorIdFromUrl(input) {\n  const m = String(input).match(/apify\\.com\\/([^/?#]+)\\/([^/?#]+)/);\n  return m ? `${m[1]}/${m[2]}` : input;\n}","typeGuard":"function isActorId(v) {\n  return typeof v === 'string' &&\n    /^[A-Za-z0-9][A-Za-z0-9_.-]*[~/][A-Za-z0-9][A-Za-z0-9_.-]*$/.test(v);\n}","tryCatchPattern":"try {\n  const runId = await startRun(actorId, input);\n} catch (err) {\n  if (String(err.message).startsWith('apify: invalid actorId')) {\n    throw new Error(`Check the actor ID/config value: ${err.message}`);\n  }\n  throw err;\n}","preventionTips":["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."],"tags":["validation","input-validation","apify","identifier"],"backgroundTag":"invalid-argument-format","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}