{"id":13478819,"url":"https://github.com/marph91/joppy","last_synced_at":"2026-01-20T19:08:58.825Z","repository":{"id":37096509,"uuid":"412736148","full_name":"marph91/joppy","owner":"marph91","description":"Python interface for the Joplin client and server API","archived":false,"fork":false,"pushed_at":"2025-01-01T21:32:39.000Z","size":179,"stargazers_count":68,"open_issues_count":2,"forks_count":10,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-29T16:11:08.246Z","etag":null,"topics":["joplin","joplin-api","python","requests"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/marph91.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-10-02T08:24:21.000Z","updated_at":"2025-02-11T22:37:27.000Z","dependencies_parsed_at":"2024-01-14T08:49:26.215Z","dependency_job_id":"f1444289-0ee4-464c-b380-676233851748","html_url":"https://github.com/marph91/joppy","commit_stats":{"total_commits":208,"total_committers":1,"mean_commits":208.0,"dds":0.0,"last_synced_commit":"65b1446db76c83b0760fee3278436d4b44c2e544"},"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marph91%2Fjoppy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marph91%2Fjoppy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marph91%2Fjoppy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marph91%2Fjoppy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/marph91","download_url":"https://codeload.github.com/marph91/joppy/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247378149,"owners_count":20929297,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["joplin","joplin-api","python","requests"],"created_at":"2024-07-31T16:02:03.849Z","updated_at":"2026-01-20T19:08:58.803Z","avatar_url":"https://github.com/marph91.png","language":"Python","funding_links":[],"categories":["Python","Tools"],"sub_categories":[],"readme":"# joppy\n\nPython interface for the [Joplin data API](https://joplinapp.org/api/references/rest_api/) (client) and the Joplin server API.\n\n[![build](https://github.com/marph91/joppy/actions/workflows/build.yml/badge.svg)](https://github.com/marph91/joppy/actions/workflows/build.yml)\n[![lint](https://github.com/marph91/joppy/actions/workflows/lint.yml/badge.svg)](https://github.com/marph91/joppy/actions/workflows/lint.yml)\n[![tests](https://github.com/marph91/joppy/actions/workflows/tests.yml/badge.svg)](https://github.com/marph91/joppy/actions/workflows/tests.yml)\n[![codecov](https://codecov.io/gh/marph91/joppy/branch/master/graph/badge.svg?token=97E6IX792A)](https://codecov.io/gh/marph91/joppy)\n\n[![https://img.shields.io/badge/Joplin-3.5.12-blueviolet](https://img.shields.io/badge/Joplin-3.5.12-blueviolet)](https://github.com/laurent22/joplin)\n[![Python version](https://img.shields.io/pypi/pyversions/joppy.svg)](https://pypi.python.org/pypi/joppy/)\n\n## Features\n\n|     | Client API Wrapper | Server API Wrapper |\n| --- | --- | --- |\n| **Supported** | All functions from the [data API](https://joplinapp.org/help/api/references/rest_api/) | Some reverse engineered functions with a similar interface like the client API wrapper. See the example below and the source code for details. |\n| **Not Supported** | -  | - Encryption \u003cbr\u003e- Some functions that were either to complex or I didn't see a use for automation. |\n\n## :computer: Installation\n\nFrom pypi:\n\n```bash\npip install joppy\n```\n\nFrom source:\n\n```bash\ngit clone https://github.com/marph91/joppy.git\ncd joppy\npip install .\n```\n\n## :wrench: Usage\n\nPlease backup your data before use!\n\n### General function description\n\n- `add_\u003ctype\u003e()`: Create a new element.\n- `delete_\u003ctype\u003e()`: Delete an element by ID.\n- `get_\u003ctype\u003e()`: Get an element by ID.\n- `get_all_\u003ctype\u003e()`: Get all elements of a kind.\n- `modify_\u003ctype\u003e()`: Modify an elements property by ID.\n- `search_all()`: Search elements using [joplins search engine](https://joplinapp.org/api/references/rest_api/#searching).\n\nFor details, consult the [implementation](joppy/api.py), [joplin documentation](https://joplinapp.org/api/references/rest_api/) or [create an issue](https://github.com/marph91/joppy/issues).\n\n## :bulb: Example snippets\n\n### Client API\n\nStart joplin and [get your API token](https://joplinapp.org/api/references/rest_api/#authorisation). Click to expand the examples.\n\n\u003cdetails\u003e\n\u003csummary\u003eGet all notes\u003c/summary\u003e\n\n```python name=get_all_notes\nfrom joppy.client_api import ClientApi\n\n# Create a new Api instance.\napi = ClientApi(token=YOUR_TOKEN)\n\n# Get all notes. Note that this method calls get_notes() multiple times to assemble the unpaginated result.\nnotes = api.get_all_notes(fields=\"title,id,parent_id,body\")\n\nfor note in notes:\n    print(note)\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eAdd a tag to a note\u003c/summary\u003e\n  \n```python name=add_tag_to_note\nfrom joppy.client_api import ClientApi\n\n# Create a new Api instance.\n\napi = ClientApi(token=YOUR_TOKEN)\n\n# Add a notebook.\n\nnotebook_id = api.add_notebook(title=\"My first notebook\")\n\n# Add a note in the previously created notebook.\n\nnote_id = api.add_note(title=\"My first note\", body=\"With some content\", parent_id=notebook_id)\n\n# Add a tag, that is not yet attached to a note.\n\ntag_id = api.add_tag(title=\"introduction\")\n\n# Link the tag to the note.\n\napi.add_tag_to_note(tag_id=tag_id, note_id=note_id)\n\n````\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eAdd a resource to a note\u003c/summary\u003e\n\n```python name=add_resource_to_note\nfrom joppy.client_api import ClientApi\nfrom joppy import tools\n\n# Create a new Api instance.\napi = ClientApi(token=YOUR_TOKEN)\n\n# Add a notebook.\nnotebook_id = api.add_notebook(title=\"My first notebook\")\n\n# Option 1: Add a note with an image data URL. This works only for images.\nimage_data = tools.encode_base64(\"path/to/image.png\")\napi.add_note(\n    title=\"My first note\",\n    image_data_url=f\"data:image/png;base64,{image_data}\",\n)\n\n# Option 2: Create note and resource separately. Link them later. This works for arbitrary attachments.\nnote_id = api.add_note(title=\"My second note\")\nresource_id = api.add_resource(filename=\"path/to/image.png\", title=\"My first resource\")\napi.add_resource_to_note(resource_id=resource_id, note_id=note_id)\n````\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eBulk remove tags\u003c/summary\u003e\n\nInspired by \u003chttps://discourse.joplinapp.org/t/bulk-tag-delete-python-script/5497/1\u003e.\n\n```python name=remove_tags\nimport re\n\nfrom joppy.client_api import ClientApi\n\n# Create a new Api instance.\napi = ClientApi(token=YOUR_TOKEN)\n\n# Iterate through all tags.\nfor tag in api.get_all_tags():\n\n    # Delete all tags that match the regex. I. e. start with \"!\".\n    if re.search(\"^!\", tag.title) is not None:\n        api.delete_tag(tag.id)\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eRemove unused tags\u003c/summary\u003e\n\nReference: \u003chttps://discourse.joplinapp.org/t/prune-empty-tags-from-web-clipper/36194\u003e\n\n```python name=remove_unused_tags\nfrom joppy.client_api import ClientApi\n\n# Create a new Api instance.\napi = ClientApi(token=YOUR_TOKEN)\n\nfor tag in api.get_all_tags():\n    notes_for_tag = api.get_all_notes(tag_id=tag.id)\n    if len(notes_for_tag) == 0:\n        print(\"Deleting tag:\", tag.title)\n        api.delete_tag(tag.id)\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eRemove spaces from tags\u003c/summary\u003e\n\nReference: \u003chttps://www.reddit.com/r/joplinapp/comments/pozric/batch_remove_spaces_from_all_tags/\u003e\n\n```python name=remove_spaces_from_tags\nimport re\n\nfrom joppy.client_api import ClientApi\n\n# Create a new Api instance.\napi = ClientApi(token=YOUR_TOKEN)\n\n# Define the conversion function.\ndef to_camel_case(name: str) -\u003e str:\n    name = re.sub(r\"(_|-)+\", \" \", name).title().replace(\" \", \"\")\n    return \"\".join([name[0].lower(), name[1:]])\n\n# Iterate through all tags and apply the conversion.\nfor tag in api.get_all_tags():\n    api.modify_tag(id_=tag.id, title=to_camel_case(tag.title))\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eRemove orphaned resources\u003c/summary\u003e\n\nInspired by \u003chttps://discourse.joplinapp.org/t/joplin-vacuum-a-python-script-to-remove-orphaned-resources/19742\u003e.\nNote: The note history is not considered. See: \u003chttps://discourse.joplinapp.org/t/joplin-vacuum-a-python-script-to-remove-orphaned-resources/19742/13\u003e.\n\n```python name=remove_orphaned_resources\nimport re\n\nfrom joppy.client_api import ClientApi\n\n# Create a new Api instance.\napi = ClientApi(token=YOUR_TOKEN)\n\n# Getting the referenced resource directly doesn't work:\n# https://github.com/laurent22/joplin/issues/4535\n# So we have to find the referenced resources by regex.\n\n# Iterate through all notes and find the referenced resources.\nreferenced_resources = set()\nfor note in api.get_all_notes(fields=\"id,body\"):\n    matches = re.findall(r\"\\[.*\\]\\(:.*\\/([A-Za-z0-9]{32})\\)\", note.body)\n    referenced_resources.update(matches)\n\nassert len(referenced_resources) \u003e 0, \"sanity check\"\n\nfor resource in api.get_all_resources():\n    if resource.id not in referenced_resources:\n        print(\"Deleting resource:\", resource.title)\n        api.delete_resource(resource.id)\n```\n\n\u003c/details\u003e\n\nFor more usage examples, check the example scripts or [tests](test/test_client_api.py).\n\n### Server API\n\nThe server API should work similarly to the client API in most cases. **Be aware that the server API is experimental and may break at any time. I can't provide any help at sync issues or lost data. Make sure you have a backup and know how to restore it.**\n\n```python\nfrom joppy.server_api import ServerApi\n\n# Create a new Api instance.\napi = ServerApi(user=\"admin@localhost\", password=\"admin\", url=\"http://localhost:22300\")\n\n# Acquire a lock.\nwith api.sync_lock():\n\n    # Add a notebook.\n    notebook_id = api.add_notebook(title=\"My first notebook\")\n\n    # Add a note in the previously created notebook.\n    note_id = api.add_note(title=\"My first note\", body=\"With some content\", parent_id=notebook_id)\n```\n\n## :newspaper: Examples\n\nBefore using joppy, you should check the [Joplin plugins](https://joplinapp.org/plugins/). They are probably more convenient. However, if you need a new feature or just want to code in python, you can use joppy.\n\n### Apps\n\n| App | Description |\n| --- | --- |\n| [jimmy](https://github.com/marph91/jimmy) | A tool to import your notes to Joplin |\n| [joplin-sticky-notes](https://github.com/marph91/joplin-sticky-notes) | Stick your Joplin notes to the desktop |\n| [joplin-vieweb](https://github.com/joplin-vieweb/django-joplin-vieweb) | A simple web viewer for Joplin |\n\n### Scripts\n\n| Script                                                              | Description                                                                                                                  |\n| ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |\n| [custom_export.py](examples/custom_export.py)                       | Export resources next to notes, instead of a separate folder.                                                                |\n| [note_export.py](examples/note_export.py)                           | Export notes to any format supported by [pandoc](https://pandoc.org/).                                                       |\n| [note_stats.py](examples/note_stats.py)                             | Get some simple statistics about your notes, based on [nltk](https://www.nltk.org/).                                         |\n| [note_tree_export.py](examples/note_tree_export.py)                 | Joplin only supports PDF export of a single note. This script allows to export one, multiple or all notebooks to PDF or TXT. |\n| [visualize_note_locations.py](examples/visualize_note_locations.py) | Visualize the locations of your notes.                                                                                       |\n| [joplin-ui-tests](https://github.com/marph91/joplin-ui-tests)       | System tests for the joplin desktop app. Based on selenium.                                                                  |\n\n## :sunny: Tests\n\nTo run the tests, some additional system packages and python modules are needed. After installing them, just run:\n\n```bash\npython -m unittest\n```\n\nIt's possible to configure the test run via some environment variables:\n\n- `SLOW_TESTS`: Set this variable to run the slow tests. Default not set.\n- `API_TOKEN`: Set this variable if there is already a joplin instance running. **Don't use your default joplin profile!** By default, a joplin instance is started inside xvfb. This takes some time, but works for CI.\n\n## :book: Changelog\n\nThe changelog for versions greater than 1.0.0 can be found at the [releases page](https://github.com/marph91/joppy/releases).\n\n### 1.0.0\n\n- Rename the client API. It should be used by `from joppy.client_api import ClientApi` instead of `from joppy.client_api import ClientApi` now.\n- Add support for the server API (\u003chttps://github.com/marph91/joppy/pull/27\u003e). It should be used by `from joppy.server_api import ServerApi`.\n\n### 0.2.3\n\n- Don't use the root logger for logging.\n- Add support for [revisions](https://joplinapp.org/help/api/references/rest_api/#revisions).\n\n### 0.2.2\n\n- Fix adding non-image ressources (\u003chttps://github.com/marph91/joppy/issues/24\u003e).\n- Cast `markup_language` to an appropriate enum type.\n- Add changelog.\n\n### 0.2.1\n\n- Fix PDF output example (\u003chttps://github.com/marph91/joppy/issues/19\u003e).\n- :warning: Drop tests for python 3.6, since it's EOL. It may still work.\n- Fix the type of `todo_completed` and `todo_due`. They are a unix timestamp, not a bool.\n\n### 0.1.1\n\n- Add typing support to the pypi module.\n\n### 0.1.0\n\n- Use a requests session for speedup (\u003chttps://github.com/marph91/joppy/issues/15\u003e).\n- :warning: Convert the API responses to data objects (\u003chttps://github.com/marph91/joppy/pull/17\u003e). Main difference is to use `note.id` instead of `note[\"id\"]` for example.\n\n### 0.0.7\n\n- Fix getting the binary resource file (\u003chttps://github.com/marph91/joppy/issues/13\u003e).\n\n### 0.0.6\n\n- Add convenience method for deleting all notes.\n- Add example scripts.\n\n### 0.0.5\n\n- Fix package publishing workflow.\n\n### 0.0.4\n\n- Add support for python 3.6 and 3.7.\n\n### 0.0.3\n\n- Fix search with special characters (\u003chttps://github.com/marph91/joppy/issues/5\u003e).\n- Remove arbitrary arguments from the internal base requests, since they aren't needed and may cause bugs.\n\n### 0.0.2\n\n- CI and test improvements.\n- Move complete setup to `setup.cfg`.\n\n### 0.0.1\n\n- Initial release.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarph91%2Fjoppy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarph91%2Fjoppy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarph91%2Fjoppy/lists"}