# 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. `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 pass) 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, 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.

## `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.

## `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.

## `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.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 fire. 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`; 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).
