# Crons

A cron sends a saved prompt to one or more of a workspace's projects on a schedule. Each time it fires, Sensei starts one Cron Run covering the selected projects in its own disposable sandbox. A run opens no chat session of its own. It is its own record, with its own transcript, files and Reply. Every run spends the workspace's Credentials, never a member's Subscription. [Crons](https://kosuke.ai/docs/automate/crons.md) explains the idea and the page in the app, and [Schedule recurring work](https://kosuke.ai/docs/recipes/schedule-recurring-work.md) walks through a first cron.

A cron belongs to its workspace, so its endpoints sit under `/api/organizations/{workspaceId}/crons`. `kosuke workspaces list` gives the workspace id. They used to sit under a project, before a cron could cover several; those endpoints are removed. Five paths answer `410` to `GET`, `POST`, `PUT`, `PATCH` and `DELETE`: `/api/projects/{projectId}/crons`, and under it `/{cronId}`, `/{cronId}/run`, `/{cronId}/runs` and `/{cronId}/runs/{runId}`. The message says to upgrade `@kosuke-ai/kosuke`, which is what a `kosuke` older than 5.0.0 gets on every `crons` command. Any other path under `/api/projects/{projectId}/crons` answers `404`.

A Personal Cron belongs to you and to no workspace, so its endpoints sit under `/api/user/crons`. In the CLI they are `kosuke crons list personal`, `get`, `create`, `update`, `delete`, `run`, `runs list` and `runs get`. You hold at most 50 Personal Crons: a create past that answers `400` naming the limit, and a member already above it keeps, edits, pauses and deletes theirs. Without repositories, each fire sends its prompt as your message to its own Sensei Thread. With Personal Repositories or Apps, it runs in its own sandbox. Both use your Subscriptions, or Sensei free when you hold none. Its crontab is read in its own `timezone`, not the Workspace Timezone. Your API key and your Sensei reach these endpoints. A chat session's sandbox token, a Cron Run's token and a Connected App's Connector Token answer `403`. Deleting a Personal Cron takes the web app or your own API key: Sensei, every other agent token and a Connected App's Connector Token answer `403`, so a prompt cannot remove your Crons. A run's transcript and Stop stay in the web app.

