Skip to content
PeriplusDocs
Open app
Schedules

Schedulescapture pages again later

Capture the same pages at an interval for comparisons and time-series analysis.

Choose pages to revisit

A schedule captures a fixed list of pages at an interval. Enter page URLs, or write SQL returning url. The pages need not be in the corpus yet: pages never captured are due immediately. Choose how often to capture them, then create the schedule. It is active immediately.

The schedule shows its projected captures per month (URLs × 30 ÷ interval in days), an estimate of the captures it will make.

A fixed list, not a live query

SQL runs once, when you create the schedule, and its URLs are locked in. New matches are not added later; create another schedule when you want a different set. Scheduled captures follow no links and always fetch again.

A page is due at its last successful capture plus the shortest interval among your organization’s active schedules for it. A success from any capture or crawl counts; failures retry with backoff. After downtime, a page is captured once, not once per missed interval. The interval is a desired frequency, not a guarantee that a website is reachable at an exact time.

Keep and compare observations

Use captures and their observation times to compare the same page over time. A schedule keeps each successful capture until the next run plus a week, so every capture can be compared with the next: pin_days defaults to every_days + 7 and follows the interval if you change it: a shorter interval can let kept captures expire, and a longer one later does not revive them. Set pin_days (1–3,650 days) to keep captures longer, for example for a longer history; a set value stays as set when the interval changes. Extending retention takes effect immediately for captures still kept; captures already near their expiry are verified first. Failed captures are not kept beyond the usual seven days.

Pausing or deleting a schedule stops future captures, not access to existing ones; a capture that has already started finishes. Other schedules may continue capturing the same pages.

Collect observations; choose how much history to keepInteractive example
Capture every2 days
Keep each capture for7 days

Filled circles: kept. Outlined circles: scheduled for deletion.

At day 14, captures from days 8, 10, 12, 14 are still within their 7-day period. Older captures are scheduled for deletion unless another pin keeps them.

Seven days is the default; pin_days on the schedule keeps each capture longer. This example assumes successful captures kept from collection; deletion may finish later.

End a schedule automatically

Set ends_at to stop a schedule at a fixed time, for example at the end of a study. Give a timestamp with a time zone offset, such as 2026-12-14T23:59:59+09:00; Periplus stores the same instant and returns it in UTC (2026-12-14T14:59:59Z). Timestamps without an offset are rejected.

The end is exclusive: no new scheduled run is admitted at or after it. That includes pages that were due but not yet admitted, and a retry after a failed run, which is a new run. A run admitted before the end is already accepted work: it may begin capturing, and retry within that run, after the end, and its result still appears in the schedule’s captures. Pause the schedule to cancel a run that has not started. ended becomes true once the end passes, and the schedule’s pages show no next due time. To keep going, set a later end or clear it with null; to stop earlier, pause the schedule.

Python

Python
from datetime import datetime, timedelta, timezone

from periplus_sdk import Client

with Client() as client:
    import os
    schedule = client.schedules.get(os.environ["SCHEDULE_ID"])
    end = datetime(2026, 12, 14, 23, 59, 59, tzinfo=timezone(timedelta(hours=9)))
    schedule = client.schedules.update(schedule, ends_at=end)
    print(schedule.ends_at, schedule.ended)

cURL

Shell
curl --fail-with-body "${PERIPLUS_API_URL:-https://api.periplus.dev}/schedules/$SCHEDULE_ID" \
  -H "Authorization: Bearer $PERIPLUS_API_KEY" \
  -X PATCH \
  -H 'Content-Type: application/json' \
  --data '{"expected_version":1,"ends_at":"2026-12-14T23:59:59+09:00"}'

Schedule repeated captures

This activates the schedule immediately. Creating a schedule also creates its retention pin, so it needs pins:write as well as schedules:write; every built-in role that can create schedules can also create pins.

Set resolve_bot_challenges: true to ask for paid bot-challenge resolution on every run. It is off by default and fixed at creation; creating such a schedule needs your organization's allowance (Settings → Budgets), or it is refused with challenge_resolution_not_allowed. The allowance is checked again for each run: if an Owner or Admin withdraws it, later runs capture without resolution instead of failing. While the allowance holds, schedules that ask for resolution never share a run with schedules that did not ask, or with another organization's: each gets its own captures, results and usage. While it is withdrawn, an asking schedule shares the ordinary runs of schedules that did not ask; when the allowance changes, a schedule's unfinished run from before the change is withdrawn, or finishes if it already started, before its next run begins, so it never has two runs at once.

