Use cases

Real jobs for real teams. Every scenario below is a complete job you can send to POST /alarms after swapping in your own URLs and credentials. The services are real, and so are their response shapes. The docs explain each field and the template language, and Integrations lists every built-in step.

Schedules run in the time zone you give them, so 09:00 in Berlin stays 09:00 in Berlin when the clocks change.

For engineering teams

The standup digest

A platform team in Berlin wants open pull requests in #eng before standup, without anyone maintaining a bot.

At 09:00 Berlin time on weekdays, list open PRs, turn each one into a line, and post them to Slack.

json
{
  "name": "Open PRs to #eng",
  "cron": { "expression": "0 9 * * 1-5", "time_zone": "Europe/Berlin" },
  "nodes": [
    { "id": "prs", "name": "Open PRs",
      "github_list_pull_requests": { "credential_id": "YOUR_GITHUB_CREDENTIAL_ID", "owner": "acme", "repo": "web", "state": "open" } },
    { "id": "lines", "name": "One line per PR",
      "transform": { "template": "{ \"count\": \"{{ $.length }}\", \"lines\": { \"{{#each $}}\": \"• <{{ $.html_url }}|#{{ $.number }}> {{ $.title }} ({{ $.user.login }})\" } }" } },
    { "id": "post", "name": "Post to #eng",
      "slack_send_message": { "webhook_url": "https://hooks.slack.com/services/T000/B000/XXXX", "text": "*{{ $.count }} open pull requests*\n{{ join($.lines, \"\\n\") }}" } }
  ]
}

Who's on call today

An SRE team rotates on-call weekly in PagerDuty, but people still ask "who's on today?" in Slack.

Every morning at 08:00 New York time, ask PagerDuty who's first in line for the escalation policy and announce it.

json
{
  "name": "On-call announcement",
  "cron": { "expression": "0 8 * * *", "time_zone": "America/New_York" },
  "nodes": [
    { "id": "oncall", "name": "Current on-call",
      "pagerduty_list_on_calls": { "credential_id": "YOUR_PAGERDUTY_API_KEY_ID", "escalation_policy": "PABC123", "limit": 1 } },
    { "id": "post", "name": "Post to #sre",
      "slack_send_message": { "webhook_url": "https://hooks.slack.com/services/T000/B000/XXXX", "text": ":rotating_light: On call today: *{{ $.oncalls[0].user.summary }}* ({{ $.oncalls[0].schedule.summary }})" } }
  ]
}

Know when your API is down

A two-person startup wants to hear their API is down before their customers tell them.

Check the health endpoint every minute, then ping Healthchecks.io. A failing check stops the job before the ping, so Healthchecks notices the missing ping and alerts you by email, SMS or Slack.

json
{
  "name": "API heartbeat",
  "cron": { "expression": "* * * * *" },
  "nodes": [
    { "id": "health", "name": "API health",
      "request": { "method": "GET", "endpoint": "https://api.acme.com/health" } },
    { "id": "ping", "name": "Tell Healthchecks",
      "healthchecks_ping": { "ping_url": "https://hc-ping.com/5f3c9a1e-8b2d-4c6f-9e7a-1d2b3c4d5e6f" } }
  ]
}

Every status code also lands in the job's run history.

Trigger the nightly backup

A team's database backup lives behind an internal endpoint, and nobody notices when the cron box that calls it dies.

At 03:00 UTC, start the backup with a shared secret, then post the result to Discord.

json
{
  "name": "Nightly backup",
  "cron": { "expression": "0 3 * * *" },
  "nodes": [
    { "id": "backup", "name": "Start backup",
      "request": { "method": "POST", "endpoint": "https://api.acme.com/internal/backups", "credential_id": "YOUR_BACKUP_SECRET_ID" } },
    { "id": "notify", "name": "Post to #ops",
      "discord_send_message": { "webhook_url": "https://discord.com/api/webhooks/123456789012345678/abcDEF_ghi-JKL", "content": "Backup {{ $.id }} started: {{ $.size_mb }} MB queued." } }
  ]
}

