Powerhouse
BlogIntegrations

Paperless-ngx - Automated PDF processing with Powerhouse

This blog will show you how you can convert unstructured PDFs into structured, queryable data, thus eliminating tedious manual entry and accelerating accounting and operational workflows.

We will use Paperless-ngx, which is good at turning incoming files into an organized document archive, saving finance and operations teams from manual data entry.

And Powerhouse, which makes structured documents available through interfaces, APIs and AI tools.

We combined them and followed one invoice through a workflow to see the result.

Powerhouse7 min read

The demo runs locally from a small Docker-based repository. A configured language model extracts the invoice fields, so you provide your own API key. The example uses OpenRouter by default, and other providers can be configured.

Explore and clone the repo

Starting with Paperless

The Docker Compose file begins with a deliberately slim Paperless-ngx setup. It runs the Paperless application with Redis as its task broker. Application data goes to SQLite, stored in Paperless's data volume.

This example accepts PDFs only. Paperless can use Tika and Gotenberg to ingest Office documents and .eml files, but those services stay disabled here to keep the setup lightweight.

We added Powerhouse Switchboard, Powerhouse Connect and a bootstrap service. The bootstrap creates the Billing drive, connects it to Paperless and registers the synchronization workflow.

To run the example, check out the repository and copy its environment template:

bash
git clone https://github.com/powerhouse-inc/paperless-billing.git
cd paperless-billing
cp .env.example .env

The template already points at OpenRouter, so an OpenRouter API key is the only value you have to supply. Paste it into .env:

.envbash
PAPERLESS_AI_BASE_URL=https://openrouter.ai/api/v1
PAPERLESS_AI_MODEL=google/gemini-2.5-flash
PAPERLESS_AI_API_KEY=your_openrouter_key_here

Those first two lines are already filled in for you, so the key is the only edit you need to make. Any OpenAI-compatible endpoint works if you point PAPERLESS_AI_BASE_URL elsewhere and set a matching PAPERLESS_AI_MODEL. Leave the base URL empty to call the Anthropic API directly, in which case the key is an Anthropic one.

Then start the stack:

bash
./start.sh

The script starts the Docker services, waits for them to become ready, configures the integration and opens both interfaces:

  • Paperless at localhost:8000, using admin / paperless as the demo credentials.
  • Connect at localhost:3000.
  • Switchboard's GraphQL API at localhost:4001/graphql.

One invoice across multiple interfaces

After startup, Paperless contains no invoices. Connect opens the newly created Billing drive, which holds the configuration that connects it to Paperless.

Upload a PDF invoice through Paperless. The bootstrap configuration creates an Invoice document type that matches documents containing the word “invoice”. A Paperless workflow then pushes the new document into the Powerhouse integration.

Step 1. PT-2026-2041.pdf is dropped onto the Paperless document list at localhost:8000.
Step 2. Paperless archives it as PT-2026-2041 and classifies it as an Invoice. The scan itself is still a scan: three line items, €13,500.00 due.

Shortly afterwards, a corresponding invoice appears in Connect.

Step 3. The same invoice in Connect, filed under September 2026: issuer, payer, dates, currency and the same three line items, now fields rather than pixels.

The invoice is no longer only a PDF. Its issuer, invoice number, issue and due dates, currency, status, totals and line items are represented in a Powerhouse invoice document managed by Switchboard.

The same data is available programmatically through Switchboard's GraphQL API:

graphql
{
  Invoice {
    documents {
      totalCount
      items {
        name
        state {
          global {
            invoiceNo
            currency
            dateIssued
            dateDue
            status
            issuer { name country }
            totalPriceTaxExcl
            totalPriceTaxIncl
            lineItems {
              description
              quantity
              unitPriceTaxExcl
              taxPercent
              totalPriceTaxIncl
            }
          }
        }
      }
    }
  }
}

Or over HTTP, asking for fewer fields:

bash
curl -s http://localhost:4001/graphql \
  -H 'content-type: application/json' \
  -d '{"query":"{ Invoice { documents { totalCount items { name state { global { invoiceNo currency totalPriceTaxIncl issuer { name } } } } } } }"}'

The full query returns the structured invoice. The response below is truncated to a single item:

