Security
How OurAI is built, and why you can trust it with your work.
Architecture
The platform is split into separate services, each with its own database, on its own network, under its own credentials. Your conversations, your files and the audit log live in the data plane. Who you are, and who else is in your organisation, lives in the control plane, alongside the identity service we run for sign-in. Your subscription and your usage ledger live in the accountant, but not your card, which Stripe holds and which never reaches a database of ours.
The data plane holds no name and no email address of yours: there, you are a random identifier. Mid-request it checks three things with the control plane: your plan, your policy and your balance. Each is a lookup by that identifier with none of what you wrote in it. The accountant never sees a prompt either; the only thing a request asks it is whether you have balance left, and the answer is a number.
Because the databases are separate, a break-in at one service does not hand over what the others hold. The one deliberate exception is our internal analytics, which reads across them under grants issued column by column. What can reach the machine at all is filtered at our hosting provider, outside the server itself: three ports are open to the internet: HTTPS, the redirect that precedes it, and the one we administer the machine through. Everything else is refused before it arrives, so a service that accidentally opened a port would still not be reachable. Your data is hosted in the EU. If you need OurAI on your own servers, contact us.
Encryption and databases
Your data is encrypted on the way to us, and every time our own services talk to each other: TLS 1.3 throughout, and between our own services both sides have to present a certificate we issued. In the database your conversations, your files and the audit log are encrypted again, each locked with a key belonging to your organisation alone. That key is not in the database, so a copy of the database, or a discarded disk, yields ciphertext and nothing else.
Which rows anyone can see is enforced by Postgres itself, rather than by our code remembering to filter. Code that forgot returns nothing rather than someone else's data.
Two things this does not cover. A conversation's title is stored in the clear, because you search on it. And none of it defends against someone who already controls the running service. That is a question of who can reach production, answered under Access.
| LAYER | MECHANISM | KEYS |
|---|---|---|
| Traffic into the platform | TLS 1.3 | Renewed automatically |
| Between our services | Mutual TLS 1.3 | Our own internal CA, one named peer |
| Conversations and files | AES-256-GCM | One key per organisation |
| Audit log | AES-256-GCM | One key per organisation |
| Disks and databases | Disk encryption | Held by our hosting provider |
Access, yours and ours
Only the people you invite get into your organisation. Sign-in runs through Authentik, an established open-source identity server that we run on our own infrastructure rather than a login we wrote ourselves. The credential it hands your browser lasts fifteen minutes before it has to be renewed, checked again on every request rather than issued once and trusted for the rest of the day. Two-factor is available on every plan, and you enrol it yourself.
Inside your organisation, your conversations remain yours alone, and a colleague does not get to one by finding the link. If someone comes across a link to a conversation of yours, they are told it does not exist, rather than that they are not allowed to open it. The wording there is deliberate: “not allowed” would confirm there was something to find.
On Business and Enterprise there is an audit log, and only administrators can open it. An administrator can read what was said. Putting a name to it takes two of them: one asks with a written reason that cannot be edited afterwards, another agrees, and the grant expires. Every reveal is written to the same log, in the same transaction, and our software has no permission to delete from it.
On our side, nothing about running the platform involves reading what you send. Prompts and responses are not written to our logs, and our internal analytics cannot see the conversation tables. We keep a test that plants a unique string in a prompt and fails the build if it ever appears in a log line. The honest limit is that a small number of us can reach the machine the platform runs on, because someone has to deploy it and fix it, and anyone who can do that can read the keys. No routine task requires touching your content, and the paths that could expose it are the ones we test.
What leaves the platform
Your conversation goes to the provider of the model you chose. What we send carries the conversation, the text of your files and the instructions we add ourselves, and nothing that says who you are: no name, no email address, no account or organisation identifier, and neither your browser nor your IP address. Files are turned into plain text on our own machines first, with no outside service involved, so your original document never leaves the platform. The connection is made by our server under our own account with that provider, so what arrives there is a conversation and not a customer.
One check runs on every message, and it is not optional. Every message you send and every answer you get back is checked against our acceptable-use rules by a small classifier we run ourselves, on our own hardware in the EU; nothing about that check leaves the platform. It returns a verdict and a category rather than an answer, and it cannot be switched off, not by you and not by your administrator. Where redaction is on, the check reads the masked text, so it does not see personal data either. If it is unreachable, times out or returns something we cannot read, the message is refused rather than forwarded unchecked.
Nothing you send trains a model. Not ours: we do not train models, and there is no path in the system that builds a dataset out of your content. For the providers we forward to, we use each one’s business API rather than a consumer product, and we choose providers whose terms state that inputs and outputs are not used for training. That is a contractual assurance from them rather than something we can enforce in code. What we can enforce is which of them your organisation may use at all: a model your administrators have not allowed is refused at the gateway, not merely hidden from the menu.
| SUB-PROCESSOR | PURPOSE | LOCATION |
|---|---|---|
| Hetzner | Running the platform | EU |
| Anthropic | Model inference, where your policy permits | United States |
| OpenAI | Model inference and image generation, where your policy permits | United States |
| Model inference, where your policy permits | United States | |
| xAI | Model inference, where your policy permits | United States |
| Mistral AI | Model inference, where your policy permits | EU |
| Alibaba Cloud (Qwen) | Model inference via the international endpoint, where your policy permits | Singapore |
| Moonshot AI | Model inference, where your policy permits | China |
| Brave | Web search, and reading the pages behind the results, when the tool is enabled | United States |
| Postmark | Sign-in, verification and invitation emails | United States |
| Sentry | Error reports from our own systems | United States |
| Stripe | Payments and invoicing | United States |
| Proton | Our own business email | Switzerland |
Retention and erasure
We keep what the product needs and no more. A conversation stays until you delete it, and deleting it is immediate and permanent: the messages, the earlier versions of them and the files attached to them all go at once, with no recycle bin and nothing to restore afterwards. If your organisation has an audit log, it has a fixed lifetime, a year on Business and Enterprise, and it is erased automatically when that lifetime runs out, rather than sitting there until somebody remembers it.
Closing the account destroys the rest. Only an owner can start it, you cancel your subscription yourself first, and then fourteen days pass during which you can still call it off. When the day comes, everything goes: your conversations, your files, what the assistant had saved to remember you, your sign-in identity, and finally your organisation. One thing is kept, because Danish bookkeeping law requires it: invoices and accounting records, for five years after the calendar year they belong to. Afterwards the same email address can sign up again, to an entirely new and empty organisation.
On the other side, we ask no provider to store your conversation. What all of them do is cache: the repeated opening of a long conversation is held briefly on their side so it does not have to be processed again, and that is measured in minutes rather than days. Beyond that short window, what a provider retains is governed by its own API terms.
Security testing
Every change runs the full test suite for each service it touches before it can merge, against a real database that has first been through the same role, grant and schema scripts production runs on its first boot. A suite run against a permissive test database would prove nothing about the isolation described on this page. The security tests are written as negatives: a certificate from our own authority, but with a name that is not on the allowlist, is refused; a query that reaches the database without an organisation attached returns nothing rather than everything; an internal endpoint reached without a client certificate is refused; the audit tables reject an update or a delete under the role the application runs as. Dependencies are locked to an exact version and hash, and every container image is pinned by cryptographic digest rather than a moving tag, so what we deploy tomorrow is what we tested today.
On top of that, any change touching sign-in, the separation between organisations, inter-service authentication, billing or the gateway gets an adversarial pass over the diff before it ships. It is run by an AI agent working from an attacker's brief rather than a checklist, and a finding counts only if it comes with a concrete path from untrusted input to the weakness: a route that lost its authentication, a query that stopped relying on row-level security, a check that used to fail closed and now fails open, a secret that reached a log. Findings are recorded with their severity, and a critical or high one stops the change until it is closed.
If you find something, write to avl@ourai.dk. You get an acknowledgement within two business days and a substantive reply within ten. Anything reachable at our domains, or running inside a container built from our own code, is in scope; third-party services we depend on are not; report those upstream. We will not pursue legal action against anyone acting in good faith who does not extract data, does not degrade the service for others, and gives us a reasonable window before publishing. We aim for ninety days between report and public disclosure, sooner if the fix ships sooner.
Service status, incidents and backups
If something breaks, you find out where we do. Our status page is driven by checks running outside our own infrastructure, on an independent monitoring service, so an outage of ours cannot take the status page down with it. Incidents open and close from the state of those checks rather than waiting for somebody to write them up. And if a breach ever touches your data, you hear from us within 48 hours of our knowing, early enough that your own 72-hour clock to the authorities is still yours to run.
Behind the public components sit heartbeat checks on the background workers: the loops that write billing records, sweep expired holds and run retention. Each pings a monitor on a schedule, and an alert fires when a ping stops. The failure a status page never catches is not a service that is down but a worker that has quietly stopped while everything else still answers. Those checks stay private, because a public list of which internal component is sick right now is a map for somebody who means harm.
The whole machine is imaged nightly by our hosting provider, and the images are kept in the EU. Because it is a whole-disk image, every database is captured in the same instant, so a restore comes up internally consistent rather than with one service's records ahead of another's. Two honest limits: the restore unit is the entire server rather than one organisation or one moment in time, so recovering a single deleted conversation from a backup is not something we can do. And the window is a night, so a restore can cost up to a day of the most recent activity. The current state of every part of the platform is at status.ourai.dk.