# AnkiConnect The AnkiConnect plugin enables external applications such as [Yomichan](https://foosoft.net/projects/yomichan/) to communicate with [Anki](https://apps.ankiweb.net/) over a network interface. This software makes it possible to execute queries against the user's card deck, automatically create new vocabulary and Kanji flash cards, and more. AnkiConnect is compatible with the latest stable (2.1.x) releases of Anki; older versions (2.0.x and below) are no longer supported. ## Table of Contents * [Installation](https://foosoft.net/projects/anki-connect/#installation) * [Notes for Windows Users](https://foosoft.net/projects/anki-connect/#notes-for-windows-users) * [Notes for Mac OS X Users](https://foosoft.net/projects/anki-connect/#notes-for-mac-os-x-users) * [Application Interface for Developers](https://foosoft.net/projects/anki-connect/#application-interface-for-developers) * [Sample Invocation](https://foosoft.net/projects/anki-connect/#sample-invocation) * [Supported Actions](https://foosoft.net/projects/anki-connect/#supported-actions) * [Miscellaneous](https://foosoft.net/projects/anki-connect/#miscellaneous) * [Decks](https://foosoft.net/projects/anki-connect/#decks) * [Models](https://foosoft.net/projects/anki-connect/#models) * [Notes](https://foosoft.net/projects/anki-connect/#notes) * [Cards](https://foosoft.net/projects/anki-connect/#cards) * [Media](https://foosoft.net/projects/anki-connect/#media) * [Graphical](https://foosoft.net/projects/anki-connect/#graphical) * [Export](https://foosoft.net/projects/anki-connect/#export) ## Installation The installation process is similar to that of other Anki plugins and can be accomplished in three steps: 1. Open the `Install Add-on` dialog by selecting `Tools` | `Add-ons` | `Browse & Install` in Anki. 2. Input [2055492159](https://ankiweb.net/shared/info/2055492159) into the text box labeled `Code` and press the `OK` button to proceed. 3. Restart Anki when prompted to do so in order to complete the installation of AnkiConnect. Anki must be kept running in the background in order for other applications to be able to use AnkiConnect. You can verify that AnkiConnect is running at any time by accessing [localhost:8765](http://localhost:8765) in your browser. If the server is running, you should see the message `AnkiConnect v.6` displayed in your browser window. ### Notes for Windows Users Windows users may see a firewall nag dialog box appear on Anki startup. This occurs because AnkiConnect hosts a local server in order to enable other applications to connect to it. The host application, Anki, must be unblocked for this plugin to function correctly. ### Notes for Mac OS X Users Starting with [Mac OS X Mavericks](https://en.wikipedia.org/wiki/OS_X_Mavericks), a feature named *App Nap* has been introduced to the operating system. This feature causes certain applications which are open (but not visible) to be placed in a suspended state. As this behavior causes AnkiConnect to stop working while you have another window in the foreground, App Nap should be disabled for Anki: 1. Start the Terminal application. 2. Execute the following commands in the terminal window: ``` defaults write net.ankiweb.dtop NSAppSleepDisabled -bool true defaults write net.ichi2.anki NSAppSleepDisabled -bool true defaults write org.qt-project.Qt.QtWebEngineCore NSAppSleepDisabled -bool true ``` 3. Restart Anki. ## Application Interface for Developers AnkiConnect exposes Anki features to external applications via an easy to use [RESTful](https://en.wikipedia.org/wiki/Representational_state_transfer) API. After it is installed, this plugin will initialize a minimal HTTP sever running on port 8765 every time Anki executes. Other applications (including browser extensions) can then communicate with it via HTTP POST requests. By default, AnkiConnect will only bind the HTTP server to the `127.0.0.1` IP address, so that you will only be able to access it from the same host on which it is running. If you need to access it over a network, you can set the environment variable `ANKICONNECT_BIND_ADDRESS` to change the binding address. For example, you can set it to `0.0.0.0` in order to bind it to all network interfaces on your host. ### Sample Invocation Every request consists of a JSON-encoded object containing an `action`, a `version`, contextual `params`, and a `key` value used for authentication (which is optional and can be omitted by default). AnkiConnect will respond with an object containing two fields: `result` and `error`. The `result` field contains the return value of the executed API, and the `error` field is a description of any exception thrown during API execution (the value `null` is used if execution completed successfully). *Sample successful response*: ```json {"result": ["Default", "Filtered Deck 1"], "error": null} ``` *Samples of failed responses*: ```json {"result": null, "error": "unsupported action"} ``` ```json {"result": null, "error": "guiBrowse() got an unexpected keyword argument 'foobar'"} ``` For compatibility with clients designed to work with older versions of AnkiConnect, failing to provide a `version` field in the request will make the version default to 4. Furthermore, when the provided version is level 4 or below, the API response will only contain the value of the `result`; no `error` field is available for error handling. You can use whatever language or tool you like to issue request to AnkiConnect, but a couple of simple examples are included below as reference. #### Curl ```bash curl localhost:8765 -X POST -d "{\"action\": \"deckNames\", \"version\": 6}" ``` #### Python ```python import json import urllib.request def request(action, **params): return {'action': action, 'params': params, 'version': 6} def invoke(action, **params): requestJson = json.dumps(request(action, **params)).encode('utf-8') response = json.load(urllib.request.urlopen(urllib.request.Request('http://localhost:8765', requestJson))) if len(response) != 2: raise Exception('response has an unexpected number of fields') if 'error' not in response: raise Exception('response is missing required error field') if 'result' not in response: raise Exception('response is missing required result field') if response['error'] is not None: raise Exception(response['error']) return response['result'] invoke('createDeck', deck='test1') result = invoke('deckNames') print('got list of decks: {}'.format(result)) ``` #### JavaScript ```javascript function invoke(action, version, params={}) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.addEventListener('error', () => reject('failed to issue request')); xhr.addEventListener('load', () => { try { const response = JSON.parse(xhr.responseText); if (Object.getOwnPropertyNames(response).length != 2) { throw 'response has an unexpected number of fields'; } if (!response.hasOwnProperty('error')) { throw 'response is missing required error field'; } if (!response.hasOwnProperty('result')) { throw 'response is missing required result field'; } if (response.error) { throw response.error; } resolve(response.result); } catch (e) { reject(e); } }); xhr.open('POST', 'http://127.0.0.1:8765'); xhr.send(JSON.stringify({action, version, params})); }); } await invoke('createDeck', {deck: 'test1'}); const result = await invoke('deckNames', 6); console.log(`got list of decks: ${result}`); ``` ### Supported Actions Below is a comprehensive list of currently supported actions. Note that deprecated APIs will continue to function despite not being listed on this page as long as your request is labeled with a version number corresponding to when the API was available for use. This page currently documents **version 6** of the API. Make sure to include this version number in your requests to guarantee that your application continues to function properly in the future. #### Miscellaneous * **version** Gets the version of the API exposed by this plugin. Currently versions `1` through `6` are defined. This should be the first call you make to make sure that your application and AnkiConnect are able to communicate properly with each other. New versions of AnkiConnect are backwards compatible; as long as you are using actions which are available in the reported AnkiConnect version or earlier, everything should work fine. *Sample request*: ```json { "action": "version", "version": 6 } ``` *Sample result*: ```json { "result": 6, "error": null } ``` * **sync** Synchronizes the local Anki collections with AnkiWeb. *Sample request*: ```json { "action": "sync", "version": 6 } ``` *Sample result*: ```json { "result": null, "error": null } ``` * **loadProfile** Selects the profile specified in request. *Sample request*: ```json { "action": "loadProfile", "params": { "name": "user1" }, "version": 6 } ``` *Sample result*: ```json { "result": true, "error": null } ``` * **multi** Performs multiple actions in one request, returning an array with the response of each action (in the given order). *Sample request*: ```json { "action": "multi", "version": 6, "params": { "actions": [ {"action": "deckNames"}, { "action": "browse", "params": {"query": "deck:current"} } ] } } ``` *Sample result*: ```json { "result": [ {"result": "Default", "error": null}, {"result": [1494723142483, 1494703460437, 1494703479525], "error": null} ], "error": null } ``` * **getNumCardsReviewedToday** Gets the count of cards that have been reviewed in the current day (with day start time as configured by user in anki) *Sample request*: ```json { "action": "getNumCardsReviewedToday", "version": 6 } ``` *Sample result*: ```json { "result": 0, "error": null } ``` * **exportPackage** Exports a given deck in `.apkg` format. Returns `true` if successful or `false` otherwise. The optional property `includeSched` (default is `false`) can be specified to include the cards' scheduling data. *Sample request*: ```json { "action": "exportPackage", "version": 6, "params": { "deck": "Default", "path": "/data/Deck.apkg", "includeSched": true } } ``` *Sample result*: ```json { "result": true, "error": null } ``` * **importPackage** Imports a file in `.apkg` format into the collection. Returns `true` if successful or `false` otherwise. Note that the file path is relative to Anki's collection.media folder, not to the client. *Sample request*: ```json { "action": "importPackage", "version": 6, "params": { "path": "/data/Deck.apkg", } } ``` *Sample result*: ```json { "result": true, "error": null } ``` #### Decks * **deckNames** Gets the complete list of deck names for the current user. *Sample request*: ```json { "action": "deckNames", "version": 6 } ``` *Sample result*: ```json { "result": ["Default"], "error": null } ``` * **deckNamesAndIds** Gets the complete list of deck names and their respective IDs for the current user. *Sample request*: ```json { "action": "deckNamesAndIds", "version": 6 } ``` *Sample result*: ```json { "result": {"Default": 1}, "error": null } ``` * **getDecks** Accepts an array of card IDs and returns an object with each deck name as a key, and its value an array of the given cards which belong to it. *Sample request*: ```json { "action": "getDecks", "version": 6, "params": { "cards": [1502298036657, 1502298033753, 1502032366472] } } ``` *Sample result*: ```json { "result": { "Default": [1502032366472], "Japanese::JLPT N3": [1502298036657, 1502298033753] }, "error": null } ``` * **createDeck** Create a new empty deck. Will not overwrite a deck that exists with the same name. *Sample request*: ```json { "action": "createDeck", "version": 6, "params": { "deck": "Japanese::Tokyo" } } ``` *Sample result*: ```json { "result": 1519323742721, "error": null } ``` * **changeDeck** Moves cards with the given IDs to a different deck, creating the deck if it doesn't exist yet. *Sample request*: ```json { "action": "changeDeck", "version": 6, "params": { "cards": [1502098034045, 1502098034048, 1502298033753], "deck": "Japanese::JLPT N3" } } ``` *Sample result*: ```json { "result": null, "error": null } ``` * **deleteDecks** Deletes decks with the given names. If `cardsToo` is `true` (defaults to `false` if unspecified), the cards within the deleted decks will also be deleted; otherwise they will be moved to the default deck. *Sample request*: ```json { "action": "deleteDecks", "version": 6, "params": { "decks": ["Japanese::JLPT N5", "Easy Spanish"], "cardsToo": true } } ``` *Sample result*: ```json { "result": null, "error": null } ``` * **getDeckConfig** Gets the configuration group object for the given deck. *Sample request*: ```json { "action": "getDeckConfig", "version": 6, "params": { "deck": "Default" } } ``` *Sample result*: ```json { "result": { "lapse": { "leechFails": 8, "delays": [10], "minInt": 1, "leechAction": 0, "mult": 0 }, "dyn": false, "autoplay": true, "mod": 1502970872, "id": 1, "maxTaken": 60, "new": { "bury": true, "order": 1, "initialFactor": 2500, "perDay": 20, "delays": [1, 10], "separate": true, "ints": [1, 4, 7] }, "name": "Default", "rev": { "bury": true, "ivlFct": 1, "ease4": 1.3, "maxIvl": 36500, "perDay": 100, "minSpace": 1, "fuzz": 0.05 }, "timer": 0, "replayq": true, "usn": -1 }, "error": null } ``` * **saveDeckConfig** Saves the given configuration group, returning `true` on success or `false` if the ID of the configuration group is invalid (such as when it does not exist). *Sample request*: ```json { "action": "saveDeckConfig", "version": 6, "params": { "config": { "lapse": { "leechFails": 8, "delays": [10], "minInt": 1, "leechAction": 0, "mult": 0 }, "dyn": false, "autoplay": true, "mod": 1502970872, "id": 1, "maxTaken": 60, "new": { "bury": true, "order": 1, "initialFactor": 2500, "perDay": 20, "delays": [1, 10], "separate": true, "ints": [1, 4, 7] }, "name": "Default", "rev": { "bury": true, "ivlFct": 1, "ease4": 1.3, "maxIvl": 36500, "perDay": 100, "minSpace": 1, "fuzz": 0.05 }, "timer": 0, "replayq": true, "usn": -1 } } } ``` *Sample result*: ```json { "result": true, "error": null } ``` * **setDeckConfigId** Changes the configuration group for the given decks to the one with the given ID. Returns `true` on success or `false` if the given configuration group or any of the given decks do not exist. *Sample request*: ```json { "action": "setDeckConfigId", "version": 6, "params": { "decks": ["Default"], "configId": 1 } } ``` *Sample result*: ```json { "result": true, "error": null } ``` * **cloneDeckConfigId** Creates a new configuration group with the given name, cloning from the group with the given ID, or from the default group if this is unspecified. Returns the ID of the new configuration group, or `false` if the specified group to clone from does not exist. *Sample request*: ```json { "action": "cloneDeckConfigId", "version": 6, "params": { "name": "Copy of Default", "cloneFrom": 1 } } ``` *Sample result*: ```json { "result": 1502972374573, "error": null } ``` * **removeDeckConfigId** Removes the configuration group with the given ID, returning `true` if successful, or `false` if attempting to remove either the default configuration group (ID = 1) or a configuration group that does not exist. *Sample request*: ```json { "action": "removeDeckConfigId", "version": 6, "params": { "configId": 1502972374573 } } ``` *Sample result*: ```json { "result": true, "error": null } ``` #### Models * **modelNames** Gets the complete list of model names for the current user. *Sample request*: ```json { "action": "modelNames", "version": 6 } ``` *Sample result*: ```json { "result": ["Basic", "Basic (and reversed card)"], "error": null } ``` * **modelNamesAndIds** Gets the complete list of model names and their corresponding IDs for the current user. *Sample request*: ```json { "action": "modelNamesAndIds", "version": 6 } ``` *Sample result*: ```json { "result": { "Basic": 1483883011648, "Basic (and reversed card)": 1483883011644, "Basic (optional reversed card)": 1483883011631, "Cloze": 1483883011630 }, "error": null } ``` * **modelFieldNames** Gets the complete list of field names for the provided model name. *Sample request*: ```json { "action": "modelFieldNames", "version": 6, "params": { "modelName": "Basic" } } ``` *Sample result*: ```json { "result": ["Front", "Back"], "error": null } ``` * **modelFieldsOnTemplates** Returns an object indicating the fields on the question and answer side of each card template for the given model name. The question side is given first in each array. *Sample request*: ```json { "action": "modelFieldsOnTemplates", "version": 6, "params": { "modelName": "Basic (and reversed card)" } } ``` *Sample result*: ```json { "result": { "Card 1": [["Front"], ["Back"]], "Card 2": [["Back"], ["Front"]] }, "error": null } ``` * **createModel** Creates a new model to be used in Anki. User must provide the `modelName`, `inOrderFields` and `cardTemplates` to be used in the model. Optionally the `Name` field can be provided for each entry of `cardTemplates`. By default the card names will be `Card 1`, `Card 2`, and so on. *Sample request* ```json { "action": "createModel", "version": 6, "params": { "modelName": "newModelName", "inOrderFields": ["Field1", "Field2", "Field3"], "css": "Optional CSS with default to builtin css", "cardTemplates": [ { "Name": "My Card 1", "Front": "Front html {{Field1}}", "Back": "Back html {{Field2}}" } ] } } ``` *Sample result* ```json { "result":{ "sortf":0, "did":1, "latexPre":"\\documentclass[12pt]{article}\n\\special{papersize=3in,5in}\n\\usepackage[utf8]{inputenc}\n\\usepackage{amssymb,amsmath}\n\\pagestyle{empty}\n\\setlength{\\parindent}{0in}\n\\begin{document}\n", "latexPost":"\\end{document}", "mod":1551462107, "usn":-1, "vers":[ ], "type":0, "css":".card {\n font-family: arial;\n font-size: 20px;\n text-align: center;\n color: black;\n background-color: white;\n}\n", "name":"TestApiModel", "flds":[ { "name":"Field1", "ord":0, "sticky":false, "rtl":false, "font":"Arial", "size":20, "media":[ ] }, { "name":"Field2", "ord":1, "sticky":false, "rtl":false, "font":"Arial", "size":20, "media":[ ] } ], "tmpls":[ { "name":"My Card 1", "ord":0, "qfmt":"", "afmt":"This is the back of the card {{Field2}}", "did":null, "bqfmt":"", "bafmt":"" } ], "tags":[ ], "id":"1551462107104", "req":[ [ 0, "none", [ ] ] ] }, "error":null } ``` * **modelTemplates** Returns an object indicating the template content for each card connected to the provided model by name. *Sample request*: ```json { "action": "modelTemplates", "version": 6, "params": { "modelName": "Basic (and reversed card)" } } ``` *Sample result* ```json { "result": { "Card 1": { "Front": "{{Front}}", "Back": "{{FrontSide}}\n\n