Response, truncated to one of 26 invoicesjson
{
  "data": {
    "Invoice": {
      "documents": {
        "totalCount": 26,
        "items": [
          {
            "name": "PT-2026-2041",
            "state": {
              "global": {
                "invoiceNo": "PT-2026-2041",
                "currency": "EUR",
                "dateIssued": "2026-09-01T00:00:00.000Z",
                "dateDue": "2026-10-01T00:00:00.000Z",
                "status": "ISSUED",
                "issuer": {
                  "name": "Lumen Type Foundry",
                  "country": "Portugal"
                },
                "totalPriceTaxExcl": 11250,
                "totalPriceTaxIncl": 13500,
                "lineItems": [
                  {
                    "description": "Brand identity system",
                    "quantity": 2,
                    "unitPriceTaxExcl": 3000,
                    "taxPercent": 20,
                    "totalPriceTaxIncl": 7200
                  },
                  {
                    "description": "Typeface licence, 5 seats",
                    "quantity": 1,
                    "unitPriceTaxExcl": 450,
                    "taxPercent": 20,
                    "totalPriceTaxIncl": 540
                  },
                  {
                    "description": "UX research workshop",
                    "quantity": 4,
                    "unitPriceTaxExcl": 1200,
                    "taxPercent": 20,
                    "totalPriceTaxIncl": 5760
                  }
                ]
              }
            }
          }
        ]
      }
    }
  }
}

Paperless remains the interface for the original document. Connect provides a visual interface for the structured invoice, and GraphQL makes the same state available to other applications.

Where AI enters the flow

Paperless handles document ingestion and extracts the text from the uploaded PDF. The paperless-sync integration sends that text to the configured language model. The model identifies the invoice fields and maps them into a Powerhouse invoice document.

Paperless's own optional AI functionality stays disabled in this example. The Powerhouse integration uses the model for one job: turning extracted invoice text into structured data.

Paperless extracts the document's content, the model structures it, and Powerhouse makes the result programmable.

Invoices from email

Manual upload shows the mechanism, but invoices usually arrive by email.

Paperless can watch a mailbox itself. Two things need to exist for that: an account that says where the mailbox is, and a rule that says which messages to take from it. Both live under Mail in the left sidebar, at localhost:8000/mail.

Step 1. Mail in the sidebar opens Mail Settings: accounts on top, rules underneath. Add Account and Add Rule open the same forms shown below.

The account is the IMAP connection. Give it a name, the mail host and port, the security setting the host expects, and the login. This example points at a throwaway Gmail mailbox: imap.gmail.com on port 993 over SSL. Gmail wants an app password here rather than the account password. Any IMAP provider works; the host, port and security setting come from whoever runs the mailbox.

Step 2. The connection. Test checks the credentials against the server before you save.

The rule decides what Paperless does with the mail it finds. Point it at the account, set the folder to INBOX, set the consumption scope to attachments only, and filter filenames with *.pdf so nothing but invoices gets consumed. Assigning the Invoice document type here is what hands the result to the Powerhouse workflow.

Step 3. The rule. Note the action: flag the mail rather than mark it read, so opening a message in a mail client does not make Paperless skip it.

With both saved, send a PDF invoice to the monitored address. Paperless checks the mailbox on a schedule, and the Process Mail button on the account runs the check immediately.

Step 4. The invoice arrives the way invoices usually do: a message with a PDF attached.

On the next mail check the attachment is consumed, archived in Paperless, and passed through the same pipeline as the manual upload. It lands in the Billing drive as a structured invoice, filed under the month it belongs to.

Step 5. The emailed attachment as a draft invoice under October 2026: DE-2026-3077, 6,783.00 EUR.

Because the attachment is already a PDF, the optional Tika and Gotenberg services are not required. The input method has changed. The downstream workflow has not.

From individual documents to a dataset

One invoice demonstrates the integration. A larger collection shows why the structured representation matters.

Drop a folder of PDFs into the repository's .local/consume directory. Paperless processes each document. The integration creates a corresponding Powerhouse invoice and updates the Billing dashboard.

The dashboard builds as invoices arrive. It reports:

  • Monthly invoice value by status.
  • Invoice counts and totals by status.
  • Outstanding amounts and cash flow by due month.
  • Category and payment-term breakdowns.
  • Totals across currencies.
The Billing dashboard over a folder of consumed invoices.

Paperless remains the archive for the original documents. Powerhouse provides the structured state that interfaces, APIs, analytics and AI tools can work with.

Building other workflows

This example uses invoices, but the integration pattern is more general. Keep the application that already handles part of a process well, then represent its important data as structured Powerhouse documents.

Paperless continues to handle document ingestion and archiving. The Powerhouse integration adds a document model, synchronization, a programmable API and domain-specific interfaces on top of it.

The same pattern applies to other document systems, operational tools and industry workflows.

Run it yourself

Start with a workflow assessment.

Find out where Powerhouse can improve operational efficiency. We map the first workflow before a build starts.

All posts