# Cron Translator — full documentation > Explain any cron expression in plain English, see the next run times in any timezone, and catch the gotchas that make schedules misfire: day-of-month/day-of-week OR semantics, steps that do not divide evenly, impossible dates, and daylight-saving shifts. **What this is not:** Does not run, install, or manage scheduled jobs. Does not support Quartz extensions (L, W, #, ?) beyond detecting and reporting them. This document is generated from the running engine, so every example below is the tool's actual current output rather than a copy that can drift. --- ## Architecture One pure function, three surfaces: | Surface | Path | Audience | | --- | --- | --- | | HTML | `/`, `/cron/{slug}` | people | | Markdown | same URLs, `Accept: text/markdown` or `?format=md` | agents | | JSON API | `/api/v1/*` | agents, developers | | MCP | `/api/mcp` | agents | The HTML, the markdown, the JSON, and the MCP tools all call the same engine. They cannot disagree. ## Access and pricing - Free: 250 calls per UTC day per caller, on every surface. - Past the free quota: HTTP 402 with an x402 v1 body — $0.001 per call, USDC (`0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`) on base. - Settlement is NOT enabled yet. The 402 body carries `payment_enabled: false` while that is true, so do not attempt payment — wait for the quota reset given in `quota.resetsAt`. - Callers are identified by `X-API-Key` if sent, then by a Web Bot Auth `Signature-Agent` header, then by a salted hash of the client IP. Raw IPs are never stored. - Every response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset`. - Agents are never blocked and never served different content. ## MCP Endpoint: `https://crontoenglish.com/api/mcp` (Streamable HTTP). Tools: ### `explain_cron` Translate a cron expression into English, list upcoming runs, and report gotchas. - `expression` (string, required) — five-field cron, e.g. `0 9 * * 1-5`. - `timezone` (string, default `UTC`) — IANA name, e.g. `America/New_York`. - `count` (integer 1-25, default 5) — how many upcoming runs. ### `next_cron_runs` Upcoming run times only, no prose. Same parameters as `explain_cron`. ### `build_cron` English to cron, rule-based. Returns an error listing supported phrasings when the phrase is outside the grammar. - `phrase` (string, required) — e.g. `every weekday at 9am`. - `timezone` (string, default `UTC`). ## JSON API All endpoints accept GET with query parameters or POST with a JSON body, and return `application/json`. CORS is open. ### `GET /api/v1/explain` Parameters: `expression` (required), `timezone` (default `UTC`), `count` (1-25, default 5). ``` curl 'https://crontoenglish.com/api/v1/explain?expression=0%209%20*%20*%201-5&timezone=America/New_York' ``` Response fields: `input`, `expression` (normalised), `description`, `fields[]` (`name`, `label`, `raw`, `description`, `values[]`), `warnings[]` (`code`, `severity`, `title`, `detail`, `fixHint`), `timezone`, `nextRuns[]` (`iso`, `local`), `runsPerDayEstimate`, `macro`. ### `GET /api/v1/next-runs` Same parameters. Returns `{ expression, timezone, count, runs[] }`. ### `GET /api/v1/build` Parameters: `phrase` (required), `timezone` (default `UTC`). ``` curl 'https://crontoenglish.com/api/v1/build?phrase=every%20weekday%20at%209am' ``` Returns `{ phrase, expression, matched_rule, ignored_words[], description, warnings[], next_runs[], timezone }`. Check `ignored_words` — leftover words can mean the phrase was misread. Supported phrasings: - `every minute` - `every 5 minutes` - `every hour` - `every 6 hours` - `every day at 9am` - `every day at 14:30` - `every monday at 9am` - `every weekday at 8:15am` - `every weekend at 10am` - `on the 1st of every month at midnight` - `every year on January 1 at noon` ## Errors Every error returns a stable shape: ```json { "error": { "code": "invalid_cron_expression", "message": "Hour value 25 is out of range (0-23).", "fix_hint": "Use a value between 0 and 23.", "field": "hour", "token": "25", "docs": "https://crontoenglish.com/docs" } } ``` Codes: `invalid_request`, `invalid_cron_expression`, `unrecognised_phrase`, `invalid_time`, `invalid_day_of_month`, `interval_out_of_range`, `empty_input`, `internal_error`. Statuses: 400 for bad input, 402 for quota exhausted, 500 for bugs. ## What the tool checks These are the reasons cron schedules misfire in production, and the reason to call this tool rather than reason about an expression directly: 1. **Day-of-month / day-of-week OR.** When both fields are restricted, standard cron fires when EITHER matches. `0 9 1-7 * 1` is not "the first Monday" — it is the 1st through 7th plus every Monday. 2. **Steps that do not divide evenly.** `*/7 * * * *` fires at minute 0, 7, …, 56, then again at 0 — an 11-minute gap, not 7. 3. **Impossible dates.** `0 0 30 2 *` never fires. `0 0 31 * *` skips the short months entirely rather than clamping. 4. **No timezone.** A cron expression has none; the scheduler's zone decides. Vercel Cron Jobs always run in UTC. 5. **Daylight saving.** Wall-clock hours near 02:00 can be skipped or repeated twice a year outside UTC. 6. **Non-standard syntax.** `L`, `W`, `#`, and `?` are Quartz extensions. Standard cron, Vercel, Kubernetes, and GitHub Actions reject them. This tool reports them and withholds run times rather than guessing. 7. **Macro portability.** `@daily` and friends are crontab conveniences many schedulers do not accept; the expanded form is always shown. ## Worked examples #### `0 9 * * 1-5` **English:** At 09:00, Monday through Friday **Fields:** - Minute `0` — At minute 0. - Hour `9` — At 09:00. - Day of month `*` — Every day of the month. - Month `*` — Every month. - Day of week `1-5` — On Monday, Tuesday, Wednesday, Thursday and Friday. **Warnings:** - [info] Interpreted in UTC — A cron expression has no timezone of its own — the scheduler's zone decides when it fires. These times are computed in UTC. The same expression on a host in another zone runs at a different moment. **Next runs (UTC):** - Thu, 2026-09-03, 09:00 (2026-09-03T09:00:00.000Z) - Fri, 2026-09-04, 09:00 (2026-09-04T09:00:00.000Z) - Mon, 2026-09-07, 09:00 (2026-09-07T09:00:00.000Z) **Frequency:** approximately 21.6 runs per month #### `0 9 1-7 * 1` **English:** At 09:00, on the 1st through 7th of the month and also on Monday **Fields:** - Minute `0` — At minute 0. - Hour `9` — At 09:00. - Day of month `1-7` — On the 1st, 2nd, 3rd, 4th, 5th, 6th and 7th. - Month `*` — Every month. - Day of week `1` — On Monday. **Warnings:** - [caution] Day-of-month and day-of-week are combined with OR, not AND — Both the day-of-month field ("1-7") and the day-of-week field ("1") are restricted. Standard cron runs the job when EITHER matches, so this fires more often than most people expect — not only on days that satisfy both. - [info] Interpreted in UTC — A cron expression has no timezone of its own — the scheduler's zone decides when it fires. These times are computed in UTC. The same expression on a host in another zone runs at a different moment. **Next runs (UTC):** - Thu, 2026-09-03, 09:00 (2026-09-03T09:00:00.000Z) - Fri, 2026-09-04, 09:00 (2026-09-04T09:00:00.000Z) - Sat, 2026-09-05, 09:00 (2026-09-05T09:00:00.000Z) **Frequency:** approximately 10.3 runs per month #### `*/7 * * * *` **English:** Every 7 minutes **Fields:** - Minute `*/7` — Every 7 minutes. - Hour `*` — Every hour. - Day of month `*` — Every day of the month. - Month `*` — Every month. - Day of week `*` — Every day of the week. **Warnings:** - [caution] Step of 7 does not divide 60 minutes evenly — "*/7" fires at 0, 7, 14 and 21, … and last at 56. Because 7 does not divide 60 evenly, the interval from 56 back around to 0 is 4, not 7. The schedule is not truly "every 7 minutes". **Next runs (UTC):** - Wed, 2026-09-02, 16:21 (2026-09-02T16:21:00.000Z) - Wed, 2026-09-02, 16:28 (2026-09-02T16:28:00.000Z) - Wed, 2026-09-02, 16:35 (2026-09-02T16:35:00.000Z) **Frequency:** approximately 216 runs per day #### `0 2 * * 6` **English:** At 02:00, on Saturday **Fields:** - Minute `0` — At minute 0. - Hour `2` — At 02:00. - Day of month `*` — Every day of the month. - Month `*` — Every month. - Day of week `6` — On Saturday. **Warnings:** - [caution] Daylight-saving transitions may skip or repeat this run — America/New_York observes daylight saving time, and this schedule fires in the hours that shift. On the spring-forward date the wall-clock hour may not exist (the run is skipped); on the autumn-back date it may occur twice. - [info] Interpreted in America/New_York — A cron expression has no timezone of its own — the scheduler's zone decides when it fires. These times are computed in America/New_York. The same expression on a host in another zone runs at a different moment. **Next runs (America/New_York):** - Sat, 2026-09-05, 02:00 (2026-09-05T06:00:00.000Z) - Sat, 2026-09-12, 02:00 (2026-09-12T06:00:00.000Z) - Sat, 2026-09-19, 02:00 (2026-09-19T06:00:00.000Z) **Frequency:** approximately 4.3 runs per month #### `0 0 31 * *` **English:** At 00:00, on the 31st of the month **Fields:** - Minute `0` — At minute 0. - Hour `0` — At 00:00. - Day of month `31` — On the 31st. - Month `*` — Every month. - Day of week `*` — Every day of the week. **Warnings:** - [caution] Some months will be skipped — Standard cron does not clamp to the end of the month, so this skips the 31st (skipped in February, April, June, September and November) entirely rather than running on the last day. - [info] Interpreted in UTC — A cron expression has no timezone of its own — the scheduler's zone decides when it fires. These times are computed in UTC. The same expression on a host in another zone runs at a different moment. **Next runs (UTC):** - Sat, 2026-10-31, 00:00 (2026-10-31T00:00:00.000Z) - Thu, 2026-12-31, 00:00 (2026-12-31T00:00:00.000Z) - Sun, 2027-01-31, 00:00 (2027-01-31T00:00:00.000Z) **Frequency:** approximately 11 runs per year ## Common schedules - `* * * * *` — Cron for every minute: https://crontoenglish.com/cron/every-minute - `*/2 * * * *` — Cron for every 2 minutes: https://crontoenglish.com/cron/every-2-minutes - `*/5 * * * *` — Cron for every 5 minutes: https://crontoenglish.com/cron/every-5-minutes - `*/10 * * * *` — Cron for every 10 minutes: https://crontoenglish.com/cron/every-10-minutes - `*/15 * * * *` — Cron for every 15 minutes: https://crontoenglish.com/cron/every-15-minutes - `*/20 * * * *` — Cron for every 20 minutes: https://crontoenglish.com/cron/every-20-minutes - `*/30 * * * *` — Cron for every 30 minutes: https://crontoenglish.com/cron/every-30-minutes - `*/45 * * * *` — Cron for every 45 minutes: https://crontoenglish.com/cron/every-45-minutes - `0 * * * *` — Cron for every hour: https://crontoenglish.com/cron/every-hour - `0 */2 * * *` — Cron for every 2 hours: https://crontoenglish.com/cron/every-2-hours - `0 */3 * * *` — Cron for every 3 hours: https://crontoenglish.com/cron/every-3-hours - `0 */4 * * *` — Cron for every 4 hours: https://crontoenglish.com/cron/every-4-hours - `0 */6 * * *` — Cron for every 6 hours: https://crontoenglish.com/cron/every-6-hours - `0 */8 * * *` — Cron for every 8 hours: https://crontoenglish.com/cron/every-8-hours - `0 */12 * * *` — Cron for every 12 hours: https://crontoenglish.com/cron/every-12-hours - `0 */5 * * *` — Cron for every 5 hours: https://crontoenglish.com/cron/every-5-hours - `0 0 * * *` — Cron for every day at midnight: https://crontoenglish.com/cron/every-day-at-midnight - `0 12 * * *` — Cron for every day at noon: https://crontoenglish.com/cron/every-day-at-noon - `0 9 * * *` — Cron for every day at 9am: https://crontoenglish.com/cron/every-day-at-9am - `0 6 * * *` — Cron for every day at 6am: https://crontoenglish.com/cron/every-day-at-6am - `0 17 * * *` — Cron for every day at 5pm: https://crontoenglish.com/cron/every-day-at-5pm - `0 23 * * *` — Cron for every day at 11pm: https://crontoenglish.com/cron/every-day-at-11pm - `0 0,12 * * *` — Cron for twice a day: https://crontoenglish.com/cron/twice-a-day - `0 9 * * 1-5` — Cron for every weekday at 9am: https://crontoenglish.com/cron/every-weekday-at-9am - `0 0 * * 1-5` — Cron for every weekday: https://crontoenglish.com/cron/every-weekday - `0 9-17 * * 1-5` — Cron for every hour during business hours: https://crontoenglish.com/cron/business-hours-every-hour - `*/15 9-17 * * 1-5` — Cron for every 15 minutes during business hours: https://crontoenglish.com/cron/every-15-minutes-during-business-hours - `0 0 * * 0,6` — Cron for every weekend day: https://crontoenglish.com/cron/every-weekend - `0 9 * * 1` — Cron for every Monday at 9am: https://crontoenglish.com/cron/every-monday-at-9am - `0 17 * * 5` — Cron for every Friday at 5pm: https://crontoenglish.com/cron/every-friday-at-5pm - `0 0 * * 0` — Cron for every Sunday at midnight: https://crontoenglish.com/cron/every-sunday-at-midnight - `0 2 * * 6` — Cron for every Saturday at 2am: https://crontoenglish.com/cron/every-saturday-at-2am - `0 0 1 * *` — Cron for the first of every month: https://crontoenglish.com/cron/first-of-the-month - `0 0 15 * *` — Cron for the 15th of every month: https://crontoenglish.com/cron/fifteenth-of-the-month - `0 0 28-31 * *` — Cron for the last day of the month: https://crontoenglish.com/cron/last-day-of-the-month - `0 0 1 1,4,7,10 *` — Cron for every quarter: https://crontoenglish.com/cron/every-quarter - `0 0 1 1 *` — Cron for once a year on January 1: https://crontoenglish.com/cron/every-year-on-january-1 - `0 9 1-7 * 1` — Cron for the first Monday of the month: https://crontoenglish.com/cron/first-monday-of-the-month --- Source: https://github.com/bwalvoord/gumball Changelog: https://crontoenglish.com/changelog.md