# Study sets

> Create study sets with their terms, edit terms one at a time or in batches, and archive sets.

Source: https://workroad.app/docs/api/sets

## Sets and terms

A study set holds a fixed maximum of terms, and each term carries its own flashcard review history. `POST /sets` takes the set's terms in the same call. `POST /sets/{setId}/terms` appends a batch of terms and leaves the existing ones untouched. `PATCH /sets/{setId}/terms/{termId}` changes only the fields you send, and `PATCH /sets/{setId}/terms` does the same for up to 500 terms at once, all or none: a `termId` that isn't the set's returns 400 with `details.reason` `unknown-term`. `POST /sets/{setId}/terms/delete` deletes up to 500 terms and their review history, and refuses with 409 `last-term` to empty the set. `DELETE /sets/{setId}/terms/{termId}` erases the term's review history for good, so it needs `sets:delete`; a set's last term can't be deleted this way, delete the set instead.

A term is at most 2000 characters and a definition at most 4000. A term has up to 10 hints of at most 500 characters each, and a hint may be empty. A `subjectId` the key's user doesn't own returns the same 400 `invalid_request` on set endpoints.

`GET /sets/{setId}/terms` pages a set's terms like any other list. Its page size defaults to 100 and goes up to 500. `GET /sets/{setId}` returns only the first terms of a large set and sets `termsTruncated` when there are more, so use the list to read the rest.

## List study sets

`GET https://workroad.app/api/v1/sets`

Newest first. Terms are omitted; fetch a single set for its terms. Archived sets are listed too unless `archived` says otherwise.

- MCP tool: `listSets` on `https://workroad.app/api/mcp`
- Required scopes: sets:view
- Test mode: `GET https://workroad.app/api/test/v1/sets` checks the request and returns the example response without changing anything.

### Query parameters

- `limit`: integer, 1 to 100, default `25`. Page size, 1-100.
- `cursor`: string or null, up to 512 characters. Opaque. Pass `nextCursor` from the previous page unchanged.
- `archived`: one of `"include"`, `"exclude"`, `"only"`, default `"include"`. `include` (the default) lists every set, `exclude` only the active ones and `only` only the archived ones.

### Response

`200` OK

- `data`: array of objects, required
  - `id`: string (uuid), required
  - `name`: string, required
  - `description`: string or null, required
  - `subjectId`: string or null, required. Subject id, when set.
  - `archived`: boolean, required. An archived set keeps its terms and review history but is left out of reviews, practice and the study picker.
  - `favorite`: boolean, required. Kept first in the user's sets list.
  - `termCount`: integer, required
  - `createdAt`: string (date-time), required. Creation timestamp.
  - `lastUsed`: string (date-time) or null, required. Last study session, if any.
  - `updatedAt`: string (date-time), required. Last update timestamp.
- `nextCursor`: string or null, required. Opaque. Pass as `?cursor=` unchanged to fetch the next page. `null` on the last page.

#### Example

```json
{
  "data": [
    {
      "id": "7e6d5c4b-3a2f-4e1d-8c0b-9a8f7e6d5c4b",
      "name": "Spanish verbs",
      "description": "Irregular preterite forms.",
      "subjectId": "5f0c1d2e-3a4b-4c5d-8e6f-7a8b9c0d1e2f",
      "archived": false,
      "favorite": false,
      "termCount": 1,
      "createdAt": "2026-01-15T09:30:00.000Z",
      "lastUsed": "2026-02-01T12:00:00.000Z",
      "updatedAt": "2026-02-01T12:00:00.000Z"
    }
  ],
  "nextCursor": null
}
```

### Errors

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

- `400`: Invalid request body or query
- `401`: Missing, invalid, disabled or expired API key
- `402`: `api_allowance_reached`: this month's API and AI app requests on the free plan are used up. `details` has `limit`, `used`, `resetsAt` and `upgradeUrl`. Nothing ran; WorkRoad Plus has no monthly limit.
- `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.
- `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

## Get a study set with its terms

`GET https://workroad.app/api/v1/sets/{setId}`

Returns the set and up to 500 of its terms. `termsTruncated` is true when the set has more.

- MCP tool: `getSet` on `https://workroad.app/api/mcp`
- Required scopes: sets:view
- Test mode: `GET https://workroad.app/api/test/v1/sets/{setId}` checks the request and returns the example response without changing anything.