| Method | Path                                                                                         | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| ------ | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| GET    | `/api/organizations/{workspaceId}/crons?projectId=&mine=&search=&page=&pageSize=`            | List the workspace's crons, newest first, with each one's projects, creator and last run status: the status of its latest run.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| POST   | `/api/organizations/{workspaceId}/crons`                                                     | Create a cron, enabled, with you as its creator, with zero or more of the workspace's projects: each fire is one Cron Run in its own sandbox, cloning only the selected repositories. Every run spends the workspace's Credentials, never a member's Subscription, so a workspace with no usable provider is refused. The model is an encoded id such as anthropic:opus__xhigh; null or left out uses the saved Workspace Default Model when its Provider has usable workspace Credentials, otherwise the platform's choice among usable workspace Credentials at run time. The crontab is read in the Workspace Timezone (`kosuke workspaces timezone set`), and may not fire more often than every 15 minutes. A body that sends timezone is refused. From an agent (Sensei, a sandbox or a Cron Run) it is refused with 409 CRON_ALREADY_EXISTS when the workspace has a Cron by that name.                                                                                                                                                                                                                                                                                                                                  |
| GET    | `/api/organizations/{workspaceId}/crons/{cronId}`                                            | Read one cron: its prompt, schedule, model, creator, when it runs next, its projects, its last run status, and how many runs in a row failed: five disable it.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| PATCH  | `/api/organizations/{workspaceId}/crons/{cronId}`                                            | Change a cron; only the fields sent change. projectIds replaces its projects. Any edit sets the cron's failure count back to zero. enabled=false pauses the cron and enabled=true resumes it; model=null goes back to the workspace default. The creator or a workspace admin only.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| DELETE | `/api/organizations/{workspaceId}/crons/{cronId}`                                            | Delete a cron with its runs and their transcripts. Their files and the sessions its runs filed stay. The creator or a workspace admin only.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| GET    | `/api/organizations/{workspaceId}/crons/{cronId}/artifacts?projectId=&kind=&page=&pageSize=` | Use `crons files list`. List the files of the four kinds a cron's runs wrote, across all of its runs.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| GET    | `/api/organizations/{workspaceId}/crons/{cronId}/files?projectId=&kind=&page=&pageSize=`     | List the files a cron's runs wrote, across all of its runs, most recently changed first: each one's title, kind and version, with the run and the project it came from, and the total the filters keep. One row per file: the same name written by two runs is two rows. Rows only: a body or a file url comes from crons runs files get, with the row's runId. The project and kind filters each take several values and keep a file matching any of them.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| POST   | `/api/organizations/{workspaceId}/crons/{cronId}/run`                                        | Fire one Cron Run covering all the cron's projects or the selected projectIds, in its own sandbox; a cron with no projects runs with none. Refused for a cron stopped with No projects left until an edit picks a project. Answers with runs: [{ runId, status, projectIds, projectId }]; projectId is deprecated and names the first project by name, or null. The creator or a workspace admin only.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| GET    | `/api/organizations/{workspaceId}/crons/{cronId}/runs?projectId=&status=&page=&pageSize=`    | List a cron's runs, newest first: each one's projects, status, the first line of its reply or its error, with the total and failed counts of the runs the filters keep. The project and status filters each take several values and keep a run matching any of them; a project filter keeps every run that covered the project.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| GET    | `/api/organizations/{workspaceId}/crons/{cronId}/runs/{runId}`                               | Read one run of a cron: the projects it covered, its status, the model it ran on, the Cron Agent's reply, and the sessions it filed with each one's status and whether it waits for an answer.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| GET    | `/api/organizations/{workspaceId}/crons/{cronId}/runs/{runId}/artifacts`                     | Use `crons runs files list`. List a run's files of the four kinds an agent writes.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| GET    | `/api/organizations/{workspaceId}/crons/{cronId}/runs/{runId}/artifacts/{artifactId}`        | Use `crons runs files get`. Read one file of a run by its id or its name: a deleted one, or one of another kind, is not found.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| POST   | `/api/organizations/{workspaceId}/crons/{cronId}/runs/{runId}/cancel`                        | Stop a run of a cron that is still going, the way Stop on its page does. The run ends cancelled; the files it stored and the sessions it filed stay, and its sandbox is removed. The creator or a workspace admin only.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| GET    | `/api/organizations/{workspaceId}/crons/{cronId}/runs/{runId}/files`                         | List the files a run of a cron wrote, most recently changed first, a deleted one as a row that says who deleted it. Rows only: a body or a file url comes from reading one.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| GET    | `/api/organizations/{workspaceId}/crons/{cronId}/runs/{runId}/files/{fileId}`                | Read one file of a run of a cron, by its id or its name: a Markdown or HTML file carries its body, any other a presigned url, its media type and its size. A deleted one says who deleted it and holds no content.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| GET    | `/api/organizations/{workspaceId}/crons/{cronId}/runs/{runId}/messages`                      | Read the Cron Agent's transcript of one run of a cron: the prompt it was given, then everything it did, with the run's status.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| GET    | `/api/user/crons?search=&page=&pageSize=`                                                    | List your Personal Crons, newest first, with each one's thread and last run status. A Personal Cron belongs to you and no workspace: with Personal Repositories or Apps it runs in its own sandbox; without, in its Sensei Thread. Both use your Subscriptions or Sensei free when you hold none. Among agents, only your Sensei reaches them.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| POST   | `/api/user/crons`                                                                            | Create a Personal Cron, enabled, with you as its owner. You hold at most 50: past that a create is refused with 400, and you delete or reuse one instead. A Personal Cron belongs to you and no workspace: with Personal Repositories or Apps it runs in its own sandbox; without, in its Sensei Thread. Both use your Subscriptions or Sensei free when you hold none. The crontab is read in the cron's own timezone, and may not fire more often than every 15 minutes. The model is an encoded id such as anthropic:opus__xhigh; repositories require an explicit Model from any released Provider on one of your Subscriptions, or kosuke-free when you hold none. Changing to Free while you hold a Subscription, or to a personal Model without one, is refused. Without repositories, null or left out runs on the thread’s model. From your Sensei it is refused with 409 CRON_ALREADY_EXISTS when you have a Personal Cron by that name: update that one instead. From Sensei, create or change one only when the member asks. To move a workspace Cron here, read it and recreate it with its timezone (its model only when the member's own Subscription runs it); the member deletes the workspace one in the app. |
| GET    | `/api/user/crons/{cronId}`                                                                   | Read one of your Personal Crons: its prompt, schedule, timezone, model, when it runs next, its thread, its last run status, and how many runs in a row failed: five disable it. A Personal Cron belongs to you and no workspace: with Personal Repositories or Apps it runs in its own sandbox; without, in its Sensei Thread. Both use your Subscriptions or Sensei free when you hold none.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| PATCH  | `/api/user/crons/{cronId}`                                                                   | Change one of your Personal Crons; only the fields sent change. Any edit sets the cron's failure count back to zero. enabled=false pauses the cron and enabled=true resumes it. No projects left stays until a save picks at least one repository, and Resume is refused meanwhile. Changing repositories rechecks the Model: a sandbox requires an explicit Model from any released Provider on one of your Subscriptions, or kosuke-free when you hold none. Changing to Free while you hold a Subscription, or to a personal Model without one, is refused. Re-sending the unchanged stored Model is accepted. Without repositories, model=null goes back to the thread’s model. The crontab is read in the cron's own timezone.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| DELETE | `/api/user/crons/{cronId}`                                                                   | Delete one of your Personal Crons with its runs and their transcripts. Their files stay in Personal Files. A run still going is stopped first. Only you can, in the app or with your API key: a sandbox, a Cron Run, Sensei and a Connected App cannot. To keep it, pause it with enabled=false instead.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| GET    | `/api/user/crons/{cronId}/artifacts?projectId=&kind=&page=&pageSize=`                        | Use `crons files list`. List the files of the four kinds a cron's runs wrote, across all of its runs.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| GET    | `/api/user/crons/{cronId}/files?projectId=&kind=&page=&pageSize=`                            | List the files a cron's runs wrote, across all of its runs, most recently changed first: each one's title, kind and version, with the run and the project it came from, and the total the filters keep. One row per file: the same name written by two runs is two rows. Rows only: a body or a file url comes from crons runs files get, with the row's runId. The project and kind filters each take several values and keep a file matching any of them.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| POST   | `/api/user/crons/{cronId}/run`                                                               | Fire one of your Personal Crons once now, on your own Subscription: with repositories it runs in its own sandbox; without, in its Sensei Thread. Resume a paused Cron first. Refused for No projects left until a save picks a repository. Answers with runs: [{ runId, status, projectIds, projectId }]. Status may be queued while the fire is pending.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| GET    | `/api/user/crons/{cronId}/runs?status=&page=&pageSize=`                                      | List the runs of one of your Personal Crons, newest first: each one's status, the first line of its report or its error, and whether it ran in a sandbox or Sensei Thread, with the total and failed counts of the runs the filter keeps. The status filter takes several values and keeps a run matching any of them.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| GET    | `/api/user/crons/{cronId}/runs/{runId}`                                                      | Read one Personal Cron run: its kind, status, Model, report, repositories and filed sessions. A sandbox run has its Cron Agent transcript and files and threadId null. A thread run names the Sensei Thread holding its Turn.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| GET    | `/api/user/crons/{cronId}/runs/{runId}/artifacts`                                            | Use `crons runs files list`. List a run's files of the four kinds an agent writes.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| GET    | `/api/user/crons/{cronId}/runs/{runId}/artifacts/{artifactId}`                               | Use `crons runs files get`. Read one file of a run by its id or its name: a deleted one, or one of another kind, is not found.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| GET    | `/api/user/crons/{cronId}/runs/{runId}/files`                                                | List the files a run of a cron wrote, most recently changed first, a deleted one as a row that says who deleted it. Rows only: a body or a file url comes from reading one.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| GET    | `/api/user/crons/{cronId}/runs/{runId}/files/{fileId}`                                       | Read one file of a run of a cron, by its id or its name: a Markdown or HTML file carries its body, any other a presigned url, its media type and its size. A deleted one says who deleted it and holds no content.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |

A Personal Cron with repositories accepts `kosuke-free` when you hold no Subscription. Both scheduled fires and **Run now** use Free while you hold none. When moving a workspace Cron to Personal, use its model only if your Subscription runs it, or `kosuke-free` with none.

Each Personal Cron run on Free uses one Source. Sensei moves it automatically when that Source reaches its limit, is paused or is removed. The model, Source and provider stay hidden in run pages and live updates.

## What the Cron Agent does

The Cron Agent clones the selected repositories at `/app/projects/<slug>` in its own sandbox. It may edit the clones, run the project’s checks and commit there. It makes no GitHub writes: no push, pull request, comment, label or review. A code change becomes a ticket sent as a message, with the patch in a file of the run. The main sandbox and live checkout stay untouched. No live app runs here; checks against public addresses must change nothing. To test the running app, it files a session in a Collaboration project, labels it `cron-run`, starts it, follows its first turn, reads the result and archives it. Save results in Files or in tickets sent as messages. Clone edits disappear when the sandbox is removed; the Reply, transcript, the run's files and filed sessions stay. It never asks a Clarification. When something is unclear, it decides and states its assumptions in its Reply. It reaches the Project Skills in the checkout and the Factory Skills, and runs only the Factory Skills that read and report, such as `kosuke:web-pentest`. A prompt that calls any other skill is passed, word for word, into a session it files or a ticket it sends.

