# client.schedules

> client.schedules — the schedules operations.

`client.schedules` — the `schedules` operations.

On `AsyncKindgi` every method is the same, awaited.

## `client.schedules.list()`

```python signature
list(
    *,
    limit: int | None = None,
    cursor: str | None = None,
    status: Literal['active', 'paused'] | None = None,
    project_id: str | UUID | None = None,
    timeout: float | None = None,
) -> ScheduleCollectionPage
```

List cron schedules. `GET /v1/schedules`

Cursor-paginated. Tombstoned rows excluded. Optional `?status=active|paused` and `?projectId=` (one project's schedules) filters. Each schedule's `owner.displayName` is the owner's name now (the person's or the service account's), when it can be read.

## `client.schedules.register()`

```python signature
register(
    body: RegisterScheduleBody | Mapping[str, Any] | None = None,
    /,
    *,
    idempotency_key: str | None = None,
    timeout: float | None = None,
    **fields: Any,
) -> ScheduleRecord
```

Register a cron schedule. `POST /v1/schedules`

Registers a `kind=cron` trigger. `nextFireAt` is precomputed from the cron expression at register time; the cron scheduler in the runtime fires due triggers. Malformed cron expressions return 400 `trigger-invalid-config` without registering. An `improve` schedule is in the project it improves, one per agent and project (a second is `409 schedule-exists`), and needs `publish` on the agent as well as `write` on the project. `toolsAllowedUnattended` names the tools whose calls in its runs needn't wait for a person's yes, though a tool asks for one in a run no person started: naming it takes `admin` on the schedule's project, each id must be a tool what it runs calls, and it's audited (`trigger-tools-allowed-unattended-set`).

## `client.schedules.get()`

```python signature
get(
    trigger_id: str | UUID,
    /,
    *,
    upcoming: int | None = None,
    timeout: float | None = None,
) -> ScheduleRecord
```

Fetch a cron schedule. `GET /v1/schedules/{triggerId}`

## `client.schedules.update()`

```python signature
update(
    trigger_id: str | UUID,
    body: PatchScheduleBody | Mapping[str, Any] | None = None,
    /,
    *,
    idempotency_key: str | None = None,
    timeout: float | None = None,
    **fields: Any,
) -> ScheduleRecord
```

Update a cron schedule. `PATCH /v1/schedules/{triggerId}`

Partial merge on `config`. If `config.cronExpression` changes, `nextFireAt` is recomputed. `label: null` clears the label; omit to leave unchanged. Malformed cron expressions return 400 `trigger-invalid-config`. `toolsAllowedUnattended` replaces the list (`[]` clears it; absent keeps it): naming it takes `admin` on the schedule's project, and every change is audited. A new target (another flow or agent) while the list isn't empty must name it again, as it is or `[]`, else 400; a new version alone keeps it. A schedule is personal or shared, and no change moves its owner: only `POST /v1/schedules/{triggerId}/owner` does. So with an authorization store, a new target (another flow or agent, or an Improve run) must be one the owner can run, a person or a service account, else `409 trigger-owner-cannot-run-target`; that's not checked when the caller is the owner, nor for an owner that was unregistered or removed. A schedule a person owns is personal: only they (holding `write` on its project) or a project admin may change it. A schedule a service account owns is shared: changing its target or version, its input or `toolsAllowedUnattended` takes `admin` on its project, and the project's writers may change its label, cron line and policies. Without an authorization store, a project admin here is a tenant-admin key. A change by anyone but a personal schedule's owner is audited as `trigger-changed-for-owner`, naming the fields changed and the schedule's version after it, never their values.

## `client.schedules.pause()`

```python signature
pause(
    trigger_id: str | UUID,
    /,
    *,
    idempotency_key: str | None = None,
    timeout: float | None = None,
) -> ScheduleRecord
```

Pause a cron schedule. `POST /v1/schedules/{triggerId}/pause`

Idempotent-per-state: pausing an already-paused row returns 409 `trigger-already-in-state`. A personal schedule's owner or a project admin pauses it; a shared one, any writer of its project. An Improve schedule also takes `publish` on its agent; an admin of its project may pause it without `publish`.

## `client.schedules.resume()`

```python signature
resume(
    trigger_id: str | UUID,
    /,
    *,
    idempotency_key: str | None = None,
    timeout: float | None = None,
) -> ScheduleRecord
```

Resume a cron schedule. `POST /v1/schedules/{triggerId}/resume`

Recomputes `nextFireAt` so the scheduler picks up the resumed row on its next tick. A personal schedule's owner or a project admin resumes it; a shared one, any writer of its project. An Improve schedule also takes `publish` on its agent.

## `client.schedules.unregister()`

```python signature
unregister(
    trigger_id: str | UUID,
    /,
    *,
    idempotency_key: str | None = None,
    timeout: float | None = None,
) -> ScheduleUnregisterResult
```

Soft-delete a cron schedule (tombstone). `POST /v1/schedules/{triggerId}/unregister`

Tombstones the schedule; the cron scheduler ignores tombstoned triggers. Retention may hard-remove tombstoned triggers after the tombstone window. A personal schedule's owner or a project admin deletes it; a shared one, any writer of its project. An Improve schedule also takes `publish` on its agent; an admin of its project may delete it without `publish`.

## `client.schedules.fires()`

```python signature
fires(
    trigger_id: str | UUID,
    /,
    *,
    limit: int | None = None,
    cursor: str | None = None,
    timeout: float | None = None,
) -> ScheduleFirePage
```

A schedule's fire history. `GET /v1/schedules/{triggerId}/fires`

Newest first: each occurrence (and `run-now`) the schedule fired for, and what came of it: the run it started, or why it was skipped, refused or failed.

## `client.schedules.list_versions()`

```python signature
list_versions(
    trigger_id: str | UUID,
    /,
    *,
    limit: int | None = None,
    cursor: str | None = None,
    timeout: float | None = None,
) -> ScheduleVersionPage
```

A schedule's versions. `GET /v1/schedules/{triggerId}/versions`

Newest first: each version of the schedule, what it was from `createdAt` on, who made it (`createdBy`) and why (`cause`). Registering makes version 1, and each change makes the next (a PATCH that alters it, or a take-over to another owner): a PATCH that changes nothing makes none, nor do pausing, resuming or deleting. A version never changes once made. A fire's run uses the newest when it starts, and keeps it whatever later edits make (the run's `trigger.version`). A schedule from before versions has none until its next change or its first fire. The versions keep every input the schedule has had, so a secret pasted into its input stays in an earlier version after you edit it out: rotate it. Whoever can read the schedule can read its versions.