### Path parameters

- `setId`: string (uuid), required. Study set id, scoped to the key's user.

### Response

`200` OK

- `data`: object, required
  - `id`: string (uuid), required
  - `name`: string, required
  - `description`: string or null, required
  - `subjectId`: string or null, required. Subject id, when set.
  - `archived`: boolean, required. An archived set keeps its terms and review history but is left out of reviews, practice and the study picker.
  - `favorite`: boolean, required. Kept first in the user's sets list.
  - `termCount`: integer, required
  - `createdAt`: string (date-time), required. Creation timestamp.
  - `lastUsed`: string (date-time) or null, required. Last study session, if any.
  - `updatedAt`: string (date-time), required. Last update timestamp.
  - `terms`: array of objects, required. The first 500 terms by id. When `termsTruncated` is true, page the rest with `GET /sets/{setId}/terms`.
    - `id`: string (uuid), required
    - `term`: string, required
    - `definition`: string, required
    - `hints`: array of string, required
  - `termsTruncated`: boolean, required

#### Example

```json
{
  "data": {
    "id": "7e6d5c4b-3a2f-4e1d-8c0b-9a8f7e6d5c4b",
    "name": "Spanish verbs",
    "description": "Irregular preterite forms.",
    "subjectId": "5f0c1d2e-3a4b-4c5d-8e6f-7a8b9c0d1e2f",
    "archived": false,
    "favorite": false,
    "termCount": 1,
    "createdAt": "2026-01-15T09:30:00.000Z",
    "lastUsed": "2026-02-01T12:00:00.000Z",
    "updatedAt": "2026-02-01T12:00:00.000Z",
    "terms": [
      {
        "id": "2a1b0c9d-8e7f-4a6b-9c5d-4e3f2a1b0c9d",
        "term": "tener (yo)",
        "definition": "tuve",
        "hints": [
          "irregular stem tuv-"
        ]
      }
    ],
    "termsTruncated": false
  }
}
```

### Errors

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

- `400`: Invalid request body or query
- `401`: Missing, invalid, disabled or expired API key
- `402`: `api_allowance_reached`: this month's API and AI app requests on the free plan are used up. `details` has `limit`, `used`, `resetsAt` and `upgradeUrl`. Nothing ran; WorkRoad Plus has no monthly limit.
- `403`: The API key lacks the required scope
- `404`: No resource with that id exists for this key's user
- `405`: Method not allowed. Use a method from the Allow header.
- `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

## List a set's terms

`GET https://workroad.app/api/v1/sets/{setId}/terms`

Ordered by id, 100 per page by default and up to 500. Use this to read terms past the 500 that `GET /sets/{setId}` returns.

- MCP tool: `listSetTerms` on `https://workroad.app/api/mcp`
- Required scopes: sets:view
- Test mode: `GET https://workroad.app/api/test/v1/sets/{setId}/terms` checks the request and returns the example response without changing anything.

### Path parameters

- `setId`: string (uuid), required. Study set id, scoped to the key's user.

### Query parameters

- `limit`: integer, 1 to 500, default `100`. Page size, 1-500.
- `cursor`: string or null, up to 512 characters. Opaque. Pass `nextCursor` from the previous page unchanged.

### Response

`200` OK

- `data`: array of objects, required
  - `id`: string (uuid), required
  - `term`: string, required
  - `definition`: string, required
  - `hints`: array of string, required
- `nextCursor`: string or null, required. Opaque. Pass as `?cursor=` unchanged to fetch the next page. `null` on the last page.

#### Example

```json
{
  "data": [
    {
      "id": "2a1b0c9d-8e7f-4a6b-9c5d-4e3f2a1b0c9d",
      "term": "tener (yo)",
      "definition": "tuve",
      "hints": [
        "irregular stem tuv-"
      ]
    }
  ],
  "nextCursor": null
}
```

### Errors

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

- `400`: Invalid request body or query
- `401`: Missing, invalid, disabled or expired API key
- `402`: `api_allowance_reached`: this month's API and AI app requests on the free plan are used up. `details` has `limit`, `used`, `resetsAt` and `upgradeUrl`. Nothing ran; WorkRoad Plus has no monthly limit.
- `403`: The API key lacks the required scope
- `404`: No study set with that id exists for this key's user.
- `405`: Method not allowed. Use a method from the Allow header.
- `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

## Create a study set

`POST https://workroad.app/api/v1/sets`