The endpoint should return 202 right away and do the work in the background, since each request has 10 seconds.

For revenue and operations

Chase failed payments

A SaaS founder loses a few customers each month to expired cards and only notices at month-end.

Each weekday at 09:00 New York time, list Stripe subscriptions that are past_due and post them to #revenue, where someone can reach out the same day.

json
{
  "name": "Past-due subscriptions to #revenue",
  "cron": { "expression": "0 9 * * 1-5", "time_zone": "America/New_York" },
  "nodes": [
    { "id": "subs", "name": "Past-due subscriptions",
      "stripe_list_subscriptions": { "credential_id": "YOUR_STRIPE_KEY_ID", "status": "past_due", "limit": 100 } },
    { "id": "lines", "name": "One line each",
      "transform": { "template": "{ \"count\": \"{{ $.data.length }}\", \"lines\": { \"{{#each $.data}}\": \"• <https://dashboard.stripe.com/customers/{{ $.customer }}|{{ $.customer }}>: <https://dashboard.stripe.com/invoices/{{ $.latest_invoice }}|unpaid invoice>\" } }" } },
    { "id": "post", "name": "Post to #revenue",
      "slack_send_message": { "webhook_url": "https://hooks.slack.com/services/T000/B000/XXXX", "text": "*{{ $.count }} subscriptions are past due*\n{{ join($.lines) }}" } }
  ]
}

Use a Stripe restricted key with read-only access to subscriptions.

The morning balance email

A founder checks the Stripe dashboard every morning for one number.

At 07:30 their time, read the Stripe balance and send it from their own Gmail account.

json
{
  "name": "Stripe balance email",
  "cron": { "expression": "30 7 * * *", "time_zone": "America/Chicago" },
  "nodes": [
    { "id": "balance", "name": "Stripe balance",
      "stripe_retrieve_balance": { "credential_id": "YOUR_STRIPE_KEY_ID" } },
    { "id": "email", "name": "Email me",
      "smtp_send_email": { "credential_id": "YOUR_GMAIL_CREDENTIAL_ID", "to": "founder@acme.com",
        "subject": "Stripe balance for {{ format(now:America/Chicago, \"dddd, MMM DD\") }}",
        "text": "Available: {{ $.available[0].amount / 100 }} {{ $.available[0].currency }}\nPending: {{ $.pending[0].amount / 100 }} {{ $.pending[0].currency }}" } }
  ]
}

Gmail needs an app password in the SMTP credential.

New customers into the CRM

A sales team wants every new paying customer in their CRM within 15 minutes, without paying for an integration platform.

Fetch recent Stripe customers and upsert them by email. The request body uses the previous step's output directly, so this job needs no transform.

json
{
  "name": "Stripe customers to CRM",
  "cron": { "expression": "*/15 * * * *" },
  "nodes": [
    { "id": "customers", "name": "Recent customers",
      "stripe_list_customers": { "credential_id": "YOUR_STRIPE_KEY_ID", "limit": 100 } },
    { "id": "upsert", "name": "Upsert contacts",
      "request": { "method": "POST", "endpoint": "https://crm.example.com/api/contacts/batch-upsert", "credential_id": "YOUR_CRM_TOKEN_ID",
        "body": "{ \"contacts\": { \"{{#each $.data}}\": { \"email\": \"{{ $.email }}\", \"name\": \"{{ $.name }}\", \"stripe_id\": \"{{ $.id }}\" } } }" } }
  ]
}

Use an upsert endpoint, since each run sends the latest 100 customers again.

Month-end numbers in the board sheet

An operations lead pastes the same three numbers into a Google Sheet on the first of every month.

On the 1st at 08:00, fetch last month's report and send only those numbers to a Zapier catch hook that appends a row. revenue and signups arrive as numbers, not strings, because each placeholder is the whole value.

json
{
  "name": "Monthly metrics to sheet",
  "cron": { "expression": "0 8 1 * *", "time_zone": "Europe/London" },
  "nodes": [
    { "id": "report", "name": "Last month",
      "request": { "method": "GET", "endpoint": "https://analytics.example.com/api/reports/last-month", "credential_id": "YOUR_ANALYTICS_TOKEN_ID" } },
    { "id": "pick", "name": "Pick numbers",
      "transform": { "template": "{ \"month\": \"{{ format(now() - months(1), \\\"YYYY-MM\\\") }}\", \"revenue\": \"{{ $.totals.revenue }}\", \"signups\": \"{{ $.totals.signups }}\" }" } },
    { "id": "append", "name": "Append row",
      "request": { "method": "POST", "endpoint": "https://hooks.zapier.com/hooks/catch/123456/abcdef/", "body": "{{ $ }}" } }
  ]
}

For online stores

Prices in the shopper's currency

A US skate shop sells to Europe and the UK, and its EUR and GBP prices drift whenever exchange rates move.

Every weekday after the European Central Bank publishes its reference rates, fetch them from the free Frankfurter API and send them to the store's currency settings.

json
{
  "name": "Daily exchange rates",
  "cron": { "expression": "30 16 * * 1-5", "time_zone": "Europe/Berlin" },
  "nodes": [
    { "id": "rates", "name": "ECB rates",
      "request": { "method": "GET", "endpoint": "https://api.frankfurter.app/latest?from=USD&to=EUR,GBP,CAD" } },
    { "id": "update", "name": "Update store",
      "request": { "method": "PUT", "endpoint": "https://shop.example.com/admin/api/currencies", "credential_id": "YOUR_STORE_TOKEN_ID",
        "body": "{ \"base\": \"USD\", \"as_of\": \"{{ $.date }}\", \"rates\": { \"EUR\": {{ $.rates.EUR }}, \"GBP\": {{ $.rates.GBP }}, \"CAD\": {{ $.rates.CAD }} } }" } }
  ]
}

For a single pair, the built-in frankfurter_latest step needs no URL at all.

Black Friday at midnight

A small store wants its Black Friday sale to start at exactly 00:00 Eastern, not whenever someone wakes up and flips the switch.

Two one-time jobs: one starts the sale at midnight Eastern on November 27, and one ends it after Cyber Monday. Each fires once and switches itself off, so nothing repeats next year.

json
{
  "name": "Black Friday 2026: start",
  "run_at": "2026-11-27T00:00:00-05:00",
  "nodes": [
    { "id": "start", "request": { "method": "POST", "endpoint": "https://shop.example.com/admin/api/sales/black-friday/start", "credential_id": "YOUR_STORE_TOKEN_ID" } },
    { "id": "tell", "slack_send_message": { "webhook_url": "https://hooks.slack.com/services/T000/B000/XXXX", "text": "Black Friday sale is live :tada:" } }
  ]
}
json
{
  "name": "Black Friday 2026: end",
  "run_at": "2026-11-30T23:59:00-05:00",
  "nodes": [
    { "id": "end", "request": { "method": "POST", "endpoint": "https://shop.example.com/admin/api/sales/black-friday/end", "credential_id": "YOUR_STORE_TOKEN_ID" } }
  ]
}

Warm the cache before the morning rush

A news site's homepage is slow on the first request after a deploy or a quiet night.

Fetch the heaviest route every 5 minutes, so readers never pay for a cold cache.

json
{
  "name": "Warm homepage cache",
  "cron": { "expression": "*/5 * * * *" },
  "nodes": [
    { "id": "warm", "request": { "method": "GET", "endpoint": "https://news.example.com/api/home?revalidate=1" } }
  ]
}

For field teams and clinics

The rain check

A landscaping crew in Portland decides every morning whether to mow or to do indoor jobs. The foreman checks three weather apps.

At 05:30 Portland time, Monday to Saturday, fetch the forecast from the free Open-Meteo API and send it to the crew's Telegram group before the trucks leave.

