JavaScript client
Call the Scrape.ly API from Node.js or the browser with scrapely-client.
scrapely-client is the JavaScript and TypeScript client for the Scrape.ly API. Use it from your own backend, script, or service to start Actors, wait for runs, read datasets and key-value stores, and manage tasks, schedules, and webhooks. It is not for writing Actors: code that runs inside an Actor should use the JavaScript SDK.
The source code is on GitHub: scrapely-app/scrapely-client.
The client is a drop-in replacement for apify-client. Class and method names match, so most existing apify-client code works after you change the import and the base URL.
Install
npm install scrapely-clientThe package ships ES module and CommonJS builds with TypeScript types:
import { ScrapelyClient } from 'scrapely-client';
// or
const { ScrapelyClient } = require('scrapely-client');ApifyClient is exported as an alias of ScrapelyClient, so import { ApifyClient } from 'scrapely-client' also works.
Browser and edge runtimes
Bundlers that honour the browser export condition pick up a browser bundle automatically. You can also import it explicitly:
import { ScrapelyClient } from 'scrapely-client/browser';The browser bundle has no proxy support and no log streaming. Edge runtimes may need Node.js compatibility enabled. Do not ship your API token in code that other people load in their browser; call the API from your backend instead.
Create a client
import { ScrapelyClient } from 'scrapely-client';
const client = new ScrapelyClient({
token: process.env.SCRAPELY_TOKEN,
baseUrl: 'https://api.scrape.ly',
});Create a token in the dashboard under Settings → API & Integrations. The client does not read the token or the base URL from environment variables; pass them explicitly. Requests send it as Authorization: Bearer <token>.
| Option | Default | Description |
|---|---|---|
token | none | API token. Without it, only public endpoints work. |
baseUrl | non-production URL | API origin, without /v2. Set to https://api.scrape.ly. |
publicBaseUrl | non-production URL | Origin used when the client builds public storage URLs (for example createItemsPublicUrl). Set to https://api.scrape.ly. |
maxRetries | 8 | Retries for network errors, 429, and 5xx responses. |
minDelayBetweenRetriesMillis | 500 | Initial retry delay. It grows exponentially. |
timeoutSecs | 360 | Upper limit for a single request. |
userAgentSuffix | none | String or array appended to the User-Agent header. |
requestInterceptors | [] | Axios request interceptors applied to every request. |
In Node.js, outgoing requests honour the standard HTTP_PROXY, HTTPS_PROXY, and NO_PROXY environment variables.
Run an Actor
actor(id).call() starts a run and waits until it finishes. Pass an Actor ID or a username/actor-name full name.
const run = await client.actor('username/my-actor').call({ url: 'https://example.com' });
console.log(run.status); // SUCCEEDED, FAILED, ABORTED, or TIMED-OUT
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);call() resolves with the run object for every final status; it does not throw when the run fails. Check run.status yourself.
To start a run without waiting, use start(), then wait later:
const run = await client.actor('username/my-actor').start(
{ url: 'https://example.com' },
{ memory: 1024, timeout: 300 },
);
// ...later
const finished = await client.run(run.id).waitForFinish({ waitSecs: 120 });waitForFinish() polls until the run reaches a final status or waitSecs elapses. Without waitSecs it waits indefinitely. It returns the latest run object either way.
On runs of someone else's Actor, run.buildId, run.buildNumber, and run.options.build are missing: build info is owner only, and the run uses the User Build.
Run options for start() and call():
| Option | Description |
|---|---|
build | Build tag or number, for example latest or 0.1.4. Defaults to the Actor's default build. Ignored on someone else's Actor, which always runs the User Build. |
memory | Memory in MB. |
timeout | Run timeout in seconds. |
maxItems | Cap on billed dataset items for Actors that charge per result item. |
maxTotalChargeUsd | Cap on the total charge in USD for pay-per-event Actors. |
restartOnError | Restart the run if it fails. |
webhooks | Ad-hoc webhooks for this run only. |
contentType | Content type of the input when you pass a string or Buffer instead of an object. |
waitForFinish | start() only. Seconds the API holds the request open (up to 60; the server stops waiting at 45). |
waitSecs | call() only. Maximum seconds to wait. Omit to wait indefinitely. |
log | call() only, Node.js only. Where to redirect the run log: 'default' (console, the default), a Log instance, or null to disable. |
The API returns a run's log as a snapshot rather than a live stream, so the redirected log in call() may be incomplete. Fetch the full log after the run with client.run(run.id).log().get().
See Run Actors for how runs, memory, and timeouts work on the platform.
Other Actor methods
| Method | Description |
|---|---|
actor(id).get() | Actor details, or undefined if it does not exist. |
actor(id).update(fields) / .delete() | Change or remove your Actor. Set the User Build with update({ defaultNonOwnerBuild: '0.1.4' }); null clears it. |
actor(id).build(versionNumber, options) | Start a build. Options: tag, useCache, betaPackages, waitForFinish. |
actor(id).defaultBuild() | Returns a build client for the default build. Owner only; for other Actors read the input schema from get() (defaultBuildActorDefinition). |
actor(id).lastRun({ status }) | Run client for the latest run; supports .get(), .dataset(), .keyValueStore(), .log(). |
actor(id).runs() / .builds() | Collections of the Actor's runs and builds. Builds are owner only. |
actor(id).versions() / .version(n) | Actor versions. Owner only. |
actor(id).webhooks() | Webhooks attached to the Actor. |
actors().list({ my, offset, limit, desc }) | Actors you created or used. |
actors().create(fields) | Create an Actor. |
Runs
const run = await client.run('RUN_ID').get();
await client.run('RUN_ID').abort({ gracefully: true });| Method | Description |
|---|---|
run(id).get({ waitForFinish }) | Run details, or undefined. |
run(id).waitForFinish({ waitSecs }) | Poll until the run finishes. |
run(id).abort({ gracefully }) | Abort the run. |
run(id).resurrect(options) | Restart a finished run with the same storages. Options: build (your own Actors only), memory, timeout, maxItems, maxTotalChargeUsd, restartOnError. |
run(id).metamorph(targetActorId, input, options) | Replace the running Actor with another one. |
run(id).reboot() | Restart the run's container. |
run(id).update({ statusMessage, isStatusMessageTerminal }) | Update the run's status message. |
run(id).charge({ eventName, count, idempotencyKey }) | Charge a pay-per-event event for the run. |
run(id).delete() | Delete the run. |
run(id).dataset() / .keyValueStore() / .requestQueue() | Clients for the run's default storages. |
run(id).log() | Log client for the run. |
runs().list({ status, offset, limit, desc, startedAfter, startedBefore }) | All your runs. status accepts a string or an array. |
Datasets
const dataset = client.dataset('DATASET_ID');
const page = await dataset.listItems({ limit: 100, fields: ['title', 'url'] });
console.log(page.total, page.items.length);
const csv = await dataset.downloadItems('csv', { bom: true });| Method | Description |
|---|---|
listItems(options) | Returns a page { items, total, offset, count, limit, desc }. Options: offset, limit, fields, omit, unwind, flatten, desc, clean, skipEmpty, skipHidden, view. |
downloadItems(format, options) | Returns a Buffer. Formats: json, jsonl, csv, xlsx, xml, rss, html. Extra options: bom, delimiter, skipHeaderRow, xmlRoot, xmlRow, attachment. |
pushItems(items) | Append an object or an array of objects. |
getStatistics() | Dataset statistics. |
createItemsPublicUrl({ expiresInSecs }) | Signed URL for the items. Defaults to one hour. |
get() / update(fields) / delete() | Dataset metadata. |
datasets().list({ unnamed, offset, limit, desc }) | Your datasets. |
datasets().getOrCreate(name) | Get a named dataset, creating it if needed. |
See Results and Storage for the data model.
Key-value stores
const store = client.keyValueStore('STORE_ID');
await store.setRecord({ key: 'config', value: { depth: 2 } });
const record = await store.getRecord('OUTPUT');
if (record) console.log(record.contentType, record.value);| Method | Description |
|---|---|
getRecord(key, { buffer, stream }) | Returns { key, value, contentType }, or undefined if the key does not exist. JSON is parsed, text is returned as a string, and binary data as a Buffer. stream is Node.js only. |
setRecord({ key, value, contentType }) | Store a record. Without contentType, objects are stored as JSON, strings as text/plain, and Buffers or streams as application/octet-stream. |
recordExists(key) | true or false. |
deleteRecord(key) | Delete a record. |
listKeys({ limit, exclusiveStartKey, prefix, collection }) | One page of keys with isTruncated and nextExclusiveStartKey. |
getRecordPublicUrl(key) / createKeysPublicUrl() | Signed public URLs. |
keyValueStores().list() / .getOrCreate(name) | Your stores, or a named store. |
Request queues
const queue = client.requestQueue('QUEUE_ID');
await queue.addRequest({ url: 'https://example.com', uniqueKey: 'https://example.com' });
const { items } = await queue.listHead({ limit: 10 });Available methods: addRequest(request, { forefront }), batchAddRequests(requests, { forefront }), getRequest(id), updateRequest(request), deleteRequest(id), batchDeleteRequests(requests), listHead({ limit }), listAndLockHead({ lockSecs, limit }), prolongRequestLock(id, { lockSecs }), deleteRequestLock(id), unlockRequests(), listRequests(), paginateRequests(), plus get(), update(), and delete(). requestQueues().getOrCreate(name) returns a named queue.
batchAddRequests splits large arrays into API-sized batches and retries requests the API reports as unprocessed.
Tasks
Tasks store an Actor's input and run options so you can run the same configuration repeatedly.
const task = await client.tasks().create({
actId: 'username/my-actor',
name: 'daily-crawl',
input: { url: 'https://example.com' },
});
const run = await client.task(task.id).call();The API requires actId and name; task names are limited to 30 characters.
| Method | Description |
|---|---|
task(id).start(input, options) / .call(input, options) | Run the task. The input you pass overrides the stored input. Options match the Actor run options, without log. |
task(id).getInput() / .updateInput(input) | Read or replace the stored input. |
task(id).get() / .update(fields) / .delete() | Task details. |
task(id).publish({ slug, seoTitle, seoDescription, useCase }) / .unpublish() | Publish the task as a public page or take it down. |
task(id).lastRun() / .runs() / .webhooks() | Related runs and webhooks. |
tasks().list() | Your tasks. |
Schedules
await client.schedules().create({
name: 'nightly',
cronExpression: '0 3 * * *',
timezone: 'UTC',
actions: [{ type: 'RUN_ACTOR', actorId: 'ACTOR_ID' }],
});The API requires cronExpression and at least one action. Actions are RUN_ACTOR (with actorId) or RUN_ACTOR_TASK (with actorTaskId). isEnabled defaults to true. timezone defaults to UTC, and the cron expression is evaluated in it (see Cron expressions). Other methods: schedules().list(), schedule(id).get(), .update(fields), .delete(), and .getLog(). See Schedules API.
Automations
const automation = await client.automations().create({
title: 'Morning digest',
trigger: { kind: 'schedule', cron: '0 7 * * 1-5', timezone: 'Europe/Berlin' },
steps: [{ kind: 'savedInput', savedInputId: 'SAVED_INPUT_ID', inputOverride: { maxItems: 20 } }],
filters: { onlyNewItems: { keyFields: ['url'] } },
ai: { mode: 'summarize', prompt: 'The five most interesting new items, with links.' },
deliveries: [{ channel: 'email' }],
});
const run = await client.automation(automation.id).run();| Method | Description |
|---|---|
automations().list({ actorId, savedInputId, search, limit, offset, desc }) | Your automations, plain schedules included. actorId and savedInputId return the automations that use them. |
automations().create(automation) | Create an automation. The body is the one of Create automation. |
automation(id).get() / .update(fields) / .delete() | Read, partly update, or delete it. update changes only the parts you pass; ai: null removes the AI step. |
automation(id).run() | Run it now. Returns the automation run. |
automation(id).listRuns({ limit, offset }) / .getRun(runId) | Run history and one run, with each step's run, the filter result, aiOutput, aiCostUsd, and delivery statuses. |
automation(id).duplicate({ title, name, isEnabled }) | Copy it. The copy is paused unless isEnabled is true. |
automations() is Scrape.ly-only and comes with version 2.25.9 of scrapely-client.
Webhooks
await client.webhooks().create({
eventTypes: ['ACTOR.RUN.SUCCEEDED', 'ACTOR.RUN.FAILED'],
condition: { actorId: 'ACTOR_ID' },
requestUrl: 'https://example.com/hooks/scrapely',
});The API requires requestUrl and at least one event type. Other methods: webhooks().list(), webhook(id).get(), .update(fields), .delete(), .test(), .dispatches(), and webhookDispatches().list(). See Webhooks API for event types and payloads.
Builds
| Method | Description |
|---|---|
build(id).get({ waitForFinish }) | Build details. |
build(id).waitForFinish({ waitSecs }) | Poll until the build finishes. |
build(id).abort() / .delete() | Abort or delete a build. |
build(id).log() | Build log client. |
builds().list() | All your builds. |
Account
const me = await client.user().get();
const usage = await client.user().monthlyUsage();
const limits = await client.user().limits();user() defaults to the account that owns the token.
Store
const { items } = await client.store().list({ search: 'google maps', limit: 10 });Options: search, limit (the API defaults to 20 and caps it at 100), offset, sortBy, category, username, and pricingModel. See Store API.
Logs
const text = await client.run('RUN_ID').log().get();
const buildLog = await client.build('BUILD_ID').log().get();log(id) on the root client accepts a run ID or a build ID. get() returns the log as a string, or undefined if it does not exist. In Node.js, stream() returns the log as a Readable. The API sends the log as it is at request time and then closes the connection, so to follow a running Actor, poll get().
Pagination
List methods return a value that is both a promise of one page and an async iterable over every item:
// One page
const page = await client.actors().list({ limit: 20 });
// Every item, fetched page by page
for await (const run of client.runs().list({ desc: true })) {
console.log(run.id, run.status);
}
for await (const item of client.dataset('DATASET_ID').listItems({ chunkSize: 1000 })) {
// ...
}chunkSize sets how many items each underlying request fetches while iterating. limit and offset still bound the overall range. keyValueStore(id).listKeys() iterates the same way using exclusiveStartKey.
Errors and retries
The client retries network errors, invalid response bodies, 429, and 5xx responses up to maxRetries times with exponential backoff. Other 4xx responses fail immediately. Each attempt's timeout doubles, up to timeoutSecs. Streaming uploads are not retried.
Failed API calls throw ScrapelyApiError (also exported as ApifyApiError):
import { ScrapelyApiError } from 'scrapely-client';
try {
await client.actor('username/missing').start();
} catch (err) {
if (err instanceof ScrapelyApiError) {
console.error(err.statusCode, err.type, err.message, err.attempt);
}
throw err;
}The error carries statusCode, type, message, attempt, httpMethod, path, clientMethod, and data. See API overview for error types.
get() methods do not throw on 404; they return undefined. Date fields whose names end in At are converted to Date objects.
Migrating from apify-client
- Replace the import with
scrapely-client.ApifyClientandApifyApiErrorstill work as aliases. - Set
baseUrl(andpublicBaseUrlif you build public URLs) tohttps://api.scrape.ly. - Use a Scrape.ly token. Apify tokens do not work.
store().list()callsGET /v2/store/actors.task(id).publish()and.unpublish()are Scrape.ly additions.- Endpoints that Scrape.ly does not implement return an error. The API reference lists what is available.