Create a recurring work item
Create a schedule that adds a work item to the project on a repeating date. Send both parts in one flat body: the blueprint, which is the work item to create, and the schedule, which says when to create it.
The response includes the first next_scheduled_at. If start_at is today, Plane creates the first work item right away. See How runs are scheduled.
Path Parameters
slug:requiredstringThe workspace slug. It appears in your Plane URLs. In https://app.plane.so/my-team/projects/, the slug is my-team.
project_id:requiredstring (uuid)The project to create the schedule in. Every work item it creates goes into this project.
Body Parameters
The schedule:
start_at:requiredstring (date or date-time)The first date a run can happen, for example 2026-08-03. Only the date counts. Plane stores it in UTC as 00:05 on that date in the project's timezone. Plane also accepts a full date-time and drops its time.
end_at:optionalstring (date or date-time) or nullThe last date a run can happen. Only the date counts. Plane stores it as 23:59:59 on that date in the project's timezone. It must be after start_at. Because Plane stores start_at as 00:05 and end_at as 23:59:59, the same date as start_at is allowed. An earlier date returns 400 invalid_request. Omit it or send null for a schedule with no end.
interval_type:optionalstringHow often to run. One of daily, weekly, monthly, or yearly. Defaults to monthly. Any other value returns 400 invalid_request.
interval_count:optionalintegerHow many intervals between runs. 2 with weekly means every two weeks. Defaults to 1. Values below 1 return 400 invalid_request.
The blueprint:
name:requiredstringThe title each created work item gets. Maximum 255 characters.
description_html:optionalstringThe description each created work item gets, as HTML. Defaults to <p></p>.
priority:optionalstringOne of none, low, medium, high, or urgent. Defaults to none.
state_id:optionalstring (uuid) or nullThe state each created work item starts in. It must be a state of the path project. In a workspace with governance turned on, states belong to the workspace catalog, so any state_id returns 400.
type_id:optionalstring (uuid) or nullThe work item type each created work item gets. It must be one of the path project's work item types.
assignee_ids:optionalarray of string (uuid)Users to assign each created work item to. Each must be an active member of the path project. Duplicates are dropped.
label_ids:optionalarray of string (uuid)Work item labels to apply. Each must be a label of the path project. Duplicates are dropped.
module_ids:optionalarray of string (uuid)Modules to add each created work item to. Each must be a module of the path project. Duplicates are dropped.
enabled, next_scheduled_at, last_run_at, and schedule_description are read-only. Plane ignores them if you send them.
Response shaping
fields:optionalstringComma-separated list of fields to return. Unrequested keys are omitted from the response, not returned as null, so absent means "not requested" and null means "actually null". id always comes back whether or not you name it.
Pass all for every requestable field. An unknown name is a 400 that lists the valid set and suggests the closest match, so a typo can't silently cost you the saving.
Requestable here: assignee_ids, created_at, created_by_id, description_html, enabled, end_at, id, interval_count, interval_type, label_ids, last_run_at, module_ids, name, next_scheduled_at, priority, schedule_description, start_at, state_id, type_id, updated_at, updated_by_id, work_item_blueprint_id.
See Sparse fields.
Scopes
projects.recurring_work_items:write
Errors
| Status | Code | Cause |
|---|---|---|
400 | invalid_request | Missing name or start_at, end_at before start_at, a bad interval_type or interval_count, or an id that doesn't belong to the project. |
401 | unauthorized | Missing or invalid credentials. |
402 | payment_required | The workspace's plan doesn't include recurring work items. |
403 | forbidden | Your project role or token scope can't create recurring work items. |
404 | not_found | No such workspace or project, or it's outside your tenant. |
406 | not_acceptable | The Accept header asks for a representation the API can't produce. |
409 | governance_migration_in_progress | A governance migration is running, so the workspace is read-only. Retry after it finishes. |
413 | payload_too_large | The request body is over the size limit. |
415 | unsupported_media_type | The Content-Type isn't one this endpoint accepts. |
429 | rate_limited | Throttled. Honor the Retry-After header before retrying. |
curl -X POST \
"https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/recurring-work-items/" \
-H "X-Api-Key: $PLANE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Weekly status report",
"description_html": "<p>Summarize progress, risks, and next steps.</p>",
"priority": "medium",
"state_id": "2c4d16f8-9b3e-4a52-8d71-1f0e6c9a5b48",
"assignee_ids": ["16c61a3a-512a-48ac-b0be-b6b46fe6f430"],
"label_ids": ["c1b8f3d6-9a44-4e12-8f7a-2b6d5c9e1a03"],
"start_at": "2026-08-03",
"end_at": "2026-12-31",
"interval_type": "weekly",
"interval_count": 1
}'import requests
response = requests.post(
"https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/recurring-work-items/",
headers={"X-Api-Key": "your-api-key"},
json={
"name": "Weekly status report",
"description_html": "<p>Summarize progress, risks, and next steps.</p>",
"priority": "medium",
"state_id": "2c4d16f8-9b3e-4a52-8d71-1f0e6c9a5b48",
"assignee_ids": ["16c61a3a-512a-48ac-b0be-b6b46fe6f430"],
"label_ids": ["c1b8f3d6-9a44-4e12-8f7a-2b6d5c9e1a03"],
"start_at": "2026-08-03",
"end_at": "2026-12-31",
"interval_type": "weekly",
"interval_count": 1,
},
)
print(response.json())const response = await fetch(
"https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/recurring-work-items/",
{
method: "POST",
headers: {
"X-Api-Key": "your-api-key",
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Weekly status report",
description_html: "<p>Summarize progress, risks, and next steps.</p>",
priority: "medium",
state_id: "2c4d16f8-9b3e-4a52-8d71-1f0e6c9a5b48",
assignee_ids: ["16c61a3a-512a-48ac-b0be-b6b46fe6f430"],
label_ids: ["c1b8f3d6-9a44-4e12-8f7a-2b6d5c9e1a03"],
start_at: "2026-08-03",
end_at: "2026-12-31",
interval_type: "weekly",
interval_count: 1,
}),
},
);
const data = await response.json();{
"id": "f3a8c1d6-2e5b-4a79-8c04-1b7e9d6f2a53",
"work_item_blueprint_id": "a9d2e6f1-7c3b-4085-b4e8-2f0c6a1d9e37",
"name": "Weekly status report",
"description_html": "<p>Summarize progress, risks, and next steps.</p>",
"priority": "medium",
"state_id": "2c4d16f8-9b3e-4a52-8d71-1f0e6c9a5b48",
"type_id": null,
"assignee_ids": ["16c61a3a-512a-48ac-b0be-b6b46fe6f430"],
"label_ids": ["c1b8f3d6-9a44-4e12-8f7a-2b6d5c9e1a03"],
"module_ids": [],
"start_at": "2026-08-03T00:05:00Z",
"end_at": "2026-12-31T23:59:59Z",
"interval_type": "weekly",
"interval_count": 1,
"next_scheduled_at": "2026-08-03T00:05:00Z",
"last_run_at": null,
"enabled": true,
"schedule_description": "Every week on Monday at 00:05",
"created_at": "2026-07-28T09:22:41.478363Z",
"updated_at": "2026-07-28T09:22:41.478363Z",
"created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430",
"updated_by_id": null
}{
"type": "invalid_request",
"code": "invalid_request",
"detail": "One or more fields failed validation.",
"errors": [
{
"field": "end_at",
"code": "invalid",
"message": "end_at must be after start_at."
}
]
}{
"type": "invalid_request",
"code": "invalid_request",
"detail": "One or more fields failed validation.",
"errors": [
{
"field": "state_id",
"code": "invalid",
"message": "State does not belong to this project."
}
]
}The examples use a project in UTC
Plane pins start_at and end_at in the project's timezone and returns them in UTC. In a project set to America/New_York, the same request stores start_at as 2026-08-03T04:05:00Z.

