Skip to main content
First create an eval suite and keep its ID. A schedule stores a run configuration plus a cadence. Each time the cadence fires, the schedule starts a normal eval run, the same kind you start with POST /api/v1/llm/evals/runs. You read its samples and metrics the same way, through Results and artifacts. Creating, updating, triggering, and deleting schedules requires an API key with write:evals. Reading schedules and their runs requires read:evals.

Create a schedule

The response is an eval.schedule object with an eval_sched_... ID. enabled is true unless you pass "enabled": false, which creates the schedule paused.

Choose a cadence

Provide exactly one of these fields: An invalid cron expression is rejected with a 400 that explains the problem. Each fire starts up to 30 seconds after its scheduled time, so schedules that share a boundary do not all start at once. A schedule set to * * * * * and created at 18:31:33 starts its first run at about 18:32. If the scheduler is unavailable when a fire is due, that fire still starts when the scheduler recovers within 5 minutes. A fire missed by more than 5 minutes is skipped.

What each run uses

  • run is a frozen copy of the run fields, without suite_id and suite_version. It sets the models, optional task subset, judge and embedding models, generation settings, concurrency, and sample cap. models is required.
  • suite_version pins one suite version. Omit it to run the suite’s active version at each fire, so a new active version takes effect at the next fire.
  • metadata is copied onto every run the schedule starts.
  • Each run executes as the identity that created the schedule, or that most recently updated it. At each fire, that identity’s access is checked again, and its API key scopes are capped at the scopes it had when it last saved the schedule.

Check a schedule

Retrieve a schedule to see when it fires next and what happened the last time it fired.
last_run_status takes these values:

List the runs a schedule created

Runs are listed newest first. Filter with status, suite_version, or suite_id. Passing a suite_id other than the schedule’s returns 400.

Trigger a run now

Trigger a schedule to fire it immediately, in addition to its normal cadence. The fire creates a run exactly as a scheduled fire would.
A 200 response means the fire was accepted, not that a run was created. Confirm the run with last_run_id or the schedule’s runs list.
  • Under the default skip overlap policy, a trigger while one of the schedule’s runs is still active returns 200, but no run is created. See Overlapping runs.
  • Triggering a paused schedule returns 409. Resume it first.
  • A 400 with code schedule_trigger_unknown means the API could not confirm whether the fire started. Check the schedule’s runs before you trigger again. Do not retry this error automatically.

Overlapping runs

overlap_policy decides what happens when a fire arrives while an earlier run from the same schedule is still active. Use skip for schedules whose runs can outlast the interval between fires. There is no team-wide cap on concurrent scheduled runs, so with allow_all a short interval can start runs faster than they finish. Keep generation_concurrency and grader_concurrency in run low enough for that.

Pause, resume, and update

Update any field with a partial request. To pause a schedule, set enabled to false. To resume it, set enabled back to true.
  • A paused schedule keeps its cadence and fires again when you resume it.
  • To switch cadence, set the new field, for example { "interval_seconds": 3600 }. The other cadence field is cleared. Sending both returns 400.
  • Replacing run replaces the whole run configuration.
  • Every successful update, even a rename, rebinds future runs to the identity making the update.
  • If an update returns a server error, retrieve the schedule. If enabled is now false, the update was saved but the schedule was paused for safety. Send the update again with "enabled": true to resume it.
The API also pauses a schedule on its own, setting enabled to false, when the target suite version is deleted or the owning identity loses access. last_run_status tells you which. Fix the cause, then set enabled to true. To stop a schedule permanently, delete it. Deleting a schedule keeps the runs it already created.

Get notified about runs

Set webhook_url to receive a POST each time a run the schedule created changes status. The URL must use HTTPS and resolve to public addresses. The webhook receives these events: Each request body has this shape:
  • When you set webhook_secret (16 to 256 characters), each request carries X-Webhook-Signature: sha256=<hmac>, an HMAC-SHA256 of the request body. The secret is never returned. webhook_configured tells you whether one is stored.
  • Delivery is at least once. Deduplicate on resource_id plus event.
  • Each event is attempted up to 3 times with backoff. Only a 2xx response counts as delivered. Events that still fail are not queued for later delivery.
Use eval.run.failed as your failure alert. A run whose samples all fail still completes, so also check request_counts.failed and metrics in eval.run.completed events.