Creates a set with 1-2000 terms in one call and returns it as `GET /sets/{setId}` would. File it under the course with `subjectId` or `subjectName`.

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

- MCP tool: `createSet` on `https://workroad.app/api/mcp`
- Required scopes: sets:create
- Test mode: `POST https://workroad.app/api/test/v1/sets` checks the request and returns the example response without changing anything.
- Next: add more terms with `addSetTerms`, then practise them with `startPracticeSession` and the set's `id` in `setIds`.

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

- `name`: string, required, 1-120 characters
- `description`: string, up to 2000 characters
- `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.
- `terms`: array of objects, required, 1-2000 items. 1-2000 terms, each a flashcard: the `term` on the front, the `definition` on the back.
  - `term`: string, required, 1-2000 characters
  - `definition`: string, required, 1-4000 characters
  - `hints`: array of string, up to 10 items, default `[]`. Hints shown while studying the term.

### Response

`201` Created

- `data`: object, required
  - `id`: string (uuid), required
  - `name`: string, required
  - `description`: string or null, required
  - `subjectId`: string or null, required. Subject id, when set.
  - `archived`: boolean, required. An archived set keeps its terms and review history but is left out of reviews, practice and the study picker.
  - `favorite`: boolean, required. Kept first in the user's sets list.
  - `termCount`: integer, required
  - `createdAt`: string (date-time), required. Creation timestamp.
  - `lastUsed`: string (date-time) or null, required. Last study session, if any.
  - `updatedAt`: string (date-time), required. Last update timestamp.
  - `terms`: array of objects, required. The first 500 terms by id. When `termsTruncated` is true, page the rest with `GET /sets/{setId}/terms`.
    - `id`: string (uuid), required
    - `term`: string, required
    - `definition`: string, required
    - `hints`: array of string, required
  - `termsTruncated`: boolean, required

#### Example

```json
{
  "data": {
    "id": "7e6d5c4b-3a2f-4e1d-8c0b-9a8f7e6d5c4b",
    "name": "Spanish verbs",
    "description": "Irregular preterite forms.",
    "subjectId": "5f0c1d2e-3a4b-4c5d-8e6f-7a8b9c0d1e2f",
    "archived": false,
    "favorite": false,
    "termCount": 1,
    "createdAt": "2026-01-15T09:30:00.000Z",
    "lastUsed": "2026-02-01T12:00:00.000Z",
    "updatedAt": "2026-02-01T12:00:00.000Z",
    "terms": [
      {
        "id": "2a1b0c9d-8e7f-4a6b-9c5d-4e3f2a1b0c9d",
        "term": "tener (yo)",
        "definition": "tuve",
        "hints": [
          "irregular stem tuv-"
        ]
      }
    ],
    "termsTruncated": false
  }
}
```

### Errors

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

- `400`: Invalid body, or the subject is not one of the user's subjects (`details.reason` `unknown-subject`, with their names in `details.subjects` when you sent `subjectName`).
- `401`: Missing, invalid, disabled or expired API key
- `402`: `api_allowance_reached`: this month's API and AI app requests on the free plan are used up. `details` has `limit`, `used`, `resetsAt` and `upgradeUrl`. Nothing ran; WorkRoad Plus has no monthly limit.
- `403`: `createSubjectIfMissing` was sent and the key lacks `subjects:create` (`details.missingScopes`).
- `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

## Update a study set

`PATCH https://workroad.app/api/v1/sets/{setId}`

Changes the name, description or subject, or archives, restores or favourites the set. Only the fields you send change; `null` clears a nullable field. Terms and their review history are never touched. To archive or restore many sets at once, use `POST /sets/archive`.

- MCP tool: `updateSet` on `https://workroad.app/api/mcp`
- Required scopes: sets:update
- Test mode: `PATCH https://workroad.app/api/test/v1/sets/{setId}` checks the request and returns the example response without changing anything.

### Path parameters

- `setId`: string (uuid), required. Study set id, scoped to the key's user.

### Request body

JSON.

