A clock for the endpoint you already built

Something has to call your URL. On time. Forever.

Register a cron, an interval or a one-off. When it comes due, Scheduler sends exactly one signed HTTPS request to a URL you own — with retries, a dead-letter queue, a delivery log, and an email the moment it starts failing. We never run your code.

$15/mo or $150/yr · 14-day free trial, cancel any time
Console · fleetproj_7f2a
14schedules
12armed
2paused
0failing
Next fire in04:12
nightly-sync
0 3 * * * · Australia/Sydney
armed
refresh-cache
every 5 min · 8,640 runs / 30 days
armed
quarter-close
0 6 1 */3 * · holds its slot while paused
paused
12 armed + 2 paused = 14. Paused schedules still count against the account-wide ceiling of 100.
The fire, as your server sees itat-least-once
POST /jobs/nightly-sync HTTP/1.1 Host: api.yours.com Parsons-Signature: eyJhbGciOiJFUzI1NiIsInR5cCI6… Parsons-Idempotency-Key: proj_7f2a.sch_nightly_sync.3.1787331600000 Parsons-Fire-Time: 1787331600000 Content-Type: application/json
200 OK84 ms · attempt 1 · lateness 0.4 s→ written to the delivery log
The thing you are actually afraid of

A schedule almost never fails loudly.

It fails the other way: the job stops, the dashboard still says green, and you find out a month later from someone else. Here are the three shapes that takes.

The line that is still there

Classic cron runs the command and moves on. It has no opinion about whether the command worked.

$ crontab -l
0 3 * * * /opt/backup.sh
# exit status: not checked, ever
$ ls -t /backups | head -1
backup-2026-07-21.tar.gz
→ newest file is 32 days old

The drift, then the stop

A repo-hosted scheduled workflow runs on shared queue capacity, so it lands minutes off its slot — and some hosts disable the schedule outright once the repo goes quiet.

schedule: 0 9 * * *
run #418  09:27
run #419  09:31
run #420  09:24
run #421 — never queued
→ the repo went quiet
→ the folk fix is a fake commit job

The mail that was never sent

Cron mails you the job's output. A job that exits 1 and writes nothing has no output — so there is nothing to mail, and half these boxes have no mail daemon anyway.

$ mail
No mail for deploy.
(no message)
the job exited 1 and wrote
nothing to stdout

Every one of these is the same failure twice: the thing didn't run, and nobody found out.

The reframe

There are three answers to “I need a timer.” Two of them want your code.

You did not come here to adopt an SDK or to add a monitoring vendor on top of the cron box you already can't find. You came here because something has to call a URL you already built.

A workflow runtimeSDK · deploy step · their runtime · per-second compute
your code──▶ lifted outtheir runtime, now hosting it
A cron monitorinbound pings · you still host the cron · it watches, it never calls
your cron box──▶ pingsa watcher──▶ alert
A clocknothing of yours moves
Scheduler──▶ one signed HTTPS requestyour endpoint, where it already lives

Scheduler is the third lane and only the third lane. It runs no user code, ever — that is the product's identity, not a gap in it. If you need replayable multi-step state, fan-out or human-in-the-loop steps, you want a workflow runtime and you should go and get one.

The mechanism

One request. Five things in it that matter.

This is the whole contract. Hover a note to light up the line it describes.

create_schedule · get_schedule · list_runs
POST /jobs/nightly-sync HTTP/1.1Host: api.yours.comParsons-Signature: eyJhbGciOiJFUzI1NiJ9…Parsons-Idempotency-Key:
  proj_7f2a.sch_nightly_sync.3.1787331600000Parsons-Fire-Time: 1787331600000Content-Type: application/json
{"window":"24h"}

Best-effort to within seconds of the slot — not to the millisecond, and never faster than one fire every 60 seconds.

The end of CRON_SECRET

Your endpoint can prove it was us.

The usual defence for a scheduled endpoint is a string you invented, pasted into two environments, and now have to rotate in both forever. Anyone who learns that string is you.

The usual way

Two copies of one secret

on the schedulerCRON_SECRET=hunter2-but-longer
on your serverCRON_SECRET=hunter2-but-longer
→ rotate it in two places, forever
→ it is in two sets of logs and one CI config
→ possession of the string is authority
With Scheduler

One public URL, nothing to leak

