Schedules

A schedule is a registered intention to fire a webhook at a future time, optionally recurring. It is the simplest of the four primitives and the foundation the others lean on: you hand us a moment (or a repeating rule) and a webhook_url, and at that moment we POST to your endpoint. Nothing runs your code in between — a schedule is a clock, not a job queue.

Reach for a schedule when the cue is the time itself: "call this agent back in three days", "post the digest every Monday at 09:00". Reach for a deadline instead when the thing you care about is an obligation with a completion state and escalation — "the certificate is due March 14, warn me a week ahead, and keep nagging if it lapses". A schedule fires and forgets; a deadline tracks whether the work got done. When in doubt: if you would ever want to mark it "done", it's a deadline.

How schedules behave

A schedule is either one-shot or recurring:

  • One-shot — you give a single fire_at timestamp (must be in the future). It fires once and is finished.
  • Recurring — you give a cron expression (recurrence) and an IANA timezone (recurrence_tz, e.g. Europe/London). We evaluate the cron in that timezone, so a 0 9 * * MON schedule stays at 9am local across daylight-saving shifts. Each occurrence is its own record; the ones sharing a rule form a series joined by a recurrence_series_id.

A recurring series advances by spawning its successor when an occurrence resolves — whether it fired successfully or failed to deliver. The next occurrence is computed from the moment it actually fired, so a backlog never snowballs into a burst of catch-up callbacks. There is always exactly one pending occurrence of a live series at a time.

Each schedule carries a status:

Status Meaning
pending Registered, waiting for its fire_at.
fired Its moment arrived and the webhook was dispatched.
failed Its moment arrived but delivery ultimately failed.
cancelled You cancelled it before it fired (see below).

"Fired" means we dispatched the webhook, not that your endpoint acknowledged it. Delivery has its own independent retry/backoff/dead-letter pipeline — 5xx/timeouts are retried with exponential backoff, 4xx are not. That contract is owned by Webhooks & signing; a recurring series keeps advancing regardless of any single delivery's fate.

Create a schedule

Every create is idempotent: pass an idempotency key and a retried create returns the original schedule instead of making a second one. See Idempotency for how to choose keys.

Go to Schedules → New schedule. Choose One-shot and pick a date and time, or choose Recurring and enter a cron expression plus a timezone. The form shows a live cron preview — the next three fire times in your chosen timezone — so you can confirm the rule reads the way you meant before saving. Paste the webhook_url the callback should POST to.

POST /v1/schedules/ with your API key as a Bearer token. A one-shot needs fire_at; a recurring one needs recurrence + recurrence_tz (the server computes the first fire_at for you). Pass an Idempotency-Key header to make retries safe.

# One-shot: fire once, three days out
curl https://aigears.example.com/v1/schedules/ \
  -H "Authorization: Bearer aigears_live_..." \
  -H "Idempotency-Key: call-me-back-42" \
  -H "Content-Type: application/json" \
  -d '{
    "fire_at": "2026-07-06T09:00:00Z",
    "webhook_url": "https://hooks.example.com/aigears",
    "payload": {"task_id": "42"}
  }'

# Recurring: every Monday at 09:00 London time
curl https://aigears.example.com/v1/schedules/ \
  -H "Authorization: Bearer aigears_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "recurrence": "0 9 * * MON",
    "recurrence_tz": "Europe/London",
    "webhook_url": "https://hooks.example.com/aigears"
  }'

The payload you supply is echoed back to you inside the fired webhook, so use it to carry whatever context your handler needs to act. Before committing to a cron rule you can dry-run it with POST /v1/schedules/cron-preview/ ({"cron": ..., "tz": ...}) — it returns the next occurrences and creates nothing, costs nothing, and touches no quota.

Ask the agent in plain language; it translates the phrasing into a schedule_create call.

"Call me back in three days to check on the migration."

schedule_create(fire_at="2026-07-06T09:00:00Z", webhook_url="https://hooks.example.com/aigears", payload={"about": "migration"})

"Post the weekly report every Monday at 9am, London time."

schedule_create(recurrence="0 9 * * MON", recurrence_tz="Europe/London", webhook_url="https://hooks.example.com/aigears")

schedule_create takes the same fields as the HTTP body — fire_at for one-shot, recurrence + recurrence_tz for recurring, plus webhook_url, payload, and an optional idempotency_key. Invalid cron or an unknown timezone comes back as a tool error the agent can read and correct.

Inspect schedules

Listing shows one row per series (the live occurrence stands in for its rule), while fetching a single schedule gives you its full state plus its delivery history — every attempt we made to reach your endpoint, with status codes.

The Schedules list shows each series as a single row with its next fire time and status. Open a row for the detail page: the rule, the upcoming occurrence, and the delivery log for what has already fired.

GET /v1/schedules/ lists your team's schedules. Narrow it with ?status=pending (or any status value) and ?series_id=<uuid> to walk a single recurring series. GET /v1/schedules/{id}/ returns one schedule with its nested deliveries array — the attempt-by-attempt delivery history.

curl "https://aigears.example.com/v1/schedules/?status=pending" \
  -H "Authorization: Bearer aigears_live_..."

schedule_list returns the team's schedules; schedule_get(schedule_id=...) returns one with its delivery history.

"Did my Monday report schedule fire last week?"

schedule_list(), then schedule_get(schedule_id="...") to read the delivery log on the relevant occurrence.

Cancel a schedule

You can only cancel a schedule while it is pending. Cancelling one that has already fired, failed, or been cancelled returns a 409 — there is nothing left to stop.

There is one rule worth internalising for recurring schedules. A live series is represented by its single pending occurrence, and cancelling that occurrence terminates the series — no successor is spawned, so the whole recurrence stops. There is no separate "delete the series" action; cancel the pending occurrence and the rule is done.

Use the Cancel action on a schedule's row or its detail page. For a recurring schedule this stops the entire series, not just the next fire.

POST /v1/schedules/{id}/cancel/. It is idempotent — pass an Idempotency-Key so a retried cancel is a no-op rather than an error.

curl -X POST https://aigears.example.com/v1/schedules/01J9Z8XKQR.../cancel/ \
  -H "Authorization: Bearer aigears_live_..."

schedule_cancel(schedule_id=...).

"Stop the weekly report — we don't need it any more."

schedule_cancel(schedule_id="..."), which ends the whole series.

The webhook you receive

When a schedule fires, we POST a schedule.fired event to its webhook_url. Inside the standard signed envelope, the data payload is small — an id and the payload you registered:

{
  "event_type": "schedule.fired",
  "data": {
    "schedule_id": "01J9Z8XKQR...",
    "payload": { "task_id": "42" }
  }
}

The envelope shape, the X-AIGears-Signature header, and the retry/dead-letter rules are the same for every primitive and are documented once in Webhooks & signing. For the how-to of standing up a receiver and verifying the signature, see Receiving webhooks. Your handler must be idempotent on event_id, because delivery is at-least-once.

Limits

Creating a schedule counts against your plan's flow limit (the number of callbacks you can register per month). Recurring occurrences that we spawn automatically do not cost extra — only the create you initiate is metered, so a weekly schedule costs one, not fifty-two. Reads and inspections are never metered. The per-tier numbers, and what happens when you reach a limit (free tiers are capped hard; paid tiers get a soft grace window), live in Limits & tiers.