- `name`: string, 1-120 characters
- `description`: string or null, up to 2000 characters. `null` or an empty string clears it.
- `subjectId`: string (uuid) or null. Subject id owned by the user. `null` unfiles the set.
- `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.
- `archived`: boolean. Archives or restores the set. An archived set keeps its terms and review history but leaves reviews, practice and the study picker.
- `favorite`: boolean. Keeps the set first in the user's sets list.

### Response

`200` OK

- `data`: object, required
  - `id`: string (uuid), required
  - `name`: string, required
  - `description`: string or null, required
  - `subjectId`: string or null, required. Subject id, when set.
  - `archived`: boolean, required. An archived set keeps its terms and review history but is left out of reviews, practice and the study picker.
  - `favorite`: boolean, required. Kept first in the user's sets list.
  - `termCount`: integer, required
  - `createdAt`: string (date-time), required. Creation timestamp.
  - `lastUsed`: string (date-time) or null, required. Last study session, if any.
  - `updatedAt`: string (date-time), required. Last update timestamp.

#### Example

```json
{
  "data": {
    "id": "7e6d5c4b-3a2f-4e1d-8c0b-9a8f7e6d5c4b",
    "name": "Spanish verbs",
    "description": "Irregular preterite forms.",
    "subjectId": "5f0c1d2e-3a4b-4c5d-8e6f-7a8b9c0d1e2f",
    "archived": false,
    "favorite": false,
    "termCount": 1,
    "createdAt": "2026-01-15T09:30:00.000Z",
    "lastUsed": "2026-02-01T12:00:00.000Z",
    "updatedAt": "2026-02-01T12:00:00.000Z"
  }
}
```

### Errors

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

- `400`: Invalid body, no field to update, or the subject is not one of the user's subjects.
- `401`: Missing, invalid, disabled or expired API key
- `402`: `api_allowance_reached`: this month's API and AI app requests on the free plan are used up. `details` has `limit`, `used`, `resetsAt` and `upgradeUrl`. Nothing ran; WorkRoad Plus has no monthly limit.
- `403`: The API key lacks the required scope
- `404`: No study set with that id exists for this key's user.
- `405`: Method not allowed. Use a method from the Allow header.
- `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

## Archive or restore several study sets

`POST https://workroad.app/api/v1/sets/archive`

Archives up to 500 sets in one call, or restores them with `archived: false`. An archived set keeps its terms and review history but leaves reviews, practice and the study picker. Sets that aren't the user's, are in the trash or are already in that state are skipped. To retire every set of a course that is over, archive its subject instead (`PATCH /subjects/{subjectId}`).

- MCP tool: `archiveSets` on `https://workroad.app/api/mcp`
- Required scopes: sets:update
- Test mode: `POST https://workroad.app/api/test/v1/sets/archive` checks the request and returns the example response without changing anything.
- Next: `archiveSets` with `archived: false` restores them.

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

- `setIds`: array of string (uuid), required, 1-500 items. 1-500 study set ids.
- `archived`: boolean, required. `true` archives the sets, `false` restores them.

### Response

`200` OK

- `data`: object, required
  - `count`: integer, required. How many sets changed. Sets that aren't the user's, are in the trash or are already in that state are skipped.

#### Example

```json
{
  "data": {
    "count": 1
  }
}
```

### Errors

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

- `400`: Invalid body.
- `401`: Missing, invalid, disabled or expired API key
- `402`: `api_allowance_reached`: this month's API and AI app requests on the free plan are used up. `details` has `limit`, `used`, `resetsAt` and `upgradeUrl`. Nothing ran; WorkRoad Plus has no monthly limit.
- `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

## Move a study set to the trash

`DELETE https://workroad.app/api/v1/sets/{setId}`

The set disappears from every read. Its terms, images and review history stay as they are, and `POST /sets/{setId}/restore` brings it back. The trash is emptied after 30 days, which removes the set's review history for good.

- MCP tool: `deleteSet` on `https://workroad.app/api/mcp`
- Required scopes: sets:delete
- Test mode: `DELETE https://workroad.app/api/test/v1/sets/{setId}` checks the request and returns the example response without changing anything.
- Next: `restoreSet` brings it back from the trash.

### Path parameters

- `setId`: string (uuid), required. Study set id, scoped to the key's user.

### Response

`200` OK

- `data`: object, required
  - `id`: string (uuid), required
  - `deleted`: `true`, required