json
{
  "name": "Crew weather brief",
  "cron": { "expression": "30 5 * * 1-6", "time_zone": "America/Los_Angeles" },
  "nodes": [
    { "id": "forecast", "name": "Forecast",
      "open_meteo_forecast": { "latitude": "45.52", "longitude": "-122.68", "units": "imperial" } },
    { "id": "text", "name": "Tell the crew",
      "telegram_send_message": { "credential_id": "YOUR_TELEGRAM_BOT_ID", "chat_id": "-1001234567890",
        "text": "Today in Portland: {{ $.daily.precipitation_sum[0] }} in of rain, {{ $.daily.temperature_2m_min[0] }}–{{ $.daily.temperature_2m_max[0] }}°F. Wind now {{ $.current.wind_speed_10m }} mph." } }
  ]
}

The bot token lives in a credential, so it never appears in the job.

Tomorrow's appointment reminders

A dental clinic in London has patients forget appointments, and the front desk spends its evenings on reminder calls.

At 18:00 London time, Sunday to Thursday, ask the booking system for tomorrow's appointments and send them all to the SMS provider in one batch.

json
{
  "name": "Appointment reminders",
  "cron": { "expression": "0 18 * * 0-4", "time_zone": "Europe/London" },
  "nodes": [
    { "id": "bookings", "name": "Tomorrow's bookings",
      "request": { "method": "GET", "endpoint": "https://bookings.example.com/api/appointments?day={{ format(now:Europe/London + days(1), \"YYYY-MM-DD\") }}", "credential_id": "YOUR_BOOKING_KEY_ID" } },
    { "id": "sms", "name": "Send SMS batch",
      "request": { "method": "POST", "endpoint": "https://sms.example.com/v1/messages/batch", "credential_id": "YOUR_SMS_KEY_ID",
        "body": "{ \"messages\": { \"{{#each $.appointments}}\": { \"to\": \"{{ $.patient.phone }}\", \"body\": \"Hi {{ $.patient.first_name }}, a reminder of your appointment tomorrow at {{ $.time }} with {{ $.dentist }}. Reply C to cancel.\" } } }" } }
  ]
}

On a day with no bookings, {{#each}} over the empty list sends { "messages": [] }, so make sure the batch endpoint accepts it. For a single text, the built-in twilio_send_sms step does it with your Twilio account.

For AI agents

Morning ticket triage

A support team runs an AI agent that reads new tickets, tags them, and drafts replies. It works, but someone has to remember to start it.

Start the agent every weekday at 06:00. The run takes minutes, longer than the 10-second timeout, so the agent's API queues it and returns 202 right away.

json
{
  "name": "Morning support triage",
  "cron": { "expression": "0 6 * * 1-5", "time_zone": "America/Denver" },
  "nodes": [
    { "id": "start", "name": "Start agent run",
      "request": { "method": "POST", "endpoint": "https://agents.acme.com/api/runs", "credential_id": "YOUR_AGENT_KEY_ID",
        "body": "{ \"agent\": \"support-triage\", \"task\": \"Tag and draft replies for every ticket opened since {{ now:America/Denver - days(1) }}. Post a summary to #support.\" }" } }
  ]
}

An agent that schedules its own follow-up

A coding agent finishes a migration and needs to check the result tomorrow, after the conversation has ended.

The agent calls POST /alarms itself with a one-time job that wakes it again. It fires once and switches itself off. See the agent guide for the rules it should follow.

json
{
  "name": "acme-api: verify orders backfill",
  "run_at": "2026-11-02T09:00:00-08:00",
  "nodes": [
    { "id": "reopen", "name": "Re-open task",
      "request": { "method": "POST", "endpoint": "https://agents.acme.com/api/runs", "credential_id": "YOUR_AGENT_KEY_ID",
        "body": "{ \"agent\": \"backend\", \"task\": \"Check that the orders backfill finished: row counts match and the error log is clean.\" }" } }
  ]
}

When it's not the right fit

The work needs… Do this instead
More than 10 seconds per request Put it behind an endpoint that returns 202, and schedule that endpoint
One request per item in a list Send the list to a batch endpoint, as in appointment reminders
Filtering, branching, or loops Filter with the source API's query parameters (like Stripe's status=past_due), or do it in your own endpoint
Faster than once a minute Use a long-running worker or a queue
Direct database access Expose an endpoint, then schedule it