---
title: "Get or create a monthly content calendar"
method: POST
path: "/v1/projects/{projectId}/content-calendar"
tags: ["Content"]
---

# Get or create a monthly content calendar

`POST /v1/projects/{projectId}/content-calendar`

## Request

- Base URL: `https://www.seoforgpt.io/api`
- URL: `https://www.seoforgpt.io/api/v1/projects/{projectId}/content-calendar`
- Auth: HTTP bearer

Returns persisted calendar items for a month. For V3 projects, explicitly send setup=true when the user requests planning; omit setup and refresh for reads and polling. Empty V3 calendars do not auto-start. Only poll while data.status is generating, waiting retry_after_seconds. Respect planning_outcome restrictions. Use refresh=true to explicitly retry or continue an existing V3 strategy, not initial setup. Legacy empty eligible months retain automatic generation. Planning does not generate articles or enable publishing.

## Path parameters

- `projectId` string, uuid, required

## Request body

- object
  - `planningMonth` string: Calendar month in YYYY-MM format; defaults to the current UTC month.
  - `setup` boolean: Explicitly build a V3 calendar, including when seed recommendations already exist. Omit for retrieval or polling. Does not generate articles or enable publishing.
  - `previewSetup` boolean: Return a read-only setup preview with period, affected topics and revision.
  - `existingRecommendations` 'keep' | 'replace_untouched': Replacement requires explicit user confirmation of the preview, setup=true and previewRevision.
  - `previewRevision` string: Revision from the setup preview confirmed by the user.
  - `refresh` boolean: Retry generation only when the selected current or future month previously completed with zero items.

## Response `200`

Existing, created, empty, or not-ready calendar

- ContentCalendarResponse
  - `data` object
    - `project_id` string, uuid
    - `planning_month` string
    - `calendar_url` string
    - `status` 'existing' | 'generating' | 'created' | 'empty' | 'not_ready'
    - `created` boolean: True for only one successful read after a generation attempt creates items.
    - `count` integer
    - `items` ContentCalendarItem[]
      - `id` string, uuid, nullable
      - `title` string
      - `topic_line` string, nullable
      - `content_type` string
      - `source` string
      - `source_label` string, nullable
      - `status` string
      - `scheduled_for` string, date, nullable
      - `why_scheduled` string, nullable
      - `signal` string, nullable
      - `priority` number, nullable
      - `score` number, nullable
      - `content_action` string
      - `target_url` string, nullable
      - `generated_content_id` string, uuid, nullable
      - `automated_post_id` string, uuid, nullable
    - `job_id` string, uuid
    - `message` string
    - `retry_after_seconds` integer
    - `missing_inputs` string[]
  - `meta` BillingMeta: Plan and quota snapshot (shape may evolve)

## Other responses

- `202`: Calendar generation queued or processing
- `400`: Invalid input
- `401`: Missing or invalid bearer token
- `404`: Resource not found
- `429`: Rate limit exceeded. Responses include the current IETF `RateLimit` and `RateLimit-Policy` structured fields, compatibility `RateLimit-*` and `X-RateLimit-*` fields, and `Retry-After` on 429.
- `500`: Server error
- `default`: Any documented or unexpected 4xx or 5xx response uses the same typed error envelope.

---

[API](https://skmtc.dev/seoforgpt/apis/seoforgpt-public-api.md) · [All operations](https://skmtc.dev/seoforgpt/apis/seoforgpt-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/seoforgpt/apis/seoforgpt-public-api/revisions/d90da8f1ea73?raw)
