easypaperless

easypaperless — Python API wrapper for paperless-ngx.

 1"""easypaperless — Python API wrapper for paperless-ngx."""
 2
 3import importlib.metadata
 4import logging
 5
 6__version__: str = importlib.metadata.version("easypaperless")
 7
 8from easypaperless.client import PaperlessClient
 9from easypaperless.exceptions import (
10    AuthError,
11    NotFoundError,
12    PaperlessError,
13    ServerError,
14    TaskTimeoutError,
15    UploadError,
16    ValidationError,
17)
18from easypaperless.models import (
19    MatchingAlgorithm,
20    Correspondent,
21    CustomField,
22    CustomFieldValue,
23    Document,
24    DocumentMetadata,
25    DocumentNote,
26    DocumentType,
27    FieldDataType,
28    FileMetadataEntry,
29    PermissionSet,
30    SearchHit,
31    SetPermissions,
32    StoragePath,
33    Tag,
34    Task,
35    TaskStatus,
36)
37from easypaperless.sync import SyncPaperlessClient
38
39logging.getLogger("easypaperless").addHandler(logging.NullHandler())
40
41__all__ = [
42    "__version__",
43    # Clients
44    "PaperlessClient",
45    "SyncPaperlessClient",
46    # Models
47    "MatchingAlgorithm",
48    "Correspondent",
49    "CustomField",
50    "CustomFieldValue",
51    "Document",
52    "DocumentMetadata",
53    "DocumentNote",
54    "DocumentType",
55    "FieldDataType",
56    "FileMetadataEntry",
57    "PermissionSet",
58    "SearchHit",
59    "SetPermissions",
60    "StoragePath",
61    "Tag",
62    "Task",
63    "TaskStatus",
64    # Exceptions
65    "PaperlessError",
66    "AuthError",
67    "NotFoundError",
68    "ValidationError",
69    "ServerError",
70    "UploadError",
71    "TaskTimeoutError",
72]
__version__: str = '0.1.0'
class PaperlessClient(easypaperless._internal.mixins.documents.DocumentsMixin, easypaperless._internal.mixins.notes.NotesMixin, easypaperless._internal.mixins.upload.UploadMixin, easypaperless._internal.mixins.document_bulk.DocumentBulkMixin, easypaperless._internal.mixins.non_document_bulk.NonDocumentBulkMixin, easypaperless._internal.mixins.tags.TagsMixin, easypaperless._internal.mixins.correspondents.CorrespondentsMixin, easypaperless._internal.mixins.document_types.DocumentTypesMixin, easypaperless._internal.mixins.storage_paths.StoragePathsMixin, easypaperless._internal.mixins.custom_fields.CustomFieldsMixin, easypaperless.client._ClientCore):
105class PaperlessClient(
106    DocumentsMixin,
107    NotesMixin,
108    UploadMixin,
109    DocumentBulkMixin,
110    NonDocumentBulkMixin,
111    TagsMixin,
112    CorrespondentsMixin,
113    DocumentTypesMixin,
114    StoragePathsMixin,
115    CustomFieldsMixin,
116    _ClientCore,
117):
118    """Async client for the paperless-ngx API.
119
120    All operations are flat methods on the client.  String names are resolved
121    to integer IDs automatically wherever the API requires IDs (tags,
122    correspondents, document types, storage paths).
123
124    Use as an async context manager to ensure the underlying HTTP connection
125    pool is closed when you are done:
126
127    Example:
128        async with PaperlessClient(url="http://localhost:8000", api_key="abc") as client:
129            docs = await client.list_documents()
130    """
131
132    # Explicit assignments so pdoc documents these inherited methods.
133    # Documents
134    list_documents = DocumentsMixin.list_documents
135    get_document = DocumentsMixin.get_document
136    get_document_metadata = DocumentsMixin.get_document_metadata
137    update_document = DocumentsMixin.update_document
138    delete_document = DocumentsMixin.delete_document
139    download_document = DocumentsMixin.download_document
140    # Notes
141    get_notes = NotesMixin.get_notes
142    create_note = NotesMixin.create_note
143    delete_note = NotesMixin.delete_note
144    # Upload
145    upload_document = UploadMixin.upload_document
146    # Document bulk operations
147    bulk_edit = DocumentBulkMixin.bulk_edit
148    bulk_add_tag = DocumentBulkMixin.bulk_add_tag
149    bulk_remove_tag = DocumentBulkMixin.bulk_remove_tag
150    bulk_modify_tags = DocumentBulkMixin.bulk_modify_tags
151    bulk_delete = DocumentBulkMixin.bulk_delete
152    bulk_set_correspondent = DocumentBulkMixin.bulk_set_correspondent
153    bulk_set_document_type = DocumentBulkMixin.bulk_set_document_type
154    bulk_set_storage_path = DocumentBulkMixin.bulk_set_storage_path
155    bulk_modify_custom_fields = DocumentBulkMixin.bulk_modify_custom_fields
156    bulk_set_permissions = DocumentBulkMixin.bulk_set_permissions
157    # Non-document bulk operations
158    bulk_edit_objects = NonDocumentBulkMixin.bulk_edit_objects
159    bulk_delete_tags = NonDocumentBulkMixin.bulk_delete_tags
160    bulk_delete_correspondents = NonDocumentBulkMixin.bulk_delete_correspondents
161    bulk_delete_document_types = NonDocumentBulkMixin.bulk_delete_document_types
162    bulk_delete_storage_paths = NonDocumentBulkMixin.bulk_delete_storage_paths
163    bulk_set_permissions_tags = NonDocumentBulkMixin.bulk_set_permissions_tags
164    bulk_set_permissions_correspondents = NonDocumentBulkMixin.bulk_set_permissions_correspondents
165    bulk_set_permissions_document_types = NonDocumentBulkMixin.bulk_set_permissions_document_types
166    bulk_set_permissions_storage_paths = NonDocumentBulkMixin.bulk_set_permissions_storage_paths
167    # Tags
168    list_tags = TagsMixin.list_tags
169    get_tag = TagsMixin.get_tag
170    create_tag = TagsMixin.create_tag
171    update_tag = TagsMixin.update_tag
172    delete_tag = TagsMixin.delete_tag
173    # Correspondents
174    list_correspondents = CorrespondentsMixin.list_correspondents
175    get_correspondent = CorrespondentsMixin.get_correspondent
176    create_correspondent = CorrespondentsMixin.create_correspondent
177    update_correspondent = CorrespondentsMixin.update_correspondent
178    delete_correspondent = CorrespondentsMixin.delete_correspondent
179    # Document types
180    list_document_types = DocumentTypesMixin.list_document_types
181    get_document_type = DocumentTypesMixin.get_document_type
182    create_document_type = DocumentTypesMixin.create_document_type
183    update_document_type = DocumentTypesMixin.update_document_type
184    delete_document_type = DocumentTypesMixin.delete_document_type
185    # Storage paths
186    list_storage_paths = StoragePathsMixin.list_storage_paths
187    get_storage_path = StoragePathsMixin.get_storage_path
188    create_storage_path = StoragePathsMixin.create_storage_path
189    update_storage_path = StoragePathsMixin.update_storage_path
190    delete_storage_path = StoragePathsMixin.delete_storage_path
191    # Custom fields
192    list_custom_fields = CustomFieldsMixin.list_custom_fields
193    get_custom_field = CustomFieldsMixin.get_custom_field
194    create_custom_field = CustomFieldsMixin.create_custom_field
195    update_custom_field = CustomFieldsMixin.update_custom_field
196    delete_custom_field = CustomFieldsMixin.delete_custom_field
197
198
199
200    def __init__(
201        self,
202        url: str,
203        api_key: str,
204        *,
205        timeout: float = 30.0,
206        poll_interval: float = 2.0,
207        poll_timeout: float = 60.0,
208    ) -> None:
209        """Create an async paperless-ngx client.
210
211        Args:
212            url: Base URL of the paperless-ngx instance
213                (e.g. ``"http://localhost:8000"``).
214            api_key: API token.  Generate one in paperless-ngx under
215                *Settings → API → Generate Token*.
216            timeout: Default request timeout in seconds.  Individual
217                operations (e.g. uploads) may override this per-call.
218                Default: ``30.0``.
219            poll_interval: Seconds between status checks when ``wait=True``
220                is passed to :meth:`upload_document`.  Default: ``2.0``.
221            poll_timeout: Maximum seconds to wait for a document to finish
222                processing before raising
223                :exc:`~easypaperless.exceptions.TaskTimeoutError`.
224                Default: ``60.0``.
225        """
226        super().__init__(
227            url, api_key, timeout=timeout, poll_interval=poll_interval, poll_timeout=poll_timeout
228        )
229
230    async def __aenter__(self) -> PaperlessClient:
231        return self
232
233    async def __aexit__(self, *args: Any) -> None:
234        await self.close()

Async client for the paperless-ngx API.

All operations are flat methods on the client. String names are resolved to integer IDs automatically wherever the API requires IDs (tags, correspondents, document types, storage paths).

Use as an async context manager to ensure the underlying HTTP connection pool is closed when you are done:

Example: async with PaperlessClient(url="http://localhost:8000", api_key="abc") as client: docs = await client.list_documents()

PaperlessClient( url: str, api_key: str, *, timeout: float = 30.0, poll_interval: float = 2.0, poll_timeout: float = 60.0)
200    def __init__(
201        self,
202        url: str,
203        api_key: str,
204        *,
205        timeout: float = 30.0,
206        poll_interval: float = 2.0,
207        poll_timeout: float = 60.0,
208    ) -> None:
209        """Create an async paperless-ngx client.
210
211        Args:
212            url: Base URL of the paperless-ngx instance
213                (e.g. ``"http://localhost:8000"``).
214            api_key: API token.  Generate one in paperless-ngx under
215                *Settings → API → Generate Token*.
216            timeout: Default request timeout in seconds.  Individual
217                operations (e.g. uploads) may override this per-call.
218                Default: ``30.0``.
219            poll_interval: Seconds between status checks when ``wait=True``
220                is passed to :meth:`upload_document`.  Default: ``2.0``.
221            poll_timeout: Maximum seconds to wait for a document to finish
222                processing before raising
223                :exc:`~easypaperless.exceptions.TaskTimeoutError`.
224                Default: ``60.0``.
225        """
226        super().__init__(
227            url, api_key, timeout=timeout, poll_interval=poll_interval, poll_timeout=poll_timeout
228        )

Create an async paperless-ngx client.

Args: url: Base URL of the paperless-ngx instance (e.g. "http://localhost:8000"). api_key: API token. Generate one in paperless-ngx under *Settings → API → Generate Token*. timeout: Default request timeout in seconds. Individual operations (e.g. uploads) may override this per-call. Default: 30.0. poll_interval: Seconds between status checks when wait=True is passed to upload_document(). Default: 2.0. poll_timeout: Maximum seconds to wait for a document to finish processing before raising ~easypaperless.exceptions.TaskTimeoutError. Default: 60.0.

async def list_documents( self, *, search: str | None = None, search_mode: str = 'title_or_text', ids: list[int] | None = None, tags: list[int | str] | None = None, any_tags: list[int | str] | None = None, exclude_tags: list[int | str] | None = None, correspondent: int | str | None = None, any_correspondent: list[int | str] | None = None, exclude_correspondents: list[int | str] | None = None, document_type: int | str | None = None, any_document_type: list[int | str] | None = None, exclude_document_types: list[int | str] | None = None, storage_path: int | str | None = None, any_storage_paths: list[int | str] | None = None, exclude_storage_paths: list[int | str] | None = None, owner: int | None = None, exclude_owners: list[int] | None = None, custom_fields: list[int | str] | None = None, any_custom_fields: list[int | str] | None = None, exclude_custom_fields: list[int | str] | None = None, custom_field_query: list[typing.Any] | None = None, archive_serial_number: int | None = None, archive_serial_number_from: int | None = None, archive_serial_number_till: int | None = None, created_after: datetime.date | str | None = None, created_before: datetime.date | str | None = None, added_after: datetime.date | datetime.datetime | str | None = None, added_from: datetime.date | datetime.datetime | str | None = None, added_before: datetime.date | datetime.datetime | str | None = None, added_until: datetime.date | datetime.datetime | str | None = None, modified_after: datetime.date | datetime.datetime | str | None = None, modified_from: datetime.date | datetime.datetime | str | None = None, modified_before: datetime.date | datetime.datetime | str | None = None, modified_until: datetime.date | datetime.datetime | str | None = None, checksum: str | None = None, page_size: int = 25, page: int | None = None, ordering: str | None = None, descending: bool = False, max_results: int | None = None, on_page: Optional[Callable[[int, int | None], NoneType]] = None) -> list[Document]:
115    async def list_documents(
116        self,
117        *,
118        search: str | None = None,
119        search_mode: str = "title_or_text",
120        ids: list[int] | None = None,
121        tags: list[int | str] | None = None,
122        any_tags: list[int | str] | None = None,
123        exclude_tags: list[int | str] | None = None,
124        correspondent: int | str | None = None,
125        any_correspondent: list[int | str] | None = None,
126        exclude_correspondents: list[int | str] | None = None,
127        document_type: int | str | None = None,
128        any_document_type: list[int | str] | None = None,
129        exclude_document_types: list[int | str] | None = None,
130        storage_path: int | str | None = None,
131        any_storage_paths: list[int | str] | None = None,
132        exclude_storage_paths: list[int | str] | None = None,
133        owner: int | None = None,
134        exclude_owners: list[int] | None = None,
135        custom_fields: list[int | str] | None = None,
136        any_custom_fields: list[int | str] | None = None,
137        exclude_custom_fields: list[int | str] | None = None,
138        custom_field_query: list[Any] | None = None,
139        archive_serial_number: int | None = None,
140        archive_serial_number_from: int | None = None,
141        archive_serial_number_till: int | None = None,
142        created_after: date | str | None = None,
143        created_before: date | str | None = None,
144        added_after: date | datetime | str | None = None,
145        added_from: date | datetime | str | None = None,
146        added_before: date | datetime | str | None = None,
147        added_until: date | datetime | str | None = None,
148        modified_after: date | datetime | str | None = None,
149        modified_from: date | datetime | str | None = None,
150        modified_before: date | datetime | str | None = None,
151        modified_until: date | datetime | str | None = None,
152        checksum: str | None = None,
153        page_size: int = 25,
154        page: int | None = None,
155        ordering: str | None = None,
156        descending: bool = False,
157        max_results: int | None = None,
158        on_page: Callable[[int, int | None], None] | None = None,
159    ) -> list[Document]:
160        """Return a filtered list of documents.
161
162        All tag, correspondent, document-type, storage-path, and custom-field
163        parameters accept either integer IDs or string names — names are
164        resolved to IDs transparently.
165
166        Args:
167            search: Search string.  Behaviour depends on ``search_mode``.
168            search_mode: How ``search`` is applied.  One of:
169
170                * ``"title_or_text"`` *(default)* — full-text search across
171                  title and OCR content (Whoosh FTS, raw API ``search``
172                  parameter).
173                * ``"title"`` — case-insensitive substring match on title
174                  only (raw API ``title__icontains``).
175                * ``"query"`` — paperless query language, e.g.
176                  ``"tag:invoice date:[2024 TO *]"`` (raw API ``query``).
177                * ``"original_filename"`` — case-insensitive substring match
178                  on the original file name (raw API
179                  ``original_filename__icontains``).
180
181            ids: Return only documents whose ID is in this list.
182            tags: Documents must have **all** of these tags (AND semantics).
183                Accepts tag IDs or tag names.
184            any_tags: Documents must have **at least one** of these tags
185                (OR semantics).  Accepts tag IDs or tag names.
186            exclude_tags: Documents must have **none** of these tags.
187                Accepts tag IDs or tag names.
188            correspondent: Filter to documents assigned to exactly this
189                correspondent.  Accepts a correspondent ID or name.
190            any_correspondent: Filter to documents assigned to **any** of
191                these correspondents (OR semantics).  Accepts IDs or names.
192                Takes precedence over ``correspondent`` when both are given.
193            exclude_correspondents: Exclude documents assigned to any of
194                these correspondents.  Accepts IDs or names.
195            document_type: Filter to documents of exactly this type.
196                Accepts a document-type ID or name.
197            any_document_type: Filter to documents whose type is **any** of
198                these (OR semantics).  Accepts IDs or names.  Takes
199                precedence over ``document_type`` when both are given.
200            exclude_document_types: Exclude documents whose type is any of
201                these.  Accepts IDs or names.
202            storage_path: Filter to documents assigned to exactly this
203                storage path.  Accepts a storage path ID or name.
204            any_storage_paths: Filter to documents assigned to **any** of
205                these storage paths (OR semantics).  Accepts IDs or names.
206                Takes precedence over ``storage_path`` when both are given.
207            exclude_storage_paths: Exclude documents assigned to any of
208                these storage paths.  Accepts IDs or names.
209            owner: Filter to documents owned by this user ID.
210            exclude_owners: Exclude documents owned by any of these user IDs.
211            custom_fields: Documents must have **all** of these custom fields
212                set (AND semantics).  Accepts custom field IDs or names.
213            any_custom_fields: Documents must have **at least one** of these
214                custom fields set (OR semantics).  Accepts IDs or names.
215            exclude_custom_fields: Documents must have **none** of these
216                custom fields set.  Accepts IDs or names.
217            custom_field_query: Filter documents by custom field values using
218                a structured query.  Simple form:
219                ``["Invoice Amount", "gt", 100]``.  Compound form:
220                ``["AND", [["Amount", "gt", 100], ["Status", "exact", "paid"]]]``.
221                Field references accept integer IDs or string names (resolved
222                server-side, not by the client).
223            archive_serial_number: Filter by exact archive serial number.
224            archive_serial_number_from: Filter by archive serial number
225                greater than or equal to this value.
226            archive_serial_number_till: Filter by archive serial number
227                less than or equal to this value.
228            created_after: ISO-8601 date string or ``date`` object.  Only
229                documents created **after** this date are returned.
230            created_before: ISO-8601 date string or ``date`` object.  Only
231                documents created **before** this date are returned.
232            added_after: Date, datetime, or ISO-8601 string.  Only
233                documents **added** (ingested) after this date/time.
234            added_from: Date, datetime, or ISO-8601 string.  Only
235                documents **added** on or after this date/time.
236            added_before: Date, datetime, or ISO-8601 string.  Only
237                documents **added** before this date/time.
238            added_until: Date, datetime, or ISO-8601 string.  Only
239                documents **added** on or before this date/time.
240            modified_after: Date, datetime, or ISO-8601 string.  Only
241                documents **modified** after this date/time.
242            modified_from: Date, datetime, or ISO-8601 string.  Only
243                documents **modified** on or after this date/time.
244            modified_before: Date, datetime, or ISO-8601 string.  Only
245                documents **modified** before this date/time.
246            modified_until: Date, datetime, or ISO-8601 string.  Only
247                documents **modified** on or before this date/time.
248            checksum: MD5 checksum of the original file (exact match).
249            page_size: Number of results per API page.  Default: ``25``.
250            page: Return only this specific page of results (1-based). When
251                set, auto-pagination is disabled. Default: ``None``.
252            ordering: Field name to sort by. Default: ``None``.
253            descending: When ``True``, reverses the sort direction.
254                Default: ``False``.
255            max_results: Stop after collecting this many documents.
256                Default: ``None``.
257            on_page: Callback invoked after each page fetch.  Receives
258                ``(fetched_so_far, total)``.  Ignored when ``page`` is set.
259
260        Returns:
261            List of :class:`~easypaperless.models.documents.Document`
262            objects.
263        """
264        params: dict[str, Any] = {"page_size": page_size}
265
266        if search is not None:
267            api_param = _SEARCH_MODE_MAP.get(search_mode, "search")
268            params[api_param] = search
269
270        if ids is not None:
271            params["id__in"] = ",".join(str(i) for i in ids)
272
273        if tags is not None:
274            resolved = await self._resolver.resolve_list("tags", tags)
275            params["tags__id__all"] = ",".join(str(t) for t in resolved)
276
277        if any_tags is not None:
278            resolved = await self._resolver.resolve_list("tags", any_tags)
279            params["tags__id__in"] = ",".join(str(t) for t in resolved)
280
281        if exclude_tags is not None:
282            resolved = await self._resolver.resolve_list("tags", exclude_tags)
283            params["tags__id__none"] = ",".join(str(t) for t in resolved)
284
285        if any_correspondent is not None:
286            resolved = await self._resolver.resolve_list("correspondents", any_correspondent)
287            params["correspondent__id__in"] = ",".join(str(c) for c in resolved)
288        elif correspondent is not None:
289            resolved_id = await self._resolver.resolve("correspondents", correspondent)
290            params["correspondent__id__in"] = resolved_id
291
292        if exclude_correspondents is not None:
293            resolved = await self._resolver.resolve_list("correspondents", exclude_correspondents)
294            params["correspondent__id__none"] = ",".join(str(c) for c in resolved)
295
296        if any_document_type is not None:
297            resolved = await self._resolver.resolve_list("document_types", any_document_type)
298            params["document_type__id__in"] = ",".join(str(d) for d in resolved)
299        elif document_type is not None:
300            resolved_id = await self._resolver.resolve("document_types", document_type)
301            params["document_type"] = resolved_id
302
303        if exclude_document_types is not None:
304            resolved = await self._resolver.resolve_list("document_types", exclude_document_types)
305            params["document_type__id__none"] = ",".join(str(d) for d in resolved)
306
307        # Storage path filters
308        if any_storage_paths is not None:
309            resolved = await self._resolver.resolve_list("storage_paths", any_storage_paths)
310            params["storage_path__id__in"] = ",".join(str(s) for s in resolved)
311        elif storage_path is not None:
312            resolved_id = await self._resolver.resolve("storage_paths", storage_path)
313            params["storage_path__id__in"] = resolved_id
314
315        if exclude_storage_paths is not None:
316            resolved = await self._resolver.resolve_list("storage_paths", exclude_storage_paths)
317            params["storage_path__id__none"] = ",".join(str(s) for s in resolved)
318
319        # Owner filters
320        if owner is not None:
321            params["owner__id__in"] = owner
322
323        if exclude_owners is not None:
324            params["owner__id__none"] = ",".join(str(o) for o in exclude_owners)
325
326        # Custom field existence filters
327        if custom_fields is not None:
328            resolved = await self._resolver.resolve_list("custom_fields", custom_fields)
329            params["custom_fields__id__all"] = ",".join(str(f) for f in resolved)
330
331        if any_custom_fields is not None:
332            resolved = await self._resolver.resolve_list("custom_fields", any_custom_fields)
333            params["custom_fields__id__in"] = ",".join(str(f) for f in resolved)
334
335        if exclude_custom_fields is not None:
336            resolved = await self._resolver.resolve_list("custom_fields", exclude_custom_fields)
337            params["custom_fields__id__none"] = ",".join(str(f) for f in resolved)
338
339        # Custom field value query
340        if custom_field_query is not None:
341            params["custom_field_query"] = json.dumps(custom_field_query)
342
343        # Archive serial number filters
344        if archive_serial_number is not None:
345            params["archive_serial_number"] = archive_serial_number
346
347        if archive_serial_number_from is not None:
348            params["archive_serial_number__gte"] = archive_serial_number_from
349
350        if archive_serial_number_till is not None:
351            params["archive_serial_number__lte"] = archive_serial_number_till
352
353        # Date filters — created (date only)
354        if created_after is not None:
355            params["created__date__gt"] = self._format_date_value(created_after)
356
357        if created_before is not None:
358            params["created__date__lt"] = self._format_date_value(created_before)
359
360        # Date filters — added (date or datetime)
361        if added_after is not None:
362            key = "added__gt" if self._is_datetime(added_after) else "added__date__gt"
363            params[key] = self._format_date_value(added_after)
364
365        if added_from is not None:
366            key = "added__gte" if self._is_datetime(added_from) else "added__date__gte"
367            params[key] = self._format_date_value(added_from)
368
369        if added_before is not None:
370            key = "added__lt" if self._is_datetime(added_before) else "added__date__lt"
371            params[key] = self._format_date_value(added_before)
372
373        if added_until is not None:
374            key = "added__lte" if self._is_datetime(added_until) else "added__date__lte"
375            params[key] = self._format_date_value(added_until)
376
377        # Date filters — modified (date or datetime)
378        if modified_after is not None:
379            key = "modified__gt" if self._is_datetime(modified_after) else "modified__date__gt"
380            params[key] = self._format_date_value(modified_after)
381
382        if modified_from is not None:
383            key = "modified__gte" if self._is_datetime(modified_from) else "modified__date__gte"
384            params[key] = self._format_date_value(modified_from)
385
386        if modified_before is not None:
387            key = "modified__lt" if self._is_datetime(modified_before) else "modified__date__lt"
388            params[key] = self._format_date_value(modified_before)
389
390        if modified_until is not None:
391            key = "modified__lte" if self._is_datetime(modified_until) else "modified__date__lte"
392            params[key] = self._format_date_value(modified_until)
393
394        if checksum is not None:
395            params["checksum__iexact"] = checksum
396
397        if ordering is not None:
398            params["ordering"] = f"-{ordering}" if descending else ordering
399
400        if page is not None:
401            params["page"] = page
402            resp = await self._session.get("/documents/", params=params)
403            items = resp.json().get("results", [])
404            if max_results is not None:
405                items = items[:max_results]
406            return [Document.model_validate(item) for item in items]
407
408        items = await self._session.get_all_pages(
409            "/documents/", params, max_results=max_results, on_page=on_page
410        )
411        return [Document.model_validate(item) for item in items]

