> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hiveku.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Scheduling and the Content Calendar

> Scheduling a content item now lands on the CMS scheduler that executes, from the editor, the dashboard API, the Olympus API and the MCP tools; unlinked items cannot be scheduled; and the content calendar shows what is scheduled, published, failed or merely recorded, with the account's publishing cadence

A content item used to carry a `scheduled_publish_at` date that nothing executed. The editor wrote it, the API answered 201, the row said "scheduled", and the post never went live; the editor eventually dropped its schedule input because of that, so a writer who wanted a post out Monday at 8 had to leave for the CMS entry sidebar. Nor was there any view of what the account had planned: "what is going out this month" meant opening every item.

Both are fixed. Every surface that schedules a content item now writes the one scheduler that runs, and a calendar under **Marketing > Content** shows what it will do.

## Scheduling that executes

The scheduler behind [CMS scheduled publishing](/cms/scheduled-publishing) is a queue of publish actions keyed on the site entry (project, collection, entry slug), claimed by a cron on the minute the action falls due. A content item is scheduled by putting its entry on that queue, which has two consequences:

* **An item must be linked to a site collection first.** The schedule is keyed on the entry, so an item with no `website_project_id` and `cms_collection_id` has nothing to schedule. Every surface answers `409 not_linked` with the link step named, never an inert row. Link the item from the editor's **Publish to** dialog, or with `content_link_to_cms` or `content_update`, then schedule.
* **A linked item that was never pushed to the site gets its entry created as a draft** at schedule time, through the same mapping and validation a publish uses, with the site's live signal off. Nothing goes live before the scheduled time, and the entry exists for the cron to flip.

At the scheduled time the cron flips the entry live, then deploys the chosen target on a Hiveku-hosted site or publishes the item on a Webflow-backed one, and stamps the content item: `status` becomes `published`, `published_at` is set on the first go-live, and the `scheduled_publish_at` mirror is cleared because the schedule has run. A failure is retried; after five attempts the cron stops and the row stays visible as failed rather than disappearing. One pending row exists per entry, so scheduling again replaces the earlier time.

### From the editor

A **Schedule** action appears in the sidebar's **Status** section and in the header menu of a CMS-linked, saved item. The **Schedule publish** dialog takes a date and time in your local zone and a **Then** choice: the deploy targets the scheduler executes.

| Site           | Options                                                                                                                                                                                    |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Hiveku-hosted  | **Deploy to production when it publishes** (default), **Deploy to staging** (when the project has staging enabled), **Deploy to the dev preview**, **Do not deploy; I will deploy myself** |
| Webflow-backed | **Publish to Webflow when it publishes**: the item goes live on the Webflow site at the scheduled time; the site is never published for you                                                |

The dialog says what it will do: "The post stays off the site until this time, then publishes on the site's schedule. Unsaved edits are saved with it." Once scheduled, the sidebar, the toast and the Settings publishing card all read "Goes live at `<time>` on the site's schedule", and **Cancel schedule** in the dialog clears it.

### From the dashboard API

`PUT /api/marketing/content/{id}` is the editor's own save, and it takes the whole item on every autosave, so `scheduled_publish_at` arrives on every request. The route reads intent from a change, not a presence:

* A `scheduled_publish_at` (ISO, in the future) whose instant **differs** from the stored column is a schedule request. It is validated before any write: a past time or an unknown `deploy_target` is a 400, an unlinked item is `409 not_linked`, and neither writes anything.
* The value the row already holds is an autosave echo and is ignored.
* `null` never cancels. An editor tab opened before an agent scheduled the item would otherwise cancel that schedule on its next keystroke. The cancel is the explicit `cancel_scheduled_publish: true`.
* `deploy_target` is `production` (default), `staging`, `dev`, `none` or `publish`.

The field update itself never writes the column; the schedule helper owns it, so the column can no longer claim a time nothing will execute. A successful schedule answers with the saved item and the queue row:

```json theme={null}
{
  "contentItem": { "id": "a1b2...", "scheduled_publish_at": "2026-09-15T13:00:00.000Z", "...": "..." },
  "publishResult": null,
  "publishError": null,
  "scheduled_publish": {
    "id": "7f3c...",
    "action_type": "publish",
    "scheduled_at": "2026-09-15T13:00:00.000Z",
    "deploy_target": "production",
    "status": "pending",
    "attempts": 0,
    "error": null,
    "executed_at": null,
    "executes_via": "cms_scheduled_actions",
    "entry": { "project_id": "f7a0...", "collection_id": "blog", "slug": "cnc-uptime-year-three", "publish_at": "2026-09-15T13:00:00.000Z", "materialized": true }
  }
}
```

When the fields saved but the schedule could not be set (the collection no longer exists, the entry failed validation), the response carries the helper's status and error plus `note: "Fields were saved; the schedule was NOT set."`, so a save is never mistaken for a schedule.

### From the Olympus API and MCP

| Call                                                                                              | What it does                                                                                                                                                                                                                                                                                                                                                                                      |
| ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `POST /api/olympus/marketing/content/:contentId/schedule` (`content_schedule`)                    | Body `scheduled_at` (ISO, in the future; `run_at` and `scheduled_publish_at` are accepted as aliases), `action_type` `publish` (the only value the cron executes; anything else is a 400; to take a page down now use `content_publish_to_site` with `unpublish: true`), `deploy_target`. Answers 201 with the row shape above and `warnings` when the entry was materialised with dropped fields |
| `PATCH /api/olympus/marketing/content/:contentId` with `scheduled_publish_at` and `deploy_target` | Schedules after the field update, through the same helper; `null` cancels on this route                                                                                                                                                                                                                                                                                                           |
| `GET /api/olympus/marketing/content/:contentId/schedule?status=` (`content_schedule_list`)        | `{ data: [row...], total, linked, executes_via: "cms_scheduled_actions" }`; `status` is `pending`, `completed` or `failed`, and `failed` means the cron gave up, kept visible so it is not mistaken for nothing scheduled                                                                                                                                                                         |
| `DELETE /api/olympus/marketing/content/:contentId/schedule`                                       | Cancels the pending row and clears the entry's publish date and the item's mirror: `{ data: { cancelled, linked, removed, entry_cleared } }`                                                                                                                                                                                                                                                      |
| `content_get`                                                                                     | Carries `scheduled_publish`: the pending row for the item's entry, or `null`                                                                                                                                                                                                                                                                                                                      |

## The content calendar

**Marketing > Content > Calendar** (`/dashboard/marketing/content/calendar`, `marketing.content` read) is a month grid and a day-grouped list of everything the account has planned or published. Days are wall-clock days in your browser's zone, named at the top right, and the grid opens on your current month. Each day shows up to three items with a time and a title; more than three collapses to "+n more". Clicking an item opens it in the editor, or the CMS entry when no content item is linked to a scheduled entry.

Four kinds of event, each with its own colour:

| Kind              | What it means                                                                                                                                                                                            |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Scheduled**     | A pending row on the scheduler. This will publish                                                                                                                                                        |
| **Published**     | The item went live (its `published_at`), or the scheduler completed the row                                                                                                                              |
| **Failed**        | The cron gave up after its attempts. The row keeps its error, and a banner at the top says how many need attention: open each to see the error and schedule it again                                     |
| **Recorded only** | A date stored on the item that nothing executes, shown with a dashed border. The footnote says it plainly: link the item to a site collection and schedule it from the editor to put it on the scheduler |

The header line is the account's cadence: "12 published in the last 30 days, 3 scheduled, target cadence from memory not set", with "2 failed publishes needing attention" added when there are any. The target is read from the Content department's memory (the `content` domain first, then `marketing`) when a line there states a publishing cadence: "publish 2 blog posts per week", "one article a month", "twice a week". It is normalised to pieces per week and the line it was read from is shown under the summary, so you can see what the department believes the target is and correct the memory when it is wrong. Those counts are account-wide, not limited to the month on screen.

<Note>
  The calendar tells the truth about two tables. A pending scheduler row is the schedule, and the item's own date for the same entry is dropped rather than shown twice. A completed row is dropped when the item already records its `published_at` (one dot per publication) and otherwise shows as published from the scheduler. A row the cron gave up on shows as failed, never as pending. An item date with no row for its entry shows as recorded only, because nothing will publish it.
</Note>

### The calendar API

`GET /api/marketing/content/calendar?from=&to=` feeds the page and is available to any signed-in user with `marketing.content` read. `from` and `to` are ISO datetimes or `YYYY-MM-DD` (a date-only `to` means the end of that day, UTC); both default to 45 days either side of now, and a window wider than 190 days is refused with a 400 rather than silently clipped.

```json theme={null}
{
  "range": { "from": "2026-08-30T00:00:00.000Z", "to": "2026-10-03T23:59:59.999Z" },
  "events": [
    {
      "id": "action:7f3c...",
      "kind": "scheduled",
      "at": "2026-09-15T13:00:00.000Z",
      "title": "Why CNC uptime drops in year three",
      "content_id": "a1b2...",
      "content_type": "blog_post",
      "content_status": "approved",
      "href": "/dashboard/marketing/content/a1b2...",
      "project_id": "f7a0...",
      "project_name": "Example site",
      "collection_id": "blog",
      "entry_slug": "cnc-uptime-year-three",
      "source": "cms_scheduled_actions",
      "deploy_target": "production",
      "error": null,
      "executes": true
    }
  ],
  "cadence": {
    "published_last_30_days": 12,
    "scheduled_pending": 3,
    "failed_pending": 0,
    "target": { "per_week": 2, "raw": "Cadence: publish 2 blog posts per week" },
    "target_source": "content"
  },
  "summary": "12 published in the last 30 days, 3 scheduled, target cadence from memory 2 per week (about 8.57 per 30 days)"
}
```

`kind` is `scheduled`, `published`, `failed` or `recorded_only`; `source` says which table the event came from; `executes` is false only for `recorded_only`. Event ids are stable across reloads (`item:<id>:published`, `item:<id>:recorded`, `action:<rowId>`).

## Related pages

* [The Content Department](/marketing/content-department) — the writer whose memory the cadence target is read from
* [Pre-Publish SEO Check](/marketing/content-seo-check) — runs when the item is published, scheduled or not
* [Scheduled Publishing](/cms/scheduled-publishing) — the same scheduler from the CMS entry sidebar, with the deploy behaviour in detail
* [Webflow as the Content Source](/integrations/webflow/content#scheduled-publishing-on-a-webflow-entry) — what a scheduled publish does on a Webflow-backed project