#### Example

```json
{
  "data": {
    "id": "7e6d5c4b-3a2f-4e1d-8c0b-9a8f7e6d5c4b",
    "deleted": true
  }
}
```

### Errors

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

- `400`: Invalid request body or query
- `401`: Missing, invalid, disabled or expired API key
- `402`: `api_allowance_reached`: this month's API and AI app requests on the free plan are used up. `details` has `limit`, `used`, `resetsAt` and `upgradeUrl`. Nothing ran; WorkRoad Plus has no monthly limit.
- `403`: The API key lacks the required scope
- `404`: No study set with that id exists for this key's user.
- `405`: Method not allowed. Use a method from the Allow header.
- `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

## Restore a study set from the trash

`POST https://workroad.app/api/v1/sets/{setId}/restore`

Brings a trashed set back with its terms, images and review history, and returns it as `GET /sets/{setId}` would. Needs `sets:delete`, the scope that trashed it. A set whose folder is still in the trash comes back unfiled.

- MCP tool: `restoreSet` on `https://workroad.app/api/mcp`
- Required scopes: sets:delete
- Test mode: `POST https://workroad.app/api/test/v1/sets/{setId}/restore` checks the request and returns the example response without changing anything.

### Path parameters

- `setId`: string (uuid), required. Study set id, scoped to the key's user.

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

### Response

`200` OK

- `data`: object, required
  - `id`: string (uuid), required
  - `name`: string, required
  - `description`: string or null, required
  - `subjectId`: string or null, required. Subject id, when set.
  - `archived`: boolean, required. An archived set keeps its terms and review history but is left out of reviews, practice and the study picker.
  - `favorite`: boolean, required. Kept first in the user's sets list.
  - `termCount`: integer, required
  - `createdAt`: string (date-time), required. Creation timestamp.
  - `lastUsed`: string (date-time) or null, required. Last study session, if any.
  - `updatedAt`: string (date-time), required. Last update timestamp.
  - `terms`: array of objects, required. The first 500 terms by id. When `termsTruncated` is true, page the rest with `GET /sets/{setId}/terms`.
    - `id`: string (uuid), required
    - `term`: string, required
    - `definition`: string, required
    - `hints`: array of string, required
  - `termsTruncated`: boolean, required

#### Example

```json
{
  "data": {
    "id": "7e6d5c4b-3a2f-4e1d-8c0b-9a8f7e6d5c4b",
    "name": "Spanish verbs",
    "description": "Irregular preterite forms.",
    "subjectId": "5f0c1d2e-3a4b-4c5d-8e6f-7a8b9c0d1e2f",
    "archived": false,
    "favorite": false,
    "termCount": 1,
    "createdAt": "2026-01-15T09:30:00.000Z",
    "lastUsed": "2026-02-01T12:00:00.000Z",
    "updatedAt": "2026-02-01T12:00:00.000Z",
    "terms": [
      {
        "id": "2a1b0c9d-8e7f-4a6b-9c5d-4e3f2a1b0c9d",
        "term": "tener (yo)",
        "definition": "tuve",
        "hints": [
          "irregular stem tuv-"
        ]
      }
    ],
    "termsTruncated": false
  }
}
```

### Errors

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

- `400`: Invalid request body or query
- `401`: Missing, invalid, disabled or expired API key
- `402`: `api_allowance_reached`: this month's API and AI app requests on the free plan are used up. `details` has `limit`, `used`, `resetsAt` and `upgradeUrl`. Nothing ran; WorkRoad Plus has no monthly limit.
- `403`: The API key lacks the required scope
- `404`: No study set with that id is in the trash for this key's user.
- `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

## Add terms to a study set

`POST https://workroad.app/api/v1/sets/{setId}/terms`

Appends up to 500 terms per call. Existing terms and their review history stay as they are. A set holds at most 2000 terms, and a call that would pass that adds nothing.

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

- MCP tool: `addSetTerms` on `https://workroad.app/api/mcp`
- Required scopes: sets:update
- Test mode: `POST https://workroad.app/api/test/v1/sets/{setId}/terms` checks the request and returns the example response without changing anything.

### Path parameters

- `setId`: string (uuid), required. Study set id, scoped to the key's user.

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