Python

Python
from periplus_sdk import Client

with Client() as client:
    schedule = client.schedules.create(
        name="Weekly observations", urls=["https://example.com/"],
        every_days=7, pin_days=90,
    )
    print(schedule.id, schedule.projected_captures_per_month)

cURL

Shell
curl --fail-with-body "${PERIPLUS_API_URL:-https://api.periplus.dev}/schedules" \
  -H "Authorization: Bearer $PERIPLUS_API_KEY" \
  -X POST \
  -H 'Content-Type: application/json' \
  --data '{"name":"Weekly observations","every_days":7,"pin_days":90,"urls":["https://example.com/"]}'

Describe a schedule

An optional Markdown description explains what a schedule monitors and why. Add it on creation or change it later with expected_version; a null or blank description clears it, and HTML is not rendered. Agents use schedule_create or schedule_update, for example schedule_update id=<schedule> expected_version=2 description="Daily **EU prices** for the pricing review.".

Select scheduled pages with SQL

Running the same query beforehand shows what it would select today; the list is locked in when the schedule is created. SQL selections also require permission to execute SQL.

Python

Python
from periplus_sdk import Client

with Client() as client:
    schedule = client.schedules.create(
        name="Sector observations", every_days=7,
        sql="SELECT DISTINCT url FROM captures WHERE domain(url) = ?",
        parameters=["example.com"],
    )
    print(schedule.id, schedule.url_count)

cURL

Shell
curl --fail-with-body "${PERIPLUS_API_URL:-https://api.periplus.dev}/schedules" \
  -H "Authorization: Bearer $PERIPLUS_API_KEY" \
  -X POST \
  -H 'Content-Type: application/json' \
  --data '{"name":"Sector observations","every_days":7,"sql":"SELECT DISTINCT url FROM captures WHERE domain(url) = ?","parameters":["example.com"]}'

Check every scheduled URL

The schedule's URLs show each one's latest success, failure, and next due time. The next due time is an observed calculation, not a reservation. Scheduled captures, successful and failed (failed ones keep their capture_id), are listed separately, newest first.

Python

Python
from periplus_sdk import Client

with Client() as client:
    import os
    page = client.schedules.urls(os.environ["SCHEDULE_ID"], limit=100)
    for item in page.items:
        print(item.url, item.last_success_at, item.next_due_at)
    for capture in client.schedules.captures(os.environ["SCHEDULE_ID"]).items:
        print(capture.url, capture.status, capture.capture_id)

cURL

Shell
curl --fail-with-body "${PERIPLUS_API_URL:-https://api.periplus.dev}/schedules/$SCHEDULE_ID/urls?limit=100" \
  -H "Authorization: Bearer $PERIPLUS_API_KEY"

Create a schedule

Projects

Every schedule belongs to exactly one project, and projects are how you organize schedules. Your organization starts with a Default Project, and a schedule created without a project lands there, so nothing changes for work that never mentions projects. Create projects for separate pieces of work, such as one per product or customer, and keep each schedule with the queries, pins and schedules around it. Projects organize your work, not your data: SQL reads every capture your organization can see, whichever project a query is in.

Choose a project with the switcher in the sidebar: Schedules lists that project's schedules, and new schedules go into it. To move a schedule, use Move… on its page, or select several and choose Move to project. Moving never changes what a schedule does. From the API, pass project_id when creating a schedule, or move it later with project_id and its current version as expected_version. To find a schedule by name, pass search when listing: it keeps schedules whose name contains the text, ignoring case. Agents find project IDs with projects_list and pass project_id to schedule_create, schedule_update and schedule_list; there is no active project, so name it on every call.

Python
from periplus_sdk import Client

with Client() as client:
    default_project = next(client.projects.iter())    # listed first
    project = client.projects.create(name="Product A")
    client.schedules.create(name="Pricing", every_days=7, urls=["https://example.com/pricing"], project_id=project)
    for schedule in client.schedules.list(project_id=default_project).items:
        client.schedules.move(schedule, project_id=project)
    print(client.projects.get(project).item_counts.schedules)