Error handling
Based on the endpoint, the client automatically extracts the relevant data and returns it in the expected format. Date strings, for instance, are converted to Date objects. For details, see Typed models. If an error occurs, the client throws an ApifyApiError, which wraps the plain JSON errors returned by the API and enriches them with other context for easier debugging.
import { ApifyApiError, ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'MY-APIFY-TOKEN' });
try {
const { items } = await client.dataset('non-existing-dataset-id').listItems();
} catch (error) {
if (!(error instanceof ApifyApiError)) throw error;
// Log the details of the failed request for easier debugging.
const { message, type, statusCode, clientMethod, path } = error;
console.log({ message, type, statusCode, clientMethod, path });
}
Error subclasses
The client throws the ApifyApiError subclass that matches the HTTP status code of the response, so a catch block can branch on instanceof instead of comparing status codes:
| Status | Subclass |
|---|---|
| 400 | InvalidRequestError |
| 401 | UnauthorizedError |
| 403 | ForbiddenError |
| 404 | NotFoundError |
| 409 | ConflictError |
| 429 | RateLimitError |
| 5xx | ServerError |
Any other status code throws a plain ApifyApiError. Every subclass extends ApifyApiError, so instanceof ApifyApiError still matches all of them. Check for a specific subclass when you want to react differently to, for example, a missing resource or a rate limit:
import { ApifyClient, NotFoundError, RateLimitError } from 'apify-client';
const client = new ApifyClient({ token: 'MY-APIFY-TOKEN' });
try {
await client.actor('my-actor').call({ url: 'https://example.com' });
} catch (error) {
if (error instanceof NotFoundError) {
// The Actor doesn't exist, or the token can't see it.
} else if (error instanceof RateLimitError) {
// The retries are exhausted, so back off and try again later.
} else {
throw error;
}
}
Errors with the same status code differ in type, the machine-readable identifier the API returns. The field is typed with the known values, so your editor autocompletes them:
import { ApifyApiError, ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'MY-APIFY-TOKEN' });
try {
await client.actor('my-actor').call({ url: 'https://example.com' }, { memory: 32768 });
} catch (error) {
if (error instanceof ApifyApiError && error.type === 'actor-memory-limit-exceeded') {
// The account has no memory left for another run, so wait and start it later.
} else {
throw error;
}
}
Missing resources
When you address a resource by ID, get() resolves to undefined on a 404 instead of throwing, and delete() resolves without error. getRecord() and getRequest() do the same for a missing record or request, recordExists() answers false, and lastRun().get() resolves to undefined for an Actor or task with no matching run.
Everywhere else a 404 throws a NotFoundError. That covers clients chained off a run or build without an ID, such as client.run('run-id').dataset() or client.build('build-id').log(), where the missing resource may be the parent rather than the sub-resource. It also covers fixed sub-paths such as getStatistics(), monthlyUsage(), limits(), getLog(), getInput() and test(), where a 404 means the parent is gone.
import { ApifyClient, NotFoundError } from 'apify-client';
const client = new ApifyClient({ token: 'MY-APIFY-TOKEN' });
// Resolves to `undefined`.
const actor = await client.actor('missing-actor').get();
try {
await client.run('missing-run').dataset().get();
} catch (error) {
if (error instanceof NotFoundError) {
// Either the run or its default dataset doesn't exist.
}
}
Invalid arguments
Before sending a request, the client validates the arguments you passed. When a value doesn't match the expected shape, the client throws an ArgumentValidationError without reaching the API. Its message names the offending field and the value it received. For programmatic inspection, issues carries the structured zod issues and cause carries the original ZodError.
import { ApifyClient, ArgumentValidationError } from 'apify-client';
const client = new ApifyClient({ token: 'MY-APIFY-TOKEN' });
try {
// @ts-expect-error TypeScript rejects the string at compile time, plain JavaScript reaches the runtime check.
await client.dataset('my-dataset').listItems({ limit: 'ten' });
} catch (error) {
if (error instanceof ArgumentValidationError) {
// Invalid input: expected number, received the string `ten` at `limit` in `DatasetClientListItemOptions`
console.log(error.message);
// [{ code: 'invalid_type', expected: 'number', path: ['limit'], ... }]
console.log(error.issues);
}
}
Invalid responses
The client validates every response it turns into a typed value against the Apify OpenAPI specification. A response that doesn't match throws a ResponseValidationError. Its message names the request and every offending field, and issues and cause carry the structured detail, the same way ArgumentValidationError does. For what the validation checks and what it lets through, see Response validation.
import { ApifyClient, ResponseValidationError } from 'apify-client';
const client = new ApifyClient({ token: 'MY-APIFY-TOKEN' });
try {
await client.actor('my-actor').get();
} catch (error) {
if (error instanceof ResponseValidationError) {
// Response from GET https://api.apify.com/v2/acts/my-actor does not match the API schema:
// Invalid input: expected string, received null at `name`
// The API returned something its OpenAPI specification does not describe. Please report this at https://github.com/apify/apify-client-js/issues.
console.log(error.message);
console.log(error.issues);
}
}
Catching all client errors
Every error the client throws for a failed API call extends ApifyClientError: ApifyApiError and its subclasses, ResponseValidationError, and InvalidResponseBodyError, which the client throws when a response body doesn't parse as its content type claims. A single instanceof ApifyClientError check catches all of them:
import { ApifyClient, ApifyClientError } from 'apify-client';
const client = new ApifyClient({ token: 'MY-APIFY-TOKEN' });
try {
await client.actor('my-actor').call({ url: 'https://example.com' });
} catch (error) {
if (error instanceof ApifyClientError) {
// An ApifyApiError, an InvalidResponseBodyError or a ResponseValidationError.
console.log(`${error.name}: ${error.message}`);
} else {
throw error;
}
}
ArgumentValidationError doesn't extend ApifyClientError, and neither do network errors, timeouts and aborts, which surface as the errors the HTTP client raised.
For the full list of error classes, see the ApifyClientError reference.