Repository navigation
ANKIDROID
Source:
ankidroid/Anki-Android(api/module), the AnkiDroid API wiki page and the JavaScript API wiki page, plus theankidroid/apisampledemo app. AnkiDroid is the official Anki client for Android; these APIs are official for the Android app, but they are Android-IPC surfaces — nothing here talks to ankiweb.net. For the network APIs see SYNC.md and ENDPOINTS.md.
Jump to: Surfaces · Setup · Methods · Provider URIs · Columns · Intents · Hidden surfaces · JS API · Testing
AnkiDroid exposes three independent APIs:
| Surface | Audience | Transport | Use for |
|---|---|---|---|
Instant-Add / ContentProvider (api/) |
other Android apps | content://com.ichi2.anki.flashcards |
Reading/writing notes, cards, decks, models, media |
| Intents | any Android app | Android intents | Fallback sharing + triggering a sync |
JavaScript API (AnkiDroidJS) |
deck/template authors | WebView bridge inside the reviewer | Custom card UIs calling native functions |
A fourth path — bulk/low-level access — is just the raw ContentProvider with your own queries; the Instant-Add library is the supported wrapper over it.
Gradle (JitPack), from the wiki:
// root build.gradle
repositories { maven { url "https://jitpack.io" } }
// module build.gradle
dependencies {
implementation 'com.github.ankidroid:Anki-Android:api-v1.1.0'
}Required manifest entry on Android 11+ (SDK 30), otherwise you get
Failed to find provider info for com.ichi2.anki.flashcards:
<manifest>
...
<queries>
<package android:name="com.ichi2.anki" />
</queries>
</manifest>Permissions and switches:
| Item | Value |
|---|---|
| Permission (merged into your manifest by the library) | com.ichi2.anki.permission.READ_WRITE_DATABASE |
| Runtime permission | Enforced dynamically on Android M+; deny ⇒ fall back to the ACTION_SEND intent |
| User toggle | AnkiDroid → Settings → Advanced → Enable AnkiDroid API (off ⇒ provider unavailable) |
| Availability check |
AddContentApi.getAnkiDroidPackageName(context) → package name or null
|
| Provider spec version |
api.apiHostSpecVersion → 1 (AnkiDroid 2.5: slow bulk ops, model-persist quirks) or 2 (2.6+); -1 if not installed |
| Constants |
AddContentApi.DEFAULT_DECK_ID = 1, AddContentApi.READ_WRITE_PERMISSION
|
Minimal add (from the wiki; addNotes() is orders of magnitude faster than
looping addNote()):
if (AddContentApi.getAnkiDroidPackageName(context) != null) {
AddContentApi api = new AddContentApi(context);
long deckId = api.addNewDeck("My app name");
long modelId = api.addNewBasicModel("com.something.myapp");
api.addNote(modelId, deckId, new String[] {"日の出", "sunrise"}, null);
} else {
// fallback: ACTION_SEND share intent (see Intent API)
}From api/src/main/java/com/ichi2/anki/api/AddContentApi.kt:
| Method | Returns | Purpose |
|---|---|---|
addNote(modelId, deckId, fields, tags) |
Long? |
Insert one note; cards land in deckId; no duplicate check |
addNotes(modelId, deckId, fieldsList, tagsList) |
Int |
Bulk insert (bulkInsert + ?deckId= query param); returns count added |
addMediaFromUri(fileUri, preferredName, mimeType) |
String? |
Copy a file into collection.media; returns stored file name |
findDuplicateNotes(mid, key) |
List<NoteInfo?> |
Duplicate lookup for one first-field key |
findDuplicateNotes(mid, keys) |
SparseArray<…>? |
Duplicate lookup for many keys (fast path on spec ≥ 2) |
getNoteCount(mid) |
Int |
Note count for a model |
updateNoteTags(noteId, tags) |
Boolean |
Replace a note's tags; throws SecurityException if permission missing |
updateNoteFields(noteId, fields) |
Boolean |
Replace a note's fields |
getNote(noteId) |
NoteInfo? |
Read id/fields/tags of one note |
previewNewNote(mid, flds) |
Map<String, Map<String, String>>? |
Render a would-be note (requires full read-write permission) |
addNewBasicModel(name) |
Long? |
Create the standard two-field note type |
addNewBasic2Model(name) |
Long? |
Create the "Basic (and reversed card)" note type |
addNewCustomModel(name, fields, cards, qfmt, afmt, css, did, sortf, …) |
Long? |
Create a fully custom note type |
getFieldList(modelId) |
Array<String>? |
Field names of a model |
getModelList(minNumFields) |
Map<Long, String>? |
Model id → name (filtered by field count) |
getModelName(mid) |
String? |
Model name lookup |
addNewDeck(deckName) |
Long? |
Create a deck; returns its id |
setFlagRaw(noteId, rawFlag) |
Boolean |
Set flag by raw int on all of the note's cards |
setFlag(noteId, flag) |
Boolean |
Same, using the Flag enum |
selectedDeckName (property)
|
String? |
Name of the currently selected deck |
deckList (property)
|
Map<Long, String>? |
All deck id → name pairs |
getDeckName(did) |
String? |
Deck name by id |
apiHostSpecVersion (property)
|
Int |
Installed provider's spec version (1/2, -1 absent) |
getAnkiDroidPackageName(context) (static)
|
String? |
Package providing authority com.ichi2.anki.flashcards
|
Supporting types (same package):
-
NoteInfo—getId(),getFields(),getTags(),getKey()(first field). -
Flag—NONE(0) RED(1) ORANGE(2) GREEN(3) BLUE(4) PINK(5) TURQUOISE(6) PURPLE(7). -
Ease—EASE_1(1) … EASE_4(4)(Again/Hard/Good/Easy).
Authority: com.ichi2.anki.flashcards (constant FlashCardsContract.AUTHORITY),
base content://com.ichi2.anki.flashcards. The contract lives in
FlashCardsContract.kt.
| URI (path under the authority) | Query | Insert | Update | Delete | Notes |
|---|---|---|---|---|---|
/notes |
✅ | ✅ | — | — | Selection is libanki browser search syntax; insert needs mid, flds, tags; bulk insert honours ?deckId=
|
/notes_v2 |
✅ | — | — | — | Same rows, selection is a direct SQL WHERE |
/notes/<id> |
✅ | — | ✅ | ✅ | Update: flds, tags. Delete removes the note |
/notes/<id>/cards |
✅ | — | ✗ | — | All cards of the note (deck, question, answer) |
/notes/<id>/cards/<ord> |
✅ | — | ✅ | — | Update: deck_id (move card), flags
|
/models |
✅ | ✅ | ✗ | — | Note types; bulk update rejected |
/models/<id> (or current) |
✅ | — | ✅ | — | Update: deck_id (default deck for the type) |
/models/<id>/empty_cards |
— | — | — | ✅ | Registered + getType, but query throws; delete purges empty cards |
/models/<id>/templates |
✅ | ✅ | ✗ | — | Read/add card templates (see CardTemplate below) |
/models/<id>/templates/<ord> |
✅ | — | ✅ | — | Edit one template's qfmt/afmt |
/models/<id>/fields |
— | ✅ | — | — | Insert-only — query is not implemented and throws |
/cards |
✅ | — | — | — | All cards with scheduling columns (Card.DEFAULT_COLUMNS) |
/cards/<id> |
✅ | — | — | — | Single card, read-only |
/schedule |
✅ | — | ✅ | — | ReviewInfo: query next due cards (limit=?, deckID=?), update = answer a card |
/decks |
✅ | ✅ | ✗ | — | All decks; insert = create (deck_name) |
/decks/<id> |
✅ | — | ✗* | — | *Update is stubbed “Not yet implemented” |
/selected_deck |
✅ | — | ✅ | — | Update: deck_id selects the active deck |
/media |
— | ✅ | — | — | Copy a file into collection.media (file_uri, preferred_name) |
— = no branch in the provider (throws IllegalArgumentException /
UnsupportedOperationException); ✗ = explicitly rejected with a message.
The full matcher list is the addUri(...) block in
CardContentProvider.kt
(lines 186–207) — 18 paths, several of which the wiki never mentions.
There is no ContentProvider.call() RPC (no call() override exists), so
this URI surface is the entire provider API.
MIME types follow vnd.android.cursor.(dir|item)/vnd.com.ichi2.anki.(note|card|model).
| Column | Type | Notes |
|---|---|---|
_id |
long | Note id (same as Anki's note id) |
guid |
string | Stable unique id |
mid |
long | Model (note type) id |
flds |
string | Fields joined with \x1f (Utils.joinFields) |
tags |
string | Space-separated tag list |
sfld |
string | Sort field |
csum |
long | Checksum of the first field (duplicate detection) |
flags, data, mod, usn, allow_empty
|
— | Card flags, aux data, modification metadata |
Insert with ContentValues(mid, flds, tags); bulk insert honours the deckId
query parameter (Note.DECK_ID_QUERY_PARAM).
| Column | Notes |
|---|---|
_id, note_id, ord, card_name, deck_id
|
Identity + template ordinal |
question, answer, question_simple, answer_simple, answer_pure
|
Rendered content (read-only views) |
reps, lapses, type, original_deck_id
|
Counters and origin |
queue, due, original_due, interval, sm2_factor, left, original_position, custom_data
|
Raw scheduling |
fsrs_stability, fsrs_difficulty, fsrs_desired_retention, fsrs_decay, last_review_time_secs
|
FSRS scheduler state |
flags |
Flag colour (see Flag enum) |
Default projection: _id, note_id, ord, card_name, deck_id, question, answer, flags.
_id, name, field_names, num_cards, note_count, css,
sort_field_index, type, latex_pre, latex_post, deck_id.
CURRENT_MODEL_ID = "current" addresses the current model.
_id, model_id, ord, card_template_name (NAME),
question_format (QFMT), answer_format (AFMT),
browser_question_format, browser_answer_format, card_count.
Queryable per template via /models/<id>/templates/<ord>; insertable in bulk
via /models/<id>/templates.
deck_name, deck_id, deck_count, options, deck_dyn, deck_desc.
Read: note_id, ord, button_count, next_review_times, media_files.
Write (answer a card): answer_ease (1–4, see Ease), time_taken,
bury, suspended. Query selection: limit=?, deckID=?.
file_uri (source Uri of the file), preferred_name (name in
collection.media); insert returns the stored file's Uri.
Fallback / automation paths that need no permission and work on every AnkiDroid version (from the API wiki):
| Intent | Details |
|---|---|
ACTION_SEND (recommended fallback) |
Type text/plain; subject = front, text = back. Build with ShareCompat.IntentBuilder so your app name is carried along. One card per send; the user completes the add in AnkiDroid. |
org.openintents.action.CREATE_FLASHCARD |
Deprecated since AnkiDroid 2.5. Extras SOURCE_TEXT (front) and TARGET_TEXT (back). Kept for backwards compatibility only. |
com.ichi2.anki.DO_SYNC |
Triggers a background sync (target type Activity). Throttled: a "server is busy" error appears if fired more often than once per 5 minutes. Works from Tasker/Automate; from a shell: am start -a 'com.ichi2.anki.DO_SYNC'. |
Share example:
Intent shareIntent = ShareCompat.IntentBuilder.from(context)
.setType("text/plain")
.setSubject("日の出") // front
.setText("sunrise") // back
.getIntent();
if (shareIntent.resolveActivity(context.getPackageManager()) != null) {
context.startActivity(shareIntent);
}Not mentioned on the API wiki — recovered from AndroidManifest.xml,
IntentHandler.kt and CardContentProvider.kt on main:
-
Card Browser deep link — an exported activity-alias handles the x-callback-url scheme, including an optional search query:
am start -a android.intent.action.VIEW \ -d 'anki://x-callback-url/browser?search=deck%3Adefault'(
anki://x-callback-url/browserwith nosearchjust opens the browser.) -
Direct-to-reviewer intent — any intent addressed to the exported
com.ichi2.anki.IntentHandlerthat carries a long extraEXTRA_DECK_IDselects that deck and jumps straight into the reviewer:am start -n com.ichi2.anki/com.ichi2.anki.IntentHandler --el EXTRA_DECK_ID 1
Upstream's own helper
IntentHandler.getReviewDeckIntent(context, deckId)builds exactly this intent (used by widgets) — but because the activity is exported, any app can start studying a deck by id. -
com.ichi2.anki.COPY_DEBUG_INFO— action handled byIntentHandlerwith a string extraclip_data: AnkiDroid copies the supplied text to the clipboard and finishes. It has no intent-filter in the manifest, so it only works as an explicit-component intent. -
The DO_SYNC throttle is 2 minutes, not 5. The wiki says "once every 5 minutes", but current source defines
INTENT_SYNC_MIN_INTERVAL = 2 * 60000(2 min) inIntentHandler.ktand shows a "try again in N s" style notice inside that window. -
No hidden provider RPC.
CardContentProviderdoes not overrideContentProvider.call(), so the 18 URIs above (query/insert/update/delete) are the entire provider surface; the provider spec version is advertised via thecom.ichi2.anki.provider.specmanifest meta-data. -
Internal-only broadcasts.
com.ichi2.widget.UPDATE_WIDGETandcom.ichi2.widget.ACTION_ROLLOVERexist in the manifest but their receivers areexported="false"— not callable by third-party apps. -
Richer implicit-intent matrix than the wiki's ACTION_SEND example. The launcher activity also accepts
ACTION_VIEW/ACTION_SENDforapplication/apkg,application/colpkg,application/vnd.anki,application/x-apkg,x-colpkg, CSV/TSV (and extension-less variants Gmail hides), plain text (shared-text → add-note flow), anyimage/*(image-occlusion import), andACTION_PROCESS_TEXTfrom Android's text-selection menu.
For deck/template authors: card templates can call native AnkiDroid functions
inside the reviewer's WebView via the injected AnkiDroidJS bridge.
<script>
// api version 0.0.3 with AnkiDroid 2.18 (0.0.2 with 2.17)
var jsApiContract = { version: "0.0.3", developer: /* your email */ };
var api = new AnkiDroidJS(jsApiContract);
api.ankiGetCardDid().then(r => console.log(r)); // {success: true, value: '1707102058557'}
</script>- Every call returns
Promise<{ success: boolean, value: any }>. - On failure:
success: falseplus a type-safe default invalue—false,-1,"unknown",[], or{}. - Failures are normal in the Previewer (e.g.
ankiGetETA()is unavailable there). - A
developercontact must be provided; it is shown to users on breaking changes.
Enumerate at runtime with
console.log(Object.keys(jsApiList).sort().join("\r\n")).
| Group | Methods |
|---|---|
| Answering |
ankiAnswerEase1..4, ankiShowAnswer, ankiIsDisplayingAnswer
|
| Bury / suspend |
ankiBuryCard, ankiBuryNote, ankiSuspendCard, ankiSuspendNote
|
| Mark / flag / tags |
ankiMarkCard, ankiToggleFlag (none/red/orange/green/blue), ankiAddTagToCard, ankiSetNoteTags(tags[])
|
| Scheduling |
ankiSetCardDue(days), ankiResetProgress, ankiSearchCard, ankiSearchCardWithCallback
|
| Current card info |
ankiGetCardId, ankiGetCardNid, ankiGetCardDid, ankiGetCardDue, ankiGetCardMod, ankiGetCardFactor, ankiGetCardFlag, ankiGetCardInterval, ankiGetCardLapses, ankiGetCardLeft, ankiGetCardMark, ankiGetCardODid, ankiGetCardODue, ankiGetCardQueue, ankiGetCardReps, ankiGetCardType
|
| Deck / counts |
ankiGetDeckName, ankiGetNewCardCount, ankiGetLrnCardCount, ankiGetRevCardCount, ankiGetETA, ankiGetNextTime1..4
|
| UI state |
ankiIsInFullscreen, ankiIsTopbarShown, ankiIsInNightMode, ankiIsActiveNetworkMetered, ankiEnableHorizontalScrollbar, ankiEnableVerticalScrollbar
|
| UI actions |
ankiShowToast(msg), ankiShowOptionsMenu, ankiShowNavigationDrawer
|
| TTS / STT |
ankiTtsSpeak, ankiTtsStop, ankiTtsSetLanguage, ankiTtsSetPitch, ankiTtsSetSpeechRate, ankiTtsIsSpeaking, ankiTtsFieldModifierIsAvailable, ankiSttStart, ankiSttStop, ankiSttSetLanguage
|
Legacy global functions (no api. prefix) also work in templates:
showAnswer(), buttonAnswerEase1()…buttonAnswerEase4() (Again/Hard/Good/Easy),
ankiShowToast(msg), ankiShowOptionsMenu(), ankiShowNavDrawer().
A desktop-Anki port of most of this surface exists as the third-party AnkiJS-API add-on (not part of either org).
-
Sample app:
ankidroid/apisample— a Japanese→English dictionary prototype covering permission checks, duplicate checks, deck/model id persistence, tags and custom models. ItsAnkiDroid_Sync.prf.xmlis a ready-made Tasker profile forDO_SYNC. - Manual test checklist (wiki): verify adds land in a deck named after your app; toggle Enable AnkiDroid API off and confirm the intent fallback runs; on Android 6+ deny the runtime permission and confirm the fallback, then grant and confirm direct adds; uninstall AnkiDroid and confirm your app hides the option.
- JS API test deck: ankiweb.net/shared/info/1045118423.
- Low-level provider implementation:
AnkiDroid/src/main/java/com/ichi2/anki/provider.
Related pages: SYNC.md — how AnkiDroid syncs over HTTP ·
ENDPOINTS.md — the AnkiWeb /svc/* API ·
ECOSYSTEM.md — map of every Anki API surface.