This page describes how invoia is built and which security measures are actually implemented in the product today. It does not describe intentions, roadmaps or attestations. Where a measure does not exist, it says so directly.
Platform and hosting
invoia is one application. It is built in Next.js and hosted on Vercel. All data is stored in a single Postgres database at Supabase in the AWS region eu-west-1 (Ireland).
Scheduled and event-triggered background work — the daily synchronisation with the accounting system, invoice issuance and the monthly revenue recognition run — is orchestrated through Inngest and writes to that same database.
There is no local installation, no self-hosted edition and no other operating platform.
We make no statement about TLS versions, firewalls, network segmentation or network-layer attack protection. Those are properties of the hosting platforms, not something invoia configures itself, and we will not claim measures we neither set nor control.
Account access
Authentication is handled by Supabase Auth. There are three ways to sign in: email and password, a Google account, or a Microsoft account. There are no other sign-in methods. The sign-in screen shows a passkey button; that path is not in operation and does not give access to an account today.
Two-factor authentication is mandatory. Every single account sets up TOTP during registration — there is no step that skips it, and no account gets past without one. The shared TOTP secret is encrypted at the application level with AES-256-GCM, using a randomly generated initialisation vector per encryption and an authentication tag, before it is written to the database at all.
One-time codes sent by email — for confirming an email address and for resetting a password — are never stored in clear text. We keep only a SHA-256 digest of the code. The code expires after 10 minutes, there are at most 5 attempts per code, and a new code invalidates every earlier one.
Passwords must be at least 12 characters. At registration and at reset, the chosen password is checked against Have I Been Pwned's register of passwords from known breaches. The check uses the k-anonymity model: the password is hashed with SHA-1 on our own server, and only the first five characters of the digest are sent onward. The full password never leaves the server, and neither does your IP address. At reset, a password that appears in the register is refused; at registration the result is shown as a warning. If the register does not respond, we do not block — the check is an aid, not a guarantee.
A password reset request is answered identically whether or not the address exists with us. The flow does not reveal whether there is an account.
Every server action in the sign-in and registration flow validates its input against a schema before touching the database.
Isolation and encryption
Isolation between customers is anchored in the database. Row Level Security is enabled on every table. The policies call four helper functions in the database when they are tested: which account the user belongs to, whether the user is an administrator, and which integrations the user has been granted access to. Account data is scoped by the account. The invoicing dataset — customers, invoices, invoice lines, work registrations, accrual vouchers and recurring series — is scoped by integration access. On an ordinary connection, a query for another customer's data fails in the database, not first in the application layer.
The administrator role is enforced in both places — in the database policies and in the application code — for invitations, user management, integrations, API keys, webhook endpoints and subscription.
The tables behind the OAuth server and behind the MCP server's write proposals have row-level security enabled with no policy at all. They are therefore closed to any ordinary connection. The application uses three separate database clients. One of them bypasses row-level security and is used by the background jobs and by a number of actions inside the app; on those paths it is the application code that scopes the query to the account and to the integration.
Precisely what is encrypted. There are three layers to keep apart:
- Transport. Traffic between the browser and invoia, and between invoia and every subprocessor, runs over encrypted connections. We do not state a protocol version, because it is a property of the hosting platform and not something invoia sets.
- Storage medium. The database is operated by Supabase on AWS. Whether and how the storage medium itself is encrypted is a property of that platform; we do not state it, because we neither set nor control it.
- Application level. Exactly one field is encrypted by invoia itself before it is written: the TOTP secret, with AES-256-GCM.
Card details are not stored — neither encrypted nor in clear text. The card is entered directly into Stripe's own embedded field, and invoia's servers never see the card number.
invoia's own secrets — the API keys for the services we call — exist solely as environment variables in the runtime environment and are not part of the source code. The credentials belonging to your own account are a different matter: they sit in the database. Your Dinero and e-conomic tokens and the signing key for a webhook endpoint are stored as ordinary text, because they have to be usable again. They are protected by the database's access control, not by application-level encryption.
Subprocessors
The list is complete for the services the application calls or loads today.
| Provider | Used for | Location |
|---|---|---|
| Supabase | Database and authentication. Receives all data in the product. | AWS eu-west-1, Ireland |
| Vercel | Hosting, content delivery and web analytics. The analytics run on every page, including the signed-in part. | Globally distributed network |
| Stripe | Subscription and payment. Receives company name, billing address, VAT number and billing email. | Global |
| Resend | Transactional email to your own users: email confirmation, password reset and invitations. | Determined by the vendor |
| Dinero (Visma) | Invoicing and bookkeeping. Receives your debtors' master data, invoices, credit notes, products and accrual vouchers. Dinero itself delivers the invoice document to your customer. | Determined by the vendor |
| e-conomic (Visma) | Invoicing and bookkeeping. Receives your debtors' master data, invoices, credit notes, products and accrual vouchers. e-conomic itself delivers the invoice document to your customer. Back the other way we retrieve the agreement's own details — company name, agreement number, company registration number, base currency — and the agreement's debtors and products, which we hold as a copy; that copy is deleted 30 days after the connection ends, except what an issued invoice refers to. | Determined by the vendor |
| Inngest | Execution of scheduled and event-triggered background work. Events may contain user id, name, email address and IP address. | Determined by the vendor |
| Google and Microsoft | Sign-in with a Google or Microsoft account, brokered through Supabase Auth. | Global |
| Have I Been Pwned | Checking passwords against known breaches. Receives the first five characters of a SHA-1 digest and our server's IP address — not the password and not your IP address. | Global, delivered via Cloudflare |
| Frankfurter | Daily exchange rates from the European Central Bank. Receives currency codes and dates, no personal data. | Determined by the vendor |
| Upstash | Rate limiting on the public API, where the service is enabled in the runtime environment. Receives only identifiers for API keys. The limit is not a guarantee: if the lookup does not answer, the call goes through, and the MCP server is not covered. | Determined by the vendor |
| Twilio | SMS delivery. Set up as a vendor but not in use: no data is sent to Twilio today. | Determined by the vendor |
Where it says determined by the vendor, the location is a property of that vendor's own platform and not something invoia configures. We do not state it, because we cannot vouch for it.
invoia does not send data to an AI vendor. If you yourself grant an AI client access through the MCP server or an API key, that is your own decision and your own access — see the next section.
Machine access
invoia has three machine interfaces: a REST API, an MCP server and webhooks.
API keys. A key is generated from a cryptographic source of randomness and shown once, at the moment it is created. We keep only a SHA-256 digest of it. The full key does not exist in our database and cannot be shown again. Keys are created and revoked solely by an administrator inside the app. A key does not expire on its own — it is valid until someone revokes it.
The MCP server is an OAuth 2.1 resource server with its own authorization server. PKCE with S256 is required, enforced by a constraint in the database. Authorization codes are single-use and live for around a minute. Access tokens are signed with EdDSA, live for ten minutes and are bound to this service as their audience. Refresh tokens are stored only as digests, are rotated on every use, and an attempt to reuse an already-used token revokes the entire chain.
A token is bound to one integration. When you approve a client, you pick exactly one of your connected accounting organisations, and the access reaches no further than that. The approval, though, is all-or-nothing: you cannot trim the permissions the client asks for, and granting them does not require the administrator role — any user with access to that integration can approve. Clients register themselves with no prior vetting by us, and the MCP server has no rate limiting.
Webhooks. You can register an endpoint. Each delivery is signed with HMAC-SHA256 over a timestamped payload so you can verify the sender, and verification uses a comparison that takes the same time regardless of the outcome. Delivery itself is not in operation today.
What is worth understanding: an API key or an OAuth token carries your own authority. The system cannot tell the difference between you and whoever holds the key. A key with write access can book invoices into your own accounting system and have them sent to your own customers — acts that are irreversible in your own accounts. Treat a key as a power of attorney.
Your control is revocation. An administrator can revoke an API key in the app, and it takes effect immediately. For OAuth access granted to an MCP client there is, today, no list in the app and no button that withdraws the access. Write to support@invoia.io and we will withdraw it for you.
Deletion
Deleting an account is done manually. Write to support@invoia.io from an address attached to the account and we will delete the account and the data held in our own database. It is a hand-run process, not an automated one. It reaches no further than our own database: your customer record at Stripe — company name, billing address, VAT number, billing email and Stripe's own invoice history — stays with Stripe, and backups and log files at our vendors expire on their terms and not on ours. What has been written into your own accounting system also stays — see below.
There is no self-service deletion in the app, and there is no export function. If you want data out before we delete, it has to be agreed in the same exchange.
We state no deadline for deletion. We have no system that measures one, and we would rather write nothing than a figure we cannot stand behind.
What invoia has written into your own Dinero organisation or e-conomic agreement stays there. Booked invoices, credit notes and accrual vouchers are your own bookkeeping records in your own accounting system. invoia cannot delete them — both systems refuse deletion of booked material — and that is not the intention either: retention of that material follows the bookkeeping rules you are subject to and your own agreement with the accounting system.
If you disconnect Dinero or e-conomic, the invoicing dataset is not deleted. The connection is closed, and our copy of the credential and the accounting system's own reference lists are deleted at once. Customers, invoices, invoice lines, work registrations, accrual vouchers, recurring series and credit notes stay — they are your own financial history, not a copy of the accounting system's. The debtors and products we mirrored from the accounting system are deleted 30 days after the connection ended, except those an issued invoice refers to: they are part of that document and are kept with it. The 30 days are the window for reconnecting the same organisation and finding the catalogue intact. The bookkeeping in the accounting system is unaffected.
Reporting a vulnerability
If you have found a vulnerability, write to support@invoia.io with the word Sårbarhed (or Vulnerability) in the subject line. Describe what you found, how it is reproduced step by step, and what you assess the impact to be. Attach a request, a log excerpt or a screenshot if you have one.
We ask for coordinated disclosure: give us reasonable time to fix the issue before you publish anything. In return, we give no commitment to a response time or a remediation deadline. We have nothing that measures one, and a figure without measurement is worth nothing.
In scope: invoia.io and the application itself, the public API at /api/v1, the MCP server at /api/mcp and the associated OAuth endpoints.
Out of scope: our vendors' own systems, which should be reported to the vendor directly; denial-of-service attacks and load testing; social engineering of staff or customers; physical access; reports consisting solely of scanner output with no demonstrated impact; and findings that require access to the victim's own device or mailbox.
Test only against your own account and your own data. Do not extract data belonging to others, and do not alter or delete anything you did not create yourself.
What we do not have
invoia holds no SOC 2 report and no ISO 27001 certification. We do not commission annual penetration tests. We run no bug bounty programme and pay no bounties. We run no round-the-clock monitoring and no status page.
It says so here because the alternative would be invented: a badge, an audit date, a certification "in progress". This page describes what has been built — not what has been attested. The descriptions of how the product is built correspond to code that exists in it today. Deletion, withdrawal of an OAuth grant and the handling of a vulnerability report are manual processes with no code behind them — it says so where that applies. When the code changes, the page changes.
invoia is operated by Creo Digital, CVR DK45246132. Questions about this page: support@invoia.io.
Language
This page is written in Danish. The English version is a translation provided for convenience. In the event of any discrepancy between the two versions, the Danish version governs.