Skip to the page

Guides

Decks and cards

A deck holds cards. A card holds one term and everything you know about it. Scheduling is per direction, so the same card can be easy to recognise and hard to produce.

Decks

A deck is a name and two defaults. defaultLanguage prefills the language on every card added to it, and directions decides which way its cards are asked. Only the name is required.

POST /api/decks
{
  "name": "Italian",
  "defaultLanguage": "it",
  "directions": "both"
}

Listing decks gives you the counts too, so a dashboard needs one call: total is the active cards in the deck and due is how many card states are ready to review now.

Cards

Only deckId and term are required. Send what you have; the rest can be filled later, in the app or by a second call.

FieldTypeNotes
deckIdstringRequired. Which deck the card lands in.
termstringRequired. What the card asks about, as you met it.
meaningstringWhat it means, in your own language.
pronunciationstringIPA or a plain respelling.
examplestringOne sentence using the term.
notesstringAnything else worth keeping. Markdown: **bold**, *italic*, and lists starting with - or 1. Anything else, HTML included, shows as typed.
languagestring | nullBCP 47 tag. Defaults to the deck's.
tagsstring[]Up to 20. Free text.
sourcestringWhere it came from: a lesson, a book, a film.
directions"recognition" | "production" | "both" | nullOverrides the deck for this card.
meaningSource"lesson" | "ai" | "manual"Who wrote the meaning. Set ai when a model did.
exampleSource"lesson" | "ai" | "manual"Who wrote the example.
pronunciationSource"lesson" | "ai" | "manual"Who wrote the pronunciation.
enrichmentStatus"working" | "failed" | nullRead-only. Set while Lymi is filling the card's empty fields, and null once it settles.

Lymi fills what you leave empty

A card added with fields missing enriches itself in the background: meaning, example, pronunciation and language, written in the learner's meaning language. It fills only what is empty, so any text you send is kept exactly as you sent it, whatever its source. Each filled field is recorded as "ai" and carries the badge.

While a job is outstanding the card reads enrichmentStatus: "working", and "failed" if it gives up. Poll the card to watch it settle to null. The add itself always succeeds; enrichment never holds it up or fails it.

The same term twice is not an error

Adding a term you already have is skipped and reported, never rejected. You get 200 instead of 201, with the card that already holds the term and the deck it lives in. This makes a script safe to re-run.

Response · 200
{
  "status": "skipped",
  "term": "la nebbia",
  "existing": { "id": "0mtoyiymrvqpdz02hlv", "term": "la nebbia" },
  "deckName": "Italian"
}

What counts as a duplicate

Two cards collide when their normalised term and language both match, anywhere in your decks, not only in the one you are adding to. Normalising trims the term, collapses runs of whitespace, folds case and applies Unicode NFC.

  • Accents are kept. In Italian pesca (peach) and pèsca (fishing) are two words, and Lymi treats them as two.
  • Articles are kept. la nebbia and nebbia are different terms.
  • A card with no language only collides with other cards that have no language.
  • Archived cards do not collide. Adding a term you archived makes a fresh card.

A batch checks itself as well: send the same term twice in one call and the second one is skipped against the first.

Directions decide how often you see a card

Each direction gets its own schedule, so one card can hold two of them.

recognition
You see the term and recall the meaning. The easier way round.
production
You see the meaning and produce the term. The harder way round.
both
Two schedules for one card, asked independently.

A card without directions follows its deck. Setting the field on a card overrides the deck for that card alone. A new deck is recognition unless you say otherwise, so pass directions when you create one if you want both ways round.

Reviews are scheduled with FSRS

Lymi uses FSRS with one learning step of ten minutes. A new card state starts at 0 and moves through Learning, Review and Relearning as you grade it. How reviews are scheduled explains the intervals and the order cards come up in.

stateMeans
0New. Never reviewed.
1Learning. Inside its first step.
2Review. Graduated, on a real interval.
3Relearning. Forgotten and going through the step again.

Reading the queue is open to any key. GET /api/review/queue returns today’s cards in the order a review would take them if every grade succeeded, and each item carries next: the four dates each grade would schedule. A client can show “Good · 6 d” with no extra round trip. Add round=forgotten, round=new or round=slipping for one of Today’s rounds; GET /api/review/rounds counts each.

Nothing is deleted

Archiving hides a card or a deck; restoring brings it back with its schedule untouched. Archiving a deck leaves its cards alone: they stop appearing with the deck, and they come back when you restore it.

Terminal
curl -X POST "$LYMI_URL/api/cards/0mtoyiymrvqpdz02hlv/archive" -H "x-api-key: $LYMI_KEY"
curl -X POST "$LYMI_URL/api/cards/0mtoyiymrvqpdz02hlv/restore" -H "x-api-key: $LYMI_KEY"