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:
git clone https://github.com/powerhouse-inc/paperless-billing.git
cd paperless-billing
cp .env.example .envThe template already points at OpenRouter, so an OpenRouter API key is the only value you have to supply. Paste it into .env:
PAPERLESS_AI_BASE_URL=https://openrouter.ai/api/v1
PAPERLESS_AI_MODEL=google/gemini-2.5-flash
PAPERLESS_AI_API_KEY=your_openrouter_key_hereThose 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:
./start.shThe script starts the Docker services, waits for them to become ready, configures the integration and opens both interfaces:
- Paperless at
localhost:8000, usingadmin / paperlessas 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.
Shortly afterwards, a corresponding invoice appears in Connect.
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:
{
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:
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:
{
"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.
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.
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.
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.
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.
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.
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
