# Create several tasks

Source: https://workroad.app/docs/api/create-tasks

`POST https://workroad.app/api/v1/tasks/batch`

Creates up to 50 tasks in one call, each with the fields `POST /tasks` takes, and answers 200 with a result per task in the order sent: `created` with the task, or `rejected` with the error `POST /tasks` would have returned for it. Each task is created or refused on its own, so fix the rejected ones and send only those again. A subject or task type the first task creates is there for the ones after it. `type` is the kind of work and must be one of the user's task types (`GET /task-types`); the course or module is the subject, sent as `subjectId` or `subjectName`. List the subjects and types first, and create a missing subject (or ask the user) rather than putting its name in another field. An exam or in-class test is type `Test`, with its day as `dueDate` and the topics and start time in the description.

Use this to save content you wrote yourself. It uses none of the user's WorkRoad AI uses.

- MCP tool: `createTasks` on `https://workroad.app/api/mcp`
- Required scopes: tasks:create
- Test mode: `POST https://workroad.app/api/test/v1/tasks/batch` checks the request and returns the example response without changing anything.
- Next: send the `rejected` tasks again after fixing what each `error` says.

## 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.

- `tasks`: array of objects, required, 1-50 items. 1-50 tasks, each with the fields `POST /tasks` takes. Each is created or refused on its own.
  - `name`: string, required, 2-200 characters
  - `description`: string, up to 512 characters. Plain text, up to 512 characters: the topics an exam covers, the time it starts, what to hand in.
  - `dueDate`: string (date) or string (date-time) or null. The day the task is due. `null` or omitted for no due date. Send the day as `YYYY-MM-DD`: it is read in the user's time zone (`timeZone` on `GET /me`) and stored as the start of that day, as the app's task form does. A timestamp with `Z` or an offset is accepted too, and the task is due on the day it falls on in that time zone. The app shows a due date as a day, never a time, so put a time such as an exam's start in the description.
    - Or string (date)
    - Or string (date-time)
  - `type`: string, 2-32 characters. The kind of work, one of the user's task types from `GET /task-types` (such as `Homework`, `Test`, `Project`, `Study` or `Read`), matched without regard to case. Never a course or module name: that is the subject, so send it as `subjectId` or `subjectName`. An exam or in-class test is `Test`. A type the user doesn't have is refused with 400 `unknown-task-type`, whose `details.taskTypes` lists theirs, unless `createType` is true. Defaults to `Generic` when omitted.
  - `createType`: boolean. Adds `type` to the user's task types when they don't have it, as adding one in the app's task settings does. Only send it when the user asked for a new kind of task, never for a course name.
  - `subjectId`: string (uuid). Subject id owned by the user.
  - `subjectName`: string, 1-64 characters. The subject (the course or module, such as `Software Engineering`) by name instead of by id. It must match one of the user's active subjects, ignoring case and accents. A name that matches none is refused with 400 `unknown-subject`, whose `details.subjects` lists the user's subjects, unless `createSubjectIfMissing` is true. Send `subjectId` or `subjectName`, not both.
  - `createSubjectIfMissing`: boolean. With `subjectName`, creates the subject when the user has none by that name. Needs the `subjects:create` scope. Only send it when the user named a course they haven't added yet.
  - `recurrence`: object. Repeats the task. Completing it creates the next occurrence `interval` units after the due date. Needs a due date.
    - `unit`: one of `"day"`, `"week"`, `"month"`, required
    - `interval`: integer, required, 1 to 30
    - `endsAt`: string (date) or string (date-time) or null
      - Or string (date)
      - Or string (date-time)
  - `priority`: one of `"none"`, `"low"`, `"medium"`, `"high"`. How urgent the task is: `none`, `low`, `medium` or `high`, from least to most urgent. Defaults to `none`. Only send it when the user said how urgent it is.

## Response

`200` OK

- `data`: object, required
  - `createdCount`: integer, required. How many tasks were created.
  - `rejectedCount`: integer, required. How many tasks were refused.
  - `results`: array of these objects, required. One result per task, in the order sent.
    - One of `status`: `"created"`
      - `index`: integer, required. The task's position in `tasks`.
      - `status`: `"created"`, required
      - `task`: object, required
        - `id`: string (uuid), required
        - `name`: string, required
        - `description`: string or null, required
        - `type`: string, required. Free-form task type from the user's task settings.
        - `subjectId`: string or null, required. Subject id, when set.
        - `dueDate`: string (date-time) or null, required. Due date, when set.
        - `completed`: boolean, required. Public name for the `finished` column.
        - `priority`: one of `"none"`, `"low"`, `"medium"`, `"high"`, required. How urgent the task is: `none`, `low`, `medium` or `high`, from least to most urgent. `none` when unset.
        - `parentId`: string or null, required. The parent task's id for a subtask, `null` for a top-level task.
        - `createdAt`: string (date-time), required. Creation timestamp.
        - `updatedAt`: string (date-time), required. Last update timestamp.
    - Or `status`: `"rejected"`
      - `index`: integer, required. The task's position in `tasks`.
      - `status`: `"rejected"`, required
      - `error`: object, required
        - `status`: integer, required. The HTTP status the single-item call answers.
        - `code`: string, required. The error code, as in the error envelope.
        - `message`: string, required
        - `details`: any value. What to fix, such as `reason` and the user's `taskTypes` or `subjects`.

### Example

```json
{
  "data": {
    "createdCount": 1,
    "rejectedCount": 1,
    "results": [
      {
        "index": 0,
        "status": "created",
        "task": {
          "id": "4d3c2b1a-0f9e-4d8c-9b7a-6f5e4d3c2b1a",
          "name": "Finish lab report",
          "description": "Include the error analysis.",
          "type": "Generic",
          "subjectId": "5f0c1d2e-3a4b-4c5d-8e6f-7a8b9c0d1e2f",
          "dueDate": "2026-02-10T17:00:00.000Z",
          "completed": false,
          "priority": "high",
          "parentId": null,
          "createdAt": "2026-01-15T09:30:00.000Z",
          "updatedAt": "2026-02-01T12:00:00.000Z"
        }
      },
      {
        "index": 1,
        "status": "rejected",
        "error": {
          "status": 400,
          "code": "invalid_request",
          "message": "\"Software Engineering\" is not one of the user's task types: \"Generic\", \"Homework\", \"Test\". A task type is the kind of work; a course or module is the subject, so send it as subjectId or subjectName. An exam or test is \"Test\". To add a new type, send createType: true.",
          "details": {
            "field": "type",
            "reason": "unknown-task-type",
            "taskTypes": [
              "Generic",
              "Homework",
              "Test"
            ]
          }
        }
      }
    ]
  }
}
```

## Errors

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

- `400`: The body is invalid as a whole.
- `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`: 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