Return a filtered list of documents.

All tag, correspondent, document-type, storage-path, and custom-field parameters accept either integer IDs or string names — names are resolved to IDs transparently.

Args: search: Search string. Behaviour depends on search_mode. search_mode: How search is applied. One of:

    * ``"title_or_text"`` *(default)* — full-text search across
      title and OCR content (Whoosh FTS, raw API ``search``
      parameter).
    * ``"title"`` — case-insensitive substring match on title
      only (raw API ``title__icontains``).
    * ``"query"`` — paperless query language, e.g.
      ``"tag:invoice date:[2024 TO *]"`` (raw API ``query``).
    * ``"original_filename"`` — case-insensitive substring match
      on the original file name (raw API
      ``original_filename__icontains``).

ids: Return only documents whose ID is in this list.
tags: Documents must have **all** of these tags (AND semantics).
    Accepts tag IDs or tag names.
any_tags: Documents must have **at least one** of these tags
    (OR semantics).  Accepts tag IDs or tag names.
exclude_tags: Documents must have **none** of these tags.
    Accepts tag IDs or tag names.
correspondent: Filter to documents assigned to exactly this
    correspondent.  Accepts a correspondent ID or name.
any_correspondent: Filter to documents assigned to **any** of
    these correspondents (OR semantics).  Accepts IDs or names.
    Takes precedence over ``correspondent`` when both are given.
exclude_correspondents: Exclude documents assigned to any of
    these correspondents.  Accepts IDs or names.
document_type: Filter to documents of exactly this type.
    Accepts a document-type ID or name.
any_document_type: Filter to documents whose type is **any** of
    these (OR semantics).  Accepts IDs or names.  Takes
    precedence over ``document_type`` when both are given.
exclude_document_types: Exclude documents whose type is any of
    these.  Accepts IDs or names.
storage_path: Filter to documents assigned to exactly this
    storage path.  Accepts a storage path ID or name.
any_storage_paths: Filter to documents assigned to **any** of
    these storage paths (OR semantics).  Accepts IDs or names.
    Takes precedence over ``storage_path`` when both are given.
exclude_storage_paths: Exclude documents assigned to any of
    these storage paths.  Accepts IDs or names.
owner: Filter to documents owned by this user ID.
exclude_owners: Exclude documents owned by any of these user IDs.
custom_fields: Documents must have **all** of these custom fields
    set (AND semantics).  Accepts custom field IDs or names.
any_custom_fields: Documents must have **at least one** of these
    custom fields set (OR semantics).  Accepts IDs or names.
exclude_custom_fields: Documents must have **none** of these
    custom fields set.  Accepts IDs or names.
custom_field_query: Filter documents by custom field values using
    a structured query.  Simple form:
    ``["Invoice Amount", "gt", 100]``.  Compound form:
    ``["AND", [["Amount", "gt", 100], ["Status", "exact", "paid"]]]``.
    Field references accept integer IDs or string names (resolved
    server-side, not by the client).
archive_serial_number: Filter by exact archive serial number.
archive_serial_number_from: Filter by archive serial number
    greater than or equal to this value.
archive_serial_number_till: Filter by archive serial number
    less than or equal to this value.
created_after: ISO-8601 date string or ``date`` object.  Only
    documents created **after** this date are returned.
created_before: ISO-8601 date string or ``date`` object.  Only
    documents created **before** this date are returned.
added_after: Date, datetime, or ISO-8601 string.  Only
    documents **added** (ingested) after this date/time.
added_from: Date, datetime, or ISO-8601 string.  Only
    documents **added** on or after this date/time.
added_before: Date, datetime, or ISO-8601 string.  Only
    documents **added** before this date/time.
added_until: Date, datetime, or ISO-8601 string.  Only
    documents **added** on or before this date/time.
modified_after: Date, datetime, or ISO-8601 string.  Only
    documents **modified** after this date/time.
modified_from: Date, datetime, or ISO-8601 string.  Only
    documents **modified** on or after this date/time.
modified_before: Date, datetime, or ISO-8601 string.  Only
    documents **modified** before this date/time.
modified_until: Date, datetime, or ISO-8601 string.  Only
    documents **modified** on or before this date/time.
checksum: MD5 checksum of the original file (exact match).
page_size: Number of results per API page.  Default: ``25``.
page: Return only this specific page of results (1-based). When
    set, auto-pagination is disabled. Default: ``None``.
ordering: Field name to sort by. Default: ``None``.
descending: When ``True``, reverses the sort direction.
    Default: ``False``.
max_results: Stop after collecting this many documents.
    Default: ``None``.
on_page: Callback invoked after each page fetch.  Receives
    ``(fetched_so_far, total)``.  Ignored when ``page`` is set.

Returns: List of ~easypaperless.models.documents.Document objects.

async def get_document( self, id: int, *, include_metadata: bool = False) -> Document:
38    async def get_document(self, id: int, *, include_metadata: bool = False) -> Document:
39        """Fetch a single document by its ID.
40
41        Args:
42            id: Numeric paperless-ngx document ID.
43            include_metadata: When ``True``, the extended file-level metadata
44                (checksums, sizes, MIME type) is fetched concurrently from
45                ``/documents/{id}/metadata/`` and attached to
46                :attr:`~easypaperless.models.documents.Document.metadata`.
47                Default: ``False``.
48
49        Returns:
50            The :class:`~easypaperless.models.documents.Document` with the
51            given ID.  When ``include_metadata=True`` the ``metadata``
52            attribute is populated; otherwise it is ``None``.
53
54        Raises:
55            ~easypaperless.exceptions.NotFoundError: If no document exists
56                with that ID.
57        """
58        if include_metadata:
59            doc_resp, meta_resp = await asyncio.gather(
60                self._session.get(f"/documents/{id}/"),
61                self._session.get(f"/documents/{id}/metadata/"),
62            )
63            data = doc_resp.json()
64            data["metadata"] = meta_resp.json()
65        else:
66            resp = await self._session.get(f"/documents/{id}/")
67            data = resp.json()
68        return Document.model_validate(data)

Fetch a single document by its ID.

Args: id: Numeric paperless-ngx document ID. include_metadata: When True, the extended file-level metadata (checksums, sizes, MIME type) is fetched concurrently from /documents/{id}/metadata/ and attached to ~easypaperless.models.documents.Document.metadata. Default: False.

Returns: The ~easypaperless.models.documents.Document with the given ID. When include_metadata=True the metadata attribute is populated; otherwise it is None.

Raises: ~easypaperless.exceptions.NotFoundError: If no document exists with that ID.

async def get_document_metadata(self, id: int) -> DocumentMetadata:
70    async def get_document_metadata(self, id: int) -> DocumentMetadata:
71        """Fetch the extended file-level metadata for a document.
72
73        This is a lower-overhead alternative to
74        ``get_document(id, include_metadata=True)`` when you only need the
75        metadata and not the full document object.
76
77        Args:
78            id: Numeric paperless-ngx document ID.
79
80        Returns:
81            A :class:`~easypaperless.models.documents.DocumentMetadata`
82            instance containing checksums, sizes, MIME type, and embedded
83            file metadata.
84
85        Raises:
86            ~easypaperless.exceptions.NotFoundError: If no document exists
87                with that ID.
88        """
89        resp = await self._session.get(f"/documents/{id}/metadata/")
90        return DocumentMetadata.model_validate(resp.json())

Fetch the extended file-level metadata for a document.

This is a lower-overhead alternative to get_document(id, include_metadata=True) when you only need the metadata and not the full document object.

Args: id: Numeric paperless-ngx document ID.

Returns: A ~easypaperless.models.documents.DocumentMetadata instance containing checksums, sizes, MIME type, and embedded file metadata.

Raises: ~easypaperless.exceptions.NotFoundError: If no document exists with that ID.

async def update_document( self, id: int, *, title: str | None = None, content: str | None = None, date: str | None = None, correspondent: int | str | None = None, document_type: int | str | None = None, storage_path: int | str | None = None, tags: list[int | str] | None = None, asn: int | None = None, custom_fields: list[dict[str, typing.Any]] | None = None, owner: int | None = None, set_permissions: SetPermissions | None = None) -> Document:
413    async def update_document(
414        self,
415        id: int,
416        *,
417        title: str | None = None,
418        content: str | None = None,
419        date: str | None = None,
420        correspondent: int | str | None = None,
421        document_type: int | str | None = None,
422        storage_path: int | str | None = None,
423        tags: list[int | str] | None = None,
424        asn: int | None = None,
425        custom_fields: list[dict[str, Any]] | None = None,
426        owner: int | None = None,
427        set_permissions: SetPermissions | None = None,
428    ) -> Document:
429        """Partially update a document (PATCH semantics — only passed fields change).
430
431        Args:
432            id: Numeric ID of the document to update.
433            title: New document title.
434            content: OCR text content of the document.
435            date: Creation date as an ISO-8601 string (``"YYYY-MM-DD"``).
436            correspondent: Correspondent to assign, as an ID or name.
437                Pass ``0`` to clear.
438            document_type: Document type to assign, as an ID or name.
439                Pass ``0`` to clear.
440            storage_path: Storage path to assign, as an ID or name.
441                Pass ``0`` to clear.
442            tags: Full replacement list of tags (IDs or names).  The existing
443                tag list is replaced, not merged.
444            asn: Archive serial number to assign.
445            custom_fields: List of ``{"field": <field_id>, "value": ...}``
446                dicts.  Replaces the existing custom-field values.
447            owner: Numeric user ID to assign as document owner.
448            set_permissions: Explicit view/change permission sets.
449
450        Returns:
451            The updated :class:`~easypaperless.models.documents.Document`.
452        """
453        logger.debug("Updating document id=%d", id)
454        payload: dict[str, Any] = {}
455
456        if title is not None:
457            payload["title"] = title
458        if content is not None:
459            payload["content"] = content
460        if date is not None:
461            payload["created"] = date
462        if correspondent is not None:
463            payload["correspondent"] = await self._resolver.resolve("correspondents", correspondent)
464        if document_type is not None:
465            payload["document_type"] = await self._resolver.resolve("document_types", document_type)
466        if storage_path is not None:
467            payload["storage_path"] = await self._resolver.resolve("storage_paths", storage_path)
468        if tags is not None:
469            payload["tags"] = await self._resolver.resolve_list("tags", tags)
470        if asn is not None:
471            payload["archive_serial_number"] = asn
472        if custom_fields is not None:
473            payload["custom_fields"] = custom_fields
474        if owner is not None:
475            payload["owner"] = owner
476        if set_permissions is not None:
477            payload["set_permissions"] = set_permissions.model_dump()
478
479        resp = await self._session.patch(f"/documents/{id}/", json=payload)
480        return Document.model_validate(resp.json())

Partially update a document (PATCH semantics — only passed fields change).

Args: id: Numeric ID of the document to update. title: New document title. content: OCR text content of the document. date: Creation date as an ISO-8601 string ("YYYY-MM-DD"). correspondent: Correspondent to assign, as an ID or name. Pass 0 to clear. document_type: Document type to assign, as an ID or name. Pass 0 to clear. storage_path: Storage path to assign, as an ID or name. Pass 0 to clear. tags: Full replacement list of tags (IDs or names). The existing tag list is replaced, not merged. asn: Archive serial number to assign. custom_fields: List of {"field": <field_id>, "value": ...} dicts. Replaces the existing custom-field values. owner: Numeric user ID to assign as document owner. set_permissions: Explicit view/change permission sets.

Returns: The updated ~easypaperless.models.documents.Document.

async def delete_document(self, id: int) -> None:
482    async def delete_document(self, id: int) -> None:
483        """Permanently delete a document.
484
485        Args:
486            id: Numeric ID of the document to delete.
487
488        Raises:
489            ~easypaperless.exceptions.NotFoundError: If no document exists
490                with that ID.
491        """
492        logger.debug("Deleting document id=%d", id)
493        await self._session.delete(f"/documents/{id}/")

Permanently delete a document.

Args: id: Numeric ID of the document to delete.

Raises: ~easypaperless.exceptions.NotFoundError: If no document exists with that ID.

async def download_document(self, id: int, *, original: bool = False) -> bytes:
495    async def download_document(self, id: int, *, original: bool = False) -> bytes:
496        """Download the binary content of a document.
497
498        Args:
499            id: Numeric ID of the document to download.
500            original: If ``False`` *(default)*, returns the archived
501                (post-processed) PDF.  If ``True``, returns the original
502                file that was uploaded.
503
504        Returns:
505            Raw file bytes.
506        """
507        endpoint = "download" if original else "archive"
508        resp = await self._session.get_download(f"/documents/{id}/{endpoint}/")
509        content_type = resp.headers.get("content-type", "")
510        if "text/html" in content_type or resp.content[:9].lower().startswith(b"<!doctype"):
511            raise ServerError(
512                f"Download returned an HTML page (content-type: {content_type!r}). "
513                "The server redirected to a login page even after re-attaching auth. "
514                "Run with --verbose to see redirect details.",
515                status_code=None,
516            )
517        return resp.content

Download the binary content of a document.

Args: id: Numeric ID of the document to download. original: If False (default), returns the archived (post-processed) PDF. If True, returns the original file that was uploaded.

Returns: Raw file bytes.

async def get_notes( self, document_id: int) -> list[DocumentNote]:
21    async def get_notes(self, document_id: int) -> list[DocumentNote]:
22        """Fetch all notes attached to a document.
23
24        Args:
25            document_id: Numeric ID of the document whose notes to retrieve.
26
27        Returns:
28            List of :class:`~easypaperless.models.documents.DocumentNote` objects,
29            ordered by creation time.
30
31        Raises:
32            ~easypaperless.exceptions.NotFoundError: If no document exists
33                with that ID.
34        """
35        logger.debug("Fetching notes for document id=%d", document_id)
36        resp = await self._session.get(f"/documents/{document_id}/notes/")
37        return [DocumentNote.model_validate(item) for item in resp.json()]

Fetch all notes attached to a document.

Args: document_id: Numeric ID of the document whose notes to retrieve.

Returns: List of ~easypaperless.models.documents.DocumentNote objects, ordered by creation time.

Raises: ~easypaperless.exceptions.NotFoundError: If no document exists with that ID.

async def create_note( self, document_id: int, *, note: str) -> DocumentNote:
39    async def create_note(self, document_id: int, *, note: str) -> DocumentNote:
40        """Create a new note on a document.
41
42        Args:
43            document_id: Numeric ID of the document to annotate.
44            note: Text content of the note.
45
46        Returns:
47            The newly created :class:`~easypaperless.models.documents.DocumentNote`.
48
49        Raises:
50            ~easypaperless.exceptions.NotFoundError: If no document exists
51                with that ID.
52        """
53        logger.debug("Creating note for document id=%d", document_id)
54        resp = await self._session.post(
55            f"/documents/{document_id}/notes/",
56            json={"note": note},
57        )
58        # paperless-ngx returns the full list of notes after creation;
59        # the newly created note is the last item in the list.
60        data = resp.json()
61        if isinstance(data, list):
62            return DocumentNote.model_validate(data[-1])
63        return DocumentNote.model_validate(data)

Create a new note on a document.

Args: document_id: Numeric ID of the document to annotate. note: Text content of the note.

Returns: The newly created ~easypaperless.models.documents.DocumentNote.

Raises: ~easypaperless.exceptions.NotFoundError: If no document exists with that ID.

async def delete_note(self, document_id: int, note_id: int) -> None:
65    async def delete_note(self, document_id: int, note_id: int) -> None:
66        """Delete a note from a document.
67
68        Args:
69            document_id: Numeric ID of the document that owns the note.
70            note_id: Numeric ID of the note to delete.
71
72        Raises:
73            ~easypaperless.exceptions.NotFoundError: If no document or note
74                exists with the given IDs.
75        """
76        logger.debug("Deleting note id=%d from document id=%d", note_id, document_id)
77        await self._session.delete(f"/documents/{document_id}/notes/", params={"id": note_id})

Delete a note from a document.

Args: document_id: Numeric ID of the document that owns the note. note_id: Numeric ID of the note to delete.

Raises: ~easypaperless.exceptions.NotFoundError: If no document or note exists with the given IDs.