Work the Cron Agent hands over goes into a chat session as a ticket it sends as a message (`kosuke sensei messages send`, key `ticket-<slug>`): it reaches a member's Sensei, and the ticket is created in the Backlog only when that member approves. A cron prompt that explicitly says to file Backlog tickets directly is the exception: then the Cron Agent files them itself. It starts a session or sends into one only when the cron's prompt asks for exactly that. Before it sends or files, it reads the workspace's open tasks, so it does not hand over the same work twice, and it may fold news into a Todo task its own cron filed earlier in the project. A run may send or file any number of tickets. When the cron has Reports to (`reportTo`), every message goes to one of those members, and `POST /api/sensei/teammate-messages` refuses a run's message to anyone else with 403 `RECIPIENT_NOT_IN_REPORT_TO`. A run cannot change its own cron's Reports to (403 `REPORT_TO_MEMBER_ONLY`).

The sessions it files are Sensei's. Sensei creates them and is their Assignee. These sessions run on the workspace's Credentials, until a member starts one or reassigns it to someone. From then on it runs on that Assignee's credentials. Each runs on the model the prompt names, otherwise on the cron's. A model the workspace cannot run falls back to the cron's. In every other way they are ordinary sessions: they show on the board and may ask Clarifications. While a session is Sensei's, every member gets its attention cues, unread and waiting on you, as its Assignee would.

## Create a cron

`POST /api/organizations/{workspaceId}/crons` creates a cron, enabled, with you as its creator.

| Field            | Type                               | Required | What it does                                                                                                                                                                                                                                                                                                                                                                              |
| ---------------- | ---------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`           | string, 1 to 100 characters        | Yes      | The cron's name.                                                                                                                                                                                                                                                                                                                                                                          |
| `projectIds`     | array of project ids, at least one | Yes      | The projects the cron fires in, covered by one Cron Run. Every one must belong to the workspace. The list is fixed when you save: a project created later joins only when someone edits the cron to add it.                                                                                                                                                                               |
| `prompt`         | string                             | Yes      | What the Cron Agent is asked to do, handed to it word for word on every fire.                                                                                                                                                                                                                                                                                                             |
| `cronExpression` | string                             | Yes      | Five fields, for minute, hour, day of month, month and day of week, read in the Workspace Timezone. It may fire at most once every 15 minutes. At a one-hour clock change, one fixed time in the skipped hour runs once, one hour later, and one fixed time in the repeated hour runs once, on the first pass. A later time in that skipped hour can be skipped when the next run is chosen after an earlier one. A time less than 15 minutes before the clock falls back can run again on the later pass. A wildcard hour keeps real spacing across that one-hour change. A shorter change can leave a longer gap. |
| `model`          | string, or `null`                  | No       | A value from [`GET /api/models`](https://kosuke.ai/docs/endpoint-reference/models.md), such as `anthropic:opus__xhigh`. Left out or `null`, each run uses the saved Workspace Default Model when its Provider has usable workspace Credentials. Otherwise Sensei chooses a model those Credentials can run. The Cron Agent runs on this model, and so does every session it files unless the prompt names another. |

```bash
curl -s -X POST "https://app.kosuke.ai/api/organizations/$WORKSPACE_ID/crons" \
  -H "Authorization: Bearer $KOSUKE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Weekly dependency check", "projectIds": ["'"$PROJECT_ID"'"], "prompt": "List outdated dependencies and send a ticket for the safe updates.", "cronExpression": "0 9 * * 1"}'
