|
| 1 | +# Anki Python API Reference |
| 2 | + |
| 3 | +## Database schema (SQLite — access via `col.db`) |
| 4 | + |
| 5 | +### `cards` table |
| 6 | + |
| 7 | +| Column | Meaning | |
| 8 | +| ------ | ------------------------------------------ | |
| 9 | +| id | Card ID (epoch ms timestamp) | |
| 10 | +| nid | Note ID (foreign key) | |
| 11 | +| did | Deck ID | |
| 12 | +| ord | Card ordinal (template index) | |
| 13 | +| type | 0=new, 1=learning, 2=review, 3=relearning | |
| 14 | +| queue | See "Card states" below | |
| 15 | +| due | Due value (queue-dependent interpretation) | |
| 16 | +| ivl | Interval in days (review cards) | |
| 17 | +| factor | Ease factor × 1000 (e.g. 2500 = 250%) | |
| 18 | +| reps | Number of reviews | |
| 19 | +| lapses | Number of lapses | |
| 20 | + |
| 21 | +### `notes` table |
| 22 | + |
| 23 | +| Column | Meaning | |
| 24 | +| ------ | ------------------------------------------------ | |
| 25 | +| id | Note ID | |
| 26 | +| mid | Model (note type) ID | |
| 27 | +| flds | Field values separated by `\x1f` | |
| 28 | +| tags | Space-separated tags with leading/trailing space | |
| 29 | + |
| 30 | +### Card states (`cards.queue`) |
| 31 | + |
| 32 | +| Value | State | |
| 33 | +| ----- | ----------------------------------------- | |
| 34 | +| -3 | Buried manually (pre-scheduler) | |
| 35 | +| -2 | Suspended | |
| 36 | +| -1 | Buried by sibling | |
| 37 | +| 0 | New | |
| 38 | +| 1 | Learning | |
| 39 | +| 2 | Review | |
| 40 | +| 3 | Day-learn (learning with day-based steps) | |
| 41 | +| 4 | Preview | |
| 42 | + |
| 43 | +## Search syntax (pass to `col.find_notes()` or `col.find_cards()`) |
| 44 | + |
| 45 | +```python |
| 46 | +col.find_notes("deck:English") # cards in a deck |
| 47 | +col.find_notes("deck:English is:new") # new cards |
| 48 | +col.find_notes("deck:English is:due") # due cards |
| 49 | +col.find_notes("tag:vocabulary") # by tag |
| 50 | +col.find_notes('"some text"') # full-text search |
| 51 | +col.find_notes("added:7") # added in last 7 days |
| 52 | +col.find_notes("rated:1:hard") # rated Hard in last 1 day |
| 53 | +col.find_notes("prop:ivl>=21") # interval ≥ 21 days |
| 54 | +col.find_notes("prop:ease<2") # ease < 200% |
| 55 | +col.find_notes("-is:suspended -is:buried") # exclude suspended/buried |
| 56 | +``` |
| 57 | + |
| 58 | +Combine freely: `"deck:English is:review -is:due"` |
| 59 | + |
| 60 | +## Practical query recipes |
| 61 | + |
| 62 | +### Cards due today |
| 63 | + |
| 64 | +```python |
| 65 | +due = col.db.scalar( |
| 66 | + "SELECT count() FROM cards WHERE due <= ? AND queue > 0", |
| 67 | + col.sched.today |
| 68 | +) |
| 69 | +``` |
| 70 | + |
| 71 | +### Card state breakdown |
| 72 | + |
| 73 | +```python |
| 74 | +from collections import Counter |
| 75 | +states = Counter() |
| 76 | +for c in col.find_cards(""): |
| 77 | + card = col.get_card(c) |
| 78 | + # card.type: 0=new, 1=learning, 2=review, 3=relearning |
| 79 | + # card.queue: see table above |
| 80 | +``` |
| 81 | + |
| 82 | +### Ease & interval stats |
| 83 | + |
| 84 | +```python |
| 85 | +avg_ease = col.db.scalar("SELECT avg(factor)/10.0 FROM cards WHERE factor > 0") |
| 86 | +avg_ivl = col.db.scalar("SELECT avg(ivl) FROM cards WHERE ivl > 0 AND type = 2") |
| 87 | +max_ivl = col.db.scalar("SELECT max(ivl) FROM cards") |
| 88 | +``` |
| 89 | + |
| 90 | +### Cards reviewed today |
| 91 | + |
| 92 | +```python |
| 93 | +import time |
| 94 | +day_start_ms = int(time.time() - time.time() % 86400) * 1000 |
| 95 | +reviews_today = col.db.scalar( |
| 96 | + "SELECT count() FROM revlog WHERE id > ?", day_start_ms |
| 97 | +) |
| 98 | +``` |
| 99 | + |
| 100 | +### Cards added recently |
| 101 | + |
| 102 | +```python |
| 103 | +import time |
| 104 | +thirty_days_ago_ms = (int(time.time()) - 30 * 86400) * 1000 |
| 105 | +recent = col.db.scalar("SELECT count() FROM cards WHERE id > ?", thirty_days_ago_ms) |
| 106 | +``` |
| 107 | + |
| 108 | +### Iterate notes with fields |
| 109 | + |
| 110 | +```python |
| 111 | +for nid in col.find_notes("deck:English"): |
| 112 | + note = col.get_note(nid) |
| 113 | + fields = note.fields # list of strings |
| 114 | + tags = note.tags # list of strings |
| 115 | + ntype = note.note_type()["name"] |
| 116 | + print(f"[{ntype}] {fields}") |
| 117 | +``` |
| 118 | + |
| 119 | +## Writing (requires Anki to be closed) |
| 120 | + |
| 121 | +```python |
| 122 | +col = Collection(path) # Anki must NOT be running |
| 123 | + |
| 124 | +# Add a basic note |
| 125 | +note = col.new_note(col.models.by_name("Basic")) |
| 126 | +note.fields[0] = "What is X?" # Front |
| 127 | +note.fields[1] = "Y" # Back |
| 128 | +note.tags = ["vocabulary"] |
| 129 | +col.add_note(note, deck_id=col.decks.id("English")) |
| 130 | + |
| 131 | +# Add a cloze note |
| 132 | +note = col.new_note(col.models.by_name("Cloze")) |
| 133 | +note.fields[0] = "She {{c1::went}} to the store." |
| 134 | +col.add_note(note, deck_id=col.decks.id("English")) |
| 135 | + |
| 136 | +col.close() |
| 137 | +``` |
| 138 | + |
| 139 | +## Important notes |
| 140 | + |
| 141 | +- `col.close()` must be called when done (or use `try/finally`). |
| 142 | +- Card/note IDs are epoch-millisecond timestamps (e.g. `1700000000000`). |
| 143 | +- The `anki` package bundles a Rust backend (`anki._backend`) — no extra install needed. |
| 144 | +- Deck IDs are integers; use `col.decks.id("DeckName")` to resolve by name. |
| 145 | +- For read-only inspection while Anki runs, always copy the `.anki2` file first. |
0 commit comments