Schedules
Run Actors and tasks on a cron schedule, as a view of your automations, and read each schedule log.
Schedules start Actors or tasks on a recurring basis using cron expressions. All endpoints are prefixed with https://api.scrape.ly/v2. Authenticate with Authorization: Bearer $SCRAPELY_TOKEN.
Creating, updating, and deleting schedules needs an API key: the Default key, a full-access key, or a scoped key with schedules:write, actors:run, and the run permission actor-default (see API keys & permissions). Actor run tokens receive 403 insufficient-permissions. Reading schedules and their logs needs schedules:read on a scoped key.
There is no endpoint to trigger a schedule manually. To run its Actor or task now, use the Actors API or Tasks API, or Run now on the automation with the same ID.
Schedules are automations
Every schedule is an automation with a schedule trigger, and the same ID works in both APIs. /v2/schedules is a view of your automations in the shape existing schedule clients expect:
- It lists only automations with a schedule trigger. Automations that run manually or after another run finishes are not listed here, and
GET,PUT,DELETE, and/logon them return404. Use the Automations API for those. - An automation's filters, AI step, and deliveries appear under one extra key,
automation, which is only present when it has any of them. It never contains secrets:
"automation": {
"hasAiStep": true,
"filters": { "onlyWithResults": false, "onlyNewItems": { "keyFields": ["url"] }, "conditions": [] },
"deliveries": [ { "channel": "email" }, { "channel": "webhook" } ]
}- A
PUTchanges only the fields you send. Replacingactionsreplaces the run steps and keeps the automation's AI step, filters, and deliveries. To change those, use Update automation. DELETEalso deletes the automation's run history.notificationsEnabledis the automation's failure alert (options.notifyOnFailurein the Automations API). Both read and change the same setting.
Endpoints
| Method | Path | Description |
|---|---|---|
GET | /v2/schedules | List schedules you own |
POST | /v2/schedules | Create a schedule |
GET | /v2/schedules/{scheduleId} | Get a schedule |
PUT | /v2/schedules/{scheduleId} | Update a schedule |
DELETE | /v2/schedules/{scheduleId} | Delete a schedule |
GET | /v2/schedules/{scheduleId}/log | Get the schedule log |
Errors and limits
| Status | error.type | When |
|---|---|---|
400 | invalid-input | Missing name, cronExpression, or actions; more than 10 Actor or 10 task actions; a RUN_ACTOR action without actorId; a negative offset or limit. |
400 | invalid-cron-expression | The cron expression is not valid, or it never fires (for example 0 0 30 2 *). See Cron expressions. |
400 | invalid-timezone | timezone is not an IANA time zone name. |
400 | duplicate-name | You already have a schedule with this name. |
400 | actor-not-built | One of your scheduled Actors has no successful build yet. |
400 | actor-not-available | A scheduled Actor of someone else's can't be run right now. |
400 | task-not-found | A RUN_ACTOR_TASK action names a task you don't own. |
403 | insufficient-permissions | Creating, updating, or deleting with an Actor run token, or with an API key whose scopes don't cover it. |
403 | limit-reached | Creating a schedule when you already have your plan's maximum number of schedules. Schedules and automations share one limit. |
403 | forbidden | The schedule belongs to another account. |
404 | record-not-found | The schedule does not exist, or it is an automation without a schedule trigger. |
404 | record-or-token-not-found | A RUN_ACTOR action names an Actor that does not exist. |
Get list of schedules
/v2/schedules| Query param | Type | Description |
|---|---|---|
search | string | Substring match on the schedule name. |
sortBy | string | createdAt (default), name, isEnabled, lastRunAt, or nextRunAt. |
desc | boolean | 1 or true reverses the sort order. Default is ascending. |
offset | number | Number of schedules to skip (default 0). |
limit | number | Page size (default 1000, max 1000). |
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
"https://api.scrape.ly/v2/schedules?search=daily&sortBy=nextRunAt&desc=1&limit=20"nextRunAt is null while the schedule is disabled.
/v2/schedules?limit=5&sortBy=createdAtCreate schedule
/v2/schedules| Body field | Type | Required | Description |
|---|---|---|---|
name | string or null | Yes | Schedule name, unique among your schedules. The field must be present; null is allowed. |
cronExpression | string | Yes | Five-field cron expression or macro such as @daily, evaluated in timezone. For example, 0 6 * * * means daily at 06:00. See Cron expressions. |
actions | array | Yes | Non-empty list of actions. |
timezone | string | No | IANA time zone name such as Europe/Berlin (default UTC). The cron expression is evaluated in this time zone, including daylight saving. An unknown name returns 400 invalid-timezone. |
isEnabled | boolean | No | Whether the schedule fires (default true). |
isExclusive | boolean | No | Skip a firing while the previous firing's runs are still going (default false). |
title | string | No | Display title. |
description | string | No | Description. |
notificationsEnabled | boolean | No | The schedule's failure alert. Returned as true unless you turned it off. See the note below. |
consoleNotificationsEnabled | boolean | No | Stored and returned (default true). |
curl -X POST https://api.scrape.ly/v2/schedules \
-H "Authorization: Bearer $SCRAPELY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "daily-scrape",
"cronExpression": "0 6 * * *",
"actions": [
{ "type": "RUN_ACTOR_TASK", "actorTaskId": "kQ7mN2pXv9LsT4wRb" }
]
}'Returns 201 with { "data": { ...schedule } }. Actions in the response are normalized.
Actions
Each action is RUN_ACTOR or RUN_ACTOR_TASK; a missing type means RUN_ACTOR. A schedule can have at most 10 actions of each type, and every scheduled Actor must have at least one successful build. All actions start together each time the schedule fires.
| Action field | Type | Description |
|---|---|---|
type | string | RUN_ACTOR or RUN_ACTOR_TASK. Not returned in responses. |
actorId | string | Actor to run (RUN_ACTOR, required). |
actorTaskId | string | Task to run (RUN_ACTOR_TASK). |
runInput | object | { "body": "<input as a string>", "contentType": "application/json" }. Bodies with a JSON content type are parsed; others are passed as a string. input is accepted as an alias. On a RUN_ACTOR_TASK action, a JSON object is merged over the task's input (see below). |
runOptions | object | build, timeoutSecs, memoryMbytes, maxItems, maxTotalChargeUsd, and restartOnError. build is ignored on someone else's Actor, which runs the User Build, and isn't saved or returned for it. |
When a scheduled run starts, each setting falls back in order:
- Input: for a
RUN_ACTORaction, the action'srunInput, then your default saved input for the Actor, then the prefill and default values from the Actor's input schema. For aRUN_ACTOR_TASKaction, the task's input as it is when the schedule fires, with the action'srunInputmerged over it one level deep: top-level fields inrunInputreplace the task's, the other fields stay as saved. To change a nested value, send the whole top-level field. - Options: the action's
runOptions, then the task's options, then the Actor's defaults, then buildlatest, a 3,600-second timeout, and 2,048 MB of memory.maxTotalChargeUsdfalls back to the Actor's minimum.
Scheduled runs have origin set to SCHEDULER.
Responses normalize actions and give each one a 9-character id:
RUN_ACTOR_TASKreturnsid,actorTaskId,taskName(when known), and the stored input asinput.runOptionsis not returned but is still used.RUN_ACTORreturnsid,actorId,actorName(when known),runInput, andrunOptions.
A RUN_ACTOR action on create:
{
"type": "RUN_ACTOR",
"actorId": "eLN5rTB8sYTBWdfzx",
"runInput": { "body": "{\"startUrls\":[{\"url\":\"https://example.com\"}]}", "contentType": "application/json" },
"runOptions": { "memoryMbytes": 2048, "timeoutSecs": 3600 }
}Cron expressions
cronExpression has five space-separated fields, evaluated in the schedule's timezone:
| Field | Values |
|---|---|
| Minute | 0-59 |
| Hour | 0-23 |
| Day of month | 1-31 |
| Month | 1-12 or JAN-DEC |
| Day of week | 0-7 or SUN-SAT (0 and 7 are Sunday) |
Each field can be *, a number (5), a range (1-5), a list (1,15,30 or 1-5,10), or a step: */15, 1-30/5, or 5/15 (from 5 to the end of the range, every 15). Names are case-insensitive. ? means the same as * in the day fields.
Instead of the five fields, you can use a macro:
| Macro | Same as |
|---|---|
@yearly, @annually | 0 0 1 1 * |
@monthly | 0 0 1 * * |
@weekly | 0 0 * * 0 |
@daily, @midnight | 0 0 * * * |
@hourly | 0 * * * * |
The rules follow standard (Vixie) cron:
- When both day of month and day of week are restricted, a day matches if either matches.
0 9 1 * MONfires on the 1st of the month and on every Monday. - Daylight saving: a schedule with a fixed minute and hour fires once when the clock falls back, on the first occurrence of that time. If the clock springs forward over that time, it fires right after the jump. Schedules with
*or a step in the minute or hour field follow elapsed time, so they have no runs in the skipped hour and run again in the repeated hour.
| Expression | Fires |
|---|---|
*/15 * * * * | Every 15 minutes |
0 6 * * * | Daily at 06:00 in timezone |
30 8 * * MON-FRI | Weekdays at 08:30 |
0 0 1 * * | Midnight on the 1st of each month |
@daily with timezone Europe/Berlin | Midnight Berlin time (22:00 or 23:00 UTC) |
The scheduler checks for due schedules once a minute, so runs start up to about a minute after the scheduled time. Times missed while the scheduler was down are skipped, not caught up.
Get schedule
/v2/schedules/{scheduleId}curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
https://api.scrape.ly/v2/schedules/9d4b2f6e-7a1c-4e3b-8f5d-2c0a1b9e7d3fReturns { "data": { ...schedule } } with normalized actions.
/v2/schedules/9d4b2f6e-7a1c-4e3b-8f5d-2c0a1b9e7d3fUpdate schedule
/v2/schedules/{scheduleId}Send only the fields you want to change; the fields are the same as Create schedule. actions, if sent, replaces the whole list, and the action limits and build check apply to the new list. nextRunAt is recalculated.
curl -X PUT https://api.scrape.ly/v2/schedules/9d4b2f6e-7a1c-4e3b-8f5d-2c0a1b9e7d3f \
-H "Authorization: Bearer $SCRAPELY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"isEnabled": false,
"cronExpression": "0 7 * * *"
}'Returns the updated schedule.
Delete schedule
/v2/schedules/{scheduleId}Returns 204 No Content.
curl -X DELETE -H "Authorization: Bearer $SCRAPELY_TOKEN" \
https://api.scrape.ly/v2/schedules/9d4b2f6e-7a1c-4e3b-8f5d-2c0a1b9e7d3fGet schedule log
/v2/schedules/{scheduleId}/logReturns JSON { "data": [...] }, not plain text. Each entry is one firing of the schedule, oldest first. The newest 30 firings are kept.
| Field | Type | Description |
|---|---|---|
message | string | What happened when the schedule fired. |
level | string | INFO or ERROR. |
createdAt | string | ISO 8601 timestamp. |
Messages take these forms:
| Message | Level |
|---|---|
Successfully started N run(s): <runId>, <runId> | INFO |
Started N run(s), M error(s): <details> | ERROR |
Skipped: the previous run is still going (exclusive). | INFO |
Skipped a firing missed during an outage (due <time>). | INFO |
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
https://api.scrape.ly/v2/schedules/9d4b2f6e-7a1c-4e3b-8f5d-2c0a1b9e7d3f/log/v2/schedules/9d4b2f6e-7a1c-4e3b-8f5d-2c0a1b9e7d3f/log