---
title: "Lifecycle · Scrape.ly Docs"
description: "Start, exit, fail, and initialize an Actor, with exit codes and local runs."
url: https://scrape.ly/docs/sdk-node-lifecycle
markdown: https://scrape.ly/docs/sdk-node-lifecycle.md
---

# Lifecycle

> Start, exit, fail, and initialize an Actor, with exit codes and local runs.

Every Actor initializes the SDK, does its work, and shuts down so storage writes and charges are flushed before the process ends. `Actor.main()` handles all three steps; the manual methods give you control over each one.

## Lifecycle

### `Actor.main()`

`Actor.main(userFunc, options?)` calls `Actor.init(options)`, awaits your callback, shuts down events, storage, and charging, flushes standard output, and calls `process.exit()`.

```ts
import { Actor } from 'scrapely';

await Actor.main(async () => {
    const input = await Actor.getInputOrThrow<{ url: string }>();
    await Actor.pushData({ url: input.url, ok: true });
});
```

| Outcome | What happens | Exit code |
| --- | --- | --- |
| Callback resolves | Shutdown (waits up to `timeoutSecs`, default `30`) | `options.exitCode`, default `0` |
| Callback throws | Logs `[Scrapely] Actor failed: <message>` (plus the error's `cause`), then shuts down | `options.exitCode`, default `1` |

`options` accepts every `InitOptions` field plus `timeoutSecs` and `exitCode`. `main()` always exits the process, even if you pass `exit: false`. The first argument must be a function, otherwise `main()` throws before initializing.

The platform marks the run `SUCCEEDED` on exit code `0` and `FAILED` on any other code, unless the run was aborted or timed out. Because `exitCode` applies to both outcomes, setting `exitCode: 0` also makes a failed callback exit with `0`.

### Manual lifecycle

Use `init()` and `exit()` when your code must control when the process ends:

```ts
await Actor.init();
try {
    const input = await Actor.getInputOrThrow<Record<string, unknown>>();
    await Actor.pushData({ received: input });
    await Actor.exit('Completed');
} catch (error) {
    await Actor.fail(error instanceof Error ? error.message : String(error));
}
```

These methods throw `Actor.<name>() requires Actor.init() to be called first. Hint: Use Actor.main() which calls init() for you.` when called before `init()`: `getInput`, `getInputOrThrow`, `pushData`, `getValue`, `setValue`, `openDataset`, `openKeyValueStore`, `openRequestQueue`, `useState`, `setStatusMessage`, `charge`, `reboot`, `exit`, and `fail`.

### `Actor.exit()` and `Actor.fail()`

```ts
Actor.exit(messageOrOptions?: string | ExitOptions, options?: ExitOptions): Promise<void>
Actor.fail(messageOrOptions?: string | ExitOptions, options?: ExitOptions): Promise<void>
```

| `ExitOptions` field | Type | Default | Meaning |
| --- | --- | --- | --- |
| `statusMessage` | `string` | — | Printed to the log as `[Scrapely] <message>` |
| `timeoutSecs` | `number` | `30` | How long shutdown may wait for flushes |
| `exitCode` | `number` | `0` for `exit()`, `1` for `fail()` | Process exit code |
| `exit` | `boolean` | `true` | Whether to call `process.exit()` after shutdown |

```ts
await Actor.exit('Finished');
await Actor.exit({ statusMessage: 'Finished', exit: false });
await Actor.fail('Validation failed');
await Actor.fail({ statusMessage: 'Upstream down', exitCode: 2 });
```

`fail()` logs `[Scrapely] Actor failed: <message>` and calls `exit()` with `exitCode` defaulting to `1`.

> [!NOTE]
> `exit()` and `fail()` only print the status message to the log; they don't change the run's status message in the dashboard. Call `Actor.setStatusMessage(message, { isStatusMessageTerminal: true })` first if you want it shown on the run (see [Status messages](https://scrape.ly/docs/sdk-node-proxy-events.md#status-messages)).

### Exit codes

`EXIT_CODES` is exported for code that sets exit codes explicitly. `main()` and `fail()` don't use it; they use `0` and `1`.

| Constant | Value |
| --- | --- |
| `EXIT_CODES.SUCCESS` | `0` |
| `EXIT_CODES.ERROR_USER_FUNCTION_THREW` | `91` |
| `EXIT_CODES.ERROR_UNKNOWN` | `92` |

## Initialization

```ts
interface InitOptions {
    configuration?: ConfigurationOptions;
    storage?: ApiStorageConfig | StorageClient;
    gracefulShutdown?: boolean;
    gracefulShutdownDelayMillis?: number;
}
```

| Option | Default | Behavior |
| --- | --- | --- |
| `configuration` | — | Overrides SDK and Crawlee configuration values (see [Configuration](https://scrape.ly/docs/sdk-node-environment.md#configuration)) |
| `storage` | from environment | API credentials `{ apiUrl, token, runId }`, or any Crawlee `StorageClient` |
| `gracefulShutdown` | `true` | On `aborting`, calls `Actor.exit()`; on `migrating`, calls `Actor.reboot()` |
| `gracefulShutdownDelayMillis` | `0` | Delay before that automatic `exit()` or `reboot()` |

Calling `init()` again after it succeeded does nothing. If `init()` fails, it undoes the partial setup, so you can retry.

### What `init()` does

1. Installs handlers for `uncaughtException` and `unhandledRejection`. On either, the SDK logs the error, flushes pending charges for up to 5 seconds, and exits with code `1` (a hard exit after 10 seconds at most).
2. Picks storage:
   - the `storage` option, if you passed one;
   - otherwise, API-backed storage from `SCRAPELY_API_URL`, `SCRAPELY_TOKEN`, and `ACTOR_RUN_ID`;
   - otherwise, Crawlee local storage (see [Local runs](#local-runs)).
3. Connects the platform events WebSocket when the events URL, token, and run ID are all set.
4. Starts the event manager, and registers the graceful shutdown handlers unless `gracefulShutdown` is `false`.
5. Loads pricing and starts the charging manager.
6. Handles `SIGINT` and `SIGTERM`: flushes storage and charges for up to 5 seconds, then exits with code `0`.

On the platform (`SCRAPELY_IS_AT_HOME=1`), missing credentials are an error:

```
Missing required configuration for secure storage. Required: apiBaseUrl (SCRAPELY_API_URL), token (SCRAPELY_TOKEN/APIFY_TOKEN), actorRunId (ACTOR_RUN_ID). Containers must be started by the scrapely-server, or pass storage in Actor.init().
```

### Explicit storage credentials

On a platform run you don't need this. Pass `storage` only to point the SDK at a specific run's storage:

```ts
await Actor.init({
    storage: {
        apiUrl: process.env.SCRAPELY_API_URL!,
        token: process.env.SCRAPELY_TOKEN!,
        runId: process.env.ACTOR_RUN_ID!,
    },
});
```

Any other value that isn't API credentials or a `StorageClient` throws `Actor.init() storage option must be ApiStorageConfig or StorageClient`.

## Local runs

Without platform credentials and outside the platform, `init()` logs `[Scrapely] Local run detected — using Crawlee local storage` and keeps working:

| Feature | Local behavior |
| --- | --- |
| Storage | Crawlee storage in `./storage` |
| Input | `ACTOR_INPUT`, or the `INPUT` record of the local default key-value store |
| Events | No platform events; Crawlee still emits `persistState` on its interval |
| Browsers | Launched on your machine, even if the Actor uses the [Scrape.ly browser](https://scrape.ly/docs/browser.md) on the platform |
| Proxy | No platform proxy unless you set `SCRAPELY_PROXY_*`; your own `proxyUrls` work |
| Charging | Every event is priced at `1`; nothing is billed |
| `addWebhook()` | Logs a warning and returns `undefined` |

`scrapely run` sets this up for you. It purges the default local storages (keeping `INPUT.json`) unless you pass `--no-purge`, passes `-i`/`-f` input as `ACTOR_INPUT`, and starts `npm start` or `main`. See [CLI: Actor runtime](https://scrape.ly/docs/cli-actor-runtime.md).

Platform-only calls such as `metamorph()`, `reboot()`, and `setStatusMessage()` need run credentials; see each method for its local behavior.

---

- Previous: [JavaScript SDK](https://scrape.ly/docs/sdk-node.md)
- Next: [Input & output](https://scrape.ly/docs/sdk-node-input-output.md)
- All docs: [Docs index](https://scrape.ly/docs.md), [llms.txt](https://scrape.ly/llms.txt)
