# Register a cron schedule

POST

/v1/schedules

```sh
curl --request POST \
  --url https://example.com/v1/schedules \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '{ "flowId": "example", "flowVersion": "example", "agentId": "example", "agentVersion": "example", "improve": { "agentId": "example", "scope": { "kind": "tenant" } }, "projectId": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "runAsServiceAccountId": "example", "runAsProject": true, "config": { "cronExpression": "example", "timezone": "example", "input": "example" }, "catchUp": "latest", "overlap": "skip", "startingDeadlineSeconds": 1, "label": "example", "toolsAllowedUnattended": [ "example" ] }'
```

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

## Authorizations

- **[bearerAuth](https://docs.kindgi.com/v0.2.0/reference/api.md#bearerauth)**

## Parameters

### Header Parameters

**Idempotency-Key**

string

\>= 1 characters

Caller-supplied idempotency key, scoped to the caller: the same key from someone else in the tenant is their own request (from 0.1.5). Retries with the same key return the original response byte-identical (per `docs/API-ROUTE-CONVENTIONS.md` §3.1). A retry sent while the first request still runs is answered `409 idempotency-key-in-flight` with `Retry-After`, on a runtime whose store holds keys (from 0.1.5); retry after it, and you get the first request’s answer. An answer that carries a secret (a new API key or public run token, a session token, a generated signing secret) isn’t kept: a retry gets `409 idempotency-key-replay-withheld`, with the first request’s status and when it succeeded (from 0.1.5).

## Request Bodyrequired

application/json

Name what it runs: `flowId` with `flowVersion`, `agentId` (with an optional `agentVersion`), or `improve` (improvement passes). Registering needs `write` on the project and `execute` on what it runs (`publish` on the agent for `improve`); its runs act as the caller.

object

**flowId**

Run a flow (with `flowVersion`).

string

\>= 1 characters

**flowVersion**

string

\>= 1 characters

**agentId**

Run an agent (instead of a flow): at `agentVersion`, else its live version.

string

\>= 1 characters

**agentVersion**

string

\>= 1 characters

**improve**

Start an improvement pass instead of a run (instead of `flowId` or `agentId`). Its `config.input` is the pass options; registering needs `publish` on the agent.

object

**agentId**

required

string

\>= 1 characters

**scope**

required

One of: discriminator: kind

- **object**

  The agent’s default for the whole tenant.

  object

  **kind**

  required

  string

  Allowed values: tenant

- **object**

  An org’s projects.

  object

  **kind**

  required

  string

  Allowed values: org

  **orgId**

  required

  string format: uuid

- **object**

  One project.

  object

  **kind**

  required

  string

  Allowed values: project

  **projectId**

  required

  string format: uuid

- **object**

  A segment path within a project: it covers every run whose path starts with it.

  object

  **kind**

  required

  string

  Allowed values: segment

  **projectId**

  required

  string format: uuid

  **path**

  required

  Array\<object>

  \>= 1 items <= 8 items

  object

  **key**

  required

  Lowercase, like an identifier: `company`, `contact-role`.

  string

  /^\[a-z]\[a-z0-9\_-]{0,63}$/

  **value**

  required

  string

  \>= 1 characters <= 256 characters

**projectId**

The schedule’s project. Absent → the tenant’s default project.

string format: uuid

**runAsServiceAccountId**

A service account the schedule’s runs act as (its owner), named by id, instead of the caller. Needs `act_as` on that account — a tenant admin holds it — 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, and again when the schedule’s target changes: an account that can’t is refused (`409 trigger-owner-cannot-run-target`). Not a project’s own account (409 `service-account-project-owned`: name it with `runAsProject`). Absent: the caller owns it.

string

\>= 1 characters

**runAsProject**

`true`: the schedule’s runs act as its project’s own service account, made the first time with the `editor` role on the project, so it’s shared. It runs only what the project’s editors can. With an authorization store, that’s checked when it’s named, and again when the schedule’s target changes: if it can’t run the target, the request is refused (`409 trigger-owner-cannot-run-target`). Takes `admin` on the schedule’s project (without an authorization store, a tenant-admin key), not `act_as`. Not with `runAsServiceAccountId`, and not without a project (400). A runtime that can’t make the account answers 501 `trigger-operation-unsupported`. `false` or absent: as `runAsServiceAccountId` says.

boolean

**config**

required

object

**cronExpression**

required

string

\>= 1 characters

**timezone**

string

**input**

What each run gets. An agent schedule’s runs take the agent payload, `{ userMessage, parameters? }`, so it needs `userMessage`; a flow’s take the flow’s input. An improve schedule’s is `ImproveScheduleInput`, kept with its defaults applied.

**catchUp**

Default `latest`.

string

Allowed values: latest skip

**overlap**

Default `skip`.

string

Allowed values: skip allow

**startingDeadlineSeconds**

Default 600.

integer

\>= 1 <= 86400

**label**

string

**toolsAllowedUnattended**

The tools whose calls in this schedule’s runs needn’t wait for a person’s yes, though a tool asks for one in a run no person started (`needsApprovalWhenUnattended`). Naming it, `[]` included, takes `admin` on the schedule’s project. Each tool by its id, with no wildcard or pattern, and only tools this trigger’s flow or agent calls. It lifts only that wait: the tenant’s approval rules, the agent’s own rules, the permission check (as the trigger’s owner) and the tool’s own limits all still apply.

Array\<string>

<= 100 items unique items

## Responses

### 201

Schedule registered.

application/json

A schedule: what it runs (a flow at a version, or an agent), when (a cron expression in a timezone), as whom (its owner), and what it does after a gap or while a run is still going.

object

**scheduleId**

required

Domain-friendly alias for `triggerId` — the trigger id (a UUID). Use interchangeably in admin URLs.

string

**triggerId**

required

string

**flowId**

A schedule that runs a flow: the flow, at `flowVersion`.

string

\>= 1 characters

**flowVersion**

string

\>= 1 characters

**agentId**

A schedule that runs an agent: the agent, at `agentVersion`, else its version live for the schedule’s project (else the latest), as a run that names none.

string

\>= 1 characters

**agentVersion**

string

\>= 1 characters

**improve**

A schedule that starts Improve runs: on this agent, for this scope, when enough answers have been marked wrong since its last Improve run (`input`: the threshold, the monthly cap and the Improve options).

object

**agentId**

required

string

\>= 1 characters

**scope**

required

One of: discriminator: kind

- **object**

  The agent’s default for the whole tenant.

  object

  **kind**

  required

  string

  Allowed values: tenant

- **object**

  An org’s projects.

  object

  **kind**

  required

  string

  Allowed values: org

  **orgId**

  required

  string format: uuid

- **object**

  One project.

  object

  **kind**

  required

  string

  Allowed values: project

  **projectId**

  required

  string format: uuid

- **object**

  A segment path within a project: it covers every run whose path starts with it.

  object

  **kind**

  required

  string

  Allowed values: segment

  **projectId**

  required

  string format: uuid

  **path**

  required

  Array\<object>

  \>= 1 items <= 8 items

  object

  **key**

  required

  Lowercase, like an identifier: `company`, `contact-role`.

  string

  /^\[a-z]\[a-z0-9\_-]{0,63}$/

  **value**

  required

  string

  \>= 1 characters <= 256 characters

**projectId**

The schedule’s project: its runs are this project’s.

string format: uuid

**owner**

Who its runs act as. A person: a personal trigger, which only they or a project admin change. A service account: a shared trigger. Only a take-over (`POST …/owner`) changes it. Checked again at every fire.

object

**kind**

required

string

Allowed values: user service

**id**

required

string

**displayName**

The owner’s name at the time of the response: the person’s display name, or the service account’s name. Absent when it can’t be read (no directory, a removed account) and from a runtime before Kindgi 0.1.6: show the id then.

string

**isProjectAccount**

Set when the owner is the trigger’s project’s own service account (`runAsProject`): the trigger is shared, and runs as the project. Show it as “This project”. Absent otherwise.

boolean

**cronExpression**

required

5- or 6-field cron expression (croner-compatible). 6-field enables second precision.

string

\>= 1 characters

**timezone**

IANA timezone (e.g. `UTC`, `America/New_York`). Absent → `UTC`.

string

**input**

Static input handed to the run on every fire. Absent → `{}`.

**catchUp**

After a gap (the runtime was down, or a fire is later than `startingDeadlineSeconds`): `latest` runs once, for the latest missed occurrence, and its fire says how many it missed; `skip` drops the missed occurrences. Never a run per missed occurrence. A tenant’s suspension isn’t caught up either way: once the tenant is restored, a schedule that’s on fires next at its first occurrence after the restore.

string

Allowed values: latest skip

**overlap**

When an occurrence comes while the previous run of this schedule is still running: `skip` records the fire as skipped; `allow` starts another run.

string

Allowed values: skip allow

**startingDeadlineSeconds**

How late a fire may start and still count as on time; past it, `catchUp` applies.

integer

\>= 1

**label**

required

string | null

**status**

required

Lifecycle status. Only `active` triggers fire. Tombstoned rows are excluded from every read path.

string

Allowed values: active paused

**statusReason**

Why the runtime paused it: repeated fires that were refused (the owner lost access) or failed. Skipped fires (an overlap, an erasure in progress) never count.

string

**toolsAllowedUnattended**

The tools whose calls in this schedule’s runs don’t wait for a person’s yes, though a tool asks for one in a run no person started (`needsApprovalWhenUnattended`). Absent: none. Each tool by its id, with no wildcard or pattern, and only tools this trigger’s flow or agent calls. It lifts only that wait: the tenant’s approval rules, the agent’s own rules, the permission check (as the trigger’s owner) and the tool’s own limits all still apply.

Array\<string>

<= 100 items unique items

**nextFireAt**

required

Wall-clock time of the next scheduled fire. `null` on paused rows if the cron scheduler never re-armed.

string | null format: date-time

**upcoming**

The next occurrences, when the request asked for them (`?upcoming=N`).

Array\<string>

**lastFiredAt**

required

string | null format: date-time

**createdAt**

required

string format: date-time

**updatedAt**

required

string format: date-time

##### Example

```json
{
  "improve": {
    "scope": {
      "kind": "tenant"
    }
  },
  "owner": {
    "kind": "user",
    "isProjectAccount": true
  },
  "catchUp": "latest",
  "overlap": "skip",
  "status": "active"
}
```

### 400

Malformed request body, or `runAsProject` with `runAsServiceAccountId`, or for a schedule with no project (`bad-input`). Or `reference-out-of-scope`: what it runs isn’t registered in the schedule’s project, its org or the tenant, or the version it names (`flowVersion`, or `agentVersion` when given) isn’t one that project runs (`details.issues`, at `/target`, whose message names the version). Nothing is registered.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 401

Missing / malformed / expired / revoked bearer token.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 403

`permission-denied`: no `write` on the schedule’s project or no `execute` on what it runs; or no `admin` on the project where it’s needed, for `toolsAllowedUnattended` or `runAsProject`; or no `act_as` on the account `runAsServiceAccountId` names.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 409

`runAsServiceAccountId` names an unregistered account (`service-account-unregistered`), a project’s own account (`service-account-project-owned`: name it with `runAsProject`), or one that can’t run what the schedule runs (`trigger-owner-cannot-run-target`). Or, with `runAsProject`, the project’s own account can’t run what the schedule runs either (`trigger-owner-cannot-run-target`; the account stays, for the next `runAsProject`), or an active account someone made already has the project account’s name, `project-<projectId>` (`service-account-name-taken`: a tenant admin can unregister it). Or an idempotency conflict.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 413

The request body is over the API’s limit (`request-too-large`; `details.maxBytes` says how much).

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 500

Server error (unmapped domain code or framework crash).

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 501

`trigger-operation-unsupported`: `runAsProject` on a runtime that can’t make a project’s own account.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

## See also

- [`GET /v1/schedules`: List cron schedules](https://docs.kindgi.com/v0.2.0/reference/api/operations/scheduleslist.md)
- [Error code `bad-input`](https://docs.kindgi.com/v0.2.0/reference/errors.md#bad-input)
- [Error code `permission-denied`](https://docs.kindgi.com/v0.2.0/reference/errors.md#permission-denied)
- [Error code `reference-out-of-scope`](https://docs.kindgi.com/v0.2.0/reference/errors.md#reference-out-of-scope)
- [Error code `request-too-large`](https://docs.kindgi.com/v0.2.0/reference/errors.md#request-too-large)
- [Error code `service-account-name-taken`](https://docs.kindgi.com/v0.2.0/reference/errors.md#service-account-name-taken)
- [Error code `service-account-project-owned`](https://docs.kindgi.com/v0.2.0/reference/errors.md#service-account-project-owned)
- [Error code `service-account-unregistered`](https://docs.kindgi.com/v0.2.0/reference/errors.md#service-account-unregistered)
- [Error code `trigger-operation-unsupported`](https://docs.kindgi.com/v0.2.0/reference/errors.md#trigger-operation-unsupported)
- [Error code `trigger-owner-cannot-run-target`](https://docs.kindgi.com/v0.2.0/reference/errors.md#trigger-owner-cannot-run-target)
- [Every error code](https://docs.kindgi.com/v0.2.0/reference/errors.md)
