Open-source, white-label web app for Italian electronic invoicing (FatturaPA/SDI). Available as a cloud service or self-hosted with Docker or native binaries. Customize with your brand, manage invoices in minutes. Fully localized in Italian and English.
Desk is a ready-to-use frontend for the Invoicetronic API. ISVs and developers can self-host it, apply their own branding, and give their customers a complete invoicing interface — without writing a single line of UI code.
- Send & receive invoices — full-text search, date filters, server-side pagination, XML download
- Invoice detail — metadata and complete SDI status timeline
- Upload — drag-and-drop multi-file upload
- Export — filter by month/quarter/date range, download as ZIP
- Company management — CRUD for companies linked to your API key
- Dashboard — recent invoices overview and counters
- Two auth modes — multi-user (Identity + login) or standalone (single API key, no login)
- White-label — custom app name, footer, CSS variables, logo
- Localization — Italian (default) and English
- Docker ready — multi-platform (amd64/arm64), health check endpoint
- Native binaries — self-contained downloads for Windows, Linux, macOS (no .NET required)
Sign up at invoicetronic.com and get your API key from the dashboard. Desk works with both sandbox and live API keys — the environment is determined by the key you use. Start with a sandbox key for testing, then switch to live when you're ready. See the Sandbox and API Keys documentation pages for details.
Important: to use Desk in a live environment, your Invoicetronic account must meet the API prerequisites (accreditation with SDI, etc.). See the Live Environment Prerequisites for the full checklist. No prerequisites are needed for the sandbox — you can start testing immediately.
The fastest way to get started. No Docker, no servers, no configuration — just sign up at desk.invoicetronic.com and start working.
Standalone mode (single API key, no login — ideal for internal networks):
# docker-compose.yml
services:
desk:
image: invoicetronic/desk
ports:
- "8080:8080"
volumes:
- ./desk.yml:/app/desk.yml
- ./logs:/app/logs # daily rolling file logs (see Logging section)Important: create the
./logsdirectory on the host before the firstdocker compose up(e.g.mkdir logs). Docker Compose validates bind mount sources at startup and refuses to create the container if the host path is missing.
# desk.yml
desk:
api_key: itk_live_xxxxxxxxxxTip: to avoid storing the API key in a file, pass it via environment variable instead:
# docker-compose.yml services: desk: image: invoicetronic/desk ports: - "8080:8080" environment: - Desk__api_key=itk_live_xxxxxxxxxx
docker compose up -dOpen http://localhost:8080 — no registration needed, the app is ready to use.
Multi-user mode (each user registers and enters their own API key):
# docker-compose.yml
services:
desk:
image: invoicetronic/desk
ports:
- "8080:8080"
volumes:
- ./desk.yml:/app/desk.yml # optional
- ./data:/app/data # persist user database
- ./logs:/app/logs # daily rolling file logs (see Logging section)Important: create the
./dataand./logsdirectories on the host before the firstdocker compose up(e.g.mkdir data logs). Docker Compose validates bind mount sources at startup and refuses to create the container if the host path is missing.
docker compose up -dOpen http://localhost:8080, register, and enter your API key in the profile page.
Every GitHub release includes self-contained, single-file binaries — no .NET installation required.
| File | OS | Architecture |
|---|---|---|
desk-win-x64.zip |
Windows | x86-64 (Intel/AMD) |
desk-linux-x64.zip |
Linux | x86-64 (Intel/AMD) |
desk-linux-arm64.zip |
Linux | ARM64 (Raspberry Pi 4/5, AWS Graviton) |
desk-osx-x64.zip |
macOS | Intel |
desk-osx-arm64.zip |
macOS | Apple Silicon (M1/M2/M3/M4) |
Windows:
# Extract the archive, create desk.yml, then:
.\desk.exeLinux:
unzip desk-linux-x64.zip -d desk
chmod +x desk/desk
./desk/deskmacOS:
unzip desk-osx-arm64.zip -d desk # or desk-osx-x64.zip for Intel
chmod +x desk/desk
xattr -d com.apple.quarantine desk/desk # remove Gatekeeper quarantine
./desk/deskThe app listens on http://localhost:5000 by default. Place a desk.yml file in the same directory as the binary to configure it (see Configuration).
Tip: to change the listening address or port, use the
--urlsflag or theASPNETCORE_URLSenvironment variable:./desk --urls http://0.0.0.0:8080 # or export ASPNETCORE_URLS=http://0.0.0.0:8080
Requires .NET 10 SDK.
git clone https://github.com/invoicetronic/desk.git
cd desk
cp src/desk.yml.example src/desk.yml
dotnet run --project srcThe app starts at http://localhost:5100. Edit src/desk.yml to configure it — see src/desk.yml.example for all available options.
Note: Safari forces HTTPS on
localhost. Usehttp://127.0.0.1:5100instead, or trust the .NET dev certificate withdotnet dev-certs https --trust.
All configuration goes in desk.yml. The file is optional — sensible defaults are used when it's absent. To get started, copy the example:
cp src/desk.yml.example src/desk.ymldesk:
# API endpoint (default: https://api.invoicetronic.com/v1)
api_url: https://api.invoicetronic.com/v1
# API key — if set, enables standalone mode (no login required)
# If omitted, multi-user mode is active (registration + login)
# api_key: itk_live_xxxxxxxxxx
# Database (ignored in standalone mode)
database:
provider: sqlite # sqlite | pgsql
# SQLite default path: data/desk.db (persisted with ./data:/app/data volume)
# For PostgreSQL: Desk__database__connection_string=Host=...;Database=desk;...
# Branding
branding:
app_name: My Invoicing App
footer_text: "Powered by <a href=\"https://example.com\">My Company</a>"
logo_url: https://example.com/logo-light.svg # navbar (dark background)
logo_dark_url: https://example.com/logo-dark.svg # auth pages (light background)
favicon_url: https://example.com/favicon.png
primary_color: "#1A237E"
accent_color: "#E91E63"
# Language — if omitted, auto-detected from browser
# locale: it # it | en
# Logging — daily rolling file logs (see Logging section below)
# logging:
# directory: logs # bind-mount ./logs:/app/logs to persist on host
# retained_files: 30 # daily files kept before rotation
# min_level: Information # Verbose | Debug | Information | Warning | Error | FatalEnvironment variables override YAML values using the Desk__ prefix (e.g., Desk__database__connection_string).
Secrets: for sensitive values like the API key, prefer environment variables over
desk.yml:export Desk__api_key=itk_live_xxxxxxxxxxIn Docker, use
environment:in your compose file or Docker secrets. This keeps credentials out of config files and version control.
| Standalone | Multi-user | |
|---|---|---|
| When | api_key is set in desk.yml |
api_key is absent |
| Auth | None — all pages accessible | Registration + login required |
| API key | Shared, from config | Per-user, stored in profile |
| Database | In-memory (no file on disk) | SQLite file (desk.db) or PostgreSQL |
| Use case | Internal network, VPN, single tenant | SaaS, multi-tenant, public-facing |
Warning: in standalone mode anyone who can reach the host has full access. Use only in trusted networks.
In multi-user mode, users can reset their forgotten password via email. To enable this, configure an SMTP server in desk.yml:
desk:
smtp:
host: smtp.example.com
port: 587 # 587 (StartTLS) | 465 (SSL)
username: user@example.com
password: secret
sender_email: noreply@example.com
sender_name: My AppOnly host and sender_email are required. If the smtp section is not configured, the "Forgot password?" link is not shown on the login page.
Tip: pass SMTP credentials via environment variables to keep them out of config files:
# docker-compose.yml environment: - Desk__smtp__host=smtp.example.com - Desk__smtp__port=587 - Desk__smtp__username=user@example.com - Desk__smtp__password=secret - Desk__smtp__sender_email=noreply@example.com
Customize colors, logo, and favicon directly in desk.yml under the branding section (see Configuration above). All properties are optional — if omitted, Invoicetronic defaults are used.
| Property | Description |
|---|---|
app_name |
Application name shown in navbar and page titles |
footer_text |
Footer HTML |
logo_url |
Logo for the navbar (dark background). URL or path |
logo_dark_url |
Logo for auth pages (light background). URL or path |
favicon_url |
Browser favicon. URL or path |
primary_color |
Primary brand color (hex, e.g. "#1A237E") |
accent_color |
Accent color for links and buttons (hex, e.g. "#E91E63") |
For full control over the design system, mount a custom/theme.css file that overrides any CSS custom property:
:root {
--brand-primary: #1A237E;
--brand-accent: #E91E63;
--brand-font-heading: "Poppins", sans-serif;
}# docker-compose.yml
volumes:
- ./my-theme.css:/app/wwwroot/custom/theme.cssDesk supports Italian and English. By default the language is auto-detected from the browser's Accept-Language header, with Italian as the fallback.
To force a specific language for all users, set locale in desk.yml:
desk:
locale: en # it | enAll UI strings — including Identity pages (login, registration, password reset) and validation errors — are fully localized.
Invoicetronic has two web apps with different purposes:
- Dashboard is for developers and account administrators. Use it to manage API keys, configure webhooks, view API logs, switch between test/live environments, and handle billing.
- Desk (this project) is for end users who work with invoices daily. Use it to send, receive, search, upload, export invoices and manage companies — without touching code or APIs.
| Dashboard | Desk | |
|---|---|---|
| Target user | Developer / admin | Accountant / operator / ISV (internal use or integration testing) |
| API keys | Create, rotate, manage | Uses one (configured or per-user) |
| Webhooks | Configure endpoints and events | — |
| API logs | Browse request/response history | — |
| Test/Live mode | Switch environments | Determined by the API key |
| Billing | Manage subscription and payment | — |
| Send & receive invoices | — | Full-text search, filters, pagination |
| Invoice detail & SDI timeline | — | Complete status history |
| Upload invoices | — | Drag-and-drop multi-file |
| Export | — | ZIP download by date range |
| Company management | — | CRUD for linked companies |
| White-label / self-host | No | Yes (Docker, custom branding) |
In short: Dashboard is where you set up the plumbing; Desk is where you (or your customers) do the actual invoicing work. Most users need both: Dashboard once during setup, Desk every day — eventually alongside your own integrations.
Desk (this project) → frontend for end users (invoicing operations)
Dashboard → developer panel (API keys, billing, logs, webhooks)
API → shared backend (invoicetronic.com/v1)
Website → documentation and marketing (invoicetronic.com)
| Project | Link |
|---|---|
| Desk | desk.invoicetronic.com |
| Dashboard | dashboard.invoicetronic.com |
| API | API reference |
| Website & docs | invoicetronic.com |
Desk has no billing logic — it's a pure operational frontend. Authorization is entirely in the API, driven by the API key.
| Layer | Technology |
|---|---|
| Backend | ASP.NET Core 10.0 + Razor Pages |
| Data grid | AG Grid Community (MIT) |
| UI | Custom CSS design system (no Bootstrap) |
| Auth | ASP.NET Core Identity |
| Database | SQLite (default) / PostgreSQL |
| Config | YAML (desk.yml) |
| Container | Docker multi-platform (amd64/arm64) |
In production, Desk writes daily rolling log files under logs/ and keeps the last 30 days. The Docker compose files bind-mount ./logs:/app/logs by default, so on the host you can:
tail -f logs/$(date +%Y%m%d).logTo change the path, retention or verbosity, add a logging: section to desk.yml:
desk:
logging:
directory: logs # path relative to the working directory
retained_files: 30 # daily files kept before rotation
min_level: Information # Verbose | Debug | Information | Warning | Error | FatalAll keys are optional. Errors and warnings are always echoed to docker logs as well, so you can spot problems at a glance with docker logs <container>.
GET /health → {"status":"healthy"}