## `client.schedules.get_version()`

```python signature
get_version(
    trigger_id: str | UUID,
    version: int,
    /,
    *,
    timeout: float | None = None,
) -> ScheduleVersion
```

Fetch one version of a schedule. `GET /v1/schedules/{triggerId}/versions/{version}`

## `client.schedules.run_now()`

```python signature
run_now(
    trigger_id: str | UUID,
    /,
    *,
    idempotency_key: str | None = None,
    timeout: float | None = None,
) -> ScheduleFire
```

Run a schedule now. `POST /v1/schedules/{triggerId}/run-now`

One fire outside the schedule (`manual: true` in its history), starting one run as the schedule's owner. The schedule's next occurrence is unchanged. A personal schedule's owner or a project admin fires it; a shared one, any writer of its project.

## `client.schedules.take_ownership()`

```python signature
take_ownership(
    trigger_id: str | UUID,
    body: TakeTriggerOwnerBody | Mapping[str, Any] | None = None,
    /,
    *,
    idempotency_key: str | None = None,
    timeout: float | None = None,
    **fields: Any,
) -> ScheduleRecord
```

Take over a schedule. `POST /v1/schedules/{triggerId}/owner`

The caller becomes the schedule's owner, so its runs act as the caller from the next run that starts. Needs `admin` on the schedule's project (without an authorization store, a tenant-admin key) and `execute` on what it runs. For a schedule whose owner left or lost access, or to make one shared or personal. It's the one way a schedule's owner changes, audited as `trigger-owner-set`, naming the schedule's version after it; a person it's taken from is told, where the server tells owners. Name `runAsServiceAccountId` instead to make a service account the owner: that needs `act_as` on the account (a tenant admin's), and the account must itself hold the access the schedule's target needs. With an authorization store, that's checked when the account is named: an account that can't is refused (`409 trigger-owner-cannot-run-target`). Or name `runAsProject: true` to make the project's own service account the owner, made the first time with the `editor` role on the project, so the schedule is shared and runs as the project. It runs only what the project's editors can, and is checked the same way. The schedule keeps its `toolsAllowedUnattended`, now for the new owner's runs; a body naming it is refused (change it with PATCH).
