# Preview a schedule

**POST** `/api/pieces/schedule/next-runs`

Shows when a schedule trigger would run: its cron and its next 5 times.

Needs any [API token](https://docs.gorunner.io/api/authentication#scopes). No permission is checked.

## Parameters

### Body

Optional. Left out, it previews the schedule’s defaults: every hour, weekdays only. So does a body with the settings at the top level: put them under `config`.

- `config` · Optional · object: The schedule trigger’s `config`. For the fields each `frequency` takes, see [Get a trigger’s fields](https://docs.gorunner.io/api/apps/get-app-trigger).

## Check for error first

An unknown frequency or a bad cron comes back as `{"cron": "", "timezone": "", "error": "unknown frequency: …"}`, with no `nextRuns`. An unknown time zone keeps its `cron` and `timezone` beside `error`.

`run_on_weekends` is `false` by default, so `every_day` skips Saturday and Sunday unless you set it. The preview uses the scheduler’s own code, so it matches what will run. See [Run on a schedule](https://docs.gorunner.io/build/run-on-a-schedule).

## Responses

- 200 OK

  The cron and the next 5 times, in the schedule’s own time zone. A schedule that can never run answers 200 too, with `error` instead.

- 400 `VALIDATION_ERROR`

  The body isn’t JSON, or `config` isn’t an object.

- 403 `FORBIDDEN`

  The token may not do this.

- 404 `NOT_FOUND`

  The workers have no schedule app.

- 500 `INTERNAL_ERROR`

  The worker failed. Try again.

- 503 `WORKER_UNAVAILABLE`

  No worker is checked in.

Any call can also answer: [`401 UNAUTHORIZED`](https://docs.gorunner.io/errors#UNAUTHORIZED) · [`401 TOKEN_ENDED`](https://docs.gorunner.io/errors#TOKEN_ENDED) · [`403 TOKEN_SURFACE_OFF`](https://docs.gorunner.io/errors#TOKEN_SURFACE_OFF) · [`403 FEATURE_OFF`](https://docs.gorunner.io/errors#FEATURE_OFF) · [`403 EMBED_NOT_ALLOWED`](https://docs.gorunner.io/errors#EMBED_NOT_ALLOWED) · [`403 EMBED_NOT_AVAILABLE`](https://docs.gorunner.io/errors#EMBED_NOT_AVAILABLE) · [`403 ACCOUNT_SUSPENDED`](https://docs.gorunner.io/errors#ACCOUNT_SUSPENDED) · [`413 PAYLOAD_TOO_LARGE`](https://docs.gorunner.io/errors#PAYLOAD_TOO_LARGE) · [`415 INVALID_CONTENT_TYPE`](https://docs.gorunner.io/errors#INVALID_CONTENT_TYPE) · [`429 RATE_LIMITED`](https://docs.gorunner.io/errors#RATE_LIMITED) · [`503 BUSY`](https://docs.gorunner.io/errors#BUSY) · [`503 PAUSED`](https://docs.gorunner.io/errors#PAUSED) · [`503 SERVICE_UNAVAILABLE`](https://docs.gorunner.io/errors#SERVICE_UNAVAILABLE). See [Errors](https://docs.gorunner.io/api/errors#by-status).

Request

**curl**

```bash
GORUNNER="https://api.gorunner.io"
GORUNNER_TOKEN="grt_…"  # needs any API token

curl -X POST "$GORUNNER/api/pieces/schedule/next-runs" \
  -H "Authorization: Bearer $GORUNNER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "config": {
      "frequency": "every_day",
      "hour_of_the_day": "9",
      "timezone": "Europe/Berlin",
      "run_on_weekends": true
    }
  }'
```

**JavaScript**

```js
const GORUNNER = "https://api.gorunner.io";
const TOKEN = process.env.GORUNNER_TOKEN;

const res = await fetch(
  `${GORUNNER}/api/pieces/schedule/next-runs`,
  {
    method: "POST",
    headers: {
        Authorization: `Bearer ${TOKEN}`,
        "Content-Type": "application/json",
      },
    body: JSON.stringify({
        config: {
          frequency: "every_day",
          hour_of_the_day: "9",
          timezone: "Europe/Berlin",
          run_on_weekends: true,
        },
      }),
  },
);
const { data } = await res.json();
```

**Python**

```python
import os, requests

GORUNNER = "https://api.gorunner.io"
TOKEN = os.environ["GORUNNER_TOKEN"]

res = requests.post(
    f"{GORUNNER}/api/pieces/schedule/next-runs",
    headers={"Authorization": f"Bearer {TOKEN}"},
    json={
      "config": {
        "frequency": "every_day",
        "hour_of_the_day": "9",
        "timezone": "Europe/Berlin",
        "run_on_weekends": True,
      },
    },
)
data = res.json()["data"]
```

Response · 200 OK

```json
{
  "data": {
    "cron": "0 9 * * *",
    "timezone": "Europe/Berlin",
    "nextRuns": [
      "2026-10-26T09:00:00+01:00",
      "2026-10-27T09:00:00+01:00",
      "2026-10-28T09:00:00+01:00",
      "2026-10-29T09:00:00+01:00",
      "2026-10-30T09:00:00+01:00"
    ]
  }
}
```

**Every field**

`{"config": {…}}`, with the schedule trigger’s `config` under `config`, exactly as it would sit in the definition. Get its legal fields from `GET /api/pieces/schedule/triggers/schedule?frequency=<value>`.

### Body

- `config` · Optional · object: The schedule trigger’s `config`, e.g. `{"frequency":"every_day","hour_of_the_day":9,"timezone":"UTC","run_on_weekends":true}`. Absent previews the app’s defaults. Legal fields per frequency come from GET /api/pieces/schedule/triggers/schedule?frequency=\<value>.

### Response · 200 OK

- `data` · object: The compiled schedule. Either `nextRuns` or `error` is present, never both.
  - `cron` · string: The cron expression this configuration compiles to. Empty when the frequency or the cron is unusable.
  - `timezone` · string: The zone the fire times are computed in. Empty when the frequency or the cron is unusable; an unknown zone is answered as sent, beside `error`.
  - `nextRuns` · array of times: The next 5 fire times, RFC 3339 in the schedule’s own zone (`2026-10-31T09:00:00+01:00`; `Z` for UTC). Absent when `error` is set. The count is fixed and cannot be requested.
  - `error` · string: Why this schedule can never fire — an invalid cron, an unknown frequency, an unknown time zone. Carried in a 200 body, not an HTTP error.

Related: [Run on a schedule](https://docs.gorunner.io/build/run-on-a-schedule) · [Get a trigger’s fields](https://docs.gorunner.io/api/apps/get-app-trigger)

Source: https://docs.gorunner.io/api/apps/preview-schedule-next-runs
