# Paperless-ngx Connector

> Turn Paperless-ngx documents into draft vendor bills with the PDF attached - never auto-posted, guarded vendor, idempotent, read-only upstream.

- **What it is:** Turn Paperless-ngx documents into draft vendor bills with the PDF attached - never auto-posted, guarded vendor, idempotent, read-only upstream.
- **For:** Document Management
- **Odoo versions:** 16.0, 17.0, 18.0, 19.0, 20.0 - Community and Enterprise
- **Runs on:** Odoo.sh or Odoo you host yourself - not Odoo Online, which accepts no third-party apps
- **Price:** 89 EUR, one-time, on the Odoo Apps Store
- **Technical name:** bambooforge_paperless_connector
- **Run for real:** Tested for real against a current Paperless-ngx release: 3 correspondents, 3 document types, 3 tags and 3 OCR'd documents came into Odoo with 0 errors.
- **Not for:** The connector reads correspondents, document types, tags and documents (including their OCR text content) from your Paperless-ngx server and writes the corresponding Odoo catalog records. It never writes back to Paperless.; Document file binaries are not downloaded — only the title, OCR text, dates, archive serial number and the correspondent/type/tag links are imported. To open the original file, use Paperless itself.; The API token is stored in a system-only (administrator-only) field.; In scope today: read-only import of the four entities, OCR text, scheduled reconciliation, dry-run, validation, rollback snapshots, schema introspection, field mappings and the multi-step flow engine.; Out of scope / roadmap: any write-back to Paperless; downloading file binaries; webhook / real-time push (the route exists but is neutralized); incremental list filtering by timestamp (v1 lists by pagination cursor).
- **Manual:** https://docs.bambooforge.dev/bambooforge_paperless_connector/
- **Live demo:** https://demo20.bambooforge.dev/odoo/action-bambooforge_paperless_connector.action_paperless_control_tower
- **Store page:** https://apps.odoo.com/apps/modules/20.0/bambooforge_paperless_connector
- **Manual last updated:** 2026-10-15

---
# Paperless-ngx Connector

A read-only importer that catalogs your Paperless-ngx document archive inside
Odoo 20: it pulls your correspondents, document types, tags and documents — with
their OCR text — into searchable Odoo records, over the Paperless REST API, with
a resilient job queue, dry-run safety and pre-flight validation so you stay in
control.

This page is the complete manual. If you follow it top to bottom you can install,
connect, run your first import, automate it, and fix the common issues **without
contacting support**.

## Overview

The connector reads four source entities from Paperless-ngx over its REST API and
keeps a local, searchable mirror of them in Odoo. Direction of data is one way:
**Odoo ← Paperless-ngx (read-only)**. The connector never writes anything back to
Paperless.

- **Correspondents** — the senders / recipients registered in Paperless: name and
  the document count Paperless reports. Imported into **Paperless Connector ▸
  Catalog ▸ Correspondents**.
- **Document Types** — the classification types documents are filed under: name and
  document count. Imported into **Catalog ▸ Document Types**.
- **Tags** — the tags used across the archive: name, colour (the Paperless API
  exposes `colour` and/or `color`) and document count. Imported into
  **Catalog ▸ Tags**.
- **Documents** — every document with its title, **OCR content** (the extracted
  text Paperless stores), creation date, archive serial number (ASN), and its
  links to a correspondent, a document type and any number of tags. Imported into
  **Catalog ▸ Documents**.

Correspondents, document types and tags are imported **before** documents, so each
document resolves its correspondent (many-to-one), document type (many-to-one) and
tags (many-to-many) from the records already imported.

Every remote call goes through a queue: nothing is written to Odoo until a job
runs, jobs retry with back-off on transient failures, and a circuit breaker pauses
an instance that keeps failing.

## Tested against the real thing

Automated tests run against a bundled mock on every release; the line below is the part no mock can give you - the connector pointed at the real platform, with real records.

Tested for real against a current Paperless-ngx release: 3 correspondents, 3 document types, 3 tags and 3 OCR'd documents came into Odoo with 0 errors.

## Requirements