async def upload_document( self, file: str | pathlib._local.Path, *, title: str | None = None, created: str | None = None, correspondent: int | str | None = None, document_type: int | str | None = None, storage_path: int | str | None = None, tags: list[int | str] | None = None, asn: int | None = None, wait: bool = False, poll_interval: float | None = None, poll_timeout: float | None = None) -> str | Document:
 31    async def upload_document(
 32        self,
 33        file: str | Path,
 34        *,
 35        title: str | None = None,
 36        created: str | None = None,
 37        correspondent: int | str | None = None,
 38        document_type: int | str | None = None,
 39        storage_path: int | str | None = None,
 40        tags: list[int | str] | None = None,
 41        asn: int | None = None,
 42        wait: bool = False,
 43        poll_interval: float | None = None,
 44        poll_timeout: float | None = None,
 45    ) -> str | Document:
 46        """Upload a document to paperless-ngx.
 47
 48        Args:
 49            file: Path to the file to upload (``str`` or
 50                :class:`~pathlib.Path`).
 51            title: Title to assign to the document.  Paperless-ngx derives
 52                one from the file name if omitted.
 53            created: Creation date as an ISO-8601 string (``"YYYY-MM-DD"``).
 54            correspondent: Correspondent to assign, as an ID or name.
 55            document_type: Document type to assign, as an ID or name.
 56            storage_path: Storage path to assign, as an ID or name.
 57            tags: Tags to assign, as IDs or names.
 58            asn: Archive serial number to assign.
 59            wait: If ``False`` *(default)*, returns immediately with the
 60                task ID string.  If ``True``, polls the task-status endpoint
 61                until processing completes and returns the resulting
 62                :class:`~easypaperless.models.documents.Document`.
 63            poll_interval: Override the client-level ``poll_interval`` for
 64                this upload.  Only used when ``wait=True``.
 65            poll_timeout: Override the client-level ``poll_timeout`` for
 66                this upload.  Only used when ``wait=True``.
 67
 68        Returns:
 69            The Celery task ID string when ``wait=False``, or the fully
 70            processed :class:`~easypaperless.models.documents.Document`
 71            when ``wait=True``.
 72
 73        Raises:
 74            ~easypaperless.exceptions.UploadError: If paperless-ngx reports
 75                that the processing task failed.
 76            ~easypaperless.exceptions.TaskTimeoutError: If ``wait=True`` and
 77                processing does not complete within the timeout.
 78        """
 79        file_path = Path(file)
 80        file_bytes = file_path.read_bytes()
 81        logger.info("Uploading %r (%d bytes)", file_path.name, len(file_bytes))
 82
 83        data: dict[str, Any] = {}
 84        if title is not None:
 85            data["title"] = title
 86        if created is not None:
 87            data["created"] = created
 88        if correspondent is not None:
 89            data["correspondent"] = await self._resolver.resolve("correspondents", correspondent)
 90        if document_type is not None:
 91            data["document_type"] = await self._resolver.resolve("document_types", document_type)
 92        if storage_path is not None:
 93            data["storage_path"] = await self._resolver.resolve("storage_paths", storage_path)
 94        if tags is not None:
 95            resolved = await self._resolver.resolve_list("tags", tags)
 96            data["tags"] = resolved
 97        if asn is not None:
 98            data["archive_serial_number"] = asn
 99
100        files = {"document": (file_path.name, file_bytes)}
101        resp = await self._session.post("/documents/post_document/", data=data, files=files)
102        task_id: str = resp.text.strip('"')
103        logger.debug("Upload accepted, task_id=%r", task_id)
104
105        if not wait:
106            return task_id
107
108        interval = poll_interval if poll_interval is not None else self._poll_interval
109        timeout = poll_timeout if poll_timeout is not None else self._poll_timeout
110        return await self._poll_task(task_id, poll_interval=interval, poll_timeout=timeout)

Upload a document to paperless-ngx.

Args: file: Path to the file to upload (str or ~pathlib.Path). title: Title to assign to the document. Paperless-ngx derives one from the file name if omitted. created: Creation date as an ISO-8601 string ("YYYY-MM-DD"). correspondent: Correspondent to assign, as an ID or name. document_type: Document type to assign, as an ID or name. storage_path: Storage path to assign, as an ID or name. tags: Tags to assign, as IDs or names. asn: Archive serial number to assign. wait: If False (default), returns immediately with the task ID string. If True, polls the task-status endpoint until processing completes and returns the resulting ~easypaperless.models.documents.Document. poll_interval: Override the client-level poll_interval for this upload. Only used when wait=True. poll_timeout: Override the client-level poll_timeout for this upload. Only used when wait=True.

Returns: The Celery task ID string when wait=False, or the fully processed ~easypaperless.models.documents.Document when wait=True.

Raises: ~easypaperless.exceptions.UploadError: If paperless-ngx reports that the processing task failed. ~easypaperless.exceptions.TaskTimeoutError: If wait=True and processing does not complete within the timeout.

async def bulk_edit(self, document_ids: list[int], method: str, **parameters: Any) -> None:
20    async def bulk_edit(
21        self, document_ids: list[int], method: str, **parameters: Any
22    ) -> None:
23        """Execute a bulk-edit operation on a list of documents.
24
25        This is a low-level method; prefer the higher-level helpers such as
26        :meth:`bulk_add_tag`, :meth:`bulk_remove_tag`, and
27        :meth:`bulk_modify_tags`.
28
29        Args:
30            document_ids: List of document IDs to operate on.
31            method: Bulk-edit method name as recognised by paperless-ngx
32                (e.g. ``"add_tag"``, ``"remove_tag"``, ``"delete"``).
33            **parameters: Additional keyword arguments forwarded to the API as
34                the ``parameters`` object.
35        """
36        payload = {"documents": document_ids, "method": method, "parameters": parameters}
37        # Use a longer timeout for bulk operations — large batches can take
38        # considerably more than the default 30 s on the paperless-ngx side.
39        await self._session.post("/documents/bulk_edit/", json=payload, timeout=120.0)

Execute a bulk-edit operation on a list of documents.

This is a low-level method; prefer the higher-level helpers such as bulk_add_tag(), bulk_remove_tag(), and bulk_modify_tags().

Args: document_ids: List of document IDs to operate on. method: Bulk-edit method name as recognised by paperless-ngx (e.g. "add_tag", "remove_tag", "delete"). **parameters: Additional keyword arguments forwarded to the API as the parameters object.

async def bulk_add_tag(self, document_ids: list[int], tag: int | str) -> None:
41    async def bulk_add_tag(self, document_ids: list[int], tag: int | str) -> None:
42        """Add a tag to multiple documents in a single request.
43
44        Args:
45            document_ids: List of document IDs to tag.
46            tag: Tag to add, as an ID or name.
47        """
48        tag_id = await self._resolver.resolve("tags", tag)
49        await self.bulk_edit(document_ids, "add_tag", tag=tag_id)

Add a tag to multiple documents in a single request.

Args: document_ids: List of document IDs to tag. tag: Tag to add, as an ID or name.

async def bulk_remove_tag(self, document_ids: list[int], tag: int | str) -> None:
51    async def bulk_remove_tag(self, document_ids: list[int], tag: int | str) -> None:
52        """Remove a tag from multiple documents in a single request.
53
54        Args:
55            document_ids: List of document IDs to un-tag.
56            tag: Tag to remove, as an ID or name.
57        """
58        tag_id = await self._resolver.resolve("tags", tag)
59        await self.bulk_edit(document_ids, "remove_tag", tag=tag_id)

Remove a tag from multiple documents in a single request.

Args: document_ids: List of document IDs to un-tag. tag: Tag to remove, as an ID or name.

async def bulk_modify_tags( self, document_ids: list[int], *, add_tags: list[int | str] | None = None, remove_tags: list[int | str] | None = None) -> None:
61    async def bulk_modify_tags(
62        self,
63        document_ids: list[int],
64        *,
65        add_tags: list[int | str] | None = None,
66        remove_tags: list[int | str] | None = None,
67    ) -> None:
68        """Add and/or remove tags on multiple documents atomically.
69
70        Args:
71            document_ids: List of document IDs to modify.
72            add_tags: Tags to add, as IDs or names.
73            remove_tags: Tags to remove, as IDs or names.
74        """
75        add_ids = await self._resolver.resolve_list("tags", add_tags or [])
76        remove_ids = await self._resolver.resolve_list("tags", remove_tags or [])
77        await self.bulk_edit(document_ids, "modify_tags", add_tags=add_ids, remove_tags=remove_ids)

Add and/or remove tags on multiple documents atomically.

Args: document_ids: List of document IDs to modify. add_tags: Tags to add, as IDs or names. remove_tags: Tags to remove, as IDs or names.

async def bulk_delete(self, document_ids: list[int]) -> None:
79    async def bulk_delete(self, document_ids: list[int]) -> None:
80        """Permanently delete multiple documents in a single request.
81
82        Args:
83            document_ids: List of document IDs to delete.
84        """
85        await self.bulk_edit(document_ids, "delete")

Permanently delete multiple documents in a single request.

Args: document_ids: List of document IDs to delete.

async def bulk_set_correspondent(self, document_ids: list[int], correspondent: int | str | None) -> None:
 87    async def bulk_set_correspondent(
 88        self, document_ids: list[int], correspondent: int | str | None
 89    ) -> None:
 90        """Assign a correspondent to multiple documents in a single request.
 91
 92        Args:
 93            document_ids: List of document IDs to modify.
 94            correspondent: Correspondent to assign, as an ID or name.
 95                Pass ``None`` to clear the assignment.
 96        """
 97        cor_id: int | None = None
 98        if correspondent is not None:
 99            cor_id = await self._resolver.resolve("correspondents", correspondent)
100        await self.bulk_edit(document_ids, "set_correspondent", correspondent=cor_id)

Assign a correspondent to multiple documents in a single request.

Args: document_ids: List of document IDs to modify. correspondent: Correspondent to assign, as an ID or name. Pass None to clear the assignment.

async def bulk_set_document_type(self, document_ids: list[int], document_type: int | str | None) -> None:
102    async def bulk_set_document_type(
103        self, document_ids: list[int], document_type: int | str | None
104    ) -> None:
105        """Assign a document type to multiple documents in a single request.
106
107        Args:
108            document_ids: List of document IDs to modify.
109            document_type: Document type to assign, as an ID or name.
110                Pass ``None`` to clear the assignment.
111        """
112        dt_id: int | None = None
113        if document_type is not None:
114            dt_id = await self._resolver.resolve("document_types", document_type)
115        await self.bulk_edit(document_ids, "set_document_type", document_type=dt_id)

Assign a document type to multiple documents in a single request.

Args: document_ids: List of document IDs to modify. document_type: Document type to assign, as an ID or name. Pass None to clear the assignment.

async def bulk_set_storage_path(self, document_ids: list[int], storage_path: int | str | None) -> None:
117    async def bulk_set_storage_path(
118        self, document_ids: list[int], storage_path: int | str | None
119    ) -> None:
120        """Assign a storage path to multiple documents in a single request.
121
122        Args:
123            document_ids: List of document IDs to modify.
124            storage_path: Storage path to assign, as an ID or name.
125                Pass ``None`` to clear the assignment.
126        """
127        sp_id: int | None = None
128        if storage_path is not None:
129            sp_id = await self._resolver.resolve("storage_paths", storage_path)
130        await self.bulk_edit(document_ids, "set_storage_path", storage_path=sp_id)

Assign a storage path to multiple documents in a single request.

Args: document_ids: List of document IDs to modify. storage_path: Storage path to assign, as an ID or name. Pass None to clear the assignment.

async def bulk_modify_custom_fields( self, document_ids: list[int], *, add_fields: list[dict[str, typing.Any]] | None = None, remove_fields: list[int] | None = None) -> None:
132    async def bulk_modify_custom_fields(
133        self,
134        document_ids: list[int],
135        *,
136        add_fields: list[dict[str, Any]] | None = None,
137        remove_fields: list[int] | None = None,
138    ) -> None:
139        """Add and/or remove custom field values on multiple documents.
140
141        Args:
142            document_ids: List of document IDs to modify.
143            add_fields: Custom-field value dicts to add (each must contain
144                ``"field"`` and ``"value"`` keys).
145            remove_fields: Custom-field IDs whose values should be removed.
146        """
147        await self.bulk_edit(
148            document_ids,
149            "modify_custom_fields",
150            add_custom_fields=add_fields or [],
151            remove_custom_fields=remove_fields or [],
152        )

Add and/or remove custom field values on multiple documents.

Args: document_ids: List of document IDs to modify. add_fields: Custom-field value dicts to add (each must contain "field" and "value" keys). remove_fields: Custom-field IDs whose values should be removed.

async def bulk_set_permissions( self, document_ids: list[int], *, set_permissions: SetPermissions | None = None, owner: int | None = None, merge: bool = False) -> None:
154    async def bulk_set_permissions(
155        self,
156        document_ids: list[int],
157        *,
158        set_permissions: SetPermissions | None = None,
159        owner: int | None = None,
160        merge: bool = False,
161    ) -> None:
162        """Set permissions and/or owner on multiple documents.
163
164        Args:
165            document_ids: List of document IDs to modify.
166            set_permissions: Explicit view/change permission sets to apply.
167            owner: Numeric user ID to assign as document owner.
168            merge: When ``True``, new permissions are merged with existing
169                ones rather than replacing them.
170        """
171        params: dict[str, Any] = {"merge": merge}
172        if set_permissions is not None:
173            params["set_permissions"] = set_permissions.model_dump()
174        if owner is not None:
175            params["owner"] = owner
176        await self.bulk_edit(document_ids, "set_permissions", **params)

Set permissions and/or owner on multiple documents.

Args: document_ids: List of document IDs to modify. set_permissions: Explicit view/change permission sets to apply. owner: Numeric user ID to assign as document owner. merge: When True, new permissions are merged with existing ones rather than replacing them.

async def bulk_edit_objects( self, object_type: str, object_ids: list[int], operation: str, **parameters: Any) -> None:
18    async def bulk_edit_objects(
19        self,
20        object_type: str,
21        object_ids: list[int],
22        operation: str,
23        **parameters: Any,
24    ) -> None:
25        """Execute a bulk operation on non-document objects (tags, etc.).
26
27        This is a low-level method; prefer the higher-level helpers such as
28        :meth:`bulk_delete_tags` or :meth:`bulk_set_permissions_tags`.
29
30        Args:
31            object_type: The paperless-ngx object type string (e.g.
32                ``"tags"``, ``"correspondents"``).
33            object_ids: List of object IDs to operate on.
34            operation: Operation name recognised by the
35                ``/bulk_edit_objects/`` endpoint.
36            **parameters: Additional keyword arguments forwarded directly to
37                the API payload.
38        """
39        payload = {
40            "objects": object_ids,
41            "object_type": object_type,
42            "operation": operation,
43            **parameters,
44        }
45        await self._session.post("/bulk_edit_objects/", json=payload)

Execute a bulk operation on non-document objects (tags, etc.).

This is a low-level method; prefer the higher-level helpers such as bulk_delete_tags() or bulk_set_permissions_tags().

Args: object_type: The paperless-ngx object type string (e.g. "tags", "correspondents"). object_ids: List of object IDs to operate on. operation: Operation name recognised by the /bulk_edit_objects/ endpoint. **parameters: Additional keyword arguments forwarded directly to the API payload.

async def bulk_delete_tags(self, ids: list[int]) -> None:
49    async def bulk_delete_tags(self, ids: list[int]) -> None:
50        """Permanently delete multiple tags in a single request.
51
52        Args:
53            ids: List of tag IDs to delete.
54        """
55        await self.bulk_edit_objects("tags", ids, "delete")

Permanently delete multiple tags in a single request.

Args: ids: List of tag IDs to delete.

async def bulk_delete_correspondents(self, ids: list[int]) -> None:
57    async def bulk_delete_correspondents(self, ids: list[int]) -> None:
58        """Permanently delete multiple correspondents in a single request.
59
60        Args:
61            ids: List of correspondent IDs to delete.
62        """
63        await self.bulk_edit_objects("correspondents", ids, "delete")

Permanently delete multiple correspondents in a single request.

Args: ids: List of correspondent IDs to delete.

async def bulk_delete_document_types(self, ids: list[int]) -> None:
65    async def bulk_delete_document_types(self, ids: list[int]) -> None:
66        """Permanently delete multiple document types in a single request.
67
68        Args:
69            ids: List of document type IDs to delete.
70        """
71        await self.bulk_edit_objects("document_types", ids, "delete")

Permanently delete multiple document types in a single request.

Args: ids: List of document type IDs to delete.

async def bulk_delete_storage_paths(self, ids: list[int]) -> None:
73    async def bulk_delete_storage_paths(self, ids: list[int]) -> None:
74        """Permanently delete multiple storage paths in a single request.
75
76        Args:
77            ids: List of storage path IDs to delete.
78        """
79        await self.bulk_edit_objects("storage_paths", ids, "delete")

Permanently delete multiple storage paths in a single request.

Args: ids: List of storage path IDs to delete.

async def bulk_set_permissions_tags( self, ids: list[int], *, set_permissions: SetPermissions | None = None, owner: int | None = None, merge: bool = False) -> None:
 83    async def bulk_set_permissions_tags(
 84        self,
 85        ids: list[int],
 86        *,
 87        set_permissions: SetPermissions | None = None,
 88        owner: int | None = None,
 89        merge: bool = False,
 90    ) -> None:
 91        """Set permissions and/or owner on multiple tags.
 92
 93        Args:
 94            ids: List of tag IDs to modify.
 95            set_permissions: Explicit view/change permission sets to apply.
 96            owner: Numeric user ID to assign as owner.
 97            merge: When ``True``, new permissions are merged with existing
 98                ones rather than replacing them.
 99        """
100        params: dict[str, Any] = {"merge": merge}
101        if set_permissions is not None:
102            params["permissions"] = set_permissions.model_dump()
103        if owner is not None:
104            params["owner"] = owner
105        await self.bulk_edit_objects("tags", ids, "set_permissions", **params)

Set permissions and/or owner on multiple tags.

Args: ids: List of tag IDs to modify. set_permissions: Explicit view/change permission sets to apply. owner: Numeric user ID to assign as owner. merge: When True, new permissions are merged with existing ones rather than replacing them.

async def bulk_set_permissions_correspondents( self, ids: list[int], *, set_permissions: SetPermissions | None = None, owner: int | None = None, merge: bool = False) -> None:
107    async def bulk_set_permissions_correspondents(
108        self,
109        ids: list[int],
110        *,
111        set_permissions: SetPermissions | None = None,
112        owner: int | None = None,
113        merge: bool = False,
114    ) -> None:
115        """Set permissions and/or owner on multiple correspondents.
116
117        Args:
118            ids: List of correspondent IDs to modify.
119            set_permissions: Explicit view/change permission sets to apply.
120            owner: Numeric user ID to assign as owner.
121            merge: When ``True``, new permissions are merged with existing
122                ones rather than replacing them.
123        """
124        params: dict[str, Any] = {"merge": merge}
125        if set_permissions is not None:
126            params["permissions"] = set_permissions.model_dump()
127        if owner is not None:
128            params["owner"] = owner
129        await self.bulk_edit_objects("correspondents", ids, "set_permissions", **params)

Set permissions and/or owner on multiple correspondents.

Args: ids: List of correspondent IDs to modify. set_permissions: Explicit view/change permission sets to apply. owner: Numeric user ID to assign as owner. merge: When True, new permissions are merged with existing ones rather than replacing them.

async def bulk_set_permissions_document_types( self, ids: list[int], *, set_permissions: SetPermissions | None = None, owner: int | None = None, merge: bool = False) -> None:
131    async def bulk_set_permissions_document_types(
132        self,
133        ids: list[int],
134        *,
135        set_permissions: SetPermissions | None = None,
136        owner: int | None = None,
137        merge: bool = False,
138    ) -> None:
139        """Set permissions and/or owner on multiple document types.
140
141        Args:
142            ids: List of document type IDs to modify.
143            set_permissions: Explicit view/change permission sets to apply.
144            owner: Numeric user ID to assign as owner.
145            merge: When ``True``, new permissions are merged with existing
146                ones rather than replacing them.
147        """
148        params: dict[str, Any] = {"merge": merge}
149        if set_permissions is not None:
150            params["permissions"] = set_permissions.model_dump()
151        if owner is not None:
152            params["owner"] = owner
153        await self.bulk_edit_objects("document_types", ids, "set_permissions", **params)

Set permissions and/or owner on multiple document types.

Args: ids: List of document type IDs to modify. set_permissions: Explicit view/change permission sets to apply. owner: Numeric user ID to assign as owner. merge: When True, new permissions are merged with existing ones rather than replacing them.

async def bulk_set_permissions_storage_paths( self, ids: list[int], *, set_permissions: SetPermissions | None = None, owner: int | None = None, merge: bool = False) -> None:
155    async def bulk_set_permissions_storage_paths(
156        self,
157        ids: list[int],
158        *,
159        set_permissions: SetPermissions | None = None,
160        owner: int | None = None,
161        merge: bool = False,
162    ) -> None:
163        """Set permissions and/or owner on multiple storage paths.
164
165        Args:
166            ids: List of storage path IDs to modify.
167            set_permissions: Explicit view/change permission sets to apply.
168            owner: Numeric user ID to assign as owner.
169            merge: When ``True``, new permissions are merged with existing
170                ones rather than replacing them.
171        """
172        params: dict[str, Any] = {"merge": merge}
173        if set_permissions is not None:
174            params["permissions"] = set_permissions.model_dump()
175        if owner is not None:
176            params["owner"] = owner
177        await self.bulk_edit_objects("storage_paths", ids, "set_permissions", **params)

Set permissions and/or owner on multiple storage paths.

Args: ids: List of storage path IDs to modify. set_permissions: Explicit view/change permission sets to apply. owner: Numeric user ID to assign as owner. merge: When True, new permissions are merged with existing ones rather than replacing them.

async def list_tags( self, *, ids: list[int] | None = None, name_contains: str | None = None, name_exact: str | None = None, page: int | None = None, page_size: int | None = None, ordering: str | None = None, descending: bool = False) -> list[Tag]:
46    async def list_tags(
47        self,
48        *,
49        ids: list[int] | None = None,
50        name_contains: str | None = None,
51        name_exact: str | None = None,
52        page: int | None = None,
53        page_size: int | None = None,
54        ordering: str | None = None,
55        descending: bool = False,
56    ) -> list[Tag]:
57        """Return tags defined in paperless-ngx.
58
59        Args:
60            ids: Return only tags whose ID is in this list.
61            name_contains: Case-insensitive substring filter on tag name
62                (raw API ``name__icontains``).
63            name_exact: Case-insensitive exact match on tag name
64                (raw API ``name__iexact``).
65            page: Return only this specific page (1-based). Disables
66                auto-pagination. Default: ``None`` (return all).
67            page_size: Number of results per page. When omitted, the server
68                default is used.
69            ordering: Field to sort by. Examples: ``"name"``, ``"id"``.
70            descending: When ``True``, reverses the sort direction of
71                ``ordering``. Ignored when ``ordering`` is ``None``.
72
73        Returns:
74            List of :class:`~easypaperless.models.tags.Tag` objects.
75        """
76        params: dict[str, Any] = {}
77        if ids is not None:
78            params["id__in"] = ",".join(str(i) for i in ids)
79        if name_contains is not None:
80            params["name__icontains"] = name_contains
81        if name_exact is not None:
82            params["name__iexact"] = name_exact
83        if page is not None:
84            params["page"] = page
85        if page_size is not None:
86            params["page_size"] = page_size
87        if ordering is not None:
88            params["ordering"] = f"-{ordering}" if descending else ordering
89        return cast(list[Tag], await self._list_resource("tags", Tag, params or None))

Return tags defined in paperless-ngx.

Args: ids: Return only tags whose ID is in this list. name_contains: Case-insensitive substring filter on tag name (raw API name__icontains). name_exact: Case-insensitive exact match on tag name (raw API name__iexact). page: Return only this specific page (1-based). Disables auto-pagination. Default: None (return all). page_size: Number of results per page. When omitted, the server default is used. ordering: Field to sort by. Examples: "name", "id". descending: When True, reverses the sort direction of ordering. Ignored when ordering is None.

Returns: List of ~easypaperless.models.tags.Tag objects.

async def get_tag(self, id: int) -> Tag:
 91    async def get_tag(self, id: int) -> Tag:
 92        """Fetch a single tag by its ID.
 93
 94        Args:
 95            id: Numeric tag ID.
 96
 97        Returns:
 98            The :class:`~easypaperless.models.tags.Tag` with the given ID.
 99
100        Raises:
101            ~easypaperless.exceptions.NotFoundError: If no tag exists with
102                that ID.
103        """
104        return cast(Tag, await self._get_resource("tags", id, Tag))

Fetch a single tag by its ID.

Args: id: Numeric tag ID.

Returns: The ~easypaperless.models.tags.Tag with the given ID.

Raises: ~easypaperless.exceptions.NotFoundError: If no tag exists with that ID.

async def create_tag( self, *, name: str, color: str | None = None, is_inbox_tag: bool | None = None, match: str | None = None, matching_algorithm: MatchingAlgorithm | None = None, is_insensitive: bool | None = None, parent: int | None = None, owner: int | None = None, set_permissions: SetPermissions | None = None) -> Tag:
106    async def create_tag(
107        self,
108        *,
109        name: str,
110        color: str | None = None,
111        is_inbox_tag: bool | None = None,
112        match: str | None = None,
113        matching_algorithm: MatchingAlgorithm | None = None,
114        is_insensitive: bool | None = None,
115        parent: int | None = None,
116        owner: int | None = None,
117        set_permissions: SetPermissions | None = None,
118    ) -> Tag:
119        """Create a new tag.
120
121        Args:
122            name: Tag name. Must be unique.
123            color: Background colour in the paperless-ngx UI as a CSS hex
124                string (e.g. ``"#ff0000"``).
125            is_inbox_tag: When ``True``, newly ingested documents receive this
126                tag automatically until processed. At most one tag should be
127                the inbox tag.
128            match: Auto-matching pattern tested against incoming document
129                content. Interpretation depends on ``matching_algorithm``.
130            matching_algorithm: Controls how ``match`` is applied.
131                See :class:`~easypaperless.models.MatchingAlgorithm`.
132            is_insensitive: When ``True``, ``match`` is evaluated
133                case-insensitively.
134            parent: ID of an existing tag to use as parent, enabling
135                hierarchical tag trees. ``None`` creates a root-level tag.
136            owner: Numeric user ID to assign as owner. ``None`` creates the
137                tag with no owner (visible to all users).
138            set_permissions: Explicit view/change permission sets. Defaults to
139                no permissions (all users have access via public visibility).
140
141        Returns:
142            The newly created :class:`~easypaperless.models.tags.Tag`.
143        """
144        return cast(Tag, await self._create_resource(
145            "tags",
146            Tag,
147            owner=owner,
148            set_permissions=set_permissions,
149            name=name,
150            color=color,
151            is_inbox_tag=is_inbox_tag,
152            match=match,
153            matching_algorithm=matching_algorithm,
154            is_insensitive=is_insensitive,
155            parent=parent,
156        ))

Create a new tag.

Args: name: Tag name. Must be unique. color: Background colour in the paperless-ngx UI as a CSS hex string (e.g. "#ff0000"). is_inbox_tag: When True, newly ingested documents receive this tag automatically until processed. At most one tag should be the inbox tag. match: Auto-matching pattern tested against incoming document content. Interpretation depends on matching_algorithm. matching_algorithm: Controls how match is applied. See ~easypaperless.models.MatchingAlgorithm. is_insensitive: When True, match is evaluated case-insensitively. parent: ID of an existing tag to use as parent, enabling hierarchical tag trees. None creates a root-level tag. owner: Numeric user ID to assign as owner. None creates the tag with no owner (visible to all users). set_permissions: Explicit view/change permission sets. Defaults to no permissions (all users have access via public visibility).

Returns: The newly created ~easypaperless.models.tags.Tag.

async def update_tag( self, id: int, *, name: str | None = None, color: str | None = None, is_inbox_tag: bool | None = None, match: str | None = None, matching_algorithm: MatchingAlgorithm | None = None, is_insensitive: bool | None = None, parent: int | None = None) -> Tag:
158    async def update_tag(
159        self,
160        id: int,
161        *,
162        name: str | None = None,
163        color: str | None = None,
164        is_inbox_tag: bool | None = None,
165        match: str | None = None,
166        matching_algorithm: MatchingAlgorithm | None = None,
167        is_insensitive: bool | None = None,
168        parent: int | None = None,
169    ) -> Tag:
170        """Partially update a tag (PATCH semantics).
171
172        Args:
173            id: Numeric ID of the tag to update.
174            name: Tag name. Must be unique.
175            color: Background colour in the paperless-ngx UI as a CSS hex
176                string (e.g. ``"#00ff00"``).
177            is_inbox_tag: When ``True``, newly ingested documents receive this
178                tag automatically until processed. At most one tag should be
179                the inbox tag.
180            match: Auto-matching pattern tested against incoming document
181                content. Interpretation depends on ``matching_algorithm``.
182            matching_algorithm: Controls how ``match`` is applied.
183                See :class:`~easypaperless.models.MatchingAlgorithm`.
184            is_insensitive: When ``True``, ``match`` is evaluated
185                case-insensitively.
186            parent: ID of an existing tag to use as parent, enabling
187                hierarchical tag trees.
188
189        Returns:
190            The updated :class:`~easypaperless.models.tags.Tag`.
191        """
192        return cast(Tag, await self._update_resource(
193            "tags",
194            id,
195            Tag,
196            name=name,
197            color=color,
198            is_inbox_tag=is_inbox_tag,
199            match=match,
200            matching_algorithm=matching_algorithm,
201            is_insensitive=is_insensitive,
202            parent=parent,
203        ))

Partially update a tag (PATCH semantics).

Args: id: Numeric ID of the tag to update. name: Tag name. Must be unique. color: Background colour in the paperless-ngx UI as a CSS hex string (e.g. "#00ff00"). is_inbox_tag: When True, newly ingested documents receive this tag automatically until processed. At most one tag should be the inbox tag. match: Auto-matching pattern tested against incoming document content. Interpretation depends on matching_algorithm. matching_algorithm: Controls how match is applied. See ~easypaperless.models.MatchingAlgorithm. is_insensitive: When True, match is evaluated case-insensitively. parent: ID of an existing tag to use as parent, enabling hierarchical tag trees.

Returns: The updated ~easypaperless.models.tags.Tag.

async def delete_tag(self, id: int) -> None:
205    async def delete_tag(self, id: int) -> None:
206        """Delete a tag.
207
208        Args:
209            id: Numeric ID of the tag to delete.
210
211        Raises:
212            ~easypaperless.exceptions.NotFoundError: If no tag exists with
213                that ID.
214        """
215        await self._delete_resource("tags", id)

Delete a tag.

Args: id: Numeric ID of the tag to delete.

Raises: ~easypaperless.exceptions.NotFoundError: If no tag exists with that ID.

async def list_correspondents( self, *, ids: list[int] | None = None, name_contains: str | None = None, name_exact: str | None = None, page: int | None = None, page_size: int | None = None, ordering: str | None = None, descending: bool = False) -> list[Correspondent]:
46    async def list_correspondents(
47        self,
48        *,
49        ids: list[int] | None = None,
50        name_contains: str | None = None,
51        name_exact: str | None = None,
52        page: int | None = None,
53        page_size: int | None = None,
54        ordering: str | None = None,
55        descending: bool = False,
56    ) -> list[Correspondent]:
57        """Return correspondents defined in paperless-ngx.
58
59        Args:
60            ids: Return only correspondents whose ID is in this list.
61            name_contains: Case-insensitive substring filter on correspondent
62                name (raw API ``name__icontains``).
63            name_exact: Case-insensitive exact match on correspondent name
64                (raw API ``name__iexact``).
65            page: Return only this specific page (1-based). Disables
66                auto-pagination. Default: ``None`` (return all).
67            page_size: Number of results per page. When omitted, the server
68                default is used.
69            ordering: Field to sort by. Examples: ``"name"``, ``"id"``.
70            descending: When ``True``, reverses the sort direction of
71                ``ordering``. Ignored when ``ordering`` is ``None``.
72
73        Returns:
74            List of
75            :class:`~easypaperless.models.correspondents.Correspondent`
76            objects.
77        """
78        params: dict[str, Any] = {}
79        if ids is not None:
80            params["id__in"] = ",".join(str(i) for i in ids)
81        if name_contains is not None:
82            params["name__icontains"] = name_contains
83        if name_exact is not None:
84            params["name__iexact"] = name_exact
85        if page is not None:
86            params["page"] = page
87        if page_size is not None:
88            params["page_size"] = page_size
89        if ordering is not None:
90            params["ordering"] = f"-{ordering}" if descending else ordering
91        return cast(
92            list[Correspondent],
93            await self._list_resource("correspondents", Correspondent, params or None),
94        )

Return correspondents defined in paperless-ngx.

Args: ids: Return only correspondents whose ID is in this list. name_contains: Case-insensitive substring filter on correspondent name (raw API name__icontains). name_exact: Case-insensitive exact match on correspondent name (raw API name__iexact). page: Return only this specific page (1-based). Disables auto-pagination. Default: None (return all). page_size: Number of results per page. When omitted, the server default is used. ordering: Field to sort by. Examples: "name", "id". descending: When True, reverses the sort direction of ordering. Ignored when ordering is None.

Returns: List of ~easypaperless.models.correspondents.Correspondent objects.

async def get_correspondent(self, id: int) -> Correspondent:
 96    async def get_correspondent(self, id: int) -> Correspondent:
 97        """Fetch a single correspondent by its ID.
 98
 99        Args:
100            id: Numeric correspondent ID.
101
102        Returns:
103            The :class:`~easypaperless.models.correspondents.Correspondent`
104            with the given ID.
105
106        Raises:
107            ~easypaperless.exceptions.NotFoundError: If no correspondent
108                exists with that ID.
109        """
110        return cast(Correspondent, await self._get_resource("correspondents", id, Correspondent))

Fetch a single correspondent by its ID.

Args: id: Numeric correspondent ID.

Returns: The ~easypaperless.models.correspondents.Correspondent with the given ID.

Raises: ~easypaperless.exceptions.NotFoundError: If no correspondent exists with that ID.

async def create_correspondent( self, *, name: str, match: str | None = None, matching_algorithm: MatchingAlgorithm | None = None, is_insensitive: bool | None = None, owner: int | None = None, set_permissions: SetPermissions | None = None) -> Correspondent:
112    async def create_correspondent(
113        self,
114        *,
115        name: str,
116        match: str | None = None,
117        matching_algorithm: MatchingAlgorithm | None = None,
118        is_insensitive: bool | None = None,
119        owner: int | None = None,
120        set_permissions: SetPermissions | None = None,
121    ) -> Correspondent:
122        """Create a new correspondent.
123
124        Args:
125            name: Correspondent name (sender/recipient). Must be unique.
126            match: Auto-matching pattern tested against incoming document
127                content. Interpretation depends on ``matching_algorithm``.
128            matching_algorithm: Controls how ``match`` is applied.
129                See :class:`~easypaperless.models.MatchingAlgorithm`.
130            is_insensitive: When ``True``, ``match`` is evaluated
131                case-insensitively.
132            owner: Numeric user ID to assign as owner. ``None`` creates the
133                correspondent with no owner (visible to all users).
134            set_permissions: Explicit view/change permission sets. Defaults to
135                no permissions (all users have access via public visibility).
136
137        Returns:
138            The newly created
139            :class:`~easypaperless.models.correspondents.Correspondent`.
140        """
141        return cast(Correspondent, await self._create_resource(
142            "correspondents",
143            Correspondent,
144            owner=owner,
145            set_permissions=set_permissions,
146            name=name,
147            match=match,
148            matching_algorithm=matching_algorithm,
149            is_insensitive=is_insensitive,
150        ))

Create a new correspondent.

Args: name: Correspondent name (sender/recipient). Must be unique. match: Auto-matching pattern tested against incoming document content. Interpretation depends on matching_algorithm. matching_algorithm: Controls how match is applied. See ~easypaperless.models.MatchingAlgorithm. is_insensitive: When True, match is evaluated case-insensitively. owner: Numeric user ID to assign as owner. None creates the correspondent with no owner (visible to all users). set_permissions: Explicit view/change permission sets. Defaults to no permissions (all users have access via public visibility).

Returns: The newly created ~easypaperless.models.correspondents.Correspondent.

async def update_correspondent( self, id: int, *, name: str | None = None, match: str | None = None, matching_algorithm: MatchingAlgorithm | None = None, is_insensitive: bool | None = None) -> Correspondent:
152    async def update_correspondent(
153        self,
154        id: int,
155        *,
156        name: str | None = None,
157        match: str | None = None,
158        matching_algorithm: MatchingAlgorithm | None = None,
159        is_insensitive: bool | None = None,
160    ) -> Correspondent:
161        """Partially update a correspondent (PATCH semantics).
162
163        Args:
164            id: Numeric ID of the correspondent to update.
165            name: Correspondent name (sender/recipient). Must be unique.
166            match: Auto-matching pattern tested against incoming document
167                content. Interpretation depends on ``matching_algorithm``.
168            matching_algorithm: Controls how ``match`` is applied.
169                See :class:`~easypaperless.models.MatchingAlgorithm`.
170            is_insensitive: When ``True``, ``match`` is evaluated
171                case-insensitively.
172
173        Returns:
174            The updated
175            :class:`~easypaperless.models.correspondents.Correspondent`.
176        """
177        return cast(Correspondent, await self._update_resource(
178            "correspondents",
179            id,
180            Correspondent,
181            name=name,
182            match=match,
183            matching_algorithm=matching_algorithm,
184            is_insensitive=is_insensitive,
185        ))

Partially update a correspondent (PATCH semantics).

Args: id: Numeric ID of the correspondent to update. name: Correspondent name (sender/recipient). Must be unique. match: Auto-matching pattern tested against incoming document content. Interpretation depends on matching_algorithm. matching_algorithm: Controls how match is applied. See ~easypaperless.models.MatchingAlgorithm. is_insensitive: When True, match is evaluated case-insensitively.

Returns: The updated ~easypaperless.models.correspondents.Correspondent.

async def delete_correspondent(self, id: int) -> None:
187    async def delete_correspondent(self, id: int) -> None:
188        """Delete a correspondent.
189
190        Args:
191            id: Numeric ID of the correspondent to delete.
192
193        Raises:
194            ~easypaperless.exceptions.NotFoundError: If no correspondent
195                exists with that ID.
196        """
197        await self._delete_resource("correspondents", id)

Delete a correspondent.

Args: id: Numeric ID of the correspondent to delete.

Raises: ~easypaperless.exceptions.NotFoundError: If no correspondent exists with that ID.

async def list_document_types( self, *, ids: list[int] | None = None, name_contains: str | None = None, name_exact: str | None = None, page: int | None = None, page_size: int | None = None, ordering: str | None = None, descending: bool = False) -> list[DocumentType]:
46    async def list_document_types(
47        self,
48        *,
49        ids: list[int] | None = None,
50        name_contains: str | None = None,
51        name_exact: str | None = None,
52        page: int | None = None,
53        page_size: int | None = None,
54        ordering: str | None = None,
55        descending: bool = False,
56    ) -> list[DocumentType]:
57        """Return document types defined in paperless-ngx.
58
59        Args:
60            ids: Return only document types whose ID is in this list.
61            name_contains: Case-insensitive substring filter on document-type
62                name (raw API ``name__icontains``).
63            name_exact: Case-insensitive exact match on document-type name
64                (raw API ``name__iexact``).
65            page: Return only this specific page (1-based). Disables
66                auto-pagination. Default: ``None`` (return all).
67            page_size: Number of results per page. When omitted, the server
68                default is used.
69            ordering: Field to sort by. Examples: ``"name"``, ``"id"``.
70            descending: When ``True``, reverses the sort direction of
71                ``ordering``. Ignored when ``ordering`` is ``None``.
72
73        Returns:
74            List of
75            :class:`~easypaperless.models.document_types.DocumentType`
76            objects.
77        """
78        params: dict[str, Any] = {}
79        if ids is not None:
80            params["id__in"] = ",".join(str(i) for i in ids)
81        if name_contains is not None:
82            params["name__icontains"] = name_contains
83        if name_exact is not None:
84            params["name__iexact"] = name_exact
85        if page is not None:
86            params["page"] = page
87        if page_size is not None:
88            params["page_size"] = page_size
89        if ordering is not None:
90            params["ordering"] = f"-{ordering}" if descending else ordering
91        return cast(
92            list[DocumentType],
93            await self._list_resource("document_types", DocumentType, params or None),
94        )

Return document types defined in paperless-ngx.

Args: ids: Return only document types whose ID is in this list. name_contains: Case-insensitive substring filter on document-type name (raw API name__icontains). name_exact: Case-insensitive exact match on document-type name (raw API name__iexact). page: Return only this specific page (1-based). Disables auto-pagination. Default: None (return all). page_size: Number of results per page. When omitted, the server default is used. ordering: Field to sort by. Examples: "name", "id". descending: When True, reverses the sort direction of ordering. Ignored when ordering is None.

Returns: List of ~easypaperless.models.document_types.DocumentType objects.

async def get_document_type(self, id: int) -> DocumentType:
 96    async def get_document_type(self, id: int) -> DocumentType:
 97        """Fetch a single document type by its ID.
 98
 99        Args:
100            id: Numeric document-type ID.
101
102        Returns:
103            The :class:`~easypaperless.models.document_types.DocumentType`
104            with the given ID.
105
106        Raises:
107            ~easypaperless.exceptions.NotFoundError: If no document type
108                exists with that ID.
109        """
110        return cast(DocumentType, await self._get_resource("document_types", id, DocumentType))

Fetch a single document type by its ID.

Args: id: Numeric document-type ID.

Returns: The ~easypaperless.models.document_types.DocumentType with the given ID.

Raises: ~easypaperless.exceptions.NotFoundError: If no document type exists with that ID.

async def create_document_type( self, *, name: str, match: str | None = None, matching_algorithm: MatchingAlgorithm | None = None, is_insensitive: bool | None = None, owner: int | None = None, set_permissions: SetPermissions | None = None) -> DocumentType:
112    async def create_document_type(
113        self,
114        *,
115        name: str,
116        match: str | None = None,
117        matching_algorithm: MatchingAlgorithm | None = None,
118        is_insensitive: bool | None = None,
119        owner: int | None = None,
120        set_permissions: SetPermissions | None = None,
121    ) -> DocumentType:
122        """Create a new document type.
123
124        Args:
125            name: Document-type name (e.g. ``"Invoice"``, ``"Receipt"``).
126                Must be unique.
127            match: Auto-matching pattern tested against incoming document
128                content. Interpretation depends on ``matching_algorithm``.
129            matching_algorithm: Controls how ``match`` is applied.
130                See :class:`~easypaperless.models.MatchingAlgorithm`.
131            is_insensitive: When ``True``, ``match`` is evaluated
132                case-insensitively.
133            owner: Numeric user ID to assign as owner. ``None`` creates the
134                document type with no owner (visible to all users).
135            set_permissions: Explicit view/change permission sets. Defaults to
136                no permissions (all users have access via public visibility).
137
138        Returns:
139            The newly created
140            :class:`~easypaperless.models.document_types.DocumentType`.
141        """
142        return cast(DocumentType, await self._create_resource(
143            "document_types",
144            DocumentType,
145            owner=owner,
146            set_permissions=set_permissions,
147            name=name,
148            match=match,
149            matching_algorithm=matching_algorithm,
150            is_insensitive=is_insensitive,
151        ))

Create a new document type.

Args: name: Document-type name (e.g. "Invoice", "Receipt"). Must be unique. match: Auto-matching pattern tested against incoming document content. Interpretation depends on matching_algorithm. matching_algorithm: Controls how match is applied. See ~easypaperless.models.MatchingAlgorithm. is_insensitive: When True, match is evaluated case-insensitively. owner: Numeric user ID to assign as owner. None creates the document type with no owner (visible to all users). set_permissions: Explicit view/change permission sets. Defaults to no permissions (all users have access via public visibility).

Returns: The newly created ~easypaperless.models.document_types.DocumentType.

async def update_document_type( self, id: int, *, name: str | None = None, match: str | None = None, matching_algorithm: MatchingAlgorithm | None = None, is_insensitive: bool | None = None) -> DocumentType:
153    async def update_document_type(
154        self,
155        id: int,
156        *,
157        name: str | None = None,
158        match: str | None = None,
159        matching_algorithm: MatchingAlgorithm | None = None,
160        is_insensitive: bool | None = None,
161    ) -> DocumentType:
162        """Partially update a document type (PATCH semantics).
163
164        Args:
165            id: Numeric ID of the document type to update.
166            name: Document-type name (e.g. ``"Invoice"``, ``"Receipt"``).
167                Must be unique.
168            match: Auto-matching pattern tested against incoming document
169                content. Interpretation depends on ``matching_algorithm``.
170            matching_algorithm: Controls how ``match`` is applied.
171                See :class:`~easypaperless.models.MatchingAlgorithm`.
172            is_insensitive: When ``True``, ``match`` is evaluated
173                case-insensitively.
174
175        Returns:
176            The updated
177            :class:`~easypaperless.models.document_types.DocumentType`.
178        """
179        return cast(DocumentType, await self._update_resource(
180            "document_types",
181            id,
182            DocumentType,
183            name=name,
184            match=match,
185            matching_algorithm=matching_algorithm,
186            is_insensitive=is_insensitive,
187        ))

Partially update a document type (PATCH semantics).

Args: id: Numeric ID of the document type to update. name: Document-type name (e.g. "Invoice", "Receipt"). Must be unique. match: Auto-matching pattern tested against incoming document content. Interpretation depends on matching_algorithm. matching_algorithm: Controls how match is applied. See ~easypaperless.models.MatchingAlgorithm. is_insensitive: When True, match is evaluated case-insensitively.

Returns: The updated ~easypaperless.models.document_types.DocumentType.

async def delete_document_type(self, id: int) -> None:
189    async def delete_document_type(self, id: int) -> None:
190        """Delete a document type.
191
192        Args:
193            id: Numeric ID of the document type to delete.
194
195        Raises:
196            ~easypaperless.exceptions.NotFoundError: If no document type
197                exists with that ID.
198        """
199        await self._delete_resource("document_types", id)

Delete a document type.

Args: id: Numeric ID of the document type to delete.

Raises: ~easypaperless.exceptions.NotFoundError: If no document type exists with that ID.

async def list_storage_paths( self, *, ids: list[int] | None = None, name_contains: str | None = None, name_exact: str | None = None, page: int | None = None, page_size: int | None = None, ordering: str | None = None, descending: bool = False) -> list[StoragePath]:
46    async def list_storage_paths(
47        self,
48        *,
49        ids: list[int] | None = None,
50        name_contains: str | None = None,
51        name_exact: str | None = None,
52        page: int | None = None,
53        page_size: int | None = None,
54        ordering: str | None = None,
55        descending: bool = False,
56    ) -> list[StoragePath]:
57        """Return storage paths defined in paperless-ngx.
58
59        Args:
60            ids: Return only storage paths whose ID is in this list.
61            name_contains: Case-insensitive substring filter on storage-path
62                name (raw API ``name__icontains``).
63            name_exact: Case-insensitive exact match on storage-path name
64                (raw API ``name__iexact``).
65            page: Return only this specific page (1-based). Disables
66                auto-pagination. Default: ``None`` (return all).
67            page_size: Number of results per page. When omitted, the server
68                default is used.
69            ordering: Field to sort by. Examples: ``"name"``, ``"id"``.
70            descending: When ``True``, reverses the sort direction of
71                ``ordering``. Ignored when ``ordering`` is ``None``.
72
73        Returns:
74            List of
75            :class:`~easypaperless.models.storage_paths.StoragePath`
76            objects.
77        """
78        params: dict[str, Any] = {}
79        if ids is not None:
80            params["id__in"] = ",".join(str(i) for i in ids)
81        if name_contains is not None:
82            params["name__icontains"] = name_contains
83        if name_exact is not None:
84            params["name__iexact"] = name_exact
85        if page is not None:
86            params["page"] = page
87        if page_size is not None:
88            params["page_size"] = page_size
89        if ordering is not None:
90            params["ordering"] = f"-{ordering}" if descending else ordering
91        return cast(
92            list[StoragePath],
93            await self._list_resource("storage_paths", StoragePath, params or None),
94        )

Return storage paths defined in paperless-ngx.

Args: ids: Return only storage paths whose ID is in this list. name_contains: Case-insensitive substring filter on storage-path name (raw API name__icontains). name_exact: Case-insensitive exact match on storage-path name (raw API name__iexact). page: Return only this specific page (1-based). Disables auto-pagination. Default: None (return all). page_size: Number of results per page. When omitted, the server default is used. ordering: Field to sort by. Examples: "name", "id". descending: When True, reverses the sort direction of ordering. Ignored when ordering is None.

Returns: List of ~easypaperless.models.storage_paths.StoragePath objects.

async def get_storage_path(self, id: int) -> StoragePath:
 96    async def get_storage_path(self, id: int) -> StoragePath:
 97        """Fetch a single storage path by its ID.
 98
 99        Args:
100            id: Numeric storage-path ID.
101
102        Returns:
103            The :class:`~easypaperless.models.storage_paths.StoragePath`
104            with the given ID.
105
106        Raises:
107            ~easypaperless.exceptions.NotFoundError: If no storage path
108                exists with that ID.
109        """
110        return cast(StoragePath, await self._get_resource("storage_paths", id, StoragePath))

Fetch a single storage path by its ID.

Args: id: Numeric storage-path ID.

Returns: The ~easypaperless.models.storage_paths.StoragePath with the given ID.

Raises: ~easypaperless.exceptions.NotFoundError: If no storage path exists with that ID.

async def create_storage_path( self, *, name: str, path: str | None = None, match: str | None = None, matching_algorithm: MatchingAlgorithm | None = None, is_insensitive: bool | None = None, owner: int | None = None, set_permissions: SetPermissions | None = None) -> StoragePath:
112    async def create_storage_path(
113        self,
114        *,
115        name: str,
116        path: str | None = None,
117        match: str | None = None,
118        matching_algorithm: MatchingAlgorithm | None = None,
119        is_insensitive: bool | None = None,
120        owner: int | None = None,
121        set_permissions: SetPermissions | None = None,
122    ) -> StoragePath:
123        """Create a new storage path.
124
125        Args:
126            name: Storage-path name. Must be unique.
127            path: Template string for the archive file path. Supported
128                placeholders: ``{created_year}``, ``{created_month}``,
129                ``{created_day}``, ``{correspondent}``, ``{document_type}``,
130                ``{title}``, ``{asn}``. Example:
131                ``"{created_year}/{correspondent}/{title}"``. When omitted,
132                the server default location is used.
133            match: Auto-matching pattern tested against incoming document
134                content. Interpretation depends on ``matching_algorithm``.
135            matching_algorithm: Controls how ``match`` is applied.
136                See :class:`~easypaperless.models.MatchingAlgorithm`.
137            is_insensitive: When ``True``, ``match`` is evaluated
138                case-insensitively.
139            owner: Numeric user ID to assign as owner. ``None`` creates the
140                storage path with no owner (visible to all users).
141            set_permissions: Explicit view/change permission sets. Defaults to
142                no permissions (all users have access via public visibility).
143
144        Returns:
145            The newly created
146            :class:`~easypaperless.models.storage_paths.StoragePath`.
147        """
148        return cast(StoragePath, await self._create_resource(
149            "storage_paths",
150            StoragePath,
151            owner=owner,
152            set_permissions=set_permissions,
153            name=name,
154            path=path,
155            match=match,
156            matching_algorithm=matching_algorithm,
157            is_insensitive=is_insensitive,
158        ))

Create a new storage path.

Args: name: Storage-path name. Must be unique. path: Template string for the archive file path. Supported placeholders: {created_year}, {created_month}, {created_day}, {correspondent}, {document_type}, {title}, {asn}. Example: "{created_year}/{correspondent}/{title}". When omitted, the server default location is used. match: Auto-matching pattern tested against incoming document content. Interpretation depends on matching_algorithm. matching_algorithm: Controls how match is applied. See ~easypaperless.models.MatchingAlgorithm. is_insensitive: When True, match is evaluated case-insensitively. owner: Numeric user ID to assign as owner. None creates the storage path with no owner (visible to all users). set_permissions: Explicit view/change permission sets. Defaults to no permissions (all users have access via public visibility).

Returns: The newly created ~easypaperless.models.storage_paths.StoragePath.

async def update_storage_path( self, id: int, *, name: str | None = None, path: str | None = None, match: str | None = None, matching_algorithm: MatchingAlgorithm | None = None, is_insensitive: bool | None = None) -> StoragePath:
160    async def update_storage_path(
161        self,
162        id: int,
163        *,
164        name: str | None = None,
165        path: str | None = None,
166        match: str | None = None,
167        matching_algorithm: MatchingAlgorithm | None = None,
168        is_insensitive: bool | None = None,
169    ) -> StoragePath:
170        """Partially update a storage path (PATCH semantics).
171
172        Args:
173            id: Numeric ID of the storage path to update.
174            name: Storage-path name. Must be unique.
175            path: Template string for the archive file path. Supported
176                placeholders: ``{created_year}``, ``{created_month}``,
177                ``{created_day}``, ``{correspondent}``, ``{document_type}``,
178                ``{title}``, ``{asn}``. Example: ``"{title}"``.
179            match: Auto-matching pattern tested against incoming document
180                content. Interpretation depends on ``matching_algorithm``.
181            matching_algorithm: Controls how ``match`` is applied.
182                See :class:`~easypaperless.models.MatchingAlgorithm`.
183            is_insensitive: When ``True``, ``match`` is evaluated
184                case-insensitively.
185
186        Returns:
187            The updated
188            :class:`~easypaperless.models.storage_paths.StoragePath`.
189        """
190        return cast(StoragePath, await self._update_resource(
191            "storage_paths",
192            id,
193            StoragePath,
194            name=name,
195            path=path,
196            match=match,
197            matching_algorithm=matching_algorithm,
198            is_insensitive=is_insensitive,
199        ))

Partially update a storage path (PATCH semantics).

Args: id: Numeric ID of the storage path to update. name: Storage-path name. Must be unique. path: Template string for the archive file path. Supported placeholders: {created_year}, {created_month}, {created_day}, {correspondent}, {document_type}, {title}, {asn}. Example: "{title}". match: Auto-matching pattern tested against incoming document content. Interpretation depends on matching_algorithm. matching_algorithm: Controls how match is applied. See ~easypaperless.models.MatchingAlgorithm. is_insensitive: When True, match is evaluated case-insensitively.

Returns: The updated ~easypaperless.models.storage_paths.StoragePath.

async def delete_storage_path(self, id: int) -> None:
201    async def delete_storage_path(self, id: int) -> None:
202        """Delete a storage path.
203
204        Args:
205            id: Numeric ID of the storage path to delete.
206
207        Raises:
208            ~easypaperless.exceptions.NotFoundError: If no storage path
209                exists with that ID.
210        """
211        await self._delete_resource("storage_paths", id)

Delete a storage path.

Args: id: Numeric ID of the storage path to delete.

Raises: ~easypaperless.exceptions.NotFoundError: If no storage path exists with that ID.

async def list_custom_fields( self, *, page: int | None = None, page_size: int | None = None, ordering: str | None = None, descending: bool = False) -> list[CustomField]:
45    async def list_custom_fields(
46        self,
47        *,
48        page: int | None = None,
49        page_size: int | None = None,
50        ordering: str | None = None,
51        descending: bool = False,
52    ) -> list[CustomField]:
53        """Return all custom fields defined in paperless-ngx.
54
55        Args:
56            page: Return only this specific page (1-based). Disables
57                auto-pagination. Default: ``None`` (return all).
58            page_size: Number of results per page. When omitted, the server
59                default is used.
60            ordering: Field to sort by. Examples: ``"name"``, ``"id"``.
61            descending: When ``True``, reverses the sort direction of
62                ``ordering``. Ignored when ``ordering`` is ``None``.
63
64        Returns:
65            List of
66            :class:`~easypaperless.models.custom_fields.CustomField`
67            objects.
68        """
69        params: dict[str, Any] = {}
70        if page is not None:
71            params["page"] = page
72        if page_size is not None:
73            params["page_size"] = page_size
74        if ordering is not None:
75            params["ordering"] = f"-{ordering}" if descending else ordering
76        return cast(
77            list[CustomField],
78            await self._list_resource("custom_fields", CustomField, params or None),
79        )

Return all custom fields defined in paperless-ngx.

Args: page: Return only this specific page (1-based). Disables auto-pagination. Default: None (return all). page_size: Number of results per page. When omitted, the server default is used. ordering: Field to sort by. Examples: "name", "id". descending: When True, reverses the sort direction of ordering. Ignored when ordering is None.

Returns: List of ~easypaperless.models.custom_fields.CustomField objects.

async def get_custom_field(self, id: int) -> CustomField:
81    async def get_custom_field(self, id: int) -> CustomField:
82        """Fetch a single custom field by its ID.
83
84        Args:
85            id: Numeric custom-field ID.
86
87        Returns:
88            The :class:`~easypaperless.models.custom_fields.CustomField`
89            with the given ID.
90
91        Raises:
92            ~easypaperless.exceptions.NotFoundError: If no custom field
93                exists with that ID.
94        """
95        return cast(CustomField, await self._get_resource("custom_fields", id, CustomField))

Fetch a single custom field by its ID.

Args: id: Numeric custom-field ID.

Returns: The ~easypaperless.models.custom_fields.CustomField with the given ID.

Raises: ~easypaperless.exceptions.NotFoundError: If no custom field exists with that ID.

async def create_custom_field( self, *, name: str, data_type: str, extra_data: typing.Any | None = None, owner: int | None = None, set_permissions: SetPermissions | None = None) -> CustomField:
 97    async def create_custom_field(
 98        self,
 99        *,
100        name: str,
101        data_type: str,
102        extra_data: Any | None = None,
103        owner: int | None = None,
104        set_permissions: SetPermissions | None = None,
105    ) -> CustomField:
106        """Create a new custom field.
107
108        Args:
109            name: Field name shown in the UI. Must be unique.
110            data_type: Value type. One of ``"string"``, ``"boolean"``,
111                ``"integer"``, ``"float"``, ``"monetary"``, ``"date"``,
112                ``"url"``, ``"documentlink"``, ``"select"``.
113            extra_data: Additional configuration for the field type. For
114                ``data_type="select"``, pass
115                ``{"select_options": ["Option A", "Option B"]}``. For all
116                other types, leave as ``None``.
117            owner: Numeric user ID to assign as owner. ``None`` creates the
118                field with no owner (visible to all users).
119            set_permissions: Explicit view/change permission sets. Defaults to
120                no permissions (all users have access via public visibility).
121
122        Returns:
123            The newly created
124            :class:`~easypaperless.models.custom_fields.CustomField`.
125        """
126        return cast(CustomField, await self._create_resource(
127            "custom_fields",
128            CustomField,
129            owner=owner,
130            set_permissions=set_permissions,
131            name=name,
132            data_type=data_type,
133            extra_data=extra_data,
134        ))

Create a new custom field.

Args: name: Field name shown in the UI. Must be unique. data_type: Value type. One of "string", "boolean", "integer", "float", "monetary", "date", "url", "documentlink", "select". extra_data: Additional configuration for the field type. For data_type="select", pass {"select_options": ["Option A", "Option B"]}. For all other types, leave as None. owner: Numeric user ID to assign as owner. None creates the field with no owner (visible to all users). set_permissions: Explicit view/change permission sets. Defaults to no permissions (all users have access via public visibility).

Returns: The newly created ~easypaperless.models.custom_fields.CustomField.

async def update_custom_field( self, id: int, *, name: str | None = None, extra_data: typing.Any | None = None) -> CustomField:
136    async def update_custom_field(
137        self,
138        id: int,
139        *,
140        name: str | None = None,
141        extra_data: Any | None = None,
142    ) -> CustomField:
143        """Partially update a custom field (PATCH semantics).
144
145        Note:
146            ``data_type`` is intentionally excluded — the paperless-ngx API
147            does not allow changing the type of an existing custom field. To
148            change the type, delete and recreate the field.
149
150        Args:
151            id: Numeric ID of the custom field to update.
152            name: Field name shown in the UI. Must be unique.
153            extra_data: Additional configuration for the field type (e.g.
154                ``{"select_options": ["Option A", "Option B"]}`` for select
155                fields). Passing ``None`` is a no-op and will not clear the
156                existing value.
157
158        Returns:
159            The updated
160            :class:`~easypaperless.models.custom_fields.CustomField`.
161        """
162        return cast(CustomField, await self._update_resource(
163            "custom_fields",
164            id,
165            CustomField,
166            name=name,
167            extra_data=extra_data,
168        ))

Partially update a custom field (PATCH semantics).

Note: data_type is intentionally excluded — the paperless-ngx API does not allow changing the type of an existing custom field. To change the type, delete and recreate the field.

Args: id: Numeric ID of the custom field to update. name: Field name shown in the UI. Must be unique. extra_data: Additional configuration for the field type (e.g. {"select_options": ["Option A", "Option B"]} for select fields). Passing None is a no-op and will not clear the existing value.

Returns: The updated ~easypaperless.models.custom_fields.CustomField.

async def delete_custom_field(self, id: int) -> None:
170    async def delete_custom_field(self, id: int) -> None:
171        """Delete a custom field.
172
173        Args:
174            id: Numeric ID of the custom field to delete.
175
176        Raises:
177            ~easypaperless.exceptions.NotFoundError: If no custom field
178                exists with that ID.
179        """
180        await self._delete_resource("custom_fields", id)

Delete a custom field.

Args: id: Numeric ID of the custom field to delete.

Raises: ~easypaperless.exceptions.NotFoundError: If no custom field exists with that ID.

class SyncPaperlessClient(easypaperless._internal.sync_mixins.documents.SyncDocumentsMixin, easypaperless._internal.sync_mixins.notes.SyncNotesMixin, easypaperless._internal.sync_mixins.upload.SyncUploadMixin, easypaperless._internal.sync_mixins.document_bulk.SyncDocumentBulkMixin, easypaperless._internal.sync_mixins.non_document_bulk.SyncNonDocumentBulkMixin, easypaperless._internal.sync_mixins.tags.SyncTagsMixin, easypaperless._internal.sync_mixins.correspondents.SyncCorrespondentsMixin, easypaperless._internal.sync_mixins.document_types.SyncDocumentTypesMixin, easypaperless._internal.sync_mixins.storage_paths.SyncStoragePathsMixin, easypaperless._internal.sync_mixins.custom_fields.SyncCustomFieldsMixin, easypaperless.sync._SyncCore):
 65class SyncPaperlessClient(
 66    SyncDocumentsMixin,
 67    SyncNotesMixin,
 68    SyncUploadMixin,
 69    SyncDocumentBulkMixin,
 70    SyncNonDocumentBulkMixin,
 71    SyncTagsMixin,
 72    SyncCorrespondentsMixin,
 73    SyncDocumentTypesMixin,
 74    SyncStoragePathsMixin,
 75    SyncCustomFieldsMixin,
 76    _SyncCore,
 77):
 78    """Synchronous interface to paperless-ngx.
 79
 80    Exposes the same methods as
 81    :class:`~easypaperless.client.PaperlessClient` but runs them
 82    synchronously, making it suitable for scripts and REPL sessions that do
 83    not use ``asyncio``.
 84
 85    All methods have identical signatures to their async counterparts on
 86    :class:`~easypaperless.client.PaperlessClient`.  Operations run on a
 87    dedicated background event loop thread so that the httpx connection pool
 88    is reused across calls and cleanup works correctly.
 89
 90    Use as a context manager to ensure proper cleanup:
 91
 92    Example:
 93        with SyncPaperlessClient(url="http://localhost:8000", api_key="abc") as client:
 94            tags = client.list_tags()
 95            docs = client.list_documents(search="invoice")
 96
 97    Note:
 98        Cannot be used inside an already-running event loop (e.g. Jupyter
 99        notebooks).  Use :class:`~easypaperless.client.PaperlessClient`
100        directly in those environments.
101    """
102
103    # Explicit assignments so pdoc documents these inherited methods.
104    # Documents
105    list_documents = SyncDocumentsMixin.list_documents
106    get_document = SyncDocumentsMixin.get_document
107    get_document_metadata = SyncDocumentsMixin.get_document_metadata
108    update_document = SyncDocumentsMixin.update_document
109    delete_document = SyncDocumentsMixin.delete_document
110    download_document = SyncDocumentsMixin.download_document
111    # Notes
112    get_notes = SyncNotesMixin.get_notes
113    create_note = SyncNotesMixin.create_note
114    delete_note = SyncNotesMixin.delete_note
115    # Upload
116    upload_document = SyncUploadMixin.upload_document
117    # Document bulk operations
118    bulk_edit = SyncDocumentBulkMixin.bulk_edit
119    bulk_add_tag = SyncDocumentBulkMixin.bulk_add_tag
120    bulk_remove_tag = SyncDocumentBulkMixin.bulk_remove_tag
121    bulk_modify_tags = SyncDocumentBulkMixin.bulk_modify_tags
122    bulk_delete = SyncDocumentBulkMixin.bulk_delete
123    bulk_set_correspondent = SyncDocumentBulkMixin.bulk_set_correspondent
124    bulk_set_document_type = SyncDocumentBulkMixin.bulk_set_document_type
125    bulk_set_storage_path = SyncDocumentBulkMixin.bulk_set_storage_path
126    bulk_modify_custom_fields = SyncDocumentBulkMixin.bulk_modify_custom_fields
127    bulk_set_permissions = SyncDocumentBulkMixin.bulk_set_permissions
128    # Non-document bulk operations
129    bulk_edit_objects = SyncNonDocumentBulkMixin.bulk_edit_objects
130    bulk_delete_tags = SyncNonDocumentBulkMixin.bulk_delete_tags
131    bulk_delete_correspondents = SyncNonDocumentBulkMixin.bulk_delete_correspondents
132    bulk_delete_document_types = SyncNonDocumentBulkMixin.bulk_delete_document_types
133    bulk_delete_storage_paths = SyncNonDocumentBulkMixin.bulk_delete_storage_paths
134    bulk_set_permissions_tags = SyncNonDocumentBulkMixin.bulk_set_permissions_tags
135    bulk_set_permissions_correspondents = SyncNonDocumentBulkMixin.bulk_set_permissions_correspondents
136    bulk_set_permissions_document_types = SyncNonDocumentBulkMixin.bulk_set_permissions_document_types
137    bulk_set_permissions_storage_paths = SyncNonDocumentBulkMixin.bulk_set_permissions_storage_paths
138    # Tags
139    list_tags = SyncTagsMixin.list_tags
140    get_tag = SyncTagsMixin.get_tag
141    create_tag = SyncTagsMixin.create_tag
142    update_tag = SyncTagsMixin.update_tag
143    delete_tag = SyncTagsMixin.delete_tag
144    # Correspondents
145    list_correspondents = SyncCorrespondentsMixin.list_correspondents
146    get_correspondent = SyncCorrespondentsMixin.get_correspondent
147    create_correspondent = SyncCorrespondentsMixin.create_correspondent
148    update_correspondent = SyncCorrespondentsMixin.update_correspondent
149    delete_correspondent = SyncCorrespondentsMixin.delete_correspondent
150    # Document types
151    list_document_types = SyncDocumentTypesMixin.list_document_types
152    get_document_type = SyncDocumentTypesMixin.get_document_type
153    create_document_type = SyncDocumentTypesMixin.create_document_type
154    update_document_type = SyncDocumentTypesMixin.update_document_type
155    delete_document_type = SyncDocumentTypesMixin.delete_document_type
156    # Storage paths
157    list_storage_paths = SyncStoragePathsMixin.list_storage_paths
158    get_storage_path = SyncStoragePathsMixin.get_storage_path
159    create_storage_path = SyncStoragePathsMixin.create_storage_path
160    update_storage_path = SyncStoragePathsMixin.update_storage_path
161    delete_storage_path = SyncStoragePathsMixin.delete_storage_path
162    # Custom fields
163    list_custom_fields = SyncCustomFieldsMixin.list_custom_fields
164    get_custom_field = SyncCustomFieldsMixin.get_custom_field
165    create_custom_field = SyncCustomFieldsMixin.create_custom_field
166    update_custom_field = SyncCustomFieldsMixin.update_custom_field
167    delete_custom_field = SyncCustomFieldsMixin.delete_custom_field
168
169    def __init__(self, url: str, api_key: str, **kwargs: Any) -> None:
170        """Create a synchronous paperless-ngx client.
171
172        Args:
173            url: Base URL of the paperless-ngx instance
174                (e.g. ``"http://localhost:8000"``).
175            api_key: API token.  Generate one in paperless-ngx under
176                *Settings → API → Generate Token*.
177            **kwargs: Additional keyword arguments forwarded to
178                :class:`~easypaperless.client.PaperlessClient` (e.g.
179                ``poll_interval``, ``poll_timeout``).
180        """
181        super().__init__(url, api_key, **kwargs)
182
183    def __enter__(self) -> SyncPaperlessClient:
184        return self
185
186    def __exit__(self, *args: Any) -> None:
187        self.close()

Synchronous interface to paperless-ngx.

Exposes the same methods as ~easypaperless.client.PaperlessClient but runs them synchronously, making it suitable for scripts and REPL sessions that do not use asyncio.

All methods have identical signatures to their async counterparts on ~easypaperless.client.PaperlessClient. Operations run on a dedicated background event loop thread so that the httpx connection pool is reused across calls and cleanup works correctly.

Use as a context manager to ensure proper cleanup:

Example: with SyncPaperlessClient(url="http://localhost:8000", api_key="abc") as client: tags = client.list_tags() docs = client.list_documents(search="invoice")

Note: Cannot be used inside an already-running event loop (e.g. Jupyter notebooks). Use ~easypaperless.client.PaperlessClient directly in those environments.

SyncPaperlessClient(url: str, api_key: str, **kwargs: Any)
169    def __init__(self, url: str, api_key: str, **kwargs: Any) -> None:
170        """Create a synchronous paperless-ngx client.
171
172        Args:
173            url: Base URL of the paperless-ngx instance
174                (e.g. ``"http://localhost:8000"``).
175            api_key: API token.  Generate one in paperless-ngx under
176                *Settings → API → Generate Token*.
177            **kwargs: Additional keyword arguments forwarded to
178                :class:`~easypaperless.client.PaperlessClient` (e.g.
179                ``poll_interval``, ``poll_timeout``).
180        """
181        super().__init__(url, api_key, **kwargs)

Create a synchronous paperless-ngx client.

Args: url: Base URL of the paperless-ngx instance (e.g. "http://localhost:8000"). api_key: API token. Generate one in paperless-ngx under Settings → API → Generate Token. **kwargs: Additional keyword arguments forwarded to ~easypaperless.client.PaperlessClient (e.g. poll_interval, poll_timeout).

def list_documents( self, *, search: str | None = None, search_mode: str = 'title_or_text', ids: list[int] | None = None, tags: list[int | str] | None = None, any_tags: list[int | str] | None = None, exclude_tags: list[int | str] | None = None, correspondent: int | str | None = None, any_correspondent: list[int | str] | None = None, exclude_correspondents: list[int | str] | None = None, document_type: int | str | None = None, any_document_type: list[int | str] | None = None, exclude_document_types: list[int | str] | None = None, storage_path: int | str | None = None, any_storage_paths: list[int | str] | None = None, exclude_storage_paths: list[int | str] | None = None, owner: int | None = None, exclude_owners: list[int] | None = None, custom_fields: list[int | str] | None = None, any_custom_fields: list[int | str] | None = None, exclude_custom_fields: list[int | str] | None = None, custom_field_query: list[typing.Any] | None = None, archive_serial_number: int | None = None, archive_serial_number_from: int | None = None, archive_serial_number_till: int | None = None, created_after: datetime.date | str | None = None, created_before: datetime.date | str | None = None, added_after: datetime.date | datetime.datetime | str | None = None, added_from: datetime.date | datetime.datetime | str | None = None, added_before: datetime.date | datetime.datetime | str | None = None, added_until: datetime.date | datetime.datetime | str | None = None, modified_after: datetime.date | datetime.datetime | str | None = None, modified_from: datetime.date | datetime.datetime | str | None = None, modified_before: datetime.date | datetime.datetime | str | None = None, modified_until: datetime.date | datetime.datetime | str | None = None, checksum: str | None = None, page_size: int = 25, page: int | None = None, ordering: str | None = None, descending: bool = False, max_results: int | None = None, on_page: Optional[Callable[[int, int | None], NoneType]] = None) -> list[Document]:
 31    def list_documents(
 32        self,
 33        *,
 34        search: str | None = None,
 35        search_mode: str = "title_or_text",
 36        ids: list[int] | None = None,
 37        tags: list[int | str] | None = None,
 38        any_tags: list[int | str] | None = None,
 39        exclude_tags: list[int | str] | None = None,
 40        correspondent: int | str | None = None,
 41        any_correspondent: list[int | str] | None = None,
 42        exclude_correspondents: list[int | str] | None = None,
 43        document_type: int | str | None = None,
 44        any_document_type: list[int | str] | None = None,
 45        exclude_document_types: list[int | str] | None = None,
 46        storage_path: int | str | None = None,
 47        any_storage_paths: list[int | str] | None = None,
 48        exclude_storage_paths: list[int | str] | None = None,
 49        owner: int | None = None,
 50        exclude_owners: list[int] | None = None,
 51        custom_fields: list[int | str] | None = None,
 52        any_custom_fields: list[int | str] | None = None,
 53        exclude_custom_fields: list[int | str] | None = None,
 54        custom_field_query: list[Any] | None = None,
 55        archive_serial_number: int | None = None,
 56        archive_serial_number_from: int | None = None,
 57        archive_serial_number_till: int | None = None,
 58        created_after: date | str | None = None,
 59        created_before: date | str | None = None,
 60        added_after: date | datetime | str | None = None,
 61        added_from: date | datetime | str | None = None,
 62        added_before: date | datetime | str | None = None,
 63        added_until: date | datetime | str | None = None,
 64        modified_after: date | datetime | str | None = None,
 65        modified_from: date | datetime | str | None = None,
 66        modified_before: date | datetime | str | None = None,
 67        modified_until: date | datetime | str | None = None,
 68        checksum: str | None = None,
 69        page_size: int = 25,
 70        page: int | None = None,
 71        ordering: str | None = None,
 72        descending: bool = False,
 73        max_results: int | None = None,
 74        on_page: Callable[[int, int | None], None] | None = None,
 75    ) -> list[Document]:
 76        return self._run(self._async_client.list_documents(
 77            search=search,
 78            search_mode=search_mode,
 79            ids=ids,
 80            tags=tags,
 81            any_tags=any_tags,
 82            exclude_tags=exclude_tags,
 83            correspondent=correspondent,
 84            any_correspondent=any_correspondent,
 85            exclude_correspondents=exclude_correspondents,
 86            document_type=document_type,
 87            any_document_type=any_document_type,
 88            exclude_document_types=exclude_document_types,
 89            storage_path=storage_path,
 90            any_storage_paths=any_storage_paths,
 91            exclude_storage_paths=exclude_storage_paths,
 92            owner=owner,
 93            exclude_owners=exclude_owners,
 94            custom_fields=custom_fields,
 95            any_custom_fields=any_custom_fields,
 96            exclude_custom_fields=exclude_custom_fields,
 97            custom_field_query=custom_field_query,
 98            archive_serial_number=archive_serial_number,
 99            archive_serial_number_from=archive_serial_number_from,
100            archive_serial_number_till=archive_serial_number_till,
101            created_after=created_after,
102            created_before=created_before,
103            added_after=added_after,
104            added_from=added_from,
105            added_before=added_before,
106            added_until=added_until,
107            modified_after=modified_after,
108            modified_from=modified_from,
109            modified_before=modified_before,
110            modified_until=modified_until,
111            checksum=checksum,
112            page_size=page_size,
113            page=page,
114            ordering=ordering,
115            descending=descending,
116            max_results=max_results,
117            on_page=on_page,
118        ))
def get_document( self, id: int, *, include_metadata: bool = False) -> Document:
25    def get_document(self, id: int, *, include_metadata: bool = False) -> Document:
26        return self._run(self._async_client.get_document(id, include_metadata=include_metadata))
def get_document_metadata(self, id: int) -> DocumentMetadata:
28    def get_document_metadata(self, id: int) -> DocumentMetadata:
29        return self._run(self._async_client.get_document_metadata(id))
def update_document( self, id: int, *, title: str | None = None, content: str | None = None, date: str | None = None, correspondent: int | str | None = None, document_type: int | str | None = None, storage_path: int | str | None = None, tags: list[int | str] | None = None, asn: int | None = None, custom_fields: list[dict[str, typing.Any]] | None = None, owner: int | None = None, set_permissions: SetPermissions | None = None) -> Document:
120    def update_document(
121        self,
122        id: int,
123        *,
124        title: str | None = None,
125        content: str | None = None,
126        date: str | None = None,
127        correspondent: int | str | None = None,
128        document_type: int | str | None = None,
129        storage_path: int | str | None = None,
130        tags: list[int | str] | None = None,
131        asn: int | None = None,
132        custom_fields: list[dict[str, Any]] | None = None,
133        owner: int | None = None,
134        set_permissions: SetPermissions | None = None,
135    ) -> Document:
136        return self._run(self._async_client.update_document(
137            id,
138            title=title,
139            content=content,
140            date=date,
141            correspondent=correspondent,
142            document_type=document_type,
143            storage_path=storage_path,
144            tags=tags,
145            asn=asn,
146            custom_fields=custom_fields,
147            owner=owner,
148            set_permissions=set_permissions,
149        ))
def delete_document(self, id: int) -> None:
151    def delete_document(self, id: int) -> None:
152        return self._run(self._async_client.delete_document(id))
def download_document(self, id: int, *, original: bool = False) -> bytes:
154    def download_document(self, id: int, *, original: bool = False) -> bytes:
155        return self._run(self._async_client.download_document(id, original=original))
def get_notes( self, document_id: int) -> list[DocumentNote]:
23    def get_notes(self, document_id: int) -> list[DocumentNote]:
24        return self._run(self._async_client.get_notes(document_id))
def create_note( self, document_id: int, *, note: str) -> DocumentNote:
26    def create_note(self, document_id: int, *, note: str) -> DocumentNote:
27        return self._run(self._async_client.create_note(document_id, note=note))
def delete_note(self, document_id: int, note_id: int) -> None:
29    def delete_note(self, document_id: int, note_id: int) -> None:
30        return self._run(self._async_client.delete_note(document_id, note_id))
def upload_document( self, file: str | pathlib._local.Path, *, title: str | None = None, created: str | None = None, correspondent: int | str | None = None, document_type: int | str | None = None, storage_path: int | str | None = None, tags: list[int | str] | None = None, asn: int | None = None, wait: bool = False, poll_interval: float | None = None, poll_timeout: float | None = None) -> str | Document:
24    def upload_document(
25        self,
26        file: str | Path,
27        *,
28        title: str | None = None,
29        created: str | None = None,
30        correspondent: int | str | None = None,
31        document_type: int | str | None = None,
32        storage_path: int | str | None = None,
33        tags: list[int | str] | None = None,
34        asn: int | None = None,
35        wait: bool = False,
36        poll_interval: float | None = None,
37        poll_timeout: float | None = None,
38    ) -> str | Document:
39        return self._run(self._async_client.upload_document(
40            file,
41            title=title,
42            created=created,
43            correspondent=correspondent,
44            document_type=document_type,
45            storage_path=storage_path,
46            tags=tags,
47            asn=asn,
48            wait=wait,
49            poll_interval=poll_interval,
50            poll_timeout=poll_timeout,
51        ))
def bulk_edit(self, document_ids: list[int], method: str, **parameters: Any) -> None:
23    def bulk_edit(self, document_ids: list[int], method: str, **parameters: Any) -> None:
24        return self._run(self._async_client.bulk_edit(document_ids, method, **parameters))
def bulk_add_tag(self, document_ids: list[int], tag: int | str) -> None:
26    def bulk_add_tag(self, document_ids: list[int], tag: int | str) -> None:
27        return self._run(self._async_client.bulk_add_tag(document_ids, tag))
def bulk_remove_tag(self, document_ids: list[int], tag: int | str) -> None:
29    def bulk_remove_tag(self, document_ids: list[int], tag: int | str) -> None:
30        return self._run(self._async_client.bulk_remove_tag(document_ids, tag))
def bulk_modify_tags( self, document_ids: list[int], *, add_tags: list[int | str] | None = None, remove_tags: list[int | str] | None = None) -> None:
32    def bulk_modify_tags(
33        self,
34        document_ids: list[int],
35        *,
36        add_tags: list[int | str] | None = None,
37        remove_tags: list[int | str] | None = None,
38    ) -> None:
39        return self._run(self._async_client.bulk_modify_tags(
40            document_ids, add_tags=add_tags, remove_tags=remove_tags
41        ))
def bulk_delete(self, document_ids: list[int]) -> None:
43    def bulk_delete(self, document_ids: list[int]) -> None:
44        return self._run(self._async_client.bulk_delete(document_ids))
def bulk_set_correspondent(self, document_ids: list[int], correspondent: int | str | None) -> None:
46    def bulk_set_correspondent(
47        self, document_ids: list[int], correspondent: int | str | None
48    ) -> None:
49        return self._run(
50            self._async_client.bulk_set_correspondent(document_ids, correspondent)
51        )
def bulk_set_document_type(self, document_ids: list[int], document_type: int | str | None) -> None:
53    def bulk_set_document_type(
54        self, document_ids: list[int], document_type: int | str | None
55    ) -> None:
56        return self._run(
57            self._async_client.bulk_set_document_type(document_ids, document_type)
58        )
def bulk_set_storage_path(self, document_ids: list[int], storage_path: int | str | None) -> None:
60    def bulk_set_storage_path(
61        self, document_ids: list[int], storage_path: int | str | None
62    ) -> None:
63        return self._run(
64            self._async_client.bulk_set_storage_path(document_ids, storage_path)
65        )
def bulk_modify_custom_fields( self, document_ids: list[int], *, add_fields: list[dict[str, typing.Any]] | None = None, remove_fields: list[int] | None = None) -> None:
67    def bulk_modify_custom_fields(
68        self,
69        document_ids: list[int],
70        *,
71        add_fields: list[dict[str, Any]] | None = None,
72        remove_fields: list[int] | None = None,
73    ) -> None:
74        return self._run(
75            self._async_client.bulk_modify_custom_fields(
76                document_ids, add_fields=add_fields, remove_fields=remove_fields
77            )
78        )
def bulk_set_permissions( self, document_ids: list[int], *, set_permissions: SetPermissions | None = None, owner: int | None = None, merge: bool = False) -> None:
80    def bulk_set_permissions(
81        self,
82        document_ids: list[int],
83        *,
84        set_permissions: SetPermissions | None = None,
85        owner: int | None = None,
86        merge: bool = False,
87    ) -> None:
88        return self._run(
89            self._async_client.bulk_set_permissions(
90                document_ids,
91                set_permissions=set_permissions,
92                owner=owner,
93                merge=merge,
94            )
95        )
def bulk_edit_objects( self, object_type: str, object_ids: list[int], operation: str, **parameters: Any) -> None:
23    def bulk_edit_objects(
24        self,
25        object_type: str,
26        object_ids: list[int],
27        operation: str,
28        **parameters: Any,
29    ) -> None:
30        return self._run(
31            self._async_client.bulk_edit_objects(object_type, object_ids, operation, **parameters)
32        )
def bulk_delete_tags(self, ids: list[int]) -> None:
36    def bulk_delete_tags(self, ids: list[int]) -> None:
37        return self._run(self._async_client.bulk_delete_tags(ids))
def bulk_delete_correspondents(self, ids: list[int]) -> None:
39    def bulk_delete_correspondents(self, ids: list[int]) -> None:
40        return self._run(self._async_client.bulk_delete_correspondents(ids))
def bulk_delete_document_types(self, ids: list[int]) -> None:
42    def bulk_delete_document_types(self, ids: list[int]) -> None:
43        return self._run(self._async_client.bulk_delete_document_types(ids))
def bulk_delete_storage_paths(self, ids: list[int]) -> None:
45    def bulk_delete_storage_paths(self, ids: list[int]) -> None:
46        return self._run(self._async_client.bulk_delete_storage_paths(ids))
def bulk_set_permissions_tags( self, ids: list[int], *, set_permissions: SetPermissions | None = None, owner: int | None = None, merge: bool = False) -> None:
50    def bulk_set_permissions_tags(
51        self,
52        ids: list[int],
53        *,
54        set_permissions: SetPermissions | None = None,
55        owner: int | None = None,
56        merge: bool = False,
57    ) -> None:
58        return self._run(
59            self._async_client.bulk_set_permissions_tags(
60                ids, set_permissions=set_permissions, owner=owner, merge=merge
61            )
62        )
def bulk_set_permissions_correspondents( self, ids: list[int], *, set_permissions: SetPermissions | None = None, owner: int | None = None, merge: bool = False) -> None:
64    def bulk_set_permissions_correspondents(
65        self,
66        ids: list[int],
67        *,
68        set_permissions: SetPermissions | None = None,
69        owner: int | None = None,
70        merge: bool = False,
71    ) -> None:
72        return self._run(
73            self._async_client.bulk_set_permissions_correspondents(
74                ids, set_permissions=set_permissions, owner=owner, merge=merge
75            )
76        )
def bulk_set_permissions_document_types( self, ids: list[int], *, set_permissions: SetPermissions | None = None, owner: int | None = None, merge: bool = False) -> None:
78    def bulk_set_permissions_document_types(
79        self,
80        ids: list[int],
81        *,
82        set_permissions: SetPermissions | None = None,
83        owner: int | None = None,
84        merge: bool = False,
85    ) -> None:
86        return self._run(
87            self._async_client.bulk_set_permissions_document_types(
88                ids, set_permissions=set_permissions, owner=owner, merge=merge
89            )
90        )
def bulk_set_permissions_storage_paths( self, ids: list[int], *, set_permissions: SetPermissions | None = None, owner: int | None = None, merge: bool = False) -> None:
 92    def bulk_set_permissions_storage_paths(
 93        self,
 94        ids: list[int],
 95        *,
 96        set_permissions: SetPermissions | None = None,
 97        owner: int | None = None,
 98        merge: bool = False,
 99    ) -> None:
100        return self._run(
101            self._async_client.bulk_set_permissions_storage_paths(
102                ids, set_permissions=set_permissions, owner=owner, merge=merge
103            )
104        )
def list_tags( self, *, ids: list[int] | None = None, name_contains: str | None = None, name_exact: str | None = None, page: int | None = None, page_size: int | None = None, ordering: str | None = None, descending: bool = False) -> list[Tag]:
25    def list_tags(
26        self,
27        *,
28        ids: list[int] | None = None,
29        name_contains: str | None = None,
30        name_exact: str | None = None,
31        page: int | None = None,
32        page_size: int | None = None,
33        ordering: str | None = None,
34        descending: bool = False,
35    ) -> list[Tag]:
36        return self._run(self._async_client.list_tags(
37            ids=ids,
38            name_contains=name_contains,
39            name_exact=name_exact,
40            page=page,
41            page_size=page_size,
42            ordering=ordering,
43            descending=descending,
44        ))
def get_tag(self, id: int) -> Tag:
46    def get_tag(self, id: int) -> Tag:
47        return self._run(self._async_client.get_tag(id))
def create_tag( self, *, name: str, color: str | None = None, is_inbox_tag: bool | None = None, match: str | None = None, matching_algorithm: MatchingAlgorithm | None = None, is_insensitive: bool | None = None, parent: int | None = None, owner: int | None = None, set_permissions: SetPermissions | None = None) -> Tag:
49    def create_tag(
50        self,
51        *,
52        name: str,
53        color: str | None = None,
54        is_inbox_tag: bool | None = None,
55        match: str | None = None,
56        matching_algorithm: MatchingAlgorithm | None = None,
57        is_insensitive: bool | None = None,
58        parent: int | None = None,
59        owner: int | None = None,
60        set_permissions: SetPermissions | None = None,
61    ) -> Tag:
62        return self._run(self._async_client.create_tag(
63            name=name,
64            color=color,
65            is_inbox_tag=is_inbox_tag,
66            match=match,
67            matching_algorithm=matching_algorithm,
68            is_insensitive=is_insensitive,
69            parent=parent,
70            owner=owner,
71            set_permissions=set_permissions,
72        ))
def update_tag( self, id: int, *, name: str | None = None, color: str | None = None, is_inbox_tag: bool | None = None, match: str | None = None, matching_algorithm: MatchingAlgorithm | None = None, is_insensitive: bool | None = None, parent: int | None = None) -> Tag:
74    def update_tag(
75        self,
76        id: int,
77        *,
78        name: str | None = None,
79        color: str | None = None,
80        is_inbox_tag: bool | None = None,
81        match: str | None = None,
82        matching_algorithm: MatchingAlgorithm | None = None,
83        is_insensitive: bool | None = None,
84        parent: int | None = None,
85    ) -> Tag:
86        return self._run(self._async_client.update_tag(
87            id,
88            name=name,
89            color=color,
90            is_inbox_tag=is_inbox_tag,
91            match=match,
92            matching_algorithm=matching_algorithm,
93            is_insensitive=is_insensitive,
94            parent=parent,
95        ))
def delete_tag(self, id: int) -> None:
97    def delete_tag(self, id: int) -> None:
98        return self._run(self._async_client.delete_tag(id))
def list_correspondents( self, *, ids: list[int] | None = None, name_contains: str | None = None, name_exact: str | None = None, page: int | None = None, page_size: int | None = None, ordering: str | None = None, descending: bool = False) -> list[Correspondent]:
25    def list_correspondents(
26        self,
27        *,
28        ids: list[int] | None = None,
29        name_contains: str | None = None,
30        name_exact: str | None = None,
31        page: int | None = None,
32        page_size: int | None = None,
33        ordering: str | None = None,
34        descending: bool = False,
35    ) -> list[Correspondent]:
36        return self._run(self._async_client.list_correspondents(
37            ids=ids,
38            name_contains=name_contains,
39            name_exact=name_exact,
40            page=page,
41            page_size=page_size,
42            ordering=ordering,
43            descending=descending,
44        ))
def get_correspondent(self, id: int) -> Correspondent:
46    def get_correspondent(self, id: int) -> Correspondent:
47        return self._run(self._async_client.get_correspondent(id))
def create_correspondent( self, *, name: str, match: str | None = None, matching_algorithm: MatchingAlgorithm | None = None, is_insensitive: bool | None = None, owner: int | None = None, set_permissions: SetPermissions | None = None) -> Correspondent:
49    def create_correspondent(
50        self,
51        *,
52        name: str,
53        match: str | None = None,
54        matching_algorithm: MatchingAlgorithm | None = None,
55        is_insensitive: bool | None = None,
56        owner: int | None = None,
57        set_permissions: SetPermissions | None = None,
58    ) -> Correspondent:
59        return self._run(self._async_client.create_correspondent(
60            name=name,
61            match=match,
62            matching_algorithm=matching_algorithm,
63            is_insensitive=is_insensitive,
64            owner=owner,
65            set_permissions=set_permissions,
66        ))
def update_correspondent( self, id: int, *, name: str | None = None, match: str | None = None, matching_algorithm: MatchingAlgorithm | None = None, is_insensitive: bool | None = None) -> Correspondent:
68    def update_correspondent(
69        self,
70        id: int,
71        *,
72        name: str | None = None,
73        match: str | None = None,
74        matching_algorithm: MatchingAlgorithm | None = None,
75        is_insensitive: bool | None = None,
76    ) -> Correspondent:
77        return self._run(self._async_client.update_correspondent(
78            id,
79            name=name,
80            match=match,
81            matching_algorithm=matching_algorithm,
82            is_insensitive=is_insensitive,
83        ))
def delete_correspondent(self, id: int) -> None:
85    def delete_correspondent(self, id: int) -> None:
86        return self._run(self._async_client.delete_correspondent(id))
def list_document_types( self, *, ids: list[int] | None = None, name_contains: str | None = None, name_exact: str | None = None, page: int | None = None, page_size: int | None = None, ordering: str | None = None, descending: bool = False) -> list[DocumentType]:
25    def list_document_types(
26        self,
27        *,
28        ids: list[int] | None = None,
29        name_contains: str | None = None,
30        name_exact: str | None = None,
31        page: int | None = None,
32        page_size: int | None = None,
33        ordering: str | None = None,
34        descending: bool = False,
35    ) -> list[DocumentType]:
36        return self._run(self._async_client.list_document_types(
37            ids=ids,
38            name_contains=name_contains,
39            name_exact=name_exact,
40            page=page,
41            page_size=page_size,
42            ordering=ordering,
43            descending=descending,
44        ))
def get_document_type(self, id: int) -> DocumentType:
46    def get_document_type(self, id: int) -> DocumentType:
47        return self._run(self._async_client.get_document_type(id))
def create_document_type( self, *, name: str, match: str | None = None, matching_algorithm: MatchingAlgorithm | None = None, is_insensitive: bool | None = None, owner: int | None = None, set_permissions: SetPermissions | None = None) -> DocumentType:
49    def create_document_type(
50        self,
51        *,
52        name: str,
53        match: str | None = None,
54        matching_algorithm: MatchingAlgorithm | None = None,
55        is_insensitive: bool | None = None,
56        owner: int | None = None,
57        set_permissions: SetPermissions | None = None,
58    ) -> DocumentType:
59        return self._run(self._async_client.create_document_type(
60            name=name,
61            match=match,
62            matching_algorithm=matching_algorithm,
63            is_insensitive=is_insensitive,
64            owner=owner,
65            set_permissions=set_permissions,
66        ))
def update_document_type( self, id: int, *, name: str | None = None, match: str | None = None, matching_algorithm: MatchingAlgorithm | None = None, is_insensitive: bool | None = None) -> DocumentType:
68    def update_document_type(
69        self,
70        id: int,
71        *,
72        name: str | None = None,
73        match: str | None = None,
74        matching_algorithm: MatchingAlgorithm | None = None,
75        is_insensitive: bool | None = None,
76    ) -> DocumentType:
77        return self._run(self._async_client.update_document_type(
78            id,
79            name=name,
80            match=match,
81            matching_algorithm=matching_algorithm,
82            is_insensitive=is_insensitive,
83        ))
def delete_document_type(self, id: int) -> None:
85    def delete_document_type(self, id: int) -> None:
86        return self._run(self._async_client.delete_document_type(id))
def list_storage_paths( self, *, ids: list[int] | None = None, name_contains: str | None = None, name_exact: str | None = None, page: int | None = None, page_size: int | None = None, ordering: str | None = None, descending: bool = False) -> list[StoragePath]:
25    def list_storage_paths(
26        self,
27        *,
28        ids: list[int] | None = None,
29        name_contains: str | None = None,
30        name_exact: str | None = None,
31        page: int | None = None,
32        page_size: int | None = None,
33        ordering: str | None = None,
34        descending: bool = False,
35    ) -> list[StoragePath]:
36        return self._run(self._async_client.list_storage_paths(
37            ids=ids,
38            name_contains=name_contains,
39            name_exact=name_exact,
40            page=page,
41            page_size=page_size,
42            ordering=ordering,
43            descending=descending,
44        ))
def get_storage_path(self, id: int) -> StoragePath:
46    def get_storage_path(self, id: int) -> StoragePath:
47        return self._run(self._async_client.get_storage_path(id))
def create_storage_path( self, *, name: str, path: str | None = None, match: str | None = None, matching_algorithm: MatchingAlgorithm | None = None, is_insensitive: bool | None = None, owner: int | None = None, set_permissions: SetPermissions | None = None) -> StoragePath:
49    def create_storage_path(
50        self,
51        *,
52        name: str,
53        path: str | None = None,
54        match: str | None = None,
55        matching_algorithm: MatchingAlgorithm | None = None,
56        is_insensitive: bool | None = None,
57        owner: int | None = None,
58        set_permissions: SetPermissions | None = None,
59    ) -> StoragePath:
60        return self._run(self._async_client.create_storage_path(
61            name=name,
62            path=path,
63            match=match,
64            matching_algorithm=matching_algorithm,
65            is_insensitive=is_insensitive,
66            owner=owner,
67            set_permissions=set_permissions,
68        ))
def update_storage_path( self, id: int, *, name: str | None = None, path: str | None = None, match: str | None = None, matching_algorithm: MatchingAlgorithm | None = None, is_insensitive: bool | None = None) -> StoragePath:
70    def update_storage_path(
71        self,
72        id: int,
73        *,
74        name: str | None = None,
75        path: str | None = None,
76        match: str | None = None,
77        matching_algorithm: MatchingAlgorithm | None = None,
78        is_insensitive: bool | None = None,
79    ) -> StoragePath:
80        return self._run(self._async_client.update_storage_path(
81            id,
82            name=name,
83            path=path,
84            match=match,
85            matching_algorithm=matching_algorithm,
86            is_insensitive=is_insensitive,
87        ))
def delete_storage_path(self, id: int) -> None:
89    def delete_storage_path(self, id: int) -> None:
90        return self._run(self._async_client.delete_storage_path(id))
def list_custom_fields( self, *, page: int | None = None, page_size: int | None = None, ordering: str | None = None, descending: bool = False) -> list[CustomField]:
24    def list_custom_fields(
25        self,
26        *,
27        page: int | None = None,
28        page_size: int | None = None,
29        ordering: str | None = None,
30        descending: bool = False,
31    ) -> list[CustomField]:
32        return self._run(self._async_client.list_custom_fields(
33            page=page,
34            page_size=page_size,
35            ordering=ordering,
36            descending=descending,
37        ))
def get_custom_field(self, id: int) -> CustomField:
39    def get_custom_field(self, id: int) -> CustomField:
40        return self._run(self._async_client.get_custom_field(id))
def create_custom_field( self, *, name: str, data_type: str, extra_data: typing.Any | None = None, owner: int | None = None, set_permissions: SetPermissions | None = None) -> CustomField:
42    def create_custom_field(
43        self,
44        *,
45        name: str,
46        data_type: str,
47        extra_data: Any | None = None,
48        owner: int | None = None,
49        set_permissions: SetPermissions | None = None,
50    ) -> CustomField:
51        return self._run(self._async_client.create_custom_field(
52            name=name,
53            data_type=data_type,
54            extra_data=extra_data,
55            owner=owner,
56            set_permissions=set_permissions,
57        ))
def update_custom_field( self, id: int, *, name: str | None = None, extra_data: typing.Any | None = None) -> CustomField:
59    def update_custom_field(
60        self,
61        id: int,
62        *,
63        name: str | None = None,
64        extra_data: Any | None = None,
65    ) -> CustomField:
66        return self._run(self._async_client.update_custom_field(
67            id, name=name, extra_data=extra_data
68        ))
def delete_custom_field(self, id: int) -> None:
70    def delete_custom_field(self, id: int) -> None:
71        return self._run(self._async_client.delete_custom_field(id))
class MatchingAlgorithm(enum.IntEnum):
 9class MatchingAlgorithm(IntEnum):
10    """Auto-matching algorithm used by tags, correspondents, document types, and storage paths."""
11
12    NONE = 0
13    ANY_WORD = 1
14    ALL_WORDS = 2
15    EXACT = 3
16    REGEX = 4
17    FUZZY = 5
18    AUTO = 6

Auto-matching algorithm used by tags, correspondents, document types, and storage paths.

ANY_WORD = <MatchingAlgorithm.ANY_WORD: 1>
ALL_WORDS = <MatchingAlgorithm.ALL_WORDS: 2>
class Correspondent(pydantic.main.BaseModel):
13class Correspondent(BaseModel):
14    """A paperless-ngx correspondent (sender or recipient of a document).
15
16    Attributes:
17        id: Unique correspondent ID.
18        name: Correspondent name.
19        document_count: Number of documents assigned to this correspondent.
20        last_correspondence: Date of the most recent document assigned here,
21            or ``None``.
22    """
23
24    model_config = ConfigDict(extra="ignore")
25
26    id: int
27    name: str
28    slug: str | None = None
29    match: str | None = None
30    matching_algorithm: MatchingAlgorithm | None = None
31    is_insensitive: bool | None = None
32    document_count: int | None = None
33    last_correspondence: date | None = None
34    owner: int | None = None
35    user_can_change: bool | None = None

A paperless-ngx correspondent (sender or recipient of a document).

Attributes: id: Unique correspondent ID. name: Correspondent name. document_count: Number of documents assigned to this correspondent. last_correspondence: Date of the most recent document assigned here, or None.

id: int = PydanticUndefined
name: str = PydanticUndefined
slug: str | None = None
match: str | None = None
matching_algorithm: MatchingAlgorithm | None = None
is_insensitive: bool | None = None
document_count: int | None = None
last_correspondence: datetime.date | None = None
owner: int | None = None
user_can_change: bool | None = None
class CustomField(pydantic.main.BaseModel):
26class CustomField(BaseModel):
27    """A custom field definition in paperless-ngx.
28
29    Attributes:
30        id: Unique custom-field ID.
31        name: Field name shown in the UI.
32        data_type: The value type for this field (see
33            :class:`FieldDataType`).
34        extra_data: Additional configuration (e.g. select options), or
35            ``None``.
36        document_count: Number of documents that have a value for this field.
37    """
38
39    model_config = ConfigDict(extra="ignore")
40
41    id: int
42    name: str
43    data_type: FieldDataType
44    extra_data: Any = None
45    document_count: int | None = None

A custom field definition in paperless-ngx.

Attributes: id: Unique custom-field ID. name: Field name shown in the UI. data_type: The value type for this field (see FieldDataType). extra_data: Additional configuration (e.g. select options), or None. document_count: Number of documents that have a value for this field.

id: int = PydanticUndefined
name: str = PydanticUndefined
data_type: FieldDataType = PydanticUndefined
extra_data: Any = None
document_count: int | None = None
class CustomFieldValue(pydantic.main.BaseModel):
66class CustomFieldValue(BaseModel):
67    """A custom field value attached to a document.
68
69    Attributes:
70        field: ID of the :class:`~easypaperless.models.custom_fields.CustomField`
71            definition.
72        value: The stored value; its Python type depends on the field's
73            ``data_type``.
74    """
75
76    model_config = ConfigDict(extra="ignore")
77
78    field: int
79    value: Any = None

A custom field value attached to a document.

Attributes: field: ID of the ~easypaperless.models.custom_fields.CustomField definition. value: The stored value; its Python type depends on the field's data_type.

field: int = PydanticUndefined
value: Any = None
class Document(pydantic.main.BaseModel):
172class Document(BaseModel):
173    """A paperless-ngx document.
174
175    Attributes:
176        id: Unique document ID.
177        title: Document title.
178        content: Full OCR text content, if available.
179        tags: List of tag IDs assigned to this document.
180        document_type: ID of the assigned document type, or ``None``.
181        correspondent: ID of the assigned correspondent, or ``None``.
182        storage_path: ID of the assigned storage path, or ``None``.
183        created: Full creation datetime.
184        created_date: Date portion of creation (``date`` object).
185        archive_serial_number: Archive serial number (ASN), or ``None``.
186        custom_fields: List of :class:`CustomFieldValue` instances.
187        notes: User notes attached to this document.
188        search_hit: Full-text search relevance metadata, populated only when
189            the document was returned by a full-text search.
190        metadata: Extended file-level metadata (checksums, sizes, MIME type).
191            ``None`` unless the document was fetched with
192            ``include_metadata=True`` or enriched via
193            :meth:`~easypaperless.PaperlessClient.get_document_metadata`.
194    """
195
196    model_config = ConfigDict(extra="ignore", populate_by_name=True)
197
198    id: int
199    title: str
200    content: str | None = None
201    tags: list[int] = Field(default_factory=list)
202    document_type: int | None = None
203    correspondent: int | None = None
204    storage_path: int | None = None
205    created: datetime | None = None
206    created_date: date | None = None
207    modified: datetime | None = None
208    added: datetime | None = None
209    archive_serial_number: int | None = None
210    original_file_name: str | None = None
211    archived_file_name: str | None = None
212    owner: int | None = None
213    user_can_change: bool | None = None
214    is_shared_by_requester: bool | None = None
215    notes: list[DocumentNote] = Field(default_factory=list)
216    custom_fields: list[CustomFieldValue] = Field(default_factory=list)
217    search_hit: SearchHit | None = Field(default=None, alias="__search_hit__")
218    metadata: DocumentMetadata | None = None

A paperless-ngx document.

Attributes: id: Unique document ID. title: Document title. content: Full OCR text content, if available. tags: List of tag IDs assigned to this document. document_type: ID of the assigned document type, or None. correspondent: ID of the assigned correspondent, or None. storage_path: ID of the assigned storage path, or None. created: Full creation datetime. created_date: Date portion of creation (date object). archive_serial_number: Archive serial number (ASN), or None. custom_fields: List of CustomFieldValue instances. notes: User notes attached to this document. search_hit: Full-text search relevance metadata, populated only when the document was returned by a full-text search. metadata: Extended file-level metadata (checksums, sizes, MIME type). None unless the document was fetched with include_metadata=True or enriched via ~easypaperless.PaperlessClient.get_document_metadata().

id: int = PydanticUndefined
title: str = PydanticUndefined
content: str | None = None
tags: list[int] = PydanticUndefined
document_type: int | None = None
correspondent: int | None = None
storage_path: int | None = None
created: datetime.datetime | None = None
created_date: datetime.date | None = None
modified: datetime.datetime | None = None
added: datetime.datetime | None = None
archive_serial_number: int | None = None
original_file_name: str | None = None
archived_file_name: str | None = None
owner: int | None = None
user_can_change: bool | None = None
is_shared_by_requester: bool | None = None
notes: list[DocumentNote] = PydanticUndefined
custom_fields: list[CustomFieldValue] = PydanticUndefined
search_hit: SearchHit | None = None
metadata: DocumentMetadata | None = None
class DocumentMetadata(pydantic.main.BaseModel):
131class DocumentMetadata(BaseModel):
132    """Extended file-level metadata for a document.
133
134    Returned by ``GET /api/documents/{id}/metadata/`` and optionally attached
135    to a :class:`Document` when :meth:`~easypaperless.PaperlessClient.get_document`
136    is called with ``include_metadata=True``.
137
138    Because reading metadata requires disk I/O it is **not** included in
139    document list responses; it must be requested explicitly.
140
141    Attributes:
142        original_checksum: MD5 checksum of the original uploaded file.
143        original_size: Size of the original file in bytes.
144        original_mime_type: MIME type of the original file
145            (e.g. ``"application/pdf"``).
146        media_filename: Path of the archived file relative to the
147            paperless-ngx media root.
148        has_archive_version: ``True`` when paperless-ngx has produced an
149            archived (post-processed) PDF in addition to the original.
150        original_metadata: File-level metadata entries extracted from the
151            original document (PDF XMP/info tags, etc.).
152        archive_checksum: MD5 checksum of the archived file, or ``None`` if
153            no archive version exists.
154        archive_size: Size of the archived file in bytes, or ``None``.
155        archive_metadata: File-level metadata entries from the archived
156            document, or ``None``.
157    """
158
159    model_config = ConfigDict(extra="ignore")
160
161    original_checksum: str | None = None
162    original_size: int | None = None
163    original_mime_type: str | None = None
164    media_filename: str | None = None
165    has_archive_version: bool | None = None
166    original_metadata: list[FileMetadataEntry] = Field(default_factory=list)
167    archive_checksum: str | None = None
168    archive_size: int | None = None
169    archive_metadata: list[FileMetadataEntry] | None = None

Extended file-level metadata for a document.

Returned by GET /api/documents/{id}/metadata/ and optionally attached to a Document when ~easypaperless.PaperlessClient.get_document() is called with include_metadata=True.

Because reading metadata requires disk I/O it is not included in document list responses; it must be requested explicitly.

Attributes: original_checksum: MD5 checksum of the original uploaded file. original_size: Size of the original file in bytes. original_mime_type: MIME type of the original file (e.g. "application/pdf"). media_filename: Path of the archived file relative to the paperless-ngx media root. has_archive_version: True when paperless-ngx has produced an archived (post-processed) PDF in addition to the original. original_metadata: File-level metadata entries extracted from the original document (PDF XMP/info tags, etc.). archive_checksum: MD5 checksum of the archived file, or None if no archive version exists. archive_size: Size of the archived file in bytes, or None. archive_metadata: File-level metadata entries from the archived document, or None.

original_checksum: str | None = None
original_size: int | None = None
original_mime_type: str | None = None
media_filename: str | None = None
has_archive_version: bool | None = None
original_metadata: list[FileMetadataEntry] = PydanticUndefined
archive_checksum: str | None = None
archive_size: int | None = None
archive_metadata: list[FileMetadataEntry] | None = None
class DocumentNote(pydantic.main.BaseModel):
 82class DocumentNote(BaseModel):
 83    """A user note attached to a document.
 84
 85    Attributes:
 86        id: Unique note ID.
 87        note: Text content of the note.
 88        created: Timestamp when the note was created.
 89        document: ID of the parent document.
 90        user: ID of the user who created the note.
 91    """
 92
 93    model_config = ConfigDict(extra="ignore")
 94
 95    id: int | None = None
 96    note: str
 97    created: datetime | None = None
 98    document: int | None = None
 99    user: int | None = None
100
101    @field_validator("user", mode="before")
102    @classmethod
103    def _coerce_user(cls, v: object) -> int | None:
104        if isinstance(v, dict):
105            return v.get("id")
106        return v  # type: ignore[return-value]

A user note attached to a document.

Attributes: id: Unique note ID. note: Text content of the note. created: Timestamp when the note was created. document: ID of the parent document. user: ID of the user who created the note.

id: int | None = None
note: str = PydanticUndefined
created: datetime.datetime | None = None
document: int | None = None
user: int | None = None
class DocumentType(pydantic.main.BaseModel):
11class DocumentType(BaseModel):
12    """A paperless-ngx document type (e.g. Invoice, Receipt, Contract).
13
14    Attributes:
15        id: Unique document-type ID.
16        name: Document-type name.
17        document_count: Number of documents assigned to this document type.
18    """
19
20    model_config = ConfigDict(extra="ignore")
21
22    id: int
23    name: str
24    slug: str | None = None
25    match: str | None = None
26    matching_algorithm: MatchingAlgorithm | None = None
27    is_insensitive: bool | None = None
28    document_count: int | None = None
29    owner: int | None = None
30    user_can_change: bool | None = None

A paperless-ngx document type (e.g. Invoice, Receipt, Contract).

Attributes: id: Unique document-type ID. name: Document-type name. document_count: Number of documents assigned to this document type.

id: int = PydanticUndefined
name: str = PydanticUndefined
slug: str | None = None
match: str | None = None
matching_algorithm: MatchingAlgorithm | None = None
is_insensitive: bool | None = None
document_count: int | None = None
owner: int | None = None
user_can_change: bool | None = None
class FieldDataType(enum.StrEnum):
12class FieldDataType(StrEnum):
13    """Allowed data types for a custom field."""
14
15    string = "string"
16    boolean = "boolean"
17    integer = "integer"
18    float = "float"
19    monetary = "monetary"
20    date = "date"
21    url = "url"
22    documentlink = "documentlink"
23    select = "select"

Allowed data types for a custom field.

string = <FieldDataType.string: 'string'>
boolean = <FieldDataType.boolean: 'boolean'>
integer = <FieldDataType.integer: 'integer'>
float = <FieldDataType.float: 'float'>
monetary = <FieldDataType.monetary: 'monetary'>
date = <FieldDataType.date: 'date'>
url = <FieldDataType.url: 'url'>
select = <FieldDataType.select: 'select'>
class FileMetadataEntry(pydantic.main.BaseModel):
109class FileMetadataEntry(BaseModel):
110    """A single embedded metadata key-value pair from a document file.
111
112    Paperless-ngx reads file-level metadata (e.g. PDF XMP/info tags) and
113    exposes each entry in this format.  ``namespace`` and ``prefix`` are
114    ``None`` for non-namespaced entries.
115
116    Attributes:
117        namespace: XML namespace URI, or ``None``.
118        prefix: Namespace prefix (e.g. ``"pdf"``), or ``None``.
119        key: Metadata key (e.g. ``"Producer"``).
120        value: Metadata value as a string.
121    """
122
123    model_config = ConfigDict(extra="ignore")
124
125    namespace: str | None = None
126    prefix: str | None = None
127    key: str
128    value: str

A single embedded metadata key-value pair from a document file.

Paperless-ngx reads file-level metadata (e.g. PDF XMP/info tags) and exposes each entry in this format. namespace and prefix are None for non-namespaced entries.

Attributes: namespace: XML namespace URI, or None. prefix: Namespace prefix (e.g. "pdf"), or None. key: Metadata key (e.g. "Producer"). value: Metadata value as a string.

namespace: str | None = None
prefix: str | None = None
key: str = PydanticUndefined
value: str = PydanticUndefined
class PermissionSet(pydantic.main.BaseModel):
7class PermissionSet(BaseModel):
8    users: list[int] = Field(default_factory=list)
9    groups: list[int] = Field(default_factory=list)

!!! abstract "Usage Documentation" Models

A base class for creating Pydantic models.

Attributes: __class_vars__: The names of the class variables defined on the model. __private_attributes__: Metadata about the private attributes of the model. __signature__: The synthesized __init__ [Signature][inspect.Signature] of the model.

__pydantic_complete__: Whether model building is completed, or if there are still undefined fields.
__pydantic_core_schema__: The core schema of the model.
__pydantic_custom_init__: Whether the model has a custom `__init__` function.
__pydantic_decorators__: Metadata containing the decorators defined on the model.
    This replaces `Model.__validators__` and `Model.__root_validators__` from Pydantic V1.
__pydantic_generic_metadata__: Metadata for generic models; contains data used for a similar purpose to
    __args__, __origin__, __parameters__ in typing-module generics. May eventually be replaced by these.
__pydantic_parent_namespace__: Parent namespace of the model, used for automatic rebuilding of models.
__pydantic_post_init__: The name of the post-init method for the model, if defined.
__pydantic_root_model__: Whether the model is a [`RootModel`][pydantic.root_model.RootModel].
__pydantic_serializer__: The `pydantic-core` `SchemaSerializer` used to dump instances of the model.
__pydantic_validator__: The `pydantic-core` `SchemaValidator` used to validate instances of the model.

__pydantic_fields__: A dictionary of field names and their corresponding [`FieldInfo`][pydantic.fields.FieldInfo] objects.
__pydantic_computed_fields__: A dictionary of computed field names and their corresponding [`ComputedFieldInfo`][pydantic.fields.ComputedFieldInfo] objects.

__pydantic_extra__: A dictionary containing extra values, if [`extra`][pydantic.config.ConfigDict.extra]
    is set to `'allow'`.
__pydantic_fields_set__: The names of fields explicitly set during instantiation.
__pydantic_private__: Values of private attributes set on the model instance.
users: list[int] = PydanticUndefined
groups: list[int] = PydanticUndefined
class SearchHit(pydantic.main.BaseModel):
49class SearchHit(BaseModel):
50    """Full-text search relevance metadata returned alongside a document.
51
52    Attributes:
53        score: Relevance score assigned by the Whoosh FTS engine.
54        highlights: HTML snippet with matching terms highlighted.
55        rank: Position in the result set by relevance.
56    """
57
58    model_config = ConfigDict(extra="ignore")
59
60    score: float | None = None
61    highlights: str | None = None
62    note_highlights: str | None = None
63    rank: int | None = None

Full-text search relevance metadata returned alongside a document.

Attributes: score: Relevance score assigned by the Whoosh FTS engine. highlights: HTML snippet with matching terms highlighted. rank: Position in the result set by relevance.

score: float | None = None
highlights: str | None = None
note_highlights: str | None = None
rank: int | None = None
class SetPermissions(pydantic.main.BaseModel):
12class SetPermissions(BaseModel):
13    view: PermissionSet = Field(default_factory=PermissionSet)
14    change: PermissionSet = Field(default_factory=PermissionSet)

!!! abstract "Usage Documentation" Models

A base class for creating Pydantic models.

Attributes: __class_vars__: The names of the class variables defined on the model. __private_attributes__: Metadata about the private attributes of the model. __signature__: The synthesized __init__ [Signature][inspect.Signature] of the model.

__pydantic_complete__: Whether model building is completed, or if there are still undefined fields.
__pydantic_core_schema__: The core schema of the model.
__pydantic_custom_init__: Whether the model has a custom `__init__` function.
__pydantic_decorators__: Metadata containing the decorators defined on the model.
    This replaces `Model.__validators__` and `Model.__root_validators__` from Pydantic V1.
__pydantic_generic_metadata__: Metadata for generic models; contains data used for a similar purpose to
    __args__, __origin__, __parameters__ in typing-module generics. May eventually be replaced by these.
__pydantic_parent_namespace__: Parent namespace of the model, used for automatic rebuilding of models.
__pydantic_post_init__: The name of the post-init method for the model, if defined.
__pydantic_root_model__: Whether the model is a [`RootModel`][pydantic.root_model.RootModel].
__pydantic_serializer__: The `pydantic-core` `SchemaSerializer` used to dump instances of the model.
__pydantic_validator__: The `pydantic-core` `SchemaValidator` used to validate instances of the model.

__pydantic_fields__: A dictionary of field names and their corresponding [`FieldInfo`][pydantic.fields.FieldInfo] objects.
__pydantic_computed_fields__: A dictionary of computed field names and their corresponding [`ComputedFieldInfo`][pydantic.fields.ComputedFieldInfo] objects.

__pydantic_extra__: A dictionary containing extra values, if [`extra`][pydantic.config.ConfigDict.extra]
    is set to `'allow'`.
__pydantic_fields_set__: The names of fields explicitly set during instantiation.
__pydantic_private__: Values of private attributes set on the model instance.
view: PermissionSet = PydanticUndefined
change: PermissionSet = PydanticUndefined
class StoragePath(pydantic.main.BaseModel):
11class StoragePath(BaseModel):
12    """A paperless-ngx storage path — controls where archived files are stored.
13
14    Attributes:
15        id: Unique storage-path ID.
16        name: Storage-path name.
17        path: Template string used to build the storage path, e.g.
18            ``"{created_year}/{correspondent}/{title}"``.
19        document_count: Number of documents using this storage path.
20    """
21
22    model_config = ConfigDict(extra="ignore")
23
24    id: int
25    name: str
26    slug: str | None = None
27    path: str | None = None
28    match: str | None = None
29    matching_algorithm: MatchingAlgorithm | None = None
30    is_insensitive: bool | None = None
31    document_count: int | None = None
32    owner: int | None = None
33    user_can_change: bool | None = None

A paperless-ngx storage path — controls where archived files are stored.

Attributes: id: Unique storage-path ID. name: Storage-path name. path: Template string used to build the storage path, e.g. "{created_year}/{correspondent}/{title}". document_count: Number of documents using this storage path.

id: int = PydanticUndefined
name: str = PydanticUndefined
slug: str | None = None
path: str | None = None
match: str | None = None
matching_algorithm: MatchingAlgorithm | None = None
is_insensitive: bool | None = None
document_count: int | None = None
owner: int | None = None
user_can_change: bool | None = None
class Tag(pydantic.main.BaseModel):
11class Tag(BaseModel):
12    """A paperless-ngx tag.
13
14    Attributes:
15        id: Unique tag ID.
16        name: Tag name.
17        color: Hex colour code used in the UI (e.g. ``"#ff0000"``).
18        is_inbox_tag: If ``True``, newly ingested documents receive this tag
19            automatically until they are processed.
20        document_count: Number of documents currently assigned to this tag.
21        parent: ID of the parent tag for hierarchical tag trees, or ``None``.
22        children: IDs of child tags, or ``None``.
23    """
24
25    model_config = ConfigDict(extra="ignore")
26
27    id: int
28    name: str
29    slug: str | None = None
30    color: str | None = None
31    text_color: str | None = None
32    match: str | None = None
33    matching_algorithm: MatchingAlgorithm | None = None
34    is_insensitive: bool | None = None
35    is_inbox_tag: bool | None = None
36    document_count: int | None = None
37    owner: int | None = None
38    user_can_change: bool | None = None
39    parent: int | None = None
40    children: list[int] | None = None

A paperless-ngx tag.

Attributes: id: Unique tag ID. name: Tag name. color: Hex colour code used in the UI (e.g. "#ff0000"). is_inbox_tag: If True, newly ingested documents receive this tag automatically until they are processed. document_count: Number of documents currently assigned to this tag. parent: ID of the parent tag for hierarchical tag trees, or None. children: IDs of child tags, or None.

id: int = PydanticUndefined
name: str = PydanticUndefined
slug: str | None = None
color: str | None = None
text_color: str | None = None
match: str | None = None
matching_algorithm: MatchingAlgorithm | None = None
is_insensitive: bool | None = None
is_inbox_tag: bool | None = None
document_count: int | None = None
owner: int | None = None
user_can_change: bool | None = None
parent: int | None = None
children: list[int] | None = None
class Task(pydantic.main.BaseModel):
24class Task(BaseModel):
25    """A paperless-ngx background processing task (e.g. document ingestion).
26
27    Attributes:
28        task_id: Unique Celery task identifier.
29        task_file_name: Original file name submitted for processing.
30        status: Current task status as a :class:`TaskStatus` enum value.
31        result: Human-readable result or error message, set on completion.
32        related_document: String representation of the resulting document ID
33            on success, ``None`` otherwise.
34    """
35
36    model_config = ConfigDict(extra="ignore")
37
38    task_id: str
39    task_file_name: str | None = None
40    date_created: datetime | None = None
41    date_done: datetime | None = None
42    type: str | None = None
43    status: TaskStatus | None = None
44    result: str | None = None
45    acknowledged: bool | None = None
46    related_document: str | None = None

A paperless-ngx background processing task (e.g. document ingestion).

Attributes: task_id: Unique Celery task identifier. task_file_name: Original file name submitted for processing. status: Current task status as a TaskStatus enum value. result: Human-readable result or error message, set on completion. related_document: String representation of the resulting document ID on success, None otherwise.

task_id: str = PydanticUndefined
task_file_name: str | None = None
date_created: datetime.datetime | None = None
date_done: datetime.datetime | None = None
type: str | None = None
status: TaskStatus | None = None
result: str | None = None
acknowledged: bool | None = None
related_document: str | None = None
class TaskStatus(enum.StrEnum):
13class TaskStatus(StrEnum):
14    """Status values for a paperless-ngx background processing task."""
15
16    PENDING = "PENDING"
17    STARTED = "STARTED"
18    SUCCESS = "SUCCESS"
19    FAILURE = "FAILURE"
20    RETRY = "RETRY"
21    REVOKED = "REVOKED"

Status values for a paperless-ngx background processing task.

PENDING = <TaskStatus.PENDING: 'PENDING'>
STARTED = <TaskStatus.STARTED: 'STARTED'>
SUCCESS = <TaskStatus.SUCCESS: 'SUCCESS'>
FAILURE = <TaskStatus.FAILURE: 'FAILURE'>
RETRY = <TaskStatus.RETRY: 'RETRY'>
REVOKED = <TaskStatus.REVOKED: 'REVOKED'>
class PaperlessError(builtins.Exception):
 7class PaperlessError(Exception):
 8    """Base exception for all easypaperless errors.
 9
10    Attributes:
11        status_code: The HTTP status code associated with the error, or
12            ``None`` for non-HTTP errors (e.g. timeouts, local validation).
13    """
14
15    def __init__(self, message: str, status_code: int | None = None) -> None:
16        """Create a PaperlessError.
17
18        Args:
19            message: Human-readable error description.
20            status_code: HTTP status code, if applicable.
21        """
22        super().__init__(message)
23        self.status_code = status_code

Base exception for all easypaperless errors.

Attributes: status_code: The HTTP status code associated with the error, or None for non-HTTP errors (e.g. timeouts, local validation).

PaperlessError(message: str, status_code: int | None = None)
15    def __init__(self, message: str, status_code: int | None = None) -> None:
16        """Create a PaperlessError.
17
18        Args:
19            message: Human-readable error description.
20            status_code: HTTP status code, if applicable.
21        """
22        super().__init__(message)
23        self.status_code = status_code

Create a PaperlessError.

Args: message: Human-readable error description. status_code: HTTP status code, if applicable.

status_code
class AuthError(easypaperless.PaperlessError):
26class AuthError(PaperlessError):
27    """Raised on 401 or 403 responses.
28
29    Indicates that the API key is missing, invalid, or lacks permission for
30    the requested operation.
31    """

Raised on 401 or 403 responses.

Indicates that the API key is missing, invalid, or lacks permission for the requested operation.

class NotFoundError(easypaperless.PaperlessError):
34class NotFoundError(PaperlessError):
35    """Raised on 404 responses or when a name lookup fails."""

Raised on 404 responses or when a name lookup fails.

class ValidationError(easypaperless.PaperlessError):
38class ValidationError(PaperlessError):
39    """Raised on 422 responses or bad input supplied by the caller."""

Raised on 422 responses or bad input supplied by the caller.

class ServerError(easypaperless.PaperlessError):
42class ServerError(PaperlessError):
43    """Raised on 5xx responses or unrecoverable transport errors."""

Raised on 5xx responses or unrecoverable transport errors.

class UploadError(easypaperless.PaperlessError):
46class UploadError(PaperlessError):
47    """Raised when file submission or document processing fails.
48
49    Typically raised when paperless-ngx reports a ``FAILURE`` status on the
50    Celery task created by
51    :meth:`~easypaperless.client.PaperlessClient.upload_document`.
52    """

Raised when file submission or document processing fails.

Typically raised when paperless-ngx reports a FAILURE status on the Celery task created by ~easypaperless.client.PaperlessClient.upload_document().

class TaskTimeoutError(easypaperless.PaperlessError):
55class TaskTimeoutError(PaperlessError):
56    """Raised when upload polling exceeds the configured timeout.
57
58    Raised by :meth:`~easypaperless.client.PaperlessClient.upload_document`
59    (with ``wait=True``) when the document processing task does not reach a
60    terminal state within ``poll_timeout`` seconds.
61    """
62
63    def __init__(self, message: str) -> None:
64        super().__init__(message, status_code=None)

Raised when upload polling exceeds the configured timeout.

Raised by ~easypaperless.client.PaperlessClient.upload_document() (with wait=True) when the document processing task does not reach a terminal state within poll_timeout seconds.

TaskTimeoutError(message: str)
63    def __init__(self, message: str) -> None:
64        super().__init__(message, status_code=None)

Create a PaperlessError.

Args: message: Human-readable error description. status_code: HTTP status code, if applicable.