Singapore Peppol / InvoiceNow document validation platform
Validate UBL e-invoices against the official Singapore Peppol specifications, inspect human-readable validation reports, and convert invoices into presentation-quality PDFs. Ships as a web dashboard, a REST API, and a one-command Docker deployment.
What this project is. Marina Labs is a portfolio project built to showcase the full breadth of a real product in one codebase: deep familiarity with the Peppol / InvoiceNow e-invoicing domain, security engineering in a real Spring Boot application, documentation as a first-class feature, a clean Docker packaging story, and a Java + Node full-stack build (Maven orchestrates the npm frontend pipeline end-to-end). It is fully functional, but its purpose is to demonstrate craft, not to replace certified Access Point infrastructure.
Every validation artifact in this repository, every XSD schema, Schematron ruleset, and XSLT stylesheet, is an unmodified copy of the files published on peppolguide.sg, the official Singapore Peppol guide. The validation pipeline runs exactly the artifacts the specification publishes, organized per ruleset:
| Ruleset | Folder | What it validates |
|---|---|---|
| SG Peppol BIS Billing 3.0 | validation/sg-peppol-bis-billing-3.0 |
Invoices & credit notes (UBL 2.1) |
| PINT-SG Billing | validation/pint-sg-billing |
PINT invoices & credit notes for Singapore |
| GST InvoiceNow | validation/gst-invoicenow |
GST requirements layered on InvoiceNow invoices |
| SG BIS Order Balance | validation/sg-bis-order-balance |
Order documents (UBL 2.1) |
| Additional BIS documents | validation/additional-bis-documents |
Orders, order responses, application responses & related BIS transactions (UBL 2.1 / 2.3) |
Each bundle is kept self-contained and intact, including the shared UBL schemas duplicated per ruleset, precisely so the repository mirrors the official distributions file-for-file and future spec releases can be dropped in cleanly. The matching XSLT stylesheets under src/main/resources/stylesheets/ are the official presentation transforms used for PDF rendering.
Validation
- Two-stage pipeline: XSD schema validation (Xerces) followed by Schematron business-rule validation (ph-schematron + Saxon-HE), producing a structured pass/warn/fail report per rule.
- Instant anonymous validation from the landing page, no account needed to try it.
- Bulk validation: upload a ZIP of up to 100 documents and get a consolidated report.
- Persistent validation history and downloadable logs for registered users.
PDF generation
- Transforms UBL XML through the official XSLT stylesheets into HTML, then renders it to PDF with wkhtmltopdf, turning machine-readable invoices into documents humans can actually read.
Service API
- REST endpoints for validation (
/api/service/validation/invoice,/bulk), validation logs, and PDF generation (/api/service/generate-pdf), authenticated with user-generated API keys managed from the dashboard.
Platform
- Web dashboard (Thymeleaf + htmx) with usage statistics, validation history, PDF conversion, and API-key management.
- Full account lifecycle: registration (optionally gated by single-use invite keys, disabled by default, toggle with
marina.beta.enabled), email verification, password reset, all backed by transactional email. - Scheduled housekeeping: monthly usage-counter resets and automatic cleanup of never-verified accounts.
Documentation is part of the product, not an afterthought:
/docs: a built-in documentation site explaining the platform and how to integrate with it./swagger-ui.html: interactive OpenAPI reference (springdoc) scoped to the public Service API, with try-it-out support and persisted API-key authorization.
- Asymmetric JWT auth: "remember me" access/refresh token cookie pair (
ml_at15 min /ml_rt30 days) signed with RSA keypairs that are generated automatically on first boot and kept outside the codebase. - Separate keypairs for session tokens and service API keys, so compromising one never compromises the other.
- Rate limiting & lockouts with Bucket4j + Caffeine: per-account and per-IP thresholds on login, registration, email verification, and password reset, with automatic eviction (no cleanup jobs, no DB tables).
- Hardened cookies (
HttpOnly,SameSite=Lax) and proxy-aware forwarded-header handling for correct HTTPS behavior behind a reverse proxy. - Invite-key gate (optional): registration can require single-use beta keys generated by super-admins. Disabled by default; toggle with
marina.beta.enabled. - Secrets hygiene: local secrets live in a gitignored properties file, Docker secrets flow through environment variables, and the
.dockerignoreguarantees credentials can never be baked into a published image.
The entire stack (app, wkhtmltopdf, and PostgreSQL) ships as a Docker Compose package. You need nothing installed except Docker.
Clone this repository, then:
cp .env.example .env # set DB_PASSWORD (and SMTP settings if you want email)
docker compose up --buildThen open http://localhost:8085. The multi-stage build compiles the app (Maven + frontend assets) and produces a slim runtime image with the official patched-Qt wkhtmltopdf baked in; Postgres data, signing keys, and logs persist in named volumes across restarts.
You'll need Java 21, PostgreSQL (database marinalabs), and wkhtmltopdf on your PATH. Create src/main/resources/application-local.properties with your spring.datasource.password and SMTP settings, then:
./mvnw spring-boot:runBackend: Java 21 · Spring Boot (Web, Security, Data JPA, Mail, Actuator) · PostgreSQL · JJWT + BouncyCastle · Bucket4j + Caffeine · springdoc-openapi
Peppol pipeline: Saxon-HE & Xerces · ph-schematron / ph-ubl (Philip Helger's Peppol toolchain) · official XSLT stylesheets · wkhtmltopdf
Frontend: Thymeleaf + htmx · Node.js/npm build integrated into Maven (frontend-maven-plugin) · PurgeCSS-optimized Material Design Icons
Packaging: Docker multi-stage build · Docker Compose
Released under the MIT License.