---
title: "Errors & deployment · Scrape.ly Docs"
description: "Fix common SDK errors, then test and deploy your Actor."
url: https://scrape.ly/docs/sdk-node-deploy
markdown: https://scrape.ly/docs/sdk-node-deploy.md
---

# Errors & deployment

> Fix common SDK errors, then test and deploy your Actor.

This page lists common SDK errors and their fixes, and shows how to test an Actor locally and on the platform before you ship it.

## Errors and troubleshooting

| Symptom | Cause | Fix |
| --- | --- | --- |
| `Actor.<name>() requires Actor.init() to be called first` | A method that needs initialization ran before `init()` | Wrap your code in `Actor.main()` or call `Actor.init()` first |
| `Missing required configuration for secure storage` | `SCRAPELY_IS_AT_HOME` is set but the API URL, token, or run ID is missing | Don't set `SCRAPELY_IS_AT_HOME` locally; on the platform the run injects all three |
| `Input does not exist` | `getInputOrThrow()` found no input | Pass input (`-i` / `-f` locally), or use `getInput()` and handle `null` |
| `Cannot find package 'scrapely'` or missing exports locally | The SDK isn't in your local `node_modules`, or a lockfile pins the empty placeholder (`scrapely` 1.2.3 or older) | Run `npm install scrapely@latest`; see [Get the SDK](https://scrape.ly/docs/sdk-node.md#on-your-machine) |
| `cdpUrl is required. Local browser launch is disabled` | **Use Scrapely browser** is on, but a browser launched without `CDP_URL` | Don't unset `CDP_URL`; check `setCdpUrl()` calls |
| `Custom proxies are ignored when using the Scrapely remote browser` | Your own proxy URLs were passed to a browser crawler while the [Scrape.ly browser](https://scrape.ly/docs/browser.md) is on | Use [`createBrowserConfiguration()`](https://scrape.ly/docs/sdk-node-proxy-events.md#remote-browser-location) for browser location; use your proxies with HTTP crawlers |
| Proxy configuration is `undefined` | No proxy options and no platform proxy credentials (e.g. a local run) | Pass `proxyUrls`, or set `SCRAPELY_PROXY_*` locally |
| `Cannot combine custom proxies with Scrapely Proxy!` | `proxyUrls` combined with `groups` / `countryCode` | Use one mode, or put `'auto'` in `proxyUrls` |
| `Proxy group "GOOGLE_SERP" is not available on Scrapely. Available groups: RESIDENTIAL, DATACENTER.` | `groups` or `scrapelyProxyGroups` names a group Scrape.ly doesn't offer | Use `RESIDENTIAL` or `DATACENTER`; see [Unsupported groups](https://scrape.ly/docs/proxy.md#unsupported-groups) |
| `Actor.addWebhook() is only supported on the platform. Ignoring.` | `addWebhook()` ran locally | Expected; test webhooks on a platform run |
| `eventChargeLimitReached: true` | The run hit `maxTotalChargeUsd`, or the platform rejected the charge | Stop or reduce work |
| `ChargeUncertainError` | The platform didn't confirm a charge after retries | Treat the charge as possibly applied; don't charge again blindly |
| `Metamorph requires apiBaseUrl, token, and actorRunId` | `metamorph()` ran without run credentials | Metamorph only works on a platform run |
| Output missing after exit | A write wasn't awaited, or the process exited early | Await every write; exit through `Actor.main()` or `Actor.exit()` |
| Progress lost after a reboot or migration | State wasn't persisted | Use `useState()` and let the SDK handle `migrating` |
| Run failed with exit code `1` and `[Scrapely] Fatal uncaughtException` | An unhandled error or rejection | Catch errors in your handlers; the SDK flushes charges and exits with `1` |

## Testing and deployment

### On your machine

`scrapely run` runs the project in the current directory with your local Node.js. It needs the SDK installed locally (see [Get the SDK](https://scrape.ly/docs/sdk-node.md#on-your-machine)).

```bash
scrapely validate-schema
scrapely run -i '{"urls":["https://example.com"]}'
```

- Storage goes to `./storage`. The default storages are purged before each run, except `INPUT.json`; pass `--no-purge` to keep them.
- Without `-i` or `-f`, input comes from `storage/key_value_stores/default/INPUT.json`.
- `--entrypoint` runs a specific file or npm script instead of `npm start` or `main`.
- Browsers launch locally. The platform proxy and charging aren't available; see [Local runs](https://scrape.ly/docs/sdk-node-lifecycle.md#local-runs).

Extract pure helpers (parsing, URL normalization) and unit-test them with your usual test runner:

```ts
export function normalizeTitle(value: string): string {
    return value.trim().replace(/\s+/g, ' ');
}
```

### On the platform

```bash
scrapely push
scrapely call -i '{"urls":["https://example.com"]}' -o
scrapely runs log <runId>
scrapely datasets get-items <datasetId> --format json --limit 20
```

`scrapely push` uploads the source and builds it (`-w 0` returns without waiting for the build). `scrapely call` runs the Actor from `scrapely.json` on the platform, streams its log, and waits; `-o` prints the default dataset when it succeeds. Use `-b`, `-m`, and `-t` for build, memory, and timeout. See [CLI: Actors](https://scrape.ly/docs/cli-actors.md) and [CLI: Runs & builds](https://scrape.ly/docs/cli-runs-builds.md).

Start with a small input, then check the log and the default dataset in the dashboard or with the CLI. Test charging, webhooks, abort, reboot, metamorph, and standby on real runs.

Before you ship a larger crawl:

1. Don't rely on the `scrapely` version in `dependencies`; with Transform actors on, the build uses the base image's SDK.
2. Validate input before expensive work.
3. Await every dataset, key-value store, and queue write.
4. Persist crawl state with `useState()` so a reboot doesn't lose progress.
5. Check `eventChargeLimitReached` if the Actor is priced per event.
6. Keep tokens and proxy credentials out of source code and logs.

### Production template

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

type Input = { urls: string[] };

await Actor.main(async () => {
    const input = await Actor.getInputOrThrow<Input>();
    if (!input.urls?.length) throw new Error('urls is required');

    const proxyConfiguration = await Actor.createProxyConfiguration();

    const crawler = new CheerioCrawler({
        proxyConfiguration,
        async requestHandler({ request, $, log }) {
            const title = $('title').text().trim();
            log.info('Processed', { url: request.url });
            await Actor.pushData({ url: request.url, title });
        },
    });

    await crawler.run(input.urls);
});
```

On the platform, `createProxyConfiguration()` with no options uses the platform proxy (residential, billed per GB); locally it returns `undefined` and the crawler connects directly.

---

- Previous: [Standby mode](https://scrape.ly/docs/sdk-node-standby.md)
- Next: [API reference](https://scrape.ly/docs/sdk-node-reference.md)
- All docs: [Docs index](https://scrape.ly/docs.md), [llms.txt](https://scrape.ly/llms.txt)