- `terms`: array of objects, required, 1-500 items. 1-500 terms per call.
  - `term`: string, required, 1-2000 characters
  - `definition`: string, required, 1-4000 characters
  - `hints`: array of string, up to 10 items, default `[]`. Hints shown while studying the term.

### Response

`201` Created

- `data`: object, required
  - `terms`: array of objects, required. The new terms.
    - `id`: string (uuid), required
    - `term`: string, required
    - `definition`: string, required
    - `hints`: array of string, required
  - `termCount`: integer, required. The set's term count after the append.

#### Example

```json
{
  "data": {
    "terms": [
      {
        "id": "6c5b4a39-2817-4f06-9e5d-4c3b2a190817",
        "term": "ser (yo)",
        "definition": "fui",
        "hints": []
      }
    ],
    "termCount": 2
  }
}
```

### Errors

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

- `400`: Invalid body, or the set would hold more than 2000 terms (`details.reason` is `too-many-terms`).
- `401`: Missing, invalid, disabled or expired API key
- `402`: `api_allowance_reached`: this month's API and AI app requests on the free plan are used up. `details` has `limit`, `used`, `resetsAt` and `upgradeUrl`. Nothing ran; WorkRoad Plus has no monthly limit.
- `403`: The API key lacks the required scope
- `404`: No study set with that id exists for this key's user.
- `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

## Update several terms

`PATCH https://workroad.app/api/v1/sets/{setId}/terms`

Changes up to 500 terms of one set in one call. Send each term's `termId` with only the fields to change. Every term keeps its id, image and review history. All the changes are saved together, or none when a `termId` isn't the set's.

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

- MCP tool: `updateSetTerms` on `https://workroad.app/api/mcp`
- Required scopes: sets:update
- Test mode: `PATCH https://workroad.app/api/test/v1/sets/{setId}/terms` checks the request and returns the example response without changing anything.

### Path parameters

- `setId`: string (uuid), required. Study set id, scoped to the key's user.

### Request body

JSON.

- `terms`: array of objects, required, 1-500 items. 1-500 terms, each with its `termId` and only the fields to change.
  - `termId`: string (uuid), required. A term of this set.
  - `term`: string, 1-2000 characters
  - `definition`: string, 1-4000 characters
  - `hints`: array of string, up to 10 items. Replaces the full list when sent.

### Response

`200` OK

- `data`: object, required
  - `terms`: array of objects, required. The terms as saved, in the order sent.
    - `id`: string (uuid), required
    - `term`: string, required
    - `definition`: string, required
    - `hints`: array of string, required

#### Example

```json
{
  "data": {
    "terms": [
      {
        "id": "2a1b0c9d-8e7f-4a6b-9c5d-4e3f2a1b0c9d",
        "term": "tener (yo)",
        "definition": "tuve",
        "hints": [
          "irregular stem tuv-"
        ]
      }
    ]
  }
}
```

### Errors

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

- `400`: Invalid body, or `details.reason` `unknown-term` with the ids that aren't the set's in `details.termIds`.
- `401`: Missing, invalid, disabled or expired API key
- `402`: `api_allowance_reached`: this month's API and AI app requests on the free plan are used up. `details` has `limit`, `used`, `resetsAt` and `upgradeUrl`. Nothing ran; WorkRoad Plus has no monthly limit.
- `403`: The API key lacks the required scope
- `404`: No study set with that id exists for this key's user.
- `405`: Method not allowed. Use a method from the Allow header.
- `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

## Update a term

`PATCH https://workroad.app/api/v1/sets/{setId}/terms/{termId}`

Changes the term, definition or hints. Only the fields you send change. The term keeps its id, image and review history.

- MCP tool: `updateSetTerm` on `https://workroad.app/api/mcp`
- Required scopes: sets:update
- Test mode: `PATCH https://workroad.app/api/test/v1/sets/{setId}/terms/{termId}` checks the request and returns the example response without changing anything.

### Path parameters

- `setId`: string (uuid), required. Study set id, scoped to the key's user.
- `termId`: string (uuid), required. Term id within that set.

### Request body

JSON.

- `term`: string, 1-2000 characters
- `definition`: string, 1-4000 characters
- `hints`: array of string, up to 10 items. Replaces the full list when sent.

### Response

`200` OK

- `data`: object, required
  - `id`: string (uuid), required
  - `term`: string, required
  - `definition`: string, required
  - `hints`: array of string, required

