Documentation
¶
Overview ¶
Package paperless provides a Go client for the Paperless-ngx REST API: document upload, tag/correspondent/type/custom-field management, task polling, and document management.
It targets API version 10 and authenticates via the static token scheme (`Authorization: Token <token>`), which users generate in the Paperless-ngx web UI under "My Profile".
Requirements ¶
Go 1.27 or newer. The module decodes with `encoding/json/v2`, which is part of the standard library there. On a Go 1.26 or older toolchain it only exists behind `GOEXPERIMENT=jsonv2` and builds fail without it.
Options ¶
WithHTTPClient(*http.Client) — use a custom HTTP client (transport, proxies, timeouts) WithTimeout(time.Duration) — per-request timeout on the default client
Index ¶
- Constants
- Variables
- type Capabilities
- type Client
- func (c *Client) DeleteDocument(ctx context.Context, documentID int) error
- func (c *Client) DownloadDocument(ctx context.Context, documentID int) ([]byte, error)
- func (c *Client) EnsureCorrespondent(ctx context.Context, name string) (int, error)
- func (c *Client) EnsureCustomField(ctx context.Context, name string) (int, error)
- func (c *Client) EnsureDocumentType(ctx context.Context, name string) (int, error)
- func (c *Client) EnsureTag(ctx context.Context, name string) (int, error)
- func (c *Client) FindCustomField(ctx context.Context, name string) (int, bool, error)
- func (c *Client) GetCorrespondentName(ctx context.Context, id int) (string, error)
- func (c *Client) GetDocumentTypeName(ctx context.Context, id int) (string, error)
- func (c *Client) GetTask(ctx context.Context, taskID string) (TaskOutcome, bool, error)
- func (c *Client) ListDocumentChecksums(ctx context.Context) (map[string]struct{}, error)
- func (c *Client) ListDocumentMetas(ctx context.Context) ([]DocumentMeta, error)
- func (c *Client) Ping(ctx context.Context) error
- func (c *Client) ProbeCapabilities(ctx context.Context) (Capabilities, error)
- func (c *Client) UpdateDocument(ctx context.Context, documentID int, req UpdateDocumentRequest) error
- func (c *Client) Upload(ctx context.Context, req UploadRequest) (string, error)
- type CustomFieldValue
- type DocumentMeta
- type Option
- type RetryAfterError
- type TaskOutcome
- type TaskStatus
- type UpdateDocumentRequest
- type UploadRequest
Examples ¶
Constants ¶
const ( // DefaultMaxIdleConns is the total idle connection pool size. DefaultMaxIdleConns = 100 // DefaultMaxIdleConnsPerHost is the per-host idle pool — the value that // actually matters when all traffic targets one API host. DefaultMaxIdleConnsPerHost = 8 // DefaultIdleConnTimeout matches the default transport's 90s so pooled // connections do not outlive typical server keep-alive windows. DefaultIdleConnTimeout = 90 * time.Second )
Default transport tuning shared by every consumer of this SDK. The default transport keeps only 2 idle connections per host, so parallel requests to the single Paperless-ngx host would close and re-handshake TLS connections constantly; a cloned transport with a per-host idle pool turns that into connection reuse.
Variables ¶
var ErrInvalidConfig = errors.New("paperless: base URL and token are required")
ErrInvalidConfig is returned when the client is constructed with a missing base URL or token. The integration is optional; callers that have not configured Paperless should never construct a client.
Functions ¶
This section is empty.
Types ¶
type Capabilities ¶
type Capabilities struct {
// AcceptAPIVersion echoes the API version the server negotiated from
// the Accept header (empty when the response omits it).
AcceptAPIVersion string
// FlatChecksum: sampled documents carry the legacy flat checksum field.
FlatChecksum bool
// VersionedChecksum: sampled documents carry checksums in versions[].
VersionedChecksum bool
// DocumentsSampled is how many documents the probe inspected.
DocumentsSampled int
}
Capabilities reports what the connected Paperless-ngx server actually serves. The checksum shape is the load-bearing part: --verify, --prune, and --backfill all reconcile by checksum, and since paperless-ngx 3.x the flat checksum field is gone from the serializers (served via versions[] instead) — a server serving NEITHER shape makes every ledger row look drifted. Doctor surfaces this before it bites.
func (Capabilities) ChecksumShape ¶
func (c Capabilities) ChecksumShape() string
ChecksumShape names the checksum delivery the server uses, for humans.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client talks to a single Paperless-ngx instance. The zero value is not usable — construct via New.
func New ¶
New creates a client for the given base URL (e.g. "https://paperless.example.com") and API token. Returns ErrInvalidConfig when either is empty or the URL is not parseable. Options customize the HTTP transport.
Example ¶
package main
import (
"log"
"github.com/larsartmann/go-paperless"
)
func main() {
client, err := paperless.New("https://paperless.example.com", "token-from-web-ui")
if err != nil {
log.Fatal(err)
}
_ = client
}
Output:
Example (InvalidConfig) ¶
package main
import (
"errors"
"fmt"
"github.com/larsartmann/go-paperless"
)
func main() {
_, err := paperless.New("", "token-from-web-ui")
if errors.Is(err, paperless.ErrInvalidConfig) {
fmt.Println("configure the base URL before retrying")
}
}
Output: configure the base URL before retrying
Example (WithOptions) ¶
package main
import (
"log"
"time"
"github.com/larsartmann/go-paperless"
)
func main() {
client, err := paperless.New(
"https://paperless.example.com",
"token-from-web-ui",
paperless.WithTimeout(30*time.Second),
)
if err != nil {
log.Fatal(err)
}
_ = client
}
Output:
func (*Client) DeleteDocument ¶
DeleteDocument permanently removes one document from Paperless-ngx. Used by the backfill's --prune to drop duplicate server-side copies; callers own the destructive-action decision.
func (*Client) DownloadDocument ¶
DownloadDocument fetches one document's stored ORIGINAL file bytes (GET /api/documents/{id}/download/ — not the archive rendition). The decrypt-repair scans them for the /Encrypt trailer before replacing.
func (*Client) EnsureCorrespondent ¶
EnsureCorrespondent returns the ID of the correspondent with the given name, creating it when it does not exist yet.
Correspondents keep the "auto" matching algorithm (deliberately, unlike tags): learning "documents from sender X look like this" is exactly the value auto matching adds for documents NOT uploaded by this pipeline (manual scans), and correspondents carry no provenance semantics that auto prediction could corrupt.
func (*Client) EnsureCustomField ¶
EnsureCustomField returns the ID of the custom field definition with the given name, creating it as a string field when missing (paperless-ngx 2.x+ custom fields). Used for the provenance field carrying the Gmail message ID. An existing definition of any data type is kept — the caller decides whether a non-string field is acceptable by the value it assigns.
func (*Client) EnsureDocumentType ¶
EnsureDocumentType returns the ID of the document type with the given name, creating it with the "none" matching algorithm when missing. A document type applied deterministically at upload time is provenance metadata: classifier-learned matching would leak the type onto manual scans, mirroring the tags rationale. An EXISTING type keeps its configured algorithm — unlike tags there is no legacy-release shape to heal, and overriding a deliberate user config would be wrong.
func (*Client) EnsureTag ¶
EnsureTag returns the ID of the tag with the given name, creating it when it does not exist yet.
Tags are provenance metadata applied deterministically at upload time, so they are created with the "none" matching algorithm: a "gmail" tag that Paperless-ngx's classifier learns to predict would silently leak onto manually scanned documents that merely look similar. An existing tag still carrying "auto" (created by older InboxClean versions) is self-healed to "none" — the classifier then no longer trains against it. Self-healing is tag-specific on purpose: correspondents deliberately keep "auto".
Example ¶
ExampleClient_EnsureTag mirrors the README quick start so signature drift between README and code breaks the build.
package main
import (
"context"
"fmt"
"log"
"github.com/larsartmann/go-paperless"
)
func main() {
client, err := paperless.New("http://paperless.local:8000", "my-token")
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
tagID, err := client.EnsureTag(ctx, "inboxclean")
if err != nil {
log.Fatal(err)
}
fmt.Println(tagID)
}
Output:
func (*Client) FindCustomField ¶
FindCustomField looks up a custom field definition by name WITHOUT creating it — the read-only half of EnsureCustomField, used by dry-run backfills to report what they would record without mutating the server.
func (*Client) GetCorrespondentName ¶
GetCorrespondentName resolves a correspondent ID to its display name. The backfill needs it to decide whether a document's existing non-null correspondent actually matches the derived sender name — an ID alone cannot answer that question.
func (*Client) GetDocumentTypeName ¶
GetDocumentTypeName resolves a document type ID to its display name, the document-type sibling of GetCorrespondentName: the backfill compares the stored name against the configured type to repair drifted assignments.
func (*Client) GetTask ¶
GetTask fetches one consumption task by ID (the UUID Upload returns). found is false when the server has no such task — the record may not be persisted yet (polling callers retry) or was pruned.
The response is expected in the v10 paginated envelope (`{"results": [...]}`); a bare JSON array is tolerated defensively for deployments that predate pagination.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/larsartmann/go-paperless"
)
func main() {
client, err := paperless.New("https://paperless.example.com", "token-from-web-ui")
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
outcome, found, err := client.GetTask(ctx, "0198f7a2-9d3f-7c31-b5e4-5f2a9c1d8e77")
if err != nil {
log.Fatal(err)
}
if found {
fmt.Println(outcome.Status, outcome.DocumentID)
}
}
Output:
func (*Client) ListDocumentChecksums ¶
ListDocumentChecksums returns the SHA-256 checksum of every document currently stored in Paperless-ngx. Used to reconcile the local upload ledger against reality: ledger entries whose checksum disappeared from Paperless-ngx (documents deleted there) are candidates for re-upload.
func (*Client) ListDocumentMetas ¶
func (c *Client) ListDocumentMetas(ctx context.Context) ([]DocumentMeta, error)
ListDocumentMetas returns id, title, correspondent, created date, tags, and checksum of every stored document. The backfill matches ledger records against server documents (by checksum) and detects duplicates (same checksum stored more than once).
func (*Client) Ping ¶
Ping verifies base URL and token by hitting the authenticated document list view (a real JSON endpoint, bounded to one entry). The API root is deliberately avoided: Paperless-ngx serves it as browsable HTML only, so a JSON Accept header is answered 406 regardless of token validity (observed on paperless 3.0.5 — 406 classified as a client rejection, silently skipping every sync). A 401/403 means the token is wrong; a transport error means the URL is unreachable.
func (*Client) ProbeCapabilities ¶
func (c *Client) ProbeCapabilities(ctx context.Context) (Capabilities, error)
ProbeCapabilities fetches one small documents page and inspects the checksum shapes the server serves. Read-only, one request, safe to run against any paperless-ngx version.
func (*Client) UpdateDocument ¶
func (c *Client) UpdateDocument( ctx context.Context, documentID int, req UpdateDocumentRequest, ) error
UpdateDocument PATCHes one document's metadata. Empty requests are rejected so callers never burn a round trip on a no-op.
func (*Client) Upload ¶
Upload posts a document to the consumption queue. It returns the task ID (UUID) Paperless-ngx assigned; consumption itself is asynchronous — the caller can poll `/api/tasks/?task_id=<id>` for the resulting document.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/larsartmann/go-paperless"
)
func main() {
client, err := paperless.New("https://paperless.example.com", "token-from-web-ui")
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
tagID, err := client.EnsureTag(ctx, "bank-sync")
if err != nil {
log.Fatal(err)
}
taskID, err := client.Upload(ctx, paperless.UploadRequest{
Filename: "statement-2026-08.pdf",
Content: []byte("%PDF-1.4 ..."),
Title: "Statement August 2026",
TagIDs: []int{tagID},
})
if err != nil {
log.Fatal(err)
}
outcome, found, err := client.GetTask(ctx, taskID)
if err != nil {
log.Fatal(err)
}
if found && outcome.Status.Terminal() {
fmt.Println("consumed:", outcome.Status)
}
}
Output:
type CustomFieldValue ¶
CustomFieldValue assigns a value to one custom field by its definition ID.
type DocumentMeta ¶
type DocumentMeta struct {
ID int
Title string
Correspondent int
Created time.Time
TagIDs []int
DocumentTypeID int
CustomFields []CustomFieldValue
Checksum string
}
DocumentMeta is the metadata snapshot of one stored Paperless-ngx document.
type Option ¶
type Option func(*Client)
Option configures a Client at construction time.
func WithHTTPClient ¶
WithHTTPClient uses the given HTTP client instead of the default one. When set, WithTimeout has no effect (the supplied client owns its own timeout).
func WithTimeout ¶
WithTimeout sets the per-request timeout of the client's HTTP client.
type RetryAfterError ¶
type RetryAfterError struct {
Err error
// After is the parsed hint. Seconds-only or HTTP-date forms are
// accepted; a date in the past parses as 0 (retry immediately).
After time.Duration
}
RetryAfterError reports a rate-limit (or maintenance) response that carried a Retry-After header. It wraps the classified family error so retry policies can honor the server's hint instead of blind exponential backoff, while IsRetryable keeps working through Unwrap.
func (*RetryAfterError) Error ¶
func (e *RetryAfterError) Error() string
func (*RetryAfterError) Unwrap ¶
func (e *RetryAfterError) Unwrap() error
type TaskOutcome ¶
type TaskOutcome struct {
Status TaskStatus
// DocumentID is the server document this task points at: the newly
// created document after a successful consumption, or the pre-existing
// duplicate when the server refused the upload. Zero when unknown.
DocumentID int64
// DuplicateRefused is true when the consumer rejected the upload because
// an identical document already exists (checksum dedup). The ledger uses
// this to separate honest refusals from real failures.
DuplicateRefused bool
// DuplicateInTrash refines DuplicateRefused: the duplicate document is
// in the trash (server treats trash duplicates as re-consumable).
DuplicateInTrash bool
// ErrorMessage is the server's failure reason for non-duplicate failures.
ErrorMessage string
}
TaskOutcome is the classified result of one consumption-task poll.
type TaskStatus ¶
type TaskStatus string
TaskStatus is a Paperless-ngx consumption task's lifecycle state. The v10 API serves lowercase values; terminal states are success and failure (verified against paperless-ngx 3.x PaperlessTask.Status).
const ( TaskStatusPending TaskStatus = "pending" TaskStatusStarted TaskStatus = "started" TaskStatusSuccess TaskStatus = "success" TaskStatusFailure TaskStatus = "failure" )
Task consumption statuses served by the v10 API (lowercase values).
func (TaskStatus) Terminal ¶
func (s TaskStatus) Terminal() bool
Terminal reports whether the status is a final task state. Unknown status strings (future server versions) poll forever — the safe default for a caller deciding whether to retry.
type UpdateDocumentRequest ¶
type UpdateDocumentRequest struct {
Title *string
Created *time.Time
CorrespondentID *int
TagIDs []int
DocumentTypeID *int
CustomFields []CustomFieldValue
}
UpdateDocumentRequest carries the fields a metadata backfill may change. Nil pointers leave the field untouched; TagIDs replaces the full tag set (Paperless-ngx PATCH semantics for many-to-many fields).
type UploadRequest ¶
type UploadRequest struct {
// Filename is the original file name (e.g. "invoice.pdf"). Paperless-ngx
// derives the stored document name and archive serial numbering from it.
Filename string
// Content holds the raw file bytes. Gmail attachments are capped at 25 MB,
// so buffering in memory is safe.
Content []byte
// Title overrides the consumer-assigned title when set.
Title string
// Created sets the document's creation date (typically the email date).
Created time.Time
// CorrespondentID is the Paperless-ngx correspondent (e.g. the email
// sender) assigned on consumption. Zero leaves the field unset.
CorrespondentID int
// TagIDs are Paperless-ngx tag IDs applied on consumption.
TagIDs []int
// DocumentTypeID is the Paperless-ngx document type (e.g. "Email")
// applied on consumption. Zero leaves the type unset.
DocumentTypeID int
// CustomFields are custom-field values applied on consumption, e.g. the
// provenance field carrying the Gmail message ID. Nil leaves them unset.
CustomFields []CustomFieldValue
}
UploadRequest describes one document to hand to Paperless-ngx's consumer.