# Publication Scheduling

Publication scheduling describes planned publication dates, recurring issue windows, and optional
timing. Publishing itself is a separate lifecycle operation; see
[Publishing Publications](/docs/guides/editorial-workflows/publishing-publications).

Use this page as narrative guidance. The [API reference](/api) remains the source of truth for
exact request and response contracts.

## Schedule Model Overview

Every publication is in one of three scheduling states:

| State         | How it looks in the API                              |
|---------------|------------------------------------------------------|
| **Undated**   | `schedule` is absent from the publication object     |
| **Single**    | `schedule.kind = "single"` for one issue date        |
| **Recurring** | `schedule.kind = "recurring"` for a date series      |

Recurring schedules have a second-level type:

| Recurring type | When to use                                                        |
|----------------|--------------------------------------------------------------------|
| `daily`        | Platform uses a weekday-based calendar (`schedule.type = "daily"`) |
| `manual`       | Platform uses manually-entered issue dates                         |

A platform's recurring type follows its configured schedule mode (weekly or manual). To find which a
platform uses, read its schedule configuration — see
[Platform Schedule Configuration](/docs/guides/resource-guides/platform-schedule-configuration).

## Schedule Subresource

After a publication exists, schedule changes use a dedicated subresource:

```text
/api/v2/elements/{elementId}/publications/{publicationId}/schedule
```

Generic publication patch/update endpoints do not accept `schedule`; use this subresource instead.
Publication create endpoints accept an optional `schedule` in the request body.

## Read Schedule

```bash title="Read schedule"
curl https://api.kordiam.app/api/v2/elements/100/publications/200/schedule \
  -H "Authorization: Bearer ..."
```

Returns the schedule object directly, not wrapped in a `schedule` property.

:::caution{title="Reading an undated schedule"}

Reading the schedule of an undated publication returns `404 Not Found`.

:::

## Create Or Replace Schedule

`PUT` creates a new schedule or fully replaces the existing one. The request body is the complete
schedule object.

```bash title="Create or replace schedule"
curl -X PUT https://api.kordiam.app/api/v2/elements/100/publications/200/schedule \
  -H "Authorization: Bearer ..." \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "single",
    "date": "2026-04-10",
    "timing": {
      "type": "exactTime",
      "time": "11:30"
    }
  }'
```

The response body is the refreshed parent element aggregate (`ElementApiModel`). The response
includes a `Content-Location` header pointing to the parent element resource.

## Delete Schedule

```bash title="Delete schedule"
curl -X DELETE https://api.kordiam.app/api/v2/elements/100/publications/200/schedule \
  -H "Authorization: Bearer ..."
```

Returns `204 No Content`. The publication becomes undated. Repeated deletes are harmless.

## Single Schedule

Use `kind: "single"` when the publication is for one specific issue date.

Required fields: `kind`, `date`.

```json
{
  "kind": "single",
  "date": "2026-04-10"
}
```

If the platform uses manually-entered dates, the server may return `dateExternalId` in `GET`
responses when the publication date matches a manual schedule entry. This field is read-only. If
sent in a write payload, the API rejects it.

```json
{
  "kind": "single",
  "date": "2026-04-10",
  "dateExternalId": "manual-date-4711"
}
```

## Daily Recurring Schedule

Use `kind: "recurring"` with `type: "daily"` for weekday-based repeating schedules.

Required fields: `kind`, `type`, `start`, `weeklySchedule`. `weeklySchedule` must include all
seven day flags (`mon`, `tue`, `wed`, `thu`, `fri`, `sat`, `sun`); set unused days to `false`.

```json
{
  "kind": "recurring",
  "type": "daily",
  "start": "2026-04-06",
  "end": "2026-05-31",
  "weeklySchedule": {
    "mon": true,
    "tue": false,
    "wed": true,
    "thu": false,
    "fri": true,
    "sat": false,
    "sun": false
  }
}
```

Use `occurrenceCount` as an alternative to `end`: the series ends after that many scheduled dates,
and the server derives the effective end date from the platform schedule. You cannot use both `end`
and `occurrenceCount` together.

```json
{
  "kind": "recurring",
  "type": "daily",
  "start": "2026-04-06",
  "occurrenceCount": 10,
  "weeklySchedule": {
    "mon": true,
    "tue": false,
    "wed": true,
    "thu": false,
    "fri": true,
    "sat": false,
    "sun": false
  }
}
```