- **Odoo**: 20.0, Community or Enterprise. The module depends only on **base** and
  **mail**, which are always present.
- **Paperless-ngx**: any Paperless-ngx server that exposes the standard REST API
  (`/api/correspondents/`, `/api/document_types/`, `/api/tags/`,
  `/api/documents/`) with Django REST Framework pagination. It must be reachable
  from the Odoo server.
- **Python**: no extra libraries beyond a standard Odoo 20 install.
- **Odoo access**: any internal user can use the connector screens. The Paperless
  API token is stored in a system-only field, so only the **Settings /
  Administration** user can read or change it.
- **Network**: outbound HTTP/HTTPS from Odoo to your Paperless server. By default
  the connector refuses internal/loopback/private hosts as an SSRF safeguard (see
  *Safety features*).

## Installation

1. Copy `bambooforge_paperless_connector` into your Odoo addons path.
2. Restart the Odoo service.
3. Open **Apps**, click **Update Apps List**, search for *Paperless*, and press
   **Activate / Install**. Dependencies (*base*, *mail*) install automatically.

No Paperless-ngx server is required to evaluate the connector: a mock Paperless
REST API ships inside the module, so you can install, explore and run the full
import flow against sample data before pointing it at a real server. To use it,
set the instance **Base URL** to your own Odoo address, set **API Path** to
`paperless/mock_api`, enter any token, and turn **Allow internal host** on
(because the mock lives on your own server). See *Step 2* and the *FAQ*.

## Step 1 — Create an API token in Paperless-ngx

Paperless-ngx authenticates every request with a single **API token** sent as the
`Authorization: Token <token>` HTTP header. To create one:

1. Sign in to your Paperless-ngx web interface as the user whose access you want
   the connector to use.
2. Open **Settings ▸ My Profile** (the user-profile / account page).
3. Find the **API Token** (Auth Token) field. If no token is shown yet, use the
   control to **generate / regenerate** one.
4. Copy the token string. You will paste it into Odoo in the next step.

The connector only ever issues `GET` requests, so a token for a read-capable
user is enough. The token is the only credential the connector needs.

## Step 2 — Create the connection in Odoo

Open **Paperless Connector ▸ Configuration ▸ Instances** and create a record.

![Paperless instance / connection cockpit](01-connection-cockpit.png)

Key fields:

| Field | What to enter |
|---|---|
| Name | A label for this server, e.g. *Office Paperless*. |
| Base URL | Your Paperless root, e.g. `https://paperless.example.com` (or `http://localhost:8000` for a local server, which also needs *Allow internal host*). |
| Authentication | *API Token* — the only mode Paperless uses. |
| API Token | The token from Step 1. Sent as the `Authorization: Token <token>` header on every request. Visible to administrators only. |
| API Path | Leave the default `api` (the client builds `<base_url>/api/documents/`, etc.). Set it to `paperless/mock_api` only to drive the bundled mock. |
| Verify SSL | Keep on for production. Turn off only for self-signed test certificates. |
| Allow internal host | Off by default. Turn on only to reach a Paperless server on localhost or a private network — or to drive the bundled mock (lowers the SSRF guard; see *Safety features*). |

Then click **Test Connection**. The connector lists one document to confirm the
token and URL; a green *Connected* state means both are correct. If it fails, the
exact error is recorded on the form (**Last Connection Error**) and in **Logs**
(see *Troubleshooting*).

Tip: use **Quick Setup** (button on the instance) to install starter import flows
and apply a recommended safety profile in one step.

## Step 3 — First import (dry-run, then live)

New instances start with **Dry-run** ON. In dry-run, import jobs *simulate* writes:
instead of creating records they produce **Validation Results** you can review
under **Paperless Connector ▸ Operations ▸ Validation Results**. This lets you
confirm what *would* happen before anything is written.

To run a first import:

1. On the instance, click **Import Documents**. Because documents reference
   correspondents, document types and tags, this enqueues the master-data imports
   first and the documents last, so every link can resolve. (You can also import
   **Correspondents**, **Document Types** or **Tags** on their own.) Enqueuing does
   not block the UI.
