# Plan a study block

Source: https://workroad.app/docs/api/create-study-block

`POST https://workroad.app/api/v1/study-blocks`

Puts study time on the user's calendar, the way the planning copilot does: a title, a future start and end in the user's time zone, and optionally the subject, task or study set it is for. The block can't overlap a lesson (including external calendars, read live) or another block, and can't end after a linked task's due day. Find free time first with `getToday` or `getScheduleWeek`.

- MCP tool: `createStudyBlock` on `https://workroad.app/api/mcp`
- Required scopes: schedule:create
- Test mode: `POST https://workroad.app/api/test/v1/study-blocks` checks the request and returns the example response without changing anything.

## Headers

- `Idempotency-Key`: string, optional, 1-255 visible ASCII characters. Makes the POST safe to retry: a retry with the same key and body replays the first response, and the same key with a different body returns 409 `idempotency_conflict`. Kept for 24 hours. Over MCP it is the `idempotencyKey` argument.

## Request body

JSON.

- `title`: string, required, 1-120 characters. What the user studies, 1-120 characters.
- `startsAt`: string (date-time), required. When the block starts, in the future. ISO timestamp with `Z` or an offset, such as `2026-10-21T16:00:00+01:00`.
- `endsAt`: string (date-time), required. When it ends: 5 minutes to 8 hours after `startsAt`, on the same day in the user's time zone. ISO timestamp with `Z` or an offset, such as `2026-10-21T16:00:00+01:00`.
- `subjectId`: string (uuid) or null. The subject studied. Null clears it.
- `taskId`: string (uuid) or null. The task this block works on. The block must end by the task's due day. Null clears it.
- `setId`: string (uuid) or null. The study set to practise in the block. Null clears it.

## Response

`201` Created

- `data`: object, required
  - `id`: string (uuid), required
  - `title`: string, required
  - `startsAt`: string (date-time), required. Start, in UTC.
  - `endsAt`: string (date-time), required. End, in UTC.
  - `date`: string, required. The day (YYYY-MM-DD) in the user's time zone.
  - `timeZone`: string, required. The time zone the block was planned in.
  - `subjectId`: string or null, required. The subject studied, when set.
  - `taskId`: string or null, required. The task worked on, when set.
  - `setId`: string or null, required. The study set practised, when set.
  - `createdAt`: string (date-time), required. Creation timestamp.
  - `updatedAt`: string (date-time), required. Last update timestamp.

### Example

```json
{
  "data": {
    "id": "5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
    "title": "Spanish verbs",
    "startsAt": "2026-02-02T17:00:00.000Z",
    "endsAt": "2026-02-02T17:45:00.000Z",
    "date": "2026-02-02",
    "timeZone": "Europe/London",
    "subjectId": "5f0c1d2e-3a4b-4c5d-8e6f-7a8b9c0d1e2f",
    "taskId": null,
    "setId": "7e6d5c4b-3a2f-4e1d-8c0b-9a8f7e6d5c4b",
    "createdAt": "2026-01-15T09:30:00.000Z",
    "updatedAt": "2026-02-01T12:00:00.000Z"
  }
}
```

## Errors

Every error has the body `{ "error": { "code", "message", "details" } }`.

- `400`: `invalid_request` with `details.reason`: `planning-date-invalid` (in the past, shorter than 5 minutes, longer than 8 hours, or across midnight), `planning-clock-change`, or `planning-link-invalid` (a subject, task or set that isn't the user's).
- `401`: Missing, invalid, disabled or expired API key
- `403`: The API key lacks the required scope
- `404`: No endpoint exists at this path.
- `405`: Method not allowed. Use a method from the Allow header.
- `409`: `conflict` with `details.reason`: `planning-conflict` (overlaps a lesson or another block; read `getToday` or `getScheduleWeek` for free times), `planning-after-deadline` (ends after the linked task's due day), `planning-calendar-incomplete` (an external calendar couldn't be read, or the block is beyond next week while one is connected). The Idempotency-Key was reused with a different request body, or a request with the same key is still in progress.
- `429`: Rate limit exceeded. Wait for Retry-After before retrying. The rate-limit headers are absent when the key's own limiter rejected the request before the per-endpoint bucket check ran.
- `500`: Internal server error