`excluded` lists specific dates to skip within the recurring window. On `PUT`, the provided list
fully replaces any previously excluded dates.

## Manual Recurring Schedule

Use `kind: "recurring"` with `type: "manual"` for platforms with manually-entered issue dates.

Required fields: `kind`, `type`, `start`.

Manual recurring schedules can use `end` or `occurrenceCount` to bound the recurrence, can include
`excluded` dates, and can include `timing`. `weeklySchedule` is not allowed for manual recurring
schedules.

```json
{
  "kind": "recurring",
  "type": "manual",
  "start": "2026-04-06",
  "occurrenceCount": 10,
  "excluded": ["2026-04-20"]
}
```

## Timing

Timing is optional for all schedule kinds. When omitted, the publication has no explicit time or
time slot.

Two mutually exclusive timing branches are available:

| Branch     | Discriminator       | Required field                  |
|------------|---------------------|---------------------------------|
| Exact time | `type: "exactTime"` | `time` (HH:mm)                  |
| Time slot  | `type: "timeSlot"`  | `timeSlotId` (positive integer) |

Examples:

```json
{
  "kind": "single",
  "date": "2026-04-10",
  "timing": {
    "type": "exactTime",
    "time": "11:30"
  }
}
```

```json
{
  "kind": "single",
  "date": "2026-04-10",
  "timing": {
    "type": "timeSlot",
    "timeSlotId": 10
  }
}
```

You must choose exactly one timing branch. Sending both `time` and `timeSlotId` together is
rejected.

## Time Slot Context

Time slots are named scheduling windows defined per platform, such as "Morning" 06:00-09:00 or
"Evening" 18:00-21:00. Platform responses expose each slot with `id`, `name`, `start`, and `end`.
Publication schedule payloads reference an existing slot by `timeSlotId`; they do not send slot
display data.

Time slot IDs are available from platform configuration. See
[Editorial Concepts](/docs/getting-started/editorial-concepts#time-slots) for domain context and
[Platform Schedule Configuration](/docs/guides/resource-guides/platform-schedule-configuration)
for platform-level schedule rules.

## Recurring Schedule Constraints

- A recurring publication must be the only publication on the element; adding one when other publications exist is rejected with a `400` validation error.
- `end` and `occurrenceCount` cannot be used together.
- `daily` requires `weeklySchedule`; `manual` rejects it.
- `date` is only valid for `single`; recurring schedules use `start`/`end`.

## Complete Scheduling Example

<Stepper>

1. **Create an element with an undated publication**

   ```bash
   curl -X POST https://api.kordiam.app/api/v2/elements \
     -H "Authorization: Bearer ..." \
     -H "Content-Type: application/json" \
     -d '{
       "elementStatusId": 11,
       "title": "Morning briefing",
       "groupIds": [201],
       "publications": [
         {
           "externalId": "pub-web-briefing",
           "statusId": 41,
           "platformId": 101,
           "assignedTaskRefs": []
         }
       ]
     }'
   ```

1. **Set a single schedule with exact time**

   ```bash
   curl -X PUT https://api.kordiam.app/api/v2/elements/100/publications/200/schedule \
     -H "Authorization: Bearer ..." \
     -H "Content-Type: application/json" \
     -d '{
       "kind": "single",
       "date": "2026-04-10",
       "timing": {
         "type": "exactTime",
         "time": "08:00"
       }
     }'
   ```

1. **Change to a recurring daily schedule**

   ```bash
   curl -X PUT https://api.kordiam.app/api/v2/elements/100/publications/200/schedule \
     -H "Authorization: Bearer ..." \
     -H "Content-Type: application/json" \
     -d '{
       "kind": "recurring",
       "type": "daily",
       "start": "2026-04-06",
       "end": "2026-04-30",
       "weeklySchedule": {
         "mon": true,
         "tue": true,
         "wed": true,
         "thu": true,
         "fri": true,
         "sat": false,
         "sun": false
       },
       "timing": {
         "type": "exactTime",
         "time": "08:00"
       }
     }'
   ```

1. **Remove the schedule to make the publication undated again**

   ```bash
   curl -X DELETE https://api.kordiam.app/api/v2/elements/100/publications/200/schedule \
     -H "Authorization: Bearer ..."
   ```

</Stepper>
