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. 中文速览 →
"Headless" simply means zotkit never needs the Zotero app (or any window) open: it is a Python library + CLI that talks straight to the Zotero Web API, so you can search, create, tag, and organize items from any terminal — macOS, Windows, or Linux, your laptop or a remote 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 (Zotero Storage) | ✅ | ⚠️ some | ✅ upload + download |
| Attachment files on WebDAV | ✅ | ⚠️ download at best | ✅ upload + download |
| Tag conventions enforced in code | ❌ | ❌ | ✅ optional conventions.toml |
Install
Pure Python (3.11+), no platform-specific bits — the same package works on macOS, Windows, and Linux:
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). - Using Zotero Storage instead of WebDAV? Just leave the
WEBDAV_*lines out —attach/fetchautomatically use Zotero Storage through the Web API's upload/download endpoints instead. The storage mode is detected from your.env, nothing to configure.
After filling it in, run zotkit doctor — it validates the config file, API
access, and attachment storage, and tells you exactly what to fix if anything fails.
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.
Using zotkit with AI agents
zotkit is designed to be driven by coding agents (Claude Code and similar): dry-run
defaults, code-enforced tag conventions, and a ready-made Claude Code skill in
skills/zotkit/ — copy it to ~/.claude/skills/zotkit/ and
any Claude session can search, file, and attach papers for you while respecting your
taxonomy. (An MCP server is planned.)
Want to clean up a messy library, not just maintain one? The battle-tested method —
taxonomy design, parallel read-only analysis, serial reviewed writes — is written up in
docs/organizing-with-agents.md.
mkdir -p ~/.claude/skills && cp -r skills/zotkit ~/.claude/skills/
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
(With Zotero Storage, zotkit simply uses the Web API's official file endpoints — this
section is about the WebDAV mode.) 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.
Limits & roadmap
findcurrently lists the library client-side — instant for hundreds of items, sluggish for many thousands. Server-side search is planned.- Group libraries should work for item operations (untested); WebDAV file sync is personal-libraries-only (a Zotero limitation).
- Planned: an MCP server wrapper, server-side search, DOI/arXiv one-shot import.
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.3.0.tar.gz.
File metadata
- Download URL: zotkit-0.3.0.tar.gz
- Upload date:
- Size: 17.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a0c0fd6bad3560f5b4ba527d6642395416ea328282da4eaeb95011c7b9b6780d
|
|
| MD5 |
55015bbbbcbac33c228d58f50dc029fd
|
|
| BLAKE2b-256 |
5d794e1d978f3570aeadd0602704d557c892c4c5b26811e7773da50b11450a9c
|
Provenance
The following attestation bundles were made for zotkit-0.3.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.3.0.tar.gz -
Subject digest:
a0c0fd6bad3560f5b4ba527d6642395416ea328282da4eaeb95011c7b9b6780d - Sigstore transparency entry: 2142258094
- Sigstore integration time:
-
Permalink:
oldantique/zotkit@37e56a305077a4c76620c28e28df6d3690492188 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/oldantique
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@37e56a305077a4c76620c28e28df6d3690492188 -
Trigger Event:
release
-
Statement type:
File details
Details for the file zotkit-0.3.0-py3-none-any.whl.
File metadata
- Download URL: zotkit-0.3.0-py3-none-any.whl
- Upload date:
- Size: 14.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 |
60220ffe8af7b7f7e9a2528e592c5ff3cc28e7b023bdfb40ccaf1093d141607a
|
|
| MD5 |
41a811f47e71b3dc59e2f1e6a393c659
|
|
| BLAKE2b-256 |
18d5517a819da36fc95ed1cc6ab326aaafe809cc778d9340edf4c5737a7fe625
|
Provenance
The following attestation bundles were made for zotkit-0.3.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.3.0-py3-none-any.whl -
Subject digest:
60220ffe8af7b7f7e9a2528e592c5ff3cc28e7b023bdfb40ccaf1093d141607a - Sigstore transparency entry: 2142258564
- Sigstore integration time:
-
Permalink:
oldantique/zotkit@37e56a305077a4c76620c28e28df6d3690492188 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/oldantique
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@37e56a305077a4c76620c28e28df6d3690492188 -
Trigger Event:
release
-
Statement type: