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

  1. Convert any full actor URL to its ID: take the path segments after apify.com → 'owner/actor'.
  2. Trim whitespace and remove query strings/fragments; ensure exactly one '~' or '/' separator between owner and actor name.
  3. Confirm each segment starts with a letter or digit and contains only [A-Za-z0-9_.-].
  4. 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

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


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)