zotero-local/SKILL.md
Interact with a local Zotero 8 desktop application through its HTTP API at localhost:23119. Use this skill whenever the user wants to search, fetch, add, edit, or organize bibliographic items in their Zotero library, import citations (BibTeX, RIS, etc.), attach files, manage collections and tags, or retrieve full-text content from Zotero. Triggers on mentions of Zotero, citation management, reference libraries, bibliographic databases, or local library management. Also use when chaining with other catalog skills (Harvard, LOC, HathiTrust, etc.) to save found records into the user's Zotero library.
npx skillsauth add kltng/humanities-skills zotero-localInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Interact with a running Zotero 8 desktop app via its local HTTP API.
Zotero's local server has two separate API layers with different capabilities:
/api/...Mirrors the Zotero Web API v3 at http://localhost:23119/api/. Supports comprehensive read operations but no writes (POST/PUT/PATCH/DELETE all return 400).
users/0 for the local user (not the actual user ID)q, qmode, sort, direction, limit, start, format, include, itemType, tag, sinceformat=json (default), format=bib, format=citation, format=keys, format=versions, and export formats like format=bibtex/connector/...The connector endpoints handle all write operations:
| Endpoint | What it does |
|----------|-------------|
| POST /connector/saveItems | Create items with full metadata, notes, and tags |
| POST /connector/saveSnapshot | Save a webpage as a Zotero item |
| POST /connector/import | Import BibTeX, RIS, or other bibliographic formats |
| POST /connector/saveAttachment | Attach files to existing items |
| POST /connector/saveStandaloneAttachment | Save a standalone file attachment |
| POST /connector/saveSingleFile | Save SingleFile webpage snapshots |
| POST /connector/updateSession | Update tags, target collection for a save session |
| POST /connector/getSelectedCollection | Get current library/collection selection |
| POST /connector/installStyle | Install citation styles |
http://localhost:23119/api/GET http://localhost:23119/api/users/0/items?q=keyword&format=json&limit=20
Query parameters: q (search text), qmode (titleCreatorYear or everything), sort (dateAdded, dateModified, title, creator, date), direction (asc/desc), itemType (book, journalArticle, etc.), tag (filter by tag).
GET http://localhost:23119/api/users/0/items/{itemKey}?format=json
GET http://localhost:23119/api/users/0/items/{itemKey}/children?format=json
POST http://localhost:23119/connector/saveItems
Content-Type: application/json
{
"items": [{
"itemType": "book",
"title": "The Title",
"creators": [{"firstName": "First", "lastName": "Last", "creatorType": "author"}],
"date": "2024",
"publisher": "Publisher Name",
"ISBN": "978-0-123456-78-9",
"tags": [{"tag": "history"}, {"tag": "research"}],
"notes": [{"note": "<p>My note about this book</p>"}]
}],
"uri": "http://example.com",
"sessionID": "unique-session-id"
}
The uri and sessionID fields are required by the connector protocol. Use any unique string for sessionID.
The import endpoint requires a unique session query parameter:
POST http://localhost:23119/connector/import?session=unique-id
Content-Type: text/plain
@book{key2024,
author = {Author Name},
title = {Book Title},
year = {2024},
publisher = {Publisher}
}
Returns the created item(s) as JSON. Without the session parameter, repeated imports return 409 Conflict.
Upload a PDF as a standalone attachment. Zotero auto-recognizes the document and creates a parent item with extracted metadata (title, authors, DOI, etc.).
POST http://localhost:23119/connector/saveStandaloneAttachment
Content-Type: application/pdf
X-Metadata: {"sessionID": "unique-id", "url": "file:///path/to/file.pdf", "title": "file.pdf"}
<binary PDF data>
Returns {"canRecognize": true} on success (201).
Requires the Better BibTeX (BBT) extension for its debug-bridge endpoint. Without BBT, use import_pdf() to import as a standalone item instead.
POST http://127.0.0.1:23119/debug-bridge/execute
Content-Type: application/javascript
var item = await Zotero.Items.getByLibraryAndKeyAsync(
Zotero.Libraries.userLibraryID, 'ITEMKEY'
);
var att = await Zotero.Attachments.importFromFile({
file: '/path/to/file.pdf',
parentItemID: item.id
});
return JSON.stringify({key: att.key});
Get the local file path for an attachment:
GET http://localhost:23119/api/users/0/items/{attachmentKey}/file/view/url
Returns a file:// URL pointing to the file in Zotero's storage directory.
Or redirect to the file directly (returns 302):
GET http://localhost:23119/api/users/0/items/{attachmentKey}/file
GET http://localhost:23119/api/users/0/items/{itemKey}/fulltext
Returns indexed full-text content for PDFs and other indexed documents.
GET http://localhost:23119/api/users/0/collections?format=json
GET http://localhost:23119/api/users/0/collections/top?format=json
GET http://localhost:23119/api/users/0/collections/{collectionKey}/items?format=json
These operations use the Better BibTeX debug-bridge. See the "Better BibTeX" section below.
z = ZoteroLocal()
# Check if BBT is available
if not z.check_bbt():
print("Install Better BibTeX: https://retorque.re/zotero-better-bibtex/installation/")
# Create a top-level collection
col = z.create_collection("My Research")
# Returns: {"key": "ABCD1234", "name": "My Research"}
# Create a sub-collection
sub = z.create_collection("Chapter 1", parent_key=col["key"])
# Returns: {"key": "EFGH5678", "name": "Chapter 1", "parentKey": "ABCD1234"}
# Delete a collection (items remain in library)
z.delete_collection("ABCD1234")
# Delete a collection and trash items only in that collection
z.delete_collection("ABCD1234", delete_items=True)
GET http://localhost:23119/api/users/0/tags?format=json
GET http://localhost:23119/api/users/0/items?format=bibtex
GET http://localhost:23119/api/users/0/items/{itemKey}?format=ris
GET http://localhost:23119/api/users/0/items?format=bib&style=chicago-author-date
Supported export formats: bibtex, ris, csljson, mods, refer, rdf_bibliontology, rdf_dc, rdf_zotero, tei, wikipedia.
Unlike the web API, the local API can execute saved searches:
GET http://localhost:23119/api/users/0/searches/{searchKey}/items?format=json
Common types: book, bookSection, journalArticle, conferencePaper, thesis, report, webpage, document, manuscript, letter, map, artwork, film, videoRecording, audioRecording, presentation, statute, case, patent, blogPost, forumPost, encyclopediaArticle, dictionaryEntry, newspaperArticle, magazineArticle.
Get all types: GET http://localhost:23119/api/itemTypes
Get fields for a type: GET http://localhost:23119/api/itemTypeFields?itemType=book
Get creator types: GET http://localhost:23119/api/itemTypeCreatorTypes?itemType=book
Items from the local API return this structure:
{
"key": "ABC12345",
"version": 0,
"library": {"type": "user", "id": 0, "name": "My Library"},
"meta": {"creatorSummary": "Author", "numChildren": 2},
"data": {
"key": "ABC12345",
"itemType": "book",
"title": "...",
"creators": [...],
"date": "2024",
"publisher": "...",
"ISBN": "...",
"tags": [{"tag": "..."}],
"collections": ["COLLKEY1"],
"dateAdded": "2024-01-01T00:00:00Z",
"dateModified": "2024-01-01T00:00:00Z"
}
}
from scripts.zotero_api import ZoteroLocal
z = ZoteroLocal() # connects to localhost:23119
# Search
items = z.search("keyword", limit=10)
# Get item
item = z.get_item("ABC12345")
# Create book
item = z.create_item("book", title="My Book", creators=[{"firstName":"A","lastName":"B","creatorType":"author"}])
# Import BibTeX
items = z.import_bibtex('@book{...}')
# Import a PDF (auto-recognizes metadata)
result = z.import_pdf("/path/to/paper.pdf")
# Download an attachment
path = z.download_attachment("ATTKEY12", "/tmp/")
# Check if Better BibTeX is available
if z.check_bbt():
# Attach file to existing item (requires BBT)
z.attach_file("/path/to/paper.pdf", "ITEMKEY1")
# Create/delete collections (requires BBT)
col = z.create_collection("My Collection")
sub = z.create_collection("Sub-Collection", parent_key=col["key"])
z.delete_collection(sub["key"])
# Export
bibtex = z.export_items(format="bibtex")
Some features require the Better BibTeX extension, which provides a debug-bridge for executing JavaScript inside Zotero. BBT is free and open-source — any user can install it.
Features requiring BBT:
Check if BBT is installed:
z = ZoteroLocal()
if z.check_bbt():
print("BBT is available")
else:
print("Install BBT: https://retorque.re/zotero-better-bibtex/installation/")
Install BBT: Download the latest .xpi from BBT releases, then in Zotero: Tools → Add-ons → gear icon → Install Add-on From File.
saveItems requires uri and sessionID wrapper fields around the items array./api/items/new?itemType= endpoint returns empty on the local API. Use /api/itemTypeFields?itemType= to discover fields instead.import_pdf() to import files as standalone items with auto-recognition instead.references/api_reference.md — Complete endpoint reference with all parametersscripts/zotero_api.py — Python client for read and write operationsdevelopment
Query the China Biographical Database (CBDB) locally via SQLite for biographical data on 656K+ historical Chinese figures from the 7th century BCE through the 19th century CE. Use when searching for Chinese historical figures, scholars, officials, or literary figures — their biographical details, family/kinship networks, official postings, social associations, examination records, or addresses. Runs entirely locally after initial database download (~556 MB). Faster and more flexible than the API version.
development
Search for items and properties on Wikidata and retrieve entity details, claims, and external identifiers. Supports both keyword search (Wikidata Action API) and semantic/hybrid search (Wikidata Vector Database), plus direct entity retrieval (Special:EntityData) and structured querying (WDQS SPARQL).
testing
Query and explore the TGAZ (Temporal Gazetteer) SQLite database of 82,000+ historical Chinese placenames spanning 763 BCE to 1911 CE. Use this skill whenever the user asks about historical Chinese places, administrative geography, dynastic jurisdictions, place name evolution, or wants to query tgaz.db. Also trigger when the user mentions CHGIS, TGAZ, historical gazetteer, Chinese historical GIS, or asks questions like "what was X called in dynasty Y", "what counties existed in year Z", "where was X located", or any spatial/temporal query about Chinese historical geography. This skill is relevant even for casual questions like "tell me about ancient Chang'an" or "Tang dynasty cities near the Yellow River".
development
Search the National Library Board (NLB) Singapore catalog for books, audiovisual materials, and digital resources via the official Catalogue API. Use this skill whenever the user wants to search Singapore's national library holdings, check book availability across NLB branches, look up titles by ISBN/BRN, find new arrivals, browse checkout trends, or retrieve detailed bibliographic records from NLB. Triggers when referencing Singapore library collections, Southeast Asian library holdings, or NLB resources.