GET https://scheduler.parsons.ai/.well-known/jwks.json
{ "iss": "https://scheduler.parsons.ai",
  "project": "proj_7f2a",
  "schedule_id": "sch_nightly_sync",
  "version": 3, "seq": 412,
  "fire_time": 1787331600000,
  "aud": "api.yours.com",
  "exp": iat + 300 }
Pin the issuer. Pin your own hostname as the audience. Reject anything older than five minutes.
Shared secrets you now hold The signature proves we sent it and which project it belongs to. It confers zero platform authority — your endpoint still decides what that project is allowed to do. 2
When your endpoint says no

Every status code has a printed consequence.

Pick one and watch what we do about it. This is the whole retry policy — there is no other page it lives on.

2xx is success. Anything else is not, and we say so in the delivery log.

4xx other than 429 is permanent — one attempt, no retry. We do not hammer a broken route.

5xx, 429 and timeouts retry with backoff up to your max_retries (0–10, default 3).

3xx is terminal. We do not follow redirects — a moved endpoint should be a decision, not a surprise.

Your Retry-After is honoured, in delta-seconds or HTTP-date form, clamped to 15 minutes.

timeout_s defaults to 30 and maxes at 120. Delivery is at-least-once — which is exactly why the idempotency key exists.

The two ways time breaks cron

Civil time, and the morning after an outage.

Cron is resolved as civil time in the IANA zone you name. 0 9 * * MON-FRI in Australia/Sydney is 9am in Sydney in July and 9am in Sydney in January — you never do the arithmetic twice a year. We take five-field and six-field-with-seconds expressions, @daily, @hourly, and day names.

The clocks go back · a naive scheduler · 0 30 1 * * *fires twice
00:30—
01:30fire
01:30fire again
02:30—
03:30—
The same night · Parsons · the same expressionfires once
00:30—
01:30fire
02:30—
03:30—
The clocks go forward · 02:30 does not exist that nightfires at the next valid instant
01:30—
03:00fire
03:30—
04:30—
A replaying scheduler · after a four-hour gap on a five-minute cadence
four hours unreachable

48 fires land on your endpoint the instant the gap ends. Your rate limiter, your database and your on-call all find out at the same moment.

Parsons · the same gap, the same schedule
four hours unreachable

One fire, then the next slot as normal. The 47 outlines are the slots we coalesced away. A gap costs you one run, not a thundering herd — and never a burst your rate limiter has to survive.

You will find out

Two emails. Not four hundred.

Per-failure alerting dies the same way every time: the volume trains you to mute it, and once it is muted it is decoration. We page on the transition — once when a schedule starts failing, once when it recovers.

Alert on every failed attemptthe usual way
[FAILED] nightly-sync03:00
[FAILED] nightly-sync03:00
[FAILED] nightly-sync03:01
[FAILED] nightly-sync03:03
[FAILED] nightly-sync04:00
[FAILED] nightly-sync04:00
[FAILED] nightly-sync04:01
[FAILED] nightly-sync04:03
muted — this is how alerting dies
Alert on the transitionParsons
Schedule sch_nightly_sync is failingMon 03:03 · health_state healthy → failing
Schedule sch_nightly_sync recoveredFri 03:01 · sent after 3 consecutive successes
A target down for four days is two messages, not four hundred.
{"type":"notify"}

Emails the project owner. This is the default if you configure nothing at all — you do not have to opt in to being told.

{"type":"webhook","url":"…"}

POSTs the failure to a URL you own, through the same egress guard a target goes through. Public HTTPS only, checked again on the way out.

{"type":"tracker","project":"PLAT"}

Files an issue in your own Parsons Tracker project over an internal binding — no API key, no token to store. Severity high on break, low on recovery. Off-platform trackers connect with a pik_ intake key.

Up to three failure actions per schedule. Intake keys are encrypted at rest and redacted to ‹set› on every read, through the console and through MCP alike.

The question nobody asks out loud

How do you know the alarm works?

You find out when it matters, usually by discovering it didn't. So we shipped a fire drill. test_on_failure genuinely fires your configured actions once — a real email to your real address, a real POST to your webhook, a real issue in your tracker — with the subject marked [TEST].

It is a real delivery, because a fake one would prove nothing. It just doesn't lie to your history: the health state does not move and the runs log does not gain a row.

[TEST]
[TEST] Schedule sch_nightly_sync is failing
Delivered to the project owner. Your webhook and tracker actions ran too — this is the only way to know the credentials still work.
test_on_failure · run_now · list_runs
unchanged
health_state
healthy
unchanged
list_runs — row count
2026-08-22 03:00:00200
2026-08-21 03:00:00200
2026-08-20 03:00:01200
3 rows · same 3 rows after the drill
also true of
run_now
Fires immediately for testing without touching cadence or health state. It is a real, metered fire — and it refuses on a paused schedule.
See it before you commit

The create form shows you the next five fires.

Type an expression and the console tells you, in English and in dates, exactly what you just asked for — before anything is armed. This is a working copy of it. Drive it.

Resolved as civil time in the zone you pick. Not UTC with an offset bolted on.
At 09:00, Monday through Friday, Australia/Sydney
next 5 fires · computed, not promised
    fired_atattemptsstatushttp_statuslatenessdlq
    2026-08-21 09:00:001delivered2000.4 s—
    2026-08-20 09:00:001delivered2001.1 s—
    2026-08-19 09:00:002delivered2000.8 s—
    2026-08-18 09:00:004failed5030.6 strue
    2026-08-17 09:00:001delivered2000.3 s—

    The console also carries the fleet roll-up — armed, paused, failing, healthy, and a live next-fire countdown — plus a copy-ready JWKS block on every schedule's detail view. Every button in it calls the same functions the MCP tools call. There is no second-class surface.

    Schedules as code

    Run the deploy script a hundred times. Still one schedule.

    Give a schedule your own stable id and create_schedule converges instead of piling up. That is the whole infra-as-code story, and it is one argument.

    Without a stable id — run the same script four times
    $ deploy.sh   → created sch_01H8…
    $ deploy.sh   → created sch_01H9…
    $ deploy.sh   → created sch_01HA…
    $ deploy.sh   → created sch_01HB…
    schedules now armed1
    With id: "sch_nightly_sync" — the same four runs
    $ deploy.sh   → created (version 1)
    $ deploy.sh   → replaced (version 2)
    $ deploy.sh   → replaced (version 3)
    $ deploy.sh   → replaced (version 4)
    schedules now armed1

    The honest half: a create with an id you already used is a wholesale replace, not a patch. Fields you leave out go back to their defaults and the version bumps. Send the whole object every time — which is what a deploy script does anyway. Ids look like sch_[a-z0-9_], up to 64 characters, and they are yours to choose.

    Drive it from your AI

    Eleven tools, no dashboard required.

    Scheduler ships as MCP tools, so Claude, ChatGPT or Cursor can create, pause, test and audit your schedules directly. guide needs no auth at all — an agent can read the entire playbook, including the signed-request contract and the health-check 503 pattern, before it holds a token.

    guide whoami docs create_schedule list_schedules get_schedule set_enabled delete_schedule run_now list_runs test_on_failure
    one connection · minted for you at sign-in GET YOURS
    delete_schedule requires an explicit confirm — agents make mistakes too.
    Move the nightly sync to 3am Sydney instead of 9am, and open a Tracker issue if it breaks.
    get_schedule(id: "sch_nightly_sync")
    create_schedule(id: "sch_nightly_sync", cron: "0 3 * * *", timezone: "Australia/Sydney", url: "https://api.yours.com/jobs/nightly-sync", on_failure: [{type:"tracker", project:"PLAT"}])
    Replaced — version 4. Next fire Sun 23 Aug, 03:00 Australia/Sydney. Failures will open a high-severity issue in PLAT and close the loop with a low one on recovery.
    Did last night's run actually land?
    list_runs(id: "sch_nightly_sync", limit: 5)
    Yes — 200, one attempt, 0.4 s lateness. The run before that took two attempts after a 503.
    The number nobody else prints

    Here is exactly how much clock your account buys.

    Everyone in this category makes you find their real constraint in the docs, or discover it at runtime. Ours is on the landing page. Runs are counted account-wide across every project you own, per 30-day month — 20,000 of them are included in the $15, and past that they simply draw from your prepaid credits at $17 per million. Nothing refuses a funded account until the 200,000-a-month abuse fuse.

    drag the cadence you want every 5 minutes → 8,640 runs / 30 days
    60s5 min15 minhourly6 hoursdaily
    Included — 20,000 runs / 30 days43%
    2 schedules at this cadence fit in the whole account
    past the allowance → priced, not refused

    Nothing stops halfway through a month. Runs past the included 20,000 are billed daily from your prepaid balance at $17 per million — roughly wholesale plus half, printed here rather than discovered on an invoice. A hard refusal exists in exactly two places: an account with no credits parks at the allowance, and the 200,000-a-month abuse fuse (which the billing console has an ask-us-to-raise-it path for).

    Schedules themselves are not the constraint — runs are. Projects are unlimited and free, but they do not multiply the allowance: runs are counted across the account, so projects organise your work rather than reprice it.

    payload 48 KB headers 8 KB timeout ≤120 s (default 30) retries 0–10 (default 3) failure actions ≤3 minimum cadence 60 s 30 create_schedule / min 5 run_now / min 5 test_on_failure / min targets: public HTTPS only

    The inputs stay bounded (60-second floor, payload and retry ceilings), and the subscription is the whole bill for the first 20,000 runs a month. Past that the meter is public and tiny — $17 per million runs, prepaid — and there is still no per-job pricing, no seat count and no invoice surprise.

    Where we win, where we lose

    Five of these rows go against us.

    A table that only flatters the vendor who wrote it is worth nothing. These are the categories you are actually choosing between, and the rows where you should choose something else.

     
    a workflow runtime
    a cron monitor
    Scheduler
    Calls a URL on any public host you own
    via your task code
    no — it receives pings
    yes
    Runs your code for you
    yes — that is the product
    no
    no — by design
    You still host the cron yourself
    no
    yes, you do
    no
    Signed request, no shared secret
    n/a
    n/a
    ES256 + public JWKS
    Stable idempotency key on every attempt
    varies
    n/a
    yes
    Civil-time cron, DST-correct
    varies
    n/a
    yes
    Coalesces after an outage instead of replaying
    often replays
    n/a
    yes
    Failure alerting included, not a separate product
    varies
    yes — it is the product
    yes
    Sub-minute cadence
    usually
    n/a
    no — 60 s floor
    Private, VPC or internal targets
    often
    n/a
    no — public HTTPS only
    Multi-step workflows, fan-out, human-in-the-loop
    yes
    no
    no
    Price scales with the number of jobs or seats
    often per job or per run
    usually per check
    no — flat $15/mo
    Cheapest way to buy this one capability
    no — usage-priced
    often yes — one job, one price
    no — a dedicated monitor can undercut $15
    That price also covers every other Parsons product
    no
    no
    yes — auth, DB, storage, email, CRM…

    If you need us to run your code, we are the wrong product and we would rather you find that out here than in month three. And if your job already lives inside a Cloudflare Worker and UTC is fine, use Cloudflare's own Cron Triggers — they invoke that Worker's scheduled handler directly, which is a shorter path than ours and one we are happy to lose to. We are for the case where the target is somewhere else, or the time has to be local, or something has to tell you when it stops.

    Pricing

    One plan. It includes every other Parsons product.

    Monthly or yearly — a year costs exactly two months less than paying month to month. No seats, no enterprise tier, no free tier. For Scheduler the number that matters is printed on the card — the runs your whole account gets.

    Base
    $15 / month
    or $150 / year — two months free
    runs included · account-wide · 30 days
    20,000
    every 5 minutes = 43% of it · then $17 per million
    • $17 per million runs past the 20,000 — prepaid, printed here, no invoice surprise
    • Cron, interval and one-off — every feature on this page
    • Signed fires, retries, dead-letter, delivery log
    • Failure alerts by email, webhook or Tracker issue
    • Every other Parsons product, same login

    Projects are unlimited, and free.

    One for the business, one for the client work, one for the thing you are not sure about yet — each with its own data, its own members and its own spend caps, at no extra cost. There is nothing to upgrade to and there are no seats to buy. If the tool you are leaving bills by the job, by the workspace or by the seat, none of those lines exist here: a second project, and a second person, cost nothing.

    One subscription, the whole platform.

    Scheduler is one product on it. The same $15 is also your auth service, your database, your object storage, your outbound email and the rest — with real allowances, not trial-sized ones.

    SchedulerAuth · unlimited MAUStorage · 5 GBDatabase · 1 GBDeploy · 1,000,000 reqEmail · 200/dayTracker · 10,000 tasksCRM · 50,000 contactsNotifications · 200Radar · 100,000 checksJournal · 2,000 entriesMoney · 2 banks+ more

    “Is a scheduler worth $15?”

    On its own, honestly, maybe not — a single-purpose cron monitor can come in under that, and the comparison table above says so in its own row. But $15 is not a scheduler price. It is the whole account. A mainstream CRM is $48–$99 a month for one seat; a hosted auth service passes $1,000 a month at 100,000 monthly users; here both are included, and the auth users are uncapped. Most people arrive already paying more than $15 for exactly one of the things on this list.

    Then there are the lines we never add. Nothing is charged by the seat — bring the co-founder, bring the contractor. Nothing is charged by the workspace: projects are unlimited and free, each with its own data, members and caps. And nothing is charged by the job — the 20,000 included runs count the same whether they come from one schedule or a hundred, and so does the $17-per-million past them.

    Consumption never hides inside it either. The things that genuinely cost money per unit sit on a prepaid wallet with its own ceiling: AI at provider cost × 1.5, a voice minute at $0.03, an outbound SMS segment at $0.02, a US phone number at $3.99/month — and runs past the included 20,000 at $17/million. You top it up on purpose. The subscription is access, not a loss-leader with a metered bill waiting behind it.

    Parsons · Basemonthly
    Subscription — every product, one account$15.00
    Scheduler — 20,000 runs a month, account-wideincluded
    Signed fires, retries, dead-letter, delivery logincluded
    Failure alerts — email, webhook, Tracker issueincluded
    MCP tool calls — uncapped, across every productincluded
    Auth — unlimited monthly active usersincluded
    Second project — own data, own members, own caps$0.00
    A seat for your co-founderno seats
    Runs past the allowance$17 / million, prepaid
    Total$15.00 / mo
    Same account billed yearly instead — two months free$150.00 / yr

    14-day free trial on your first subscription · billed monthly or yearly, your choice at checkout · cancel any time

    Questions people actually ask

    Before you start the trial.

    Do I have to move my code?

    No. We call an HTTPS URL you already have. There is no SDK, no deploy step and no runtime of ours that your logic has to live in.

    Can it reach something on my private network?

    No — public HTTPS only, and we re-check at create time and again at every single fire. Internal hosts, cloud metadata addresses, loopback, private and CGNAT ranges and IP literals are all refused.

    Will it ever run twice?

    Delivery is at-least-once. Dedupe on Parsons-Idempotency-Key and keep the keys 24 hours. We will not claim exactly-once, because nobody honestly can.

    How exact is the timing?

    Best-effort to within seconds of the slot — not to the millisecond, and never faster than one fire per 60 seconds.

    My endpoint is up but the thing it checks is down. Then what?

    Return 503. We only ever see the outcome of the delivery, so a health-checker that returns 200 while its target is on fire will never alert. This is the single most important line on this page if you are using us as a monitor.

    Who watches the scheduler?

    Health alerts cover the delivery outcomes of your schedules. We do not claim to detect our own outage. If a schedule is load-bearing enough to need that, run an independent heartbeat against your own endpoint as well.

    Can I edit a schedule?

    You replace it. Same id, whole object, version bumps. There is no partial update — fields you omit go back to their defaults.

    Does the free trial take a card?

    Yes. It is a Stripe Checkout subscription with 14 trial days, so Checkout collects a payment method. Cancel any time inside the trial and you are not charged. The trial is first-subscription-only.

    Can I pay by the year?

    Yes. $150/yr against $180 paid monthly — exactly two months free. You pick the interval at checkout — the plan, the allowances and the feature set are identical either way.

    What happens to my schedules if I cancel?

    Fires stop. The schedules, their configuration and their delivery history are kept, not deleted — the cost-stop pauses the clock rather than shredding the data.

    Are my headers and payload visible to you?

    They are encrypted at rest and redacted on every read. Through the console and through MCP you get ‹set› — presence, never the material.

    What counts against the 20,000?

    Actual runs, account-wide, per 30-day month — including run_now. The first 20,000 are included in the $15; past that, runs draw from your prepaid credits at $17 per million, tallied daily. With no credits on the account, schedule creation parks at the allowance instead; and 200,000 a month is the abuse fuse where everything stops until you ask us to raise it.

    Can I use this without an AI?

    Yes. The console does full CRUD — create, pause, resume, run-now, delete, test-alert — through the same functions the MCP tools call. The AI path is an option, not a requirement.

    Register the schedule. Get on with your day. We'll tell you if it stops landing.

    $15/mo or $150/yr · 14-day free trial, cancel any time.