# AnkiDroid integration APIs — ContentProvider, intents, JS bridge
> **Source:** [`ankidroid/Anki-Android`](https://github.com/ankidroid/Anki-Android)
> (`api/` module), the
> [AnkiDroid API wiki page](https://github.com/ankidroid/Anki-Android/wiki/AnkiDroid-API)
> and the [JavaScript API wiki page](https://github.com/ankidroid/Anki-Android/wiki/AnkiDroid-JavaScript-API),
> plus the [`ankidroid/apisample`](https://github.com/ankidroid/apisample) demo 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](SYNC.md) and
> [ENDPOINTS.md](ENDPOINTS.md).
**Jump to:** [Surfaces](#api-surfaces) · [Setup](#client-setup-instant-add) ·
[Methods](#addcontentapi-methods) · [Provider URIs](#contentprovider-uri-map) ·
[Columns](#column-reference) · [Intents](#intent-api) ·
[Hidden surfaces](#hidden-surfaces-found-in-source) ·
[JS API](#javascript-api) · [Testing](#testing-and-samples)
## API surfaces
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.
## Client setup (Instant-Add)
Gradle (JitPack), from the wiki:
```gradle
// 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`:
```xml
...
```
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()`):
```java
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)
}
```
## AddContentApi methods
From [`api/src/main/java/com/ichi2/anki/api/AddContentApi.kt`](https://github.com/ankidroid/Anki-Android/blob/main/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` | 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>?` | 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?` | Field names of a model |
| `getModelList(minNumFields)` | `Map?` | 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?` | 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).
## ContentProvider URI map
Authority: **`com.ichi2.anki.flashcards`** (constant `FlashCardsContract.AUTHORITY`),
base `content://com.ichi2.anki.flashcards`. The contract lives in
[`FlashCardsContract.kt`](https://github.com/ankidroid/Anki-Android/blob/main/api/src/main/java/com/ichi2/anki/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/` | ✅ | — | ✅ | ✅ | Update: `flds`, `tags`. Delete removes the note |
| `/notes//cards` | ✅ | — | ✗ | — | All cards of the note (deck, question, answer) |
| `/notes//cards/` | ✅ | — | ✅ | — | Update: `deck_id` (move card), `flags` |
| `/models` | ✅ | ✅ | ✗ | — | Note types; bulk update rejected |
| `/models/` (or `current`) | ✅ | — | ✅ | — | Update: `deck_id` (default deck for the type) |
| `/models//empty_cards` | — | — | — | ✅ | Registered + `getType`, but **query throws**; delete purges empty cards |
| `/models//templates` | ✅ | ✅ | ✗ | — | Read/add card templates (see CardTemplate below) |
| `/models//templates/` | ✅ | — | ✅ | — | Edit one template's qfmt/afmt |
| `/models//fields` | — | ✅ | — | — | **Insert-only** — query is not implemented and throws |
| `/cards` | ✅ | — | — | — | All cards with scheduling columns (`Card.DEFAULT_COLUMNS`) |
| `/cards/` | ✅ | — | — | — | Single card, read-only |
| `/schedule` | ✅ | — | ✅ | — | ReviewInfo: query next due cards (`limit=?, deckID=?`), update = answer a card |
| `/decks` | ✅ | ✅ | ✗ | — | All decks; insert = create (`deck_name`) |
| `/decks/` | ✅ | — | ✗\* | — | \*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`](https://github.com/ankidroid/Anki-Android/blob/main/AnkiDroid/src/main/java/com/ichi2/anki/provider/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 reference
### Note (`/notes`)
| 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`).
### Card (`/cards`)
| 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`.
### Model (`/models`)
`_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.
### CardTemplate (`/models//templates`)
`_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//templates/`; insertable in bulk
via `/models//templates`.
### Deck (`/decks`, `/selected_deck`)
`deck_name`, `deck_id`, `deck_count`, `options`, `deck_dyn`, `deck_desc`.
### ReviewInfo (`/schedule`)
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=?`.
### Media (`/media`)
`file_uri` (source `Uri` of the file), `preferred_name` (name in
`collection.media`); insert returns the stored file's Uri.
## Intent API
Fallback / automation paths that need no permission and work on every
AnkiDroid version (from the [API wiki](https://github.com/ankidroid/Anki-Android/wiki/AnkiDroid-API)):
| 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:
```java
Intent shareIntent = ShareCompat.IntentBuilder.from(context)
.setType("text/plain")
.setSubject("日の出") // front
.setText("sunrise") // back
.getIntent();
if (shareIntent.resolveActivity(context.getPackageManager()) != null) {
context.startActivity(shareIntent);
}
```
## Hidden surfaces (found in source)
Not mentioned on the API wiki — recovered from `AndroidManifest.xml`,
`IntentHandler.kt` and `CardContentProvider.kt` on `main`:
1. **Card Browser deep link** — an exported activity-alias handles the
x-callback-url scheme, including an optional search query:
```sh
am start -a android.intent.action.VIEW \
-d 'anki://x-callback-url/browser?search=deck%3Adefault'
```
(`anki://x-callback-url/browser` with no `search` just opens the browser.)
2. **Direct-to-reviewer intent** — any intent addressed to the exported
`com.ichi2.anki.IntentHandler` that carries a **long extra
`EXTRA_DECK_ID`** selects that deck and jumps straight into the reviewer:
```sh
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.
3. **`com.ichi2.anki.COPY_DEBUG_INFO`** — action handled by `IntentHandler`
with a string extra **`clip_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.
4. **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) in `IntentHandler.kt` and
shows a "try again in N s" style notice inside that window.
5. **No hidden provider RPC.** `CardContentProvider` does **not** override
`ContentProvider.call()`, so the 18 URIs above (query/insert/update/delete)
are the entire provider surface; the provider spec version is advertised via
the `com.ichi2.anki.provider.spec` manifest meta-data.
6. **Internal-only broadcasts.** `com.ichi2.widget.UPDATE_WIDGET` and
`com.ichi2.widget.ACTION_ROLLOVER` exist in the manifest but their
receivers are `exported="false"` — not callable by third-party apps.
7. **Richer implicit-intent matrix than the wiki's ACTION_SEND example.**
The launcher activity also accepts `ACTION_VIEW`/`ACTION_SEND` for
`application/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), any `image/*`
(image-occlusion import), and `ACTION_PROCESS_TEXT` from Android's
text-selection menu.
## JavaScript API
For deck/template authors: card templates can call native AnkiDroid functions
inside the reviewer's WebView via the injected `AnkiDroidJS` bridge.
### Setup and responses
```html
```
- Every call returns `Promise<{ success: boolean, value: any }>`.
- On failure: `success: false` plus a type-safe default in `value` —
`false`, `-1`, `"unknown"`, `[]`, or `{}`.
- Failures are normal in the Previewer (e.g. `ankiGetETA()` is unavailable there).
- A `developer` contact **must** be provided; it is shown to users on breaking
changes.
### Method list (AnkiDroid 2.18)
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](https://github.com/jhhr/AnkiJS-API) add-on (not part of either
org).
## Testing and samples
- **Sample app:** [`ankidroid/apisample`](https://github.com/ankidroid/apisample) —
a Japanese→English dictionary prototype covering permission checks, duplicate
checks, deck/model id persistence, tags and custom models. Its
`AnkiDroid_Sync.prf.xml` is a ready-made Tasker profile for `DO_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](https://ankiweb.net/shared/info/1045118423).
- Low-level provider implementation:
[`AnkiDroid/src/main/java/com/ichi2/anki/provider`](https://github.com/ankidroid/Anki-Android/tree/main/AnkiDroid/src/main/java/com/ichi2/anki/provider).
---
Related pages: [SYNC.md](SYNC.md) — how AnkiDroid syncs over HTTP ·
[ENDPOINTS.md](ENDPOINTS.md) — the AnkiWeb `/svc/*` API ·
[ECOSYSTEM.md](ECOSYSTEM.md) — map of every Anki API surface.