# Start a practice session

Source: https://workroad.app/docs/api/start-practice-session

`POST https://workroad.app/api/v1/practice-sessions`

Starts flashcard practice the way the app's Review page does, over every study set the user owns or only some, and answers with the first cards. A normal session serves what is due now (learning and relearning first, then reviews, then new cards within the user's daily new-card limit), taking turns between sets, up to 30 at a time. Answer each card with `answerPracticeCard`, then call `getPracticeSession` for more. `cram: true` walks every term once without touching the schedule. A session lasts 7 days and needs no finishing.

- MCP tool: `startPracticeSession` on `https://workroad.app/api/mcp`
- Required scopes: activity:create and sets:view
- Test mode: `POST https://workroad.app/api/test/v1/practice-sessions` checks the request and returns the example response without changing anything.
- Next: answer each card with `answerPracticeCard`.

## 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), up to 200 items. Only these study sets, up to 200. Omit to practise every set the user owns.
- `subjectId`: string (uuid). Only sets filed under this subject. Combines with `setIds`.
- `cram`: boolean. Walk every term once, due or not, without changing any card's schedule. Defaults to false: a normal session serves only due and new cards and records each answer as an FSRS review.

## Response

`201` Created

- `data`: object, required
  - `id`: string (uuid), required. Pass as `sessionId` to the other practice tools.
  - `cram`: boolean, required
  - `setIds`: array of string, required. The sets the session is limited to. Empty means every set.
  - `subjectId`: string or null, required. The subject it is limited to.
  - `createdAt`: string (date-time), required. When the session started.
  - `expiresAt`: string (date-time), required. When the session stops accepting answers, 7 days after it started. Start a new one then.
  - `cards`: array of objects, required. Cards to answer now, in order, up to 30: learning and relearning first, then reviews, then new cards, taking turns between sets. Answer them with `answerPracticeCard`, then read the session again to refill.
    - `itemId`: string (uuid), required. Send this as `itemId` to `answerPracticeCard`.
    - `cardId`: string or null, required. The FSRS card. Null in a cram session for a term never practised, which gets no card.
    - `termId`: string (uuid), required. The term's id in its study set.
    - `setId`: string (uuid), required
    - `setName`: string, required
    - `subjectId`: string or null, required. The set's subject, when set.
    - `activity`: one of `"flashcards"`, `"memory"`, `"match"`, `"guess_the_term"`, `"sentence_scramble"`, required. How the app quizzes this card: `flashcards` (recall the definition), `memory` (fill the blanks), `match`, `guess_the_term` (pick the term for the definition) or `sentence_scramble`. An agent can quiz every kind by asking for the definition.
    - `term`: string, required
    - `definition`: string, required
    - `hints`: array of string, required
    - `imageUrl`: string or null, required. The term's image, when it has one.
    - `queue`: one of `"new"`, `"learning"`, `"relearning"`, `"review"`, required. The card's scheduling queue.
    - `due`: string (date-time), required. When the card became or becomes due.
  - `answeredCount`: integer, required. Answers recorded in this session.
  - `finished`: boolean, required. True when nothing is due now and today's new-card limit is spent. The session stays open: read it again at `nextDueAt`.
  - `nextDueAt`: string (date-time) or null, required. When `finished` is true, the next time a card in the session's scope becomes due. Null otherwise.
  - `undoableItemId`: string or null, required. The newest answer that `undoPracticeAnswer` can take back. Null in a cram session or when nothing can be undone.
  - `serverTime`: string (date-time), required. The server clock that decided which cards are due.

### Example

```json
{
  "data": {
    "id": "8b7a6f5e-4d3c-4b2a-9f1e-0d9c8b7a6f5e",
    "cram": false,
    "setIds": [],
    "subjectId": "5f0c1d2e-3a4b-4c5d-8e6f-7a8b9c0d1e2f",
    "createdAt": "2026-02-01T12:00:00.000Z",
    "expiresAt": "2026-02-08T12:00:00.000Z",
    "cards": [
      {
        "itemId": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
        "cardId": "3e2d1c0b-9a8f-4e7d-8c6b-5a4f3e2d1c0b",
        "termId": "2a1b0c9d-8e7f-4a6b-9c5d-4e3f2a1b0c9d",
        "setId": "7e6d5c4b-3a2f-4e1d-8c0b-9a8f7e6d5c4b",
        "setName": "Spanish verbs",
        "subjectId": "5f0c1d2e-3a4b-4c5d-8e6f-7a8b9c0d1e2f",
        "activity": "flashcards",
        "term": "tener (yo)",
        "definition": "tuve",
        "hints": [
          "irregular stem tuv-"
        ],
        "imageUrl": null,
        "queue": "review",
        "due": "2026-01-15T09:30:00.000Z"
      }
    ],
    "answeredCount": 0,
    "finished": false,
    "nextDueAt": null,
    "undoableItemId": null,
    "serverTime": "2026-02-01T12:00:00.000Z"
  }
}
```

## Errors

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

- `400`: Invalid request body or query
- `401`: Missing, invalid, disabled or expired API key
- `403`: The API key lacks the required scope
- `404`: `not_found`: a `subjectId` or one of the `setIds` isn't one of this key's user's.
- `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