```

The answer is `201` with the cron:

```json
{
  "data": {
    "id": "c4a81f3e-2b69-4d07-9e15-6f0b3c8d2a71",
    "projects": [
      {
        "id": "9b1d4e27-5c3a-4f86-a0d2-7e4b8c6f1a35",
        "name": "storefront",
        "baseBranch": "main",
        "lastRunStatus": null,
        "running": false
      }
    ],
    "consecutiveFailures": 0,
    "createdBy": "Hn2kW8xQ4pZ7cV1mB5tR9yL3sD6fG0jA",
    "createdByName": "Mara Keller",
    "name": "Weekly dependency check",
    "prompt": "List outdated dependencies and send a ticket for the safe updates.",
    "cronExpression": "0 9 * * 1",
    "timezone": "Europe/Zurich",
    "model": null,
    "enabled": true,
    "disabledReason": null,
    "lastRunAt": null,
    "nextRunAt": "2026-10-05T07:00:00.000Z",
    "lastRunStatus": null,
    "createdAt": "2026-10-01T12:30:04.512Z",
    "updatedAt": "2026-10-01T12:30:04.512Z"
  }
}
```

The same with the CLI, where `--project` takes one project and repeats for several:

```bash
kosuke crons create $WORKSPACE_ID --project $PROJECT_ID --project $OTHER_PROJECT_ID \
  --name "Weekly dependency check" \
  --prompt "List outdated dependencies and send a ticket for the safe updates." \
  --cron-expression "0 9 * * 1"
```

A cron has no timezone of its own: every cron of a workspace is read in the Workspace Timezone, UTC until an owner or admin sets another with [`PUT /api/organizations/{workspaceId}/timezone`](https://kosuke.ai/docs/endpoint-reference/workspaces.md) (`kosuke workspaces timezone set`). The cron's `timezone` is that zone, read-only. In the example above the workspace is on `Europe/Zurich`, so the 09:00 Monday fire is 07:00 UTC. A create or change whose body sends `timezone` answers `400`, pointing to `kosuke workspaces timezone set`.

The crontab is checked on every create and every change. A request answers `400` when the crontab does not have five fields or the schedule could fire twice within 15 minutes in the Workspace Timezone. That includes the days a clock change shifts. It also answers `400` when `projectIds` is empty or names a project of another workspace. A workspace with no usable Credentials can hold no cron, and a create there answers `400`.

## List and read crons

`GET /api/organizations/{workspaceId}/crons` lists the workspace's crons, newest first, as `{ "data": { "crons": [...], "total": 12 } }`.

| Parameter          | What it does                                                       |
| ------------------ | ------------------------------------------------------------------ |
| `projectId`        | Keeps the crons that include this project, alone or among others.  |
| `mine`             | `true` keeps the crons you created.                                |
| `search`           | Keeps crons whose name or prompt contains the text, ignoring case. |
| `page`, `pageSize` | The page to read. `pageSize` defaults to 25 and goes up to 100.    |

Each cron lists its `projects`, ordered by name. `lastRunStatus` is the cron's latest run status. `consecutiveFailures` counts failed runs since its last success or reset. A project's `running` says whether a running run covers it.

`GET /api/organizations/{workspaceId}/crons/{cronId}` reads one cron, with `nextRunAt`. The CLI commands are `kosuke crons list <workspaceId>`, with `--project` for the project filter, and `kosuke crons get <workspaceId> <cronId>`.

## Change a cron

`PATCH /api/organizations/{workspaceId}/crons/{cronId}` takes any field of the create body, plus `enabled`. Only the fields you send change. `projectIds` replaces the cron's projects and may not be empty. `model: null` goes back to the workspace default. Any edit resets the cron's failure count. Saving leaves a disabled cron disabled.

```bash
kosuke crons update $WORKSPACE_ID $CRON_ID --cron-expression "0 7 * * 1-5"
kosuke crons update $WORKSPACE_ID $CRON_ID --project $PROJECT_ID --project $OTHER_PROJECT_ID
```

A flag cannot send `null`, so the CLI resets the model with `--body '{"model": null}'`.

## Pause and resume

`enabled: false` pauses a cron and `enabled: true` resumes it. On the CLI these are `--no-enabled` and `--enabled`. Either one clears `disabledReason`. Resuming resets the cron's failure count.

Five failed runs in a row disable the cron with a reason. A success resets the count. Cancelled and skipped runs leave it unchanged.

When a cron's last project is deleted, the cron stays, disabled, and `disabledReason` is `No projects left`. Edit it to pick other projects, or delete it.

## Legacy project resume

The project resume endpoint returns `410 Gone`. Resume the cron instead.

## Run now

`POST .../{workspaceId}/crons/{cronId}/run` fires one run now, paused or not. With no body it covers all selected projects. `{ "projectIds": [...] }` covers only those projects, each of which must belong to the cron. The run starts its own sandbox on workspace Credentials. Its response carries one run with `projectIds`. Read the run by its `runId`. The CLI command is `kosuke crons run <workspaceId> <cronId>`, with `--project <projectId>` repeated to pick projects.

## Read runs

Every fire leaves one run covering its selected projects, including when no Cron Agent started. `GET .../{workspaceId}/crons/{cronId}/runs` lists them, newest first, as `{ "data": { "runs": [...], "total": 30, "failed": 2 } }`. It takes `page` and `pageSize`, and two filters that each take several values as repeated keys, such as `?projectId=…&projectId=…&status=failed&status=skipped`:

| Parameter   | What it does                                                                                    |
| ----------- | ----------------------------------------------------------------------------------------------- |
| `projectId` | Keeps each run that covered any of these projects.                                              |
| `status`    | Keeps the runs with these statuses: `running`, `succeeded`, `failed`, `cancelled` or `skipped`. |

`total` and `failed` count the runs the filters keep. On the CLI, `kosuke crons runs list <workspaceId> <cronId> --project <projectId> --status failed` repeats either flag for several values.

Each run carries `projectIds` and `projects`, its `status`, a one-line `summary` that is the first line of the Cron Agent's Reply, and `error`. `GET .../{workspaceId}/crons/{cronId}/runs/{runId}` adds the `model` it ran on, the `report` that holds the Reply, and `filedSessions`, the sessions the run filed, each with its status and whether it waits for an answer.

| `status`    | What it means                                                                                                 | Counts toward disabling the cron    |
| ----------- | ------------------------------------------------------------------------------------------------------------- | ----------------------------------- |
| `running`   | The Cron Agent is working.                                                                                    | No. It leaves the count as it was.  |
| `succeeded` | The Cron Agent finished.                                                                                      | No. It sets the count back to zero. |
| `failed`    | A gate refused the fire, something broke, the run passed its 5 hours, or its sandbox stopped.                 | Yes.                                |
| `cancelled` | The cron's creator or a workspace admin stopped it.                                                           | No. It leaves the count as it was.  |
| `skipped`   | The cron's previous run was still running, or a Run now met another fire of the cron that was still starting. | No. It leaves the count as it was.  |

A merge that updates the checkout does not stop a run.

A run keeps what the Cron Agent did. Its page in the app shows it, and so do these commands:

- the transcript, with `kosuke crons runs messages list`;
- its files, with `kosuke crons runs files list` and `kosuke crons runs files get`, and every run's with `kosuke crons files list`;
- the Reply, the `report` field of `kosuke crons runs get`;
- the sessions the run filed, `filedSessions` on `kosuke crons runs get`.

The sessions a run filed are ordinary sessions. They show on the board and in the session lists.

## Stop a run

`POST .../{workspaceId}/crons/{cronId}/runs/{runId}/cancel` stops a run that is still going, the way Stop on its page does. The CLI command is `kosuke crons runs cancel <workspaceId> <cronId> <runId>`. The run ends `cancelled`. The files it already stored and the sessions it already filed stay, and the cron's runs in its other projects go on. Only the cron's creator or a workspace admin can stop a run.

## Delete a cron

`DELETE /api/organizations/{workspaceId}/crons/{cronId}` answers `204`. It removes the cron with its runs and their transcripts. Their files stay in Files. A run still going in any of its projects is stopped first, and its key stops working at once. The sessions its runs filed are ordinary sessions and stay. The CLI command is `kosuke crons delete <workspaceId> <cronId>`.

## A Cron Run's own key

Each run gets a key of its own, prefixed `kosuke_crn_`, which the Cron Agent uses through the `kosuke` CLI. The key is revoked when the run ends. It acts as the workspace, in the cron's workspace only: as Sensei, an admin of that workspace and never its owner, whoever created the cron. Sensei is not a member, so it shows in no member list. A Personal Cron's key acts as its owner instead, as long as they belong to the workspace. It sends tickets as messages (`sensei messages send`, key `ticket-<slug>`), which create nothing until the routed member approves the card their Sensei draws. It files Todo tasks itself only when the cron's prompt says to file them directly (`tasks create <orgId> --hint-project <projectId> --title … --description … --reason …`), and then Sensei is their Assignee.

Acting as the workspace, the key reads everything in the workspace: its members, projects, sessions with their messages, jobs and files, tasks, Crons and their runs, files, credits, sandbox use, models, Deploys and the Deploy Chat, settings, and skills and plugins. It writes only in the projects covered by the run, where it may file, start, label and archive sessions and tasks, and it may create and change the workspace's Crons and Files. It reads a project's settings and live app only in those projects. It is refused, with `403`, on changing the workspace's AI provider, provider logins or Grok access, on disconnecting an integration, on a go-ahead and on deleting a project. It writes nothing to GitHub. A person's own things (their Memory, personal Files, Personal Crons, personal tasks, logins and Mac) and their Sensei are out of its reach, and `GET /api/user/skills` and `GET /api/user/plugins` answer an empty list. A Cron Run using GLM calls Z.ai directly with the workspace's GLM Coding Plan key, one using Kimi calls Moonshot AI directly with the workspace's Kimi Code key, and one using DeepSeek calls DeepSeek directly with the workspace's DeepSeek API key.

## Who can do what

| Action                                                                 | Who                                                                                                                  |
| ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| List and read crons and their runs                                     | Any member of the workspace.                                                                                         |
| Create a cron                                                          | Any member, over any of the workspace's projects. They become its creator, shown in `createdBy` and `createdByName`. |
| Change, pause, resume, resume a project, run now, stop a run or delete | The creator, or a workspace owner or admin.                                                                          |

The agent inside a chat session acts as the session's Assignee, when the Assignee sent the turn, and can do with crons what they could. When anyone else sent the turn, it acts as the workspace, like a Cron Run's key.

## Limits

- A run acts as the workspace, whoever created or fired the cron. The sessions it files are Sensei's, and Sensei is their Assignee.
- A run sends tickets as messages; no ticket exists until a member accepts one. A prompt that says to file Backlog tickets directly makes the run file them instead.
- A cron runs on the workspace's Credentials only. It never falls back to a member's Subscription, so a cron keeps firing after its creator leaves.
- A cron's projects are fixed when it is saved. A project created later joins only when someone edits the cron to add it.
- A run has 5 hours. At the limit it is stopped and ends `failed`.
- Runs from before the Cron Agent keep their status, summary, error and branch, but not their transcripts.

## Errors

| Status | Code                | When                                                                                                                                                                                                                                                                                                     |
| ------ | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400    | `VALIDATION_ERROR`  | The body does not match the schema: a required field is missing or empty, a field is too long or of the wrong type, the model is not a supported id, or `projectIds` is empty.                                                                                                                           |
| 400    | `BAD_REQUEST`       | The crontab or the 15-minute floor is not met, the body sends `timezone`, a project belongs to another workspace, Run now names a project that is not one of the cron's or a cron with no projects is resumed, a runs filter holds a value it does not know, or the workspace has no usable Credentials. |
| 403    | `FORBIDDEN`         | You are not a member of the workspace, you are not the cron's creator or a workspace owner or admin, or the caller is an agent token or a Cron Run's key reaching beyond what it may.                                                                                                                    |
| 404    | `NOT_FOUND`         | The cron, run or project does not exist in the workspace.                                                                                                                                                                                                                                                |
| 409    | `CRON_RUN_STARTING` | A delete met a fire that was still starting. Try again in a moment.                                                                                                                                                                                                                                      |

[Responses and errors](https://kosuke.ai/docs/api/errors.md) has the full table.

## Next steps

- [Crons in the app](https://kosuke.ai/docs/automate/crons.md)
- [Schedule recurring work](https://kosuke.ai/docs/recipes/schedule-recurring-work.md)
- [Chat sessions](https://kosuke.ai/docs/endpoint-reference/chat-sessions.md)

Each fire creates one run covering the selected projects in its own sandbox. Run records expose `projectIds` and `projects`. The deprecated `projectId` names the first remaining project by name, or is null. Manual runs accept `projectIds` to run on some projects. Five failed runs in a row disable the cron. Resume or saving an edit resets the count; saving leaves it disabled. Project resume is no longer supported.
