Headless Zotero library management: Web API CRUD plus direct WebDAV attachment upload/download — no desktop app required
Project description
zotkit
Headless Zotero library management — no desktop app required.
zotkit is a Python library + CLI that manages a Zotero library entirely through the Zotero Web API: search, create, tag, and organize items from a terminal or a server. Uniquely, if your attachments sync to a personal WebDAV server, zotkit can upload and download the files themselves by speaking Zotero's WebDAV storage format directly — the one capability the Web API does not provide, and (as far as we know) not offered by any other headless tool.
Built for servers, scripts, and LLM agents: every write is dry-run by default, batched, and version-checked, and you can define a tag taxonomy that is enforced in code so an agent (or a tired human) can't pollute your library with inconsistent tags.
Why zotkit
| Desktop app | Other CLI/MCP tools | zotkit | |
|---|---|---|---|
| Works headless (server, SSH, CI) | ❌ | ✅ read-mostly | ✅ |
| Write items/tags/collections | ✅ | ⚠️ usually needs the desktop app running | ✅ |
| Attachment files on WebDAV | ✅ | ⚠️ download at best | ✅ upload + download |
| Tag conventions enforced in code | ❌ | ❌ | ✅ optional conventions.toml |
Install
pipx install zotkit # or: uv tool install zotkit / pip install zotkit
uvx zotkit --help # …or try it without installing anything
Configure
Copy .env.example to ./.env, ~/.config/zotkit/env, or any path in
$ZOTKIT_ENV, and fill in:
- Zotero Web API: create a key (with write access) at
https://www.zotero.org/settings/keys — your numeric
ZOTERO_LIBRARY_IDis shown on the same page. - WebDAV (only for
attach/fetch): copy the exact values from the Zotero desktop app on any of your machines — Settings → Sync → File Syncing — and append/zotero/to the URL (the desktop does this implicitly).
Optionally, copy conventions.example.toml to
conventions.toml next to your .env to define a namespaced tag taxonomy
(field:physics, status:to-read, …). With it in place, zotkit create / zotkit tag
reject violations; without it, tags are unrestricted.
Quickstart
zotkit find --title "boson sampling" # search by title/tag/collection
zotkit find --tag status:to-read
zotkit create --file papers.json # dry-run: shows what would be created
zotkit create --file papers.json --apply # create (dedups by DOI/title)
zotkit attach --from papers.created.json --all # upload the PDFs to WebDAV
zotkit attach --key AB12CD34 --pdf paper.pdf # single attach
zotkit fetch --key AB12CD34 --out downloads # download attachment from WebDAV
zotkit tag AB12CD34 topic:qaoa prio:high # validated against conventions.toml
zotkit status AB12CD34 read # replaces the status: tag
zotkit move AB12CD34 "Algorithms" # or "Parent :: Child"; --add keeps old home
zotkit backup # full JSON snapshot -> backups/
zotkit lint field:physics topic:new-idea # offline tag check
Item JSON for zotkit create (a list, one object per reference):
[{"itemType": "journalArticle", "title": "…",
"creators": [{"creatorType": "author", "firstName": "A", "lastName": "B"}],
"date": "2024", "publicationTitle": "…", "DOI": "10.x/y",
"tags": ["field:physics", "status:to-read"],
"collection": "Algorithms", "file_path": "/abs/path/paper.pdf"}]
From Python
from zotkit import Zot
z = Zot() # reads .env automatically
z.find(tag="status:to-read")
z.create_items([...]) # dedup + convention checks
z.attach("AB12CD34", "paper.pdf") # PDF -> WebDAV
z.fetch("AB12CD34", "downloads")
z.set_status("AB12CD34", "read")
z.backup()
z.z is the underlying pyzotero client for
anything not wrapped.
Safety model
createis dry-run by default;--applyto execute.- Writes go through fetch→modify→update (carries the item version, so concurrent edits fail loudly with 412 instead of clobbering), in batches of ≤ 50.
zotkit backupsnapshots every item, collection, tag, and membership to one JSON file — run it before bulk operations.- Remember: writes propagate to zotero.org and all your synced devices.
How WebDAV attachments work
Zotero's WebDAV storage format is undocumented but simple: each attachment item K is
stored as K.zip (the file, zipped) plus K.prop (its md5 + mtime). zotkit creates the
attachment item via the Web API and PUTs both objects directly — after which every
desktop client syncs the file down normally. Details in
docs/webdav-format.md.
The format was determined by interoperability inspection of the author's own library. This project is not affiliated with or endorsed by Zotero.
License
MIT. If you build on the WebDAV implementation, a link back is appreciated.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file zotkit-0.1.0.tar.gz.
File metadata
- Download URL: zotkit-0.1.0.tar.gz
- Upload date:
- Size: 14.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a29637be7c880cd0b8f09316a073521b3e7829d4f6d5effd5b7688a04c74b664
|
|
| MD5 |
610db5ea985c9c3a486cfaf57663f5a3
|
|
| BLAKE2b-256 |
741e3669143011ad5f337eb011d0afd333f2f3c1045d16061c35ed983e2948ee
|
Provenance
The following attestation bundles were made for zotkit-0.1.0.tar.gz:
Publisher:
publish.yml on oldantique/zotkit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zotkit-0.1.0.tar.gz -
Subject digest:
a29637be7c880cd0b8f09316a073521b3e7829d4f6d5effd5b7688a04c74b664 - Sigstore transparency entry: 2141749948
- Sigstore integration time:
-
Permalink:
oldantique/zotkit@0b117b393f631c79be3710b7580b976cd08ac0f3 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/oldantique
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0b117b393f631c79be3710b7580b976cd08ac0f3 -
Trigger Event:
release
-
Statement type:
File details
Details for the file zotkit-0.1.0-py3-none-any.whl.
File metadata
- Download URL: zotkit-0.1.0-py3-none-any.whl
- Upload date:
- Size: 12.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7cc5c3014dcef3d021640f9edaf7cd02fdaa65c2f4693f7f785a02e67a3a3de3
|
|
| MD5 |
0b2427f2afdc69801d11eb242a06f448
|
|
| BLAKE2b-256 |
9540009e02a5853e5b0aaecc8fe3ef7ed9c36ce6ec0566fb3ede9d84ab1e177d
|
Provenance
The following attestation bundles were made for zotkit-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on oldantique/zotkit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zotkit-0.1.0-py3-none-any.whl -
Subject digest:
7cc5c3014dcef3d021640f9edaf7cd02fdaa65c2f4693f7f785a02e67a3a3de3 - Sigstore transparency entry: 2141750043
- Sigstore integration time:
-
Permalink:
oldantique/zotkit@0b117b393f631c79be3710b7580b976cd08ac0f3 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/oldantique
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0b117b393f631c79be3710b7580b976cd08ac0f3 -
Trigger Event:
release
-
Statement type: