I built einvoice-api as the API of the einvoice-hub family: an electronic
invoicing stack for the EU market. This repository is the REST entry point —
it issues scoped JWTs (OpenIddict client-credentials), stores invoices and
parties in Postgres, and publishes versioned CloudEvents to RabbitMQ through a
transactional outbox so the PDF worker (einvoice-worker) can process them
idempotently.
flowchart LR
Client[Service client] -->|POST /v1/invoices (JWT)| API[einvoice-api]
API --> PG[(PostgreSQL)]
API --> Redis[(Redis\nidempotency + rate limit)]
API -- outbox poll --> RMQ[RabbitMQ\neinvoice.events]
RMQ --> Worker[einvoice-worker\nPDF + MinIO - next repo]
This is a study project from my public portfolio. I learn by building: I use AI-assisted development tools (agent-based pair programming) while holding the project to production standards - automated tests, CI quality gates, security scanning, review pipelines and documented architecture decisions.
The scenario is simple on purpose; the engineering is not.
git clone https://github.com/TiagoR85/einvoice-api.git
cd einvoice-api
cp .env.example .env # set POSTGRES_PASSWORD, OIDC_CLIENT_SECRET and OIDC_WORKER_CLIENT_SECRET
docker compose up --build
curl http://localhost:8080/health/readyThe API is healthy when /health/ready returns 200 (typically < 2 min after
the image build completes).
curl -s http://localhost:8080/connect/token \
-d grant_type=client_credentials \
-d client_id=einvoice-client \
-d client_secret=$OIDC_CLIENT_SECRET \
-d "scope=invoice:read invoice:write"The full family (API + worker + UI + Postgres + Redis + RabbitMQ + Silo) boots from
einvoice-ui with a single
docker compose up --build — see that repo's README.
| Method | Path | Scope | Notes |
|---|---|---|---|
| POST | /v1/parties | invoice:write | Create customer/supplier |
| GET | /v1/parties | invoice:read | List parties |
| GET | /v1/parties/{id} | invoice:read | Fetch one |
| POST | /v1/invoices | invoice:write | Requires Idempotency-Key header; enqueues outbox event |
| GET | /v1/invoices | invoice:read | List invoices |
| GET | /v1/invoices/{id} | invoice:read | Fetch one with lines |
| GET | /health/live | - | Liveness |
| GET | /health/ready | - | Readiness (Postgres + Redis + RabbitMQ) |
dotnet test tests/Einvoice.Tests/Einvoice.Tests.csprojIntegration tests start PostgreSQL, Redis and RabbitMQ containers via Testcontainers — Docker is required. CI enforces line coverage >= 80%.
- ADR-0001 — OpenIddict client-credentials JWT
- ADR-0002 — Transactional outbox + CloudEvents
- ADR-0003 — Modular monolith
- ADR-0004 — Peppol deferred from v1
- ADR-0005 — Inbox consumer flips status on
invoice.completed.v1
- v1: invoices + parties + outbox events
- Worker: PDF generation (iText) + Silo storage +
invoice.completed - Blazor server-rendered UI for invoice list/creation (
einvoice-ui, one-command hub demo) - Peppol access point (see ADR-0004)
- Demo stack on my VPS with docker compose profiles
MIT — see LICENSE.