---
name: learn
description: Work with a user's Learn courses, card sets, questions, and learning sessions through the Learn API. Use when an agent is asked to read, create, edit, import, export, or study Learn content.
---

# Learn API

Use the base URL supplied by the user, or the origin serving this skill when reading it online. For an installed copy without a supplied URL, use `https://learn.senn.sh`. Fetch `${BASE_URL}/api/openapi.json` first; it is the current source for routes, request bodies, response shapes, and errors. The human-readable version is at `${BASE_URL}/api/docs`. The public discovery index is at `${BASE_URL}/llms.txt`.

Read this document directly to get started; installation is optional. Agents that support local skills can save this document as `learn/SKILL.md` in their own skills directory.

## Credentials

The user signs in and creates or retrieves a personal API key at `${BASE_URL}/settings`. Ask them to configure it in their platform's secret store or securely inject it as `LEARN_KEY` into the agent's command environment. Do not ask them to paste it into chat. Use it as `Authorization: Bearer <key>` on content routes under `/api/*`. No separate `apikey` header is needed. Never print the key, enable shell tracing, or put it in a URL, source file, log, or response. Keep requests containing the key on the selected Learn origin.

Personal keys cannot access `/auth/v1/*`, `/rest/v1/*`, profile operations, or `/api/key`. Key creation, retrieval, and revocation require the user's signed-in browser session. A `401` means the key is missing, invalid, or revoked; guide the user back to Settings rather than retrying writes with another credential.

### macOS Keychain

On macOS, use the default Keychain entry with the current macOS account and service `flashcards-api-key`. The service name is retained for existing keys. If the user uses a different Keychain account, use that account instead. Retrieve the key only into the command environment and unset it after the request:

```sh
LEARN_KEY="$(security find-generic-password -a "$(id -un)" -s flashcards-api-key -w)" || exit 1
: "${LEARN_KEY:?Learn Keychain entry is empty}"
BASE_URL="${BASE_URL:-https://learn.senn.sh}"
printf 'header = "Authorization: Bearer %s"\n' "$LEARN_KEY" |
   curl -fsS --config - "$BASE_URL/api/courses"
unset LEARN_KEY
```

Fetch the key again for later commands; do not assume a shell variable persists across tool calls. Keychain access may require approval outside an agent's sandbox. If the entry is missing, ask the user to create or retrieve the key in Settings and run `security add-generic-password -a "$(id -un)" -s flashcards-api-key -U -w` themselves (the final `-w` prompts for the key). Do not fall back to a `.env` file containing the key. On other platforms, use the configured secret store or securely injected environment variable; do not run macOS commands there.

## Content and study workflows

Start by listing courses, then sets in the relevant course. Read a set and its cards before changing an outline. Structural card writes require the set's `outline_version` as `expectedVersion`; on `409`, reload the set and cards, reconcile the user's intended change, and retry once. A card's `parent_id` and zero-based `order_index` define its outline location.

Card `title` supports inline Markdown; `content_md` supports Markdown. To edit a card's title or body, use `PATCH /api/cards/{cardId}` with `title`, `content`, and the card's current `contentVersion` as `expectedVersion`. Every edit increments `contentVersion`. On `409`, another edit happened first: reload the card, reapply the change, and retry. `expectedVersion` is optional, but without it the edit overwrites concurrent changes. Cards expose ordered `questions` and a separate `questionsVersion`. Each question has an `id` (unique within the card), a Markdown `prompt`, 2–8 ordered `choices` (`text` supports inline Markdown and optional `helpText` supports Markdown), and one zero-based `correctIndex`. Wrap code fragments, identifiers, and keywords in backticks in these fields.

A card allows up to 30 questions and 100 KB of question data. Use `GET /api/cards/{cardId}/questions` before editing and `PUT /api/cards/{cardId}/questions` with the complete new list and `expectedVersion` from that GET. A successful PUT increments the questions version. Send each existing question's `id` back unchanged, including when you reorder or edit it, so references to that question stay valid. Omit `id` for new questions; the server generates one. A changed or dropped `id` makes the question count as new. On `409`, reload and reconcile the whole list before retrying; this version is independent of the set outline version. During a learning session, `GET /api/sessions/{sessionId}` hides the correct choice and help text for unanswered questions. Answer the next question on the first unanswered card with `POST /api/sessions/{sessionId}/quiz-answer` using `position`, `questionId` (the question `id` from the session response), and `selectedIndex`. `questionIndex` still works but is deprecated; send exactly one of the two. The response includes feedback. Use the existing `/answer` route only for cards without questions. `DELETE /api/sessions/{sessionId}` ends an unfinished run and discards its progress, so a new run can start for the set; completed runs cannot be deleted this way.

Deleting a course removes its sets, cards, and learning history. Deleting a set removes its cards and learning history. Only perform destructive changes when the user's request calls for them. If an operation fails, report the API error without exposing the key.

## Import and export

Export a set with `GET /api/sets/{setId}/export`. Import with `POST /api/courses/{courseId}/sets/import`, sending the exported JSON document as the request body. The portable format is `learn-card-set` version `1`; nested `children` arrays define card hierarchy and sibling order. Import always creates a new set with fresh card and question IDs and no learning history; it does not merge into an existing set. Export excludes IDs, edit versions, and learning history. Both operations are limited to 10 MiB and 1,000 total cards. Fetch the live OpenAPI contract for the complete file schema and validation rules.