2. Jobs are processed by the **Paperless Queue Processor** scheduled action (every
   minute), or immediately if you run it manually from **Operations ▸ Queue Jobs**.
3. Review **Validation Results** while still in dry-run.
4. When satisfied, open the instance, turn **Dry-run** OFF, and run the imports
   again to write the records for real.

Imported records land under **Paperless Connector ▸ Catalog**:
**Correspondents**, **Document Types**, **Tags** and **Documents**. Each carries
its Paperless id, so re-imports update the same record instead of duplicating it.

## Field mapping & customization

- **Field Mappings** (Configuration) map Paperless source fields to Odoo fields per
  entity (`correspondent` / `document_type` / `tag` / `document`). The core
  fields (title → name, OCR `content`, `created` date, ASN, the correspondent /
  document-type / tag links) are mapped out of the box; use **Generate suggested
  mappings** on the instance to seed extra ones, then adjust.
- **Schema Fields** lists the discovered Paperless fields per entity alongside the
  matching Odoo fields. Run **Schema Introspection** on the instance to refresh it
  by sampling a live record (it falls back to the bundled mock shape if the API is
  unreachable).
- **Per-entity import limits** on the instance cap how much each run pulls:
  *Correspondents / Document Types / Tags* default to **100**, *Documents* to
  **200**. Raise them for large archives.

There is no order-status, stock, image, tax or export configuration: this is a
read-only importer. (A dormant *State Mappings* menu exists for framework
compatibility and is hidden from normal users.)

## Automation (scheduled actions)

The module ships these scheduled actions (**Settings ▸ Technical ▸ Scheduled
Actions**):

| Scheduled action | Default | Purpose |
|---|---|---|
| Paperless Queue Processor | every 1 min | Processes queued import jobs. |
| Paperless Reconciliation | every 15 min | Re-lists each enabled entity and queues imports for records missing locally or whose previous job failed. |
| Paperless Maintenance | every 1 hr | Recovers stale/locked jobs and trims old jobs and logs. |
| Paperless Flow Scheduler | every 5 min | Runs scheduled import flows. |
| Paperless Flow Metrics | every 1 hr | Aggregates flow-run metrics. |
| Paperless Auto Recover | every 15 min | Reopens a tripped circuit breaker once the server is healthy again. |

Turn on **Auto import** and/or **Auto reconcile** per entity on the instance
(correspondents, document types, tags, documents) to let the scheduled actions keep
the catalog current hands-free. They are off by default.

The connector follows the Django REST Framework pagination cursor: each list page
carries a `next` URL, and the client walks it to accumulate every page, so large
libraries import fully across pages (subject to the per-entity import limits).

**Webhooks are not used.** Paperless-ngx is imported by polling on a schedule. A
`/paperless/webhook` route exists but is intentionally neutralized — it always
answers *not supported* (HTTP 501). Real-time push is a roadmap item, not part of
this read-only version; scheduled reconciliation keeps data current.

## Safety features

- **Dry-run mode** — simulate writes and review Validation Results before going
  live (field *Dry-run*, ON by default).
- **Pre-flight validation** — jobs are validated before they run; *Minimal /
  Standard / Strict* business validation profiles gate risky writes (field
  *Business validation profile*, default *Standard*).
- **Resilient queue** — every import is a job with a retry limit and exponential
  back-off; exhausted jobs move to a dead-letter state instead of looping.
- **API-level retries** — transient HTTP failures (timeouts, `429` rate limits,
  `5xx`) are retried with their own capped exponential back-off before a job is
  marked failed.
- **Circuit breaker** — after repeated failures an instance auto-pauses
  (*Tripped*); the Auto Recover action reopens it once Paperless responds again. An
  auth/configuration failure trips it immediately.
- **Rollback snapshots** — when *Rollback* is enabled, imports capture a snapshot so
  you can undo a batch from **Operations ▸ Rollback Snapshots**.
- **SSRF guard** — the connector refuses internal/loopback/private hosts (including
  cloud metadata addresses) unless *Allow internal host* is explicitly enabled.

## Troubleshooting

| Symptom | Cause and fix |
|---|---|
| Test Connection fails with 401/403 | Wrong or expired API token, or the token's user cannot read the API. Re-check Step 1, regenerate the token in **Settings ▸ My Profile**, and paste it again. The connector sends `Authorization: Token <token>`. |
| "is not allowed because it resolves to a non-public address" | Base URL points at localhost/a private IP (or a cloud metadata address). Enable *Allow internal host* on the instance for a self-hosted / local Paperless, or use a public URL. |
| SSL errors on Test Connection | Self-signed or untrusted certificate. Install a valid certificate, or turn off *Verify SSL* for testing only. |
| Test Connection times out / network error | Paperless is unreachable from the Odoo server, or the URL is wrong. Confirm the Base URL opens in a browser from the server's network; raise *Timeout (seconds)* if the server is just slow. |
| Only part of a large library imported | Each run is capped by the per-entity import limit (Documents default 200). Raise the *Documents / Correspondents / Document Types / Tags import limit* on the instance, or let **Reconciliation** catch up over its 15-minute runs; the client already follows DRF pagination `next` to read every page. |
| A document has no correspondent / type / tags | Those parents had not been imported yet when the document ran. Re-run **Import Documents** (it imports the parents first) or run the parent imports, then re-import documents; links resolve best-effort from imported records. |
| Jobs stay in *Pending* | The Queue Processor is off, or the instance is paused. Check the **Paperless Queue Processor** scheduled action is active and the instance is not *Paused*. |
| Instance shows *Tripped* | Circuit breaker tripped after repeated failures (auth/config trips it at once). Fix the token/URL; **Auto Recover** reopens it after a cooldown, or click **Resume**. |
| Records imported twice | Imports are keyed by the Paperless id, so this should not happen. If it does, check that two instances do not point at the same Paperless server. |
| Nothing happens after Import | You are in **Dry-run**. Review Validation Results, then turn dry-run off and re-run. |
| Rate-limit (429) errors in Logs | Paperless throttled the burst. The client already retries 429s with back-off; lower the per-entity import limits or raise *API retry* settings if it persists. |

For anything else, **Operations ▸ Logs** records every API call, level and error
with a timestamp.

## Frequently asked questions

**Which versions are supported?**
Odoo 20.0 on the Odoo side. On the Paperless side the connector targets the
standard Paperless-ngx REST API with DRF pagination; validate your exact build with
the bundled mock first.

**Do I need Paperless-ngx installed to evaluate it?**
No. A mock Paperless REST API ships inside the module. Set the instance Base URL to
your Odoo address, API Path to `paperless/mock_api`, enter any token, and enable
*Allow internal host* — then run the full import flow against sample data.

**How does authentication work?**
Create an API token in Paperless under **Settings ▸ My Profile**. The connector
sends it as the `Authorization: Token <token>` header on every request. There is
no username/password, OAuth or consumer-key flow.

**Does it write anything back to Paperless?**
No. The connector is read-only by design: it only issues `GET` requests and never
creates, edits or deletes anything in Paperless. It is a safe, auditable mirror.

**Does it download the document files (PDFs, images)?**
No. It imports the document *metadata and OCR text* — title, OCR `content`,
creation date, archive serial number, and the correspondent / document-type / tag
links. The original file binaries are **not** downloaded or stored in Odoo.

**How does it keep up to date?**
By polling on a schedule. The **Reconciliation** action re-lists each enabled
entity and queues imports for anything new or previously failed. There is no
webhook ingestion (the `/paperless/webhook` route returns *not supported*).

**Is it safe to run against real data?**
Dry-run is ON by default, validation gates risky writes, and rollback snapshots let
you undo. You decide when to go live. And because it only reads from Paperless, the
remote archive is never at risk.

**What support and refund policy do I get?**
Every request is answered within 24 hours, setup help included. If you report a bug
within 2 months of purchase and it is not resolved within 15 days, you are entitled
to a full money-back refund.

## Data, privacy & limits

- The connector **reads** correspondents, document types, tags and documents
  (including their OCR text content) from your Paperless-ngx server and **writes**
  the corresponding Odoo catalog records. It never writes back to Paperless.
- **Document file binaries are not downloaded** — only the title, OCR text, dates,
  archive serial number and the correspondent/type/tag links are imported. To open
  the original file, use Paperless itself.
- The API token is stored in a system-only (administrator-only) field.
- **In scope today**: read-only import of the four entities, OCR text, scheduled
  reconciliation, dry-run, validation, rollback snapshots, schema introspection,
  field mappings and the multi-step flow engine.
- **Out of scope / roadmap**: any write-back to Paperless; downloading file
  binaries; webhook / real-time push (the route exists but is neutralized);
  incremental list filtering by timestamp (v1 lists by pagination cursor).

## Support & updates

- Support: [support@bambooforge.dev](mailto:support@bambooforge.dev) — answered within 24 hours, setup help
  included.
- Refund: report a bug within 2 months of purchase; if unresolved within 15 days,
  full refund.
- Full source is included. Updates track the supported Odoo 20 / Paperless-ngx REST
  API line.

## Upgrading & version compatibility

This build targets **Odoo 20.0**. Each Odoo major series (16.0, 17.0, 18.0, 19.0, 20.0) has its
own dedicated build of this module — always install the build that matches your Odoo
version. Mixing a build with a different Odoo series is not supported.

### Patch upgrades (same series, e.g. 20.0.1.0.0 → later)

1. **Back up** your database and filestore first.
2. Replace the module folder with the newer build.
3. Restart Odoo with the module updated:
   
   ```
   ./odoo-bin -c your.conf -u bambooforge_paperless_connector -d your_db
   ```
4. Odoo applies any schema/data changes automatically. Your existing records and
   configuration are preserved.

### Cross-version migration (moving to a newer Odoo)

Upgrading Odoo itself is a database migration handled by Odoo's standard upgrade
tooling. When you migrate the database to the next Odoo series, install the matching
build of this module for that series. Data created by this module carries over with
the database migration.

After any upgrade the module's scheduled actions resume on their normal cadence —
no manual re-activation is required.

## Uninstallation

You can remove this module at any time from **Apps → (this module) → Uninstall**,
or from the command line. Uninstalling is clean and reversible by reinstalling —
but note what is and is not deleted.

### What is removed

- The module's own tables and every record in them (24 models, prefixed `paperless.*`) — this is the data this module created.
- The menus, actions, views and reports this module installed.
- Its scheduled actions (cron jobs) — they stop immediately on uninstall.
- Connection records, credentials, field mappings, queue jobs and sync logs stored in Odoo.

### What is preserved

- **Your remote platform is never touched.** Uninstalling only removes the Odoo-side
  connector; products, customers and orders on the external store/service are untouched.
- Records already imported into standard Odoo models (e.g. contacts, products, sales
  orders) remain — they are ordinary Odoo records once created.
- Attachments and chatter messages on standard records are kept.

As always, take a database backup before uninstalling in production.

## Changelog

### 20.0.1.0.3

- Built for Odoo 20: record rules and access rights rewritten onto Odoo 20's single ir.access model, the screens rebuilt on Owl 3, and every icon moved to the icon set Odoo 20 ships.

### 20.0.1.0.0

Current release for Odoo 20.0. This build includes:

- Turn Paperless-ngx documents into DRAFT Odoo 20 vendor bills (account.move) with the PDF attached.
- Never auto-posted, guarded vendor, idempotent.
- Plus a searchable catalog of documents, correspondents, types & tags. Read-only against Paperless, resilient queue, dry-run.

Feature additions and fixes ship as new builds on the Odoo Apps store; this page and
the module's `version` reflect the current published release. Always keep the build
matched to your Odoo series (see *Upgrading & version compatibility*).

## How we make sure it works

Before every release, **81 automated checks** run against this app on Odoo 16, 17, 18, 19 and 20.
Among them:

- Token header sent not on query string.
- Entity bindings do not collide.
- Reimport does not duplicate bill.
- Resolve action always ignore.
- Dry run creates no bill or partner.

Tested for real against a current Paperless-ngx release: 3 correspondents, 3 document types, 3 tags and 3 OCR'd documents came into Odoo with 0 errors.

The complete list is in the source you receive.
