Tasks
Create tasks one at a time or in batches, set due days and recurrence, and add subtasks.
Creating and changing tasks
POST /tasks/batch creates up to 50 tasks in one call and answers 200 with one result per task, in order: created with the task, or rejected with the status, code, message and details that POST /tasks would have returned. A subject or type the call creates for one task is there for the next. Fix the rejected tasks and send only those again.
PATCH /tasks/{taskId} changes only the fields in the body. A field you leave out keeps its value, and null clears description, dueDate, subjectId or recurrence. A body without any known field returns 400 invalid_request.
Unknown fields in a request body or query string are rejected with a 400 that names the field. Dates in a request body must be strings, such as 2030-01-01 or 2030-01-01T09:00:00Z. A number, a boolean or a null where a date is required returns 400 invalid_request. null is only valid for dueDate, where it means no due date. Clearing dueDate also stops any recurrence, and a recurring task cannot lose its due date any other way.
POST /tasks/{taskId}/subtasks adds a checklist item under a top-level task. A subtask takes its parent's type and subject and cannot have subtasks of its own. A subtask name may be 1 character long at creation, but renaming it with PATCH /tasks/{taskId} needs at least 2.
Types and subjects
A task's type is the kind of work, and must be one of the user's task types from GET /task-types, such as Homework, Test or Project, matched without regard to case. The course or module is the subject, never the type. A type the user doesn't have returns 400 invalid_request with details.reason unknown-task-type and their types in details.taskTypes; send createType: true to add it, as adding a type in the app's task settings does, up to 40 types. An omitted type is Generic.
Name the subject with subjectId, or with subjectName, which must match one of the user's active subjects ignoring case and accents. A name that matches none returns 400 with details.reason unknown-subject and the user's subjects in details.subjects. Send createSubjectIfMissing: true to create it, which also needs subjects:create. Tasks, study sets and note folders all take subjectName.
A subjectId in a body that is not one of the key's user's subjects returns 400 invalid_request, with details.field "subjectId" and details.reason "unknown-subject". This holds on every endpoint that takes a subjectId. A 404 always means the resource in the path is missing.
To record an exam or test, create a task with type Test, its day as dueDate, the subject, and the topics and start time in the description.
Due dates
A due date is a day. Send YYYY-MM-DD: WorkRoad reads it in the user's time zone (timeZone on GET /me, UTC when unset) and stores 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 zone. The app shows the day, never a time. Recurrence endsAt works the same way.
Repeating tasks
A recurrence repeats a top-level task when you complete it. Set it with recurrence on POST /tasks or PATCH /tasks/{taskId}, and stop it with "recurrence": null. It needs a due date. A subtask cannot have a recurrence, and a PATCH that gives one to a subtask returns 400 invalid_request.
List tasks
Newest first, subtasks included. Filter with completed, type, subjectId, parentId and the due range dueFrom (inclusive) to dueTo (exclusive). A due range leaves out tasks without a due date.
Required scopes: tasks:view
AuthorizationBearer <token>limit?integerPage size, 1-100.
1 <= value <= 10025cursor?|Opaque. Pass nextCursor from the previous page unchanged.
completed?stringFilter by completion state. Omit for both.
"true""false"dueFrom?stringOnly tasks due at or after this ISO-8601 time.
^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$date-timedueTo?stringOnly tasks due before this ISO-8601 time.
^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$date-timetype?stringOnly tasks of this type.
1 <= length <= 32subjectId?stringOnly tasks filed under this subject.
^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$uuidparentId?|none for top-level tasks only, or a task id for that task's subtasks.
OK
application/json- response
data*array<>nextCursor*|Opaque. Pass as ?cursor= unchanged to fetch the next page. null on the last page.
curl -X GET 'https://workroad.app/api/v1/tasks' \ -H 'Authorization: Bearer $WORKROAD_API_KEY'{ "data": [ { "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" } ], "nextCursor": null}Get a task
Returns one task or subtask with its subtask count and recurrence rule. List a task's subtasks with GET /tasks?parentId=<taskId>.
Required scopes: tasks:view
AuthorizationBearer <token>taskId*stringTask id, scoped to the key's user.
^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$uuidOK
application/json- response
data*curl -X GET 'https://workroad.app/api/v1/tasks/497f6eca-6276-4993-bfeb-53cbbbba6f08' \ -H 'Authorization: Bearer $WORKROAD_API_KEY'{ "data": { "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", "subtaskCount": 2, "recurrence": { "unit": "week", "interval": 1, "endsAt": null } }}Create a task
Creates a task for the key's user. 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. To add several tasks, send them in one POST /tasks/batch.
Use this to save content you wrote yourself. It uses none of the user's WorkRoad AI uses.
Required scopes: tasks:create
AuthorizationBearer <token>Idempotency-Key?stringA client-generated key (1 to 255 visible ASCII characters) that makes this POST safe to retry. A retry with the same key and body replays the original response; the same key with a different body returns 409 idempotency_conflict. Stored for 24 hours.
^[\x21-\x7E]{1,255}$1 <= length <= 255application/json- body
name*string2 <= length <= 200description?stringPlain text, up to 512 characters: the topics an exam covers, the time it starts, what to hand in.
length <= 512dueDate?||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.
type?stringThe 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.
2 <= length <= 32createType?booleanAdds 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?stringSubject id owned by the user.
^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$uuidsubjectName?stringThe 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.
1 <= length <= 64createSubjectIfMissing?booleanWith 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?Repeats the task. Completing it creates the next occurrence interval units after the due date. Needs a due date.
priority?stringHow 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.
"none""low""medium""high"Created
application/json- response
data*curl -X POST 'https://workroad.app/api/v1/tasks' \ -H 'Authorization: Bearer $WORKROAD_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "name": "string"}'{ "data": { "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" }}Create several tasks
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.
Required scopes: tasks:create
AuthorizationBearer <token>Idempotency-Key?stringA client-generated key (1 to 255 visible ASCII characters) that makes this POST safe to retry. A retry with the same key and body replays the original response; the same key with a different body returns 409 idempotency_conflict. Stored for 24 hours.
^[\x21-\x7E]{1,255}$1 <= length <= 255application/json- body
tasks*array<>1-50 tasks, each with the fields POST /tasks takes. Each is created or refused on its own.
1 <= items <= 50OK
application/json- response
data*curl -X POST 'https://workroad.app/api/v1/tasks/batch' \ -H 'Authorization: Bearer $WORKROAD_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "tasks": [ { "name": "string" } ]}'{ "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" ] } } } ] }}Update a task
Changes only the fields you send; null clears a nullable field. completed is applied first, and completing a recurring task creates its next occurrence from the task as it was before the other changes. Everything in one call succeeds or fails together.
Required scopes: tasks:update
AuthorizationBearer <token>taskId*stringTask id, scoped to the key's user.
^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$uuidapplication/json- body
Send only the fields to change. An omitted field stays as it is.
completed?booleanCompletes or reopens the task. Completing a recurring task creates its next occurrence.
name?string2 <= length <= 200description?|Plain text. null or an empty string clears it.
dueDate?||The day the task is due. null clears it and stops any recurrence. 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.
type?stringThe 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.
2 <= length <= 32createType?booleanAdds 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.
recurrence?|Repeats the task. null stops it. Needs a due date and only works on top-level tasks.
subjectId?|Subject id owned by the user. null unfiles the task.
subjectName?stringThe 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.
1 <= length <= 64createSubjectIfMissing?booleanWith 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.
priority?stringHow urgent the task is: none, low, medium or high, from least to most urgent. none clears it.
"none""low""medium""high"OK
application/json- response
data*curl -X PATCH 'https://workroad.app/api/v1/tasks/497f6eca-6276-4993-bfeb-53cbbbba6f08' \ -H 'Authorization: Bearer $WORKROAD_API_KEY' \ -H 'Content-Type: application/json' \ -d '{}'{ "data": { "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" }}Move a task to the trash
Moves the task and its subtasks to the trash. A later occurrence of a recurring task is kept. POST /tasks/{taskId}/restore brings the task back with its subtasks. The trash is emptied after 30 days.
Required scopes: tasks:delete
AuthorizationBearer <token>taskId*stringTask id, scoped to the key's user.
^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$uuidOK
application/json- response
data*curl -X DELETE 'https://workroad.app/api/v1/tasks/497f6eca-6276-4993-bfeb-53cbbbba6f08' \ -H 'Authorization: Bearer $WORKROAD_API_KEY'{ "data": { "id": "4d3c2b1a-0f9e-4d8c-9b7a-6f5e4d3c2b1a", "deleted": true }}Restore a task from the trash
Brings a trashed task back with the subtasks that went to the trash with it, and returns it. Restoring a subtask whose task is in the trash restores that task too. Needs tasks:delete, the scope that trashed it.
Required scopes: tasks:delete
AuthorizationBearer <token>taskId*stringTask id, scoped to the key's user.
^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$uuidIdempotency-Key?stringA client-generated key (1 to 255 visible ASCII characters) that makes this POST safe to retry. A retry with the same key and body replays the original response; the same key with a different body returns 409 idempotency_conflict. Stored for 24 hours.
^[\x21-\x7E]{1,255}$1 <= length <= 255OK
application/json- response
data*curl -X POST 'https://workroad.app/api/v1/tasks/497f6eca-6276-4993-bfeb-53cbbbba6f08/restore' \ -H 'Authorization: Bearer $WORKROAD_API_KEY'{ "data": { "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" }}Add a subtask
Adds a checklist item to a top-level task. The subtask takes the parent's type and subject. A task can have at most 50 subtasks, and subtasks cannot have their own.
Required scopes: tasks:create
AuthorizationBearer <token>taskId*stringTask id, scoped to the key's user.
^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$uuidIdempotency-Key?stringA client-generated key (1 to 255 visible ASCII characters) that makes this POST safe to retry. A retry with the same key and body replays the original response; the same key with a different body returns 409 idempotency_conflict. Stored for 24 hours.
^[\x21-\x7E]{1,255}$1 <= length <= 255application/json- body
name*string1 <= length <= 64Created
application/json- response
data*curl -X POST 'https://workroad.app/api/v1/tasks/497f6eca-6276-4993-bfeb-53cbbbba6f08/subtasks' \ -H 'Authorization: Bearer $WORKROAD_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "name": "string"}'{ "data": { "id": "8b7a6f5e-4d3c-4b2a-9f1e-0d9c8b7a6f5e", "name": "Plot the titration curve", "description": null, "type": "Generic", "subjectId": "5f0c1d2e-3a4b-4c5d-8e6f-7a8b9c0d1e2f", "dueDate": "2026-02-10T17:00:00.000Z", "completed": false, "priority": "none", "parentId": "4d3c2b1a-0f9e-4d8c-9b7a-6f5e4d3c2b1a", "createdAt": "2026-01-15T09:30:00.000Z", "updatedAt": "2026-02-01T12:00:00.000Z" }}List task types
The task types from the user's task settings, in their configured order. Use one as a task's type.
Required scopes: tasks:view
AuthorizationBearer <token>OK
application/json- response
data*array<string>curl -X GET 'https://workroad.app/api/v1/task-types' \ -H 'Authorization: Bearer $WORKROAD_API_KEY'{ "data": [ "Generic", "Homework", "Test" ]}