#### Example

```json
{
  "data": {
    "id": "2a1b0c9d-8e7f-4a6b-9c5d-4e3f2a1b0c9d",
    "term": "tener (yo)",
    "definition": "tuve",
    "hints": [
      "irregular stem tuv-"
    ]
  }
}
```

### Errors

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

- `400`: Invalid body, or no field to update.
- `401`: Missing, invalid, disabled or expired API key
- `402`: `api_allowance_reached`: this month's API and AI app requests on the free plan are used up. `details` has `limit`, `used`, `resetsAt` and `upgradeUrl`. Nothing ran; WorkRoad Plus has no monthly limit.
- `403`: The API key lacks the required scope
- `404`: No study set with that id exists for this key's user, or the set has no term with that id.
- `405`: Method not allowed. Use a method from the Allow header.
- `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

## Delete several terms

`POST https://workroad.app/api/v1/sets/{setId}/terms/delete`

Deletes up to 500 terms of one set in one call, with their images. This permanently removes each term's flashcard review history. The set keeps at least one term: a call that would delete them all deletes nothing; delete the set instead. Nothing is deleted when a `termId` isn't the set's.

- MCP tool: `deleteSetTerms` on `https://workroad.app/api/mcp`
- Required scopes: sets:delete
- Test mode: `POST https://workroad.app/api/test/v1/sets/{setId}/terms/delete` checks the request and returns the example response without changing anything.

### Path parameters

- `setId`: string (uuid), required. Study set id, scoped to the key's user.

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

- `termIds`: array of string (uuid), required, 1-500 items. 1-500 ids of this set's terms.

### Response

`200` OK

- `data`: object, required
  - `termIds`: array of string (uuid), required. The deleted terms.
  - `deleted`: `true`, required

#### Example

```json
{
  "data": {
    "termIds": [
      "2a1b0c9d-8e7f-4a6b-9c5d-4e3f2a1b0c9d"
    ],
    "deleted": true
  }
}
```

### Errors

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

- `400`: Invalid body, or `details.reason` `unknown-term` with the ids that aren't the set's in `details.termIds`.
- `401`: Missing, invalid, disabled or expired API key
- `402`: `api_allowance_reached`: this month's API and AI app requests on the free plan are used up. `details` has `limit`, `used`, `resetsAt` and `upgradeUrl`. Nothing ran; WorkRoad Plus has no monthly limit.
- `403`: The API key lacks the required scope
- `404`: No study set with that id exists for this key's user.
- `405`: Method not allowed. Use a method from the Allow header.
- `409`: The call would delete every term (`details.reason` is `last-term`). 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

## Delete a term

`DELETE https://workroad.app/api/v1/sets/{setId}/terms/{termId}`

Deletes the term and its image. This permanently removes the term's flashcard review history and scheduling; nothing can restore them. To change a term's wording, update it instead. Requires `sets:delete` because it erases review history. A set's last term cannot be deleted; delete the set instead.

- MCP tool: `deleteSetTerm` on `https://workroad.app/api/mcp`
- Required scopes: sets:delete
- Test mode: `DELETE https://workroad.app/api/test/v1/sets/{setId}/terms/{termId}` checks the request and returns the example response without changing anything.

### Path parameters

- `setId`: string (uuid), required. Study set id, scoped to the key's user.
- `termId`: string (uuid), required. Term id within that set.

### Response

`200` OK

- `data`: object, required
  - `id`: string (uuid), required
  - `deleted`: `true`, required

#### Example

```json
{
  "data": {
    "id": "2a1b0c9d-8e7f-4a6b-9c5d-4e3f2a1b0c9d",
    "deleted": true
  }
}
```

### Errors

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

- `400`: Invalid request body or query
- `401`: Missing, invalid, disabled or expired API key
- `402`: `api_allowance_reached`: this month's API and AI app requests on the free plan are used up. `details` has `limit`, `used`, `resetsAt` and `upgradeUrl`. Nothing ran; WorkRoad Plus has no monthly limit.
- `403`: The API key lacks the required scope
- `404`: No study set with that id exists for this key's user, or the set has no term with that id.
- `405`: Method not allowed. Use a method from the Allow header.
- `409`: The term is the set's last one (`details.reason` is `last-term`).
- `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
