{
  "openapi": "3.1.0",
  "info": {
    "title": "CronSpell",
    "version": "0.1.0",
    "description": "Validate and explain cron expressions and list run times in any IANA time zone (DST-aware). Pure computation; nothing stored; no API key. About 60 checks per minute per IP. Schedules are read as wall-clock times in the time zone you pass (default UTC). A local time skipped when clocks jump forward does not run (a note says so), and a local time that happens twice when clocks go back is listed twice, each flagged dst \"repeated\". Cron daemons differ here (cronie, for example, runs fixed-time jobs from a skipped hour right after the jump and only once in a repeated hour), so check the notes. CronSpell is not affiliated with, endorsed by or sponsored by Meta, Anthropic, OpenAI, Anysphere or any cron software project.",
    "contact": {
      "email": "digitalpromohub.support@gmail.com"
    }
  },
  "servers": [
    {
      "url": "https://cronspell.mike-tusa.workers.dev"
    }
  ],
  "paths": {
    "/api/validate": {
      "get": {
        "operationId": "validateCron",
        "summary": "Validate a cron expression",
        "parameters": [
          {
            "name": "expr",
            "in": "query",
            "required": true,
            "description": "Cron expression (URL-encoded). Alias: expression.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            },
            "example": "0 9 * * 1-5"
          }
        ],
        "responses": {
          "200": {
            "description": "Validation result (valid true or false)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Validation"
                }
              }
            }
          },
          "400": {
            "description": "invalid_input: expr missing or too long",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "validateCronPostNotAllowed",
        "summary": "Not supported (use GET)",
        "responses": {
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/explain": {
      "get": {
        "operationId": "explainCron",
        "summary": "Explain a cron expression in plain English",
        "parameters": [
          {
            "name": "expr",
            "in": "query",
            "required": true,
            "description": "Cron expression (URL-encoded). Alias: expression.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            },
            "example": "0 9 * * 1-5"
          }
        ],
        "responses": {
          "200": {
            "description": "Explanation",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Base"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "notes": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      },
                      "required": [
                        "notes"
                      ]
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "invalid_input, invalid_expression, unsupported_syntax, never_runs or invalid_timezone (input checks run before the rate limiter)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "explainCronPostNotAllowed",
        "summary": "Not supported (use GET)",
        "responses": {
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/next": {
      "get": {
        "operationId": "nextRuns",
        "summary": "Next run times after a point in time",
        "parameters": [
          {
            "name": "expr",
            "in": "query",
            "required": true,
            "description": "Cron expression (URL-encoded). Alias: expression.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            },
            "example": "0 9 * * 1-5"
          },
          {
            "name": "tz",
            "in": "query",
            "required": false,
            "description": "IANA time zone (default UTC). Alias: timezone.",
            "schema": {
              "type": "string",
              "maxLength": 64,
              "pattern": "^[A-Za-z0-9_+\\-/]+$",
              "default": "UTC"
            },
            "example": "America/Denver"
          },
          {
            "name": "count",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 20,
              "default": 5
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Start (exclusive); default now. ISO 8601 with Z or an offset, a local time read in tz (YYYY-MM-DDTHH:MM[:SS]), or unix seconds. Years 1980-2199.",
            "schema": {
              "type": "string",
              "maxLength": 40
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Run times",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Base"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "timezone": {
                          "type": "string"
                        },
                        "runs": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Run"
                          }
                        },
                        "complete": {
                          "type": "boolean"
                        },
                        "notes": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "count": {
                          "type": "integer"
                        },
                        "from": {
                          "type": "string"
                        },
                        "direction": {
                          "type": "string",
                          "const": "next"
                        }
                      },
                      "required": [
                        "timezone",
                        "runs",
                        "complete",
                        "notes"
                      ]
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "invalid_input, invalid_expression, unsupported_syntax, never_runs or invalid_timezone (input checks run before the rate limiter)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "nextRunsPostNotAllowed",
        "summary": "Not supported (use GET)",
        "responses": {
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/previous": {
      "get": {
        "operationId": "previousRuns",
        "summary": "Most recent run times before a point in time (newest first)",
        "parameters": [
          {
            "name": "expr",
            "in": "query",
            "required": true,
            "description": "Cron expression (URL-encoded). Alias: expression.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            },
            "example": "0 9 * * 1-5"
          },
          {
            "name": "tz",
            "in": "query",
            "required": false,
            "description": "IANA time zone (default UTC). Alias: timezone.",
            "schema": {
              "type": "string",
              "maxLength": 64,
              "pattern": "^[A-Za-z0-9_+\\-/]+$",
              "default": "UTC"
            },
            "example": "America/Denver"
          },
          {
            "name": "count",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 20,
              "default": 5
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "End (exclusive); default now. ISO 8601 with Z or an offset, a local time read in tz (YYYY-MM-DDTHH:MM[:SS]), or unix seconds. Years 1980-2199.",
            "schema": {
              "type": "string",
              "maxLength": 40
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Run times",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Base"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "timezone": {
                          "type": "string"
                        },
                        "runs": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Run"
                          }
                        },
                        "complete": {
                          "type": "boolean"
                        },
                        "notes": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "count": {
                          "type": "integer"
                        },
                        "from": {
                          "type": "string"
                        },
                        "direction": {
                          "type": "string",
                          "const": "previous"
                        }
                      },
                      "required": [
                        "timezone",
                        "runs",
                        "complete",
                        "notes"
                      ]
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "invalid_input, invalid_expression, unsupported_syntax, never_runs or invalid_timezone (input checks run before the rate limiter)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "previousRunsPostNotAllowed",
        "summary": "Not supported (use GET)",
        "responses": {
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/between": {
      "get": {
        "operationId": "runsBetween",
        "summary": "All run times in a window (oldest first)",
        "parameters": [
          {
            "name": "expr",
            "in": "query",
            "required": true,
            "description": "Cron expression (URL-encoded). Alias: expression.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            },
            "example": "0 9 * * 1-5"
          },
          {
            "name": "tz",
            "in": "query",
            "required": false,
            "description": "IANA time zone (default UTC). Alias: timezone.",
            "schema": {
              "type": "string",
              "maxLength": 64,
              "pattern": "^[A-Za-z0-9_+\\-/]+$",
              "default": "UTC"
            },
            "example": "America/Denver"
          },
          {
            "name": "start",
            "in": "query",
            "required": true,
            "description": "Window start (inclusive). ISO 8601 with Z or an offset, a local time read in tz (YYYY-MM-DDTHH:MM[:SS]), or unix seconds. Years 1980-2199.",
            "schema": {
              "type": "string",
              "maxLength": 40
            }
          },
          {
            "name": "end",
            "in": "query",
            "required": true,
            "description": "Window end (inclusive). ISO 8601 with Z or an offset, a local time read in tz (YYYY-MM-DDTHH:MM[:SS]), or unix seconds. Years 1980-2199.",
            "schema": {
              "type": "string",
              "maxLength": 40
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Run times",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Base"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "timezone": {
                          "type": "string"
                        },
                        "runs": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Run"
                          }
                        },
                        "complete": {
                          "type": "boolean"
                        },
                        "notes": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "count": {
                          "type": "integer"
                        },
                        "start": {
                          "type": "string"
                        },
                        "end": {
                          "type": "string"
                        },
                        "limit": {
                          "type": "integer"
                        },
                        "truncated": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "timezone",
                        "runs",
                        "complete",
                        "notes"
                      ]
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "invalid_input, invalid_expression, unsupported_syntax, never_runs or invalid_timezone (input checks run before the rate limiter)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "runsBetweenPostNotAllowed",
        "summary": "Not supported (use GET)",
        "responses": {
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/health": {
      "get": {
        "operationId": "health",
        "summary": "Health and version",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "version": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "ok",
                    "version"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/mcp/server-card.json": {
      "get": {
        "operationId": "serverCard",
        "summary": "Static MCP server card (same tools as tools/list)",
        "responses": {
          "200": {
            "description": "Server card",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/mcp": {
      "post": {
        "operationId": "mcp",
        "summary": "Remote MCP server (JSON-RPC 2.0 over Streamable HTTP, stateless)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "JSON-RPC 2.0 request, notification or batch (max 10, at most one tools/call)"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "JSON-RPC 2.0 response (or batch array)"
                }
              }
            }
          },
          "202": {
            "description": "Notification or client response accepted (empty body)"
          },
          "400": {
            "description": "Parse error, invalid request or protocol/header mismatch",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "JSON-RPC 2.0 response (or batch array)"
                }
              }
            }
          },
          "403": {
            "description": "Origin not allowed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "JSON-RPC 2.0 response (or batch array)"
                }
              }
            }
          },
          "404": {
            "description": "Method not found (2026-07-28 requests)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "JSON-RPC 2.0 response (or batch array)"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "JSON-RPC 2.0 response (or batch array)"
                }
              }
            }
          },
          "429": {
            "description": "Too many MCP requests (Retry-After)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "JSON-RPC 2.0 response (or batch array)"
                }
              }
            }
          },
          "500": {
            "description": "Internal error"
          },
          "503": {
            "description": "Temporarily unavailable"
          }
        }
      },
      "get": {
        "operationId": "mcpGet",
        "summary": "Not supported (no SSE stream)",
        "responses": {
          "405": {
            "description": "Method not allowed",
            "headers": {
              "Allow": {
                "schema": {
                  "type": "string",
                  "const": "POST, OPTIONS"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "mcpDelete",
        "summary": "Not supported (no sessions)",
        "responses": {
          "405": {
            "description": "Method not allowed",
            "headers": {
              "Allow": {
                "schema": {
                  "type": "string",
                  "const": "POST, OPTIONS"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "invalid_input",
                  "invalid_expression",
                  "unsupported_syntax",
                  "never_runs",
                  "invalid_timezone",
                  "rate_limited",
                  "method_not_allowed",
                  "not_found",
                  "internal_error"
                ]
              },
              "message": {
                "type": "string"
              },
              "field": {
                "type": "string"
              }
            },
            "required": [
              "code",
              "message"
            ]
          }
        },
        "required": [
          "error"
        ]
      },
      "Base": {
        "type": "object",
        "properties": {
          "expression": {
            "type": "string"
          },
          "normalized": {
            "type": "string"
          },
          "format": {
            "type": "string",
            "enum": [
              "standard",
              "seconds",
              "shorthand"
            ]
          },
          "shorthand": {
            "type": "string"
          },
          "dayMatching": {
            "type": "string",
            "enum": [
              "or",
              "and"
            ]
          },
          "description": {
            "type": "string"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "expression",
          "normalized",
          "format",
          "dayMatching",
          "description",
          "version"
        ]
      },
      "Validation": {
        "type": "object",
        "properties": {
          "valid": {
            "type": "boolean"
          },
          "expression": {
            "type": "string"
          },
          "normalized": {
            "type": "string"
          },
          "format": {
            "type": "string"
          },
          "dayMatching": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "fields": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "raw": {
                  "type": "string"
                },
                "values": {
                  "type": "array",
                  "items": {
                    "type": "integer"
                  }
                }
              }
            }
          },
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "invalid_input",
                  "invalid_expression",
                  "unsupported_syntax",
                  "never_runs"
                ]
              },
              "message": {
                "type": "string"
              },
              "field": {
                "type": "string"
              }
            },
            "required": [
              "code",
              "message"
            ]
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "valid",
          "expression",
          "version"
        ]
      },
      "Run": {
        "type": "object",
        "properties": {
          "time": {
            "type": "string",
            "description": "Local time, ISO 8601 with UTC offset"
          },
          "utc": {
            "type": "string",
            "format": "date-time"
          },
          "unix": {
            "type": "integer"
          },
          "weekday": {
            "type": "string"
          },
          "offset": {
            "type": "string"
          },
          "dst": {
            "type": "string",
            "enum": [
              "repeated"
            ]
          }
        },
        "required": [
          "time",
          "utc",
          "unix",
          "weekday",
          "offset"
        ]
      }
    }
  }
}