一键导入
paperless-ngx
Use for Paperless-NGX API work: search, upload, download, metadata, tags, status, logs, bulk ops. Triggers: paperless.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Use for Paperless-NGX API work: search, upload, download, metadata, tags, status, logs, bulk ops. Triggers: paperless.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | paperless-ngx |
| description | Use for Paperless-NGX API work: search, upload, download, metadata, tags, status, logs, bulk ops. Triggers: paperless. |
Interact with a Paperless-NGX instance via its REST API using ocli
(openapi-to-cli) wrapped in a
small Python CLI.
All paths below are relative to this skill directory. When invoking the scripts from elsewhere, use their absolute path.
Connection details come entirely from the environment — nothing is hardcoded:
| Variable | Required | Purpose |
|---|---|---|
PAPERLESS_URL | yes | Base URL of your instance, e.g. https://paperless.example.com |
PAPERLESS_API_TOKEN | yes* | API token, sent as Authorization: Token <value> |
PAPERLESS_KEYRING_SERVICE | no | Keyring service name to read the token from (default: paperless) |
* The token may instead be stored in the system keyring (see Prerequisites); the env var takes precedence when both are present.
Create the token in Paperless: Settings → My Account → API Token.
ocli in PATH (npm install -g openapi-to-cli)PAPERLESS_URL exportedexport PAPERLESS_URL=https://paperless.example.com
# either:
export PAPERLESS_API_TOKEN=<your-token>
# or store it in the keyring (service defaults to "paperless"):
keyring set paperless PAPERLESS_API_TOKEN
See .env.example for a copy-ready template of these variables;
load it into your shell (set -a && source <your-env-file> && set +a) or export
the variables directly.
The
pcli.pyscript pinsocli's working directory to this repo root, so the.ocli/profile cache (which contains your token) always lands inside this repo where.gitignorecovers it — no matter which directory you launch the script from.
python3 scripts/pcli.py setup
This configures the paperless ocli profile with the pn_ command prefix,
pointing at $PAPERLESS_URL.
# Natural language search (BM25 ranking)
python3 scripts/pcli.py search "list documents by tag"
# Regex search
python3 scripts/pcli.py search-regex "documents.*list"
# List all commands
python3 scripts/pcli.py list
python3 scripts/pcli.py inspect pn_api_documents
python3 scripts/pcli.py run pn_api_documents --page 1 --page_size 25
python3 scripts/pcli.py run pn_api_documents_id_get --id 42
python3 scripts/pcli.py run pn_api_search --query "invoice 2024"
Pipe through jq for filtering:
python3 scripts/pcli.py run pn_api_documents --page_size 5 | jq '.results[].title'
| Task | Command |
|---|---|
| List recent documents | run pn_api_documents --ordering -created --page_size 10 |
| Search documents | run pn_api_search --query "search terms" |
| Get document details | run pn_api_documents_id_get --id <ID> |
| Download document | run pn_api_documents_id_download --id <ID> |
| Upload document | Use pcli_upload.py (see below) — NOT ocli |
| Update document | Use pcli_update.py (see below) — NOT ocli |
| Delete one document | run pn_api_documents_id_delete --id <ID> (see "Deleting documents") |
| List tags | run pn_api_tags_get |
| List correspondents | run pn_api_correspondents_get |
| List document types | run pn_api_document_types_get |
| Statistics | run pn_api_statistics |
| System status | run pn_api_status (requires admin token) |
Do NOT use pcli.py run pn_api_documents_id_patch — ocli cannot serialize array fields (tags, permissions). It passes them as strings instead of JSON integer arrays, so the API may return 200 OK but the data silently does not change.
Use the standalone scripts/pcli_update.py script:
# Update tags (comma-separated integer IDs — replaces the full tag list):
python3 scripts/pcli_update.py --id 49 --tags 54,55
# Update multiple fields:
python3 scripts/pcli_update.py \
--id 49 \
--title "New title" \
--correspondent 40 \
--tags 54,55
pcli_update.py sends a proper JSON PATCH via urllib, replaces the full tag list
when --tags is provided, and verifies the result with a follow-up GET.
Supported fields: --title, --tags, --correspondent, --document_type,
--storage_path, --created.
pcli.py run pn_api_tags_post --name X works but creates an owner-only object — the new tag/correspondent/document_type is visible only to the API token's user (default owner=<token-user-id>). Other users (or household/group members) then see "object does not exist" when they try to attach it. Symptom: a subsequent pn_api_documents_id_patch with the new id fails 400 Invalid pk "N" - object does not exist.
To create shared metadata visible to a group, POST with owner: null plus a
set_permissions block granting your target group. Send the JSON via urllib
(ocli cannot serialize the nested permissions object):
body = {
"name": "investment",
"owner": None,
"set_permissions": {
# Replace <GROUP_ID> with the id of the group that should see it.
# List groups with: pcli.py run pn_api_groups_get
"view": {"users": [], "groups": [<GROUP_ID>]},
"change": {"users": [], "groups": [<GROUP_ID>]},
},
}
# POST /api/tags/ (or /api/correspondents/, /api/document_types/, /api/storage_paths/)
Recovery for an existing owner-only object: PATCH it with the same
owner: null + set_permissions payload. The id stays the same; only
visibility widens.
Do NOT use pcli.py run pn_api_documents_post_document — ocli cannot serialize multipart bodies.
Use scripts/pcli_upload.py (urllib-based, supports single + batch + metadata at upload time):
# Single file with metadata
python3 scripts/pcli_upload.py --file invoice.pdf \
--title "Invoice 2026-05" --correspondent 40 --tag 54 --tag 55
# Batch — one upload per --file. Shared metadata applies to all.
# --title is FORBIDDEN in batch (would create duplicate titles).
python3 scripts/pcli_upload.py --file a.pdf --file b.pdf \
--correspondent 40 --tag 54
# Skip OCR/consume polling (return task UUIDs instead of document ids)
python3 scripts/pcli_upload.py --file a.pdf --no-wait
Supported flags: --file (repeatable, required), --title, --correspondent,
--document_type, --storage_path, --tag (repeatable), --asn, --created
(YYYY-MM-DD), --no-wait.
Default behaviour: after each upload, polls /api/tasks/?task_id=<uuid> every 2s
(timeout 5min) until status leaves PENDING/STARTED, then prints document_id.
The server consume queue is serial — one document is OCR'd/consumed at a
time. For a large batch (hundreds of files), do NOT call pcli_upload.py once
per file in the foreground waiting for each consume; a foreground wait blocks
for the whole queue. Decouple the POST from the consume instead:
--no-wait. This returns the task UUID immediately
instead of polling, so all POSTs finish quickly while the server consumes
asynchronously. Set per-file metadata at POST time (--correspondent,
--document_type, --created, repeated --tag). Batch mode forbids
--title (would duplicate titles), so run one POST per file when each needs
its own title. Collect the returned UUIDs.pn_api_tasks and confirm your tasks are PENDING/STARTED/SUCCESS with
FAILURE count = 0. A duplicate (same SHA256) shows up here as a FAILURE
with a Not consuming …: It is a duplicate result — that is the only place
you see it; the POST itself still returned 200.pn_api_tasks until PENDING+STARTED reaches 0. Use a single background
waiter that exits once when drained, not a chatty short-interval poll.Some instances run automations (e.g. classification rules, OCR/LLM enrichment
add-ons) that fire on every consumed document and may add tags, change the
document_type, or rewrite the title shortly after upload. If yours does:
created date, correspondent) as the foundation, and let the
automation enrich type/topic tags on top.Paperless auto-dedup is only byte-identical (SHA256 of file content). It does NOT catch the same logical document arriving in a different form — e.g. a phone-scanned image of an order and a downloaded PDF of the same order have different bytes, so both consume successfully and you end up with two documents for one thing.
Before a bulk upload whose items might already exist as scans (or vice versa), search for each item's stable identifier in existing content FIRST:
# order id / invoice number / any token that appears in the document body
python3 scripts/pcli.py run pn_api_search --query "<identifier>" --page_size 10 \
| jq -r '.results[] | "#\(.id) \(.title) | orig=\(.original_file_name)"'
If a pre-existing match turns up, treat the pair as a possible duplicate and decide deliberately — never auto-delete. After an approved delete, re-search the identifier to confirm exactly one document remains.
Single document: pn_api_documents_id_delete works directly (no array body,
so unlike PATCH/bulk_edit it does NOT hit the ocli array-serialization bug):
python3 scripts/pcli.py run pn_api_documents_id_delete --id 21
A successful delete returns an empty body with exit 0 (HTTP 204 No Content) — empty output is success here, not a failure. Always verify with a follow-up GET that returns 404:
# expect "not found" / 404 — the doc is gone
python3 scripts/pcli.py run pn_api_documents_id_get --id 21
Deletion is destructive and irreversible — get explicit confirmation before
deleting. It removes the document AND its files; it is a different operation
from acknowledging a failed consume task (see "File Tasks" below — that only
clears a task row, documents are untouched). For deleting many documents at
once, pn_api_documents_bulk_edit --method delete exists but is a destructive
bulk method — confirm the target id list first and verify each removal
afterward.
For the full list of endpoints, filters, and schemas, read references/api_overview.md.
pcli.py paths are relative to this skill directory. Use the full path when invoking from elsewhere.jq for extraction.--page, --page_size, --ordering.--name__icontains, --tags__id__all, etc.search or search-regex first.pn_ prefix distinguishes Paperless commands from other ocli profiles.ocli supports multiple API profiles. Only one profile is active at a time. If commands fail with "Command pn_api_... is not available for profile <other>", the wrong profile is active.
# Check which profile is active
ocli profiles list
# Switch to paperless
ocli use paperless
# Or re-run setup which activates the profile automatically
python3 scripts/pcli.py setup
pcli.py setup always activates the paperless profile at the end, so re-running setup is the safest fix.
The Settings → File Tasks UI list is /api/tasks/. Failed consume jobs stay
in the list until acknowledged; they are NOT data loss.
Duplicate detection is by SHA256 of file CONTENT, not filename. A
"Not consuming X: It is a duplicate of <title> (#<id>)" failure means the
exact bytes already exist as document #<id> (see the task's
related_document field). Identical filenames are irrelevant — Paperless would
accept a same-named file with different content. Do not treat these as false
positives; the original document is already in the system.
List failed tasks:
python3 scripts/pcli.py run pn_api_tasks --status FAILURE \
| jq -r '(if type=="array" then . else .results end)[] | "id=\(.id) related=\(.related_document) \(.result)"'
Acknowledging (clearing) tasks via API requires the change_paperlesstask
permission. A normal user token typically has only view_paperlesstask, so
POST /api/tasks/acknowledge/ returns
403 {"detail":"You do not have permission to perform this action."}. Use a
token belonging to a user who has that permission (e.g. an admin/superuser). The
example below reads it from PAPERLESS_API_TOKEN_ADMIN — set that yourself to an
admin token; it is only needed for acknowledging tasks and no script reads it:
# correct endpoint is /api/tasks/acknowledge/ — NOT /api/acknowledge_tasks/
curl -s -X POST "$PAPERLESS_URL/api/tasks/acknowledge/" \
-H "Authorization: Token $PAPERLESS_API_TOKEN_ADMIN" -H "Content-Type: application/json" \
-d '{"tasks":[3817,3824,3825]}'
ocli's pn_api_tasks_acknowledge cannot serialize the tasks array (same
array-body limitation as PATCH) — use curl/urllib directly.
Documents are untouched — only the failed-task rows are marked acknowledged.
pn_api_documents_id_patch and pn_api_documents_bulk_edit silently fail when the request includes array fields (tags). The API may return 200 OK but data does not change. Always use pcli_update.py for document mutations.scripts/pcli_upload.py (urllib-based) for document uploads.