Security

How OurAI is built, and what we do with what you send.

LAST UPDATED · 21 AUGUST 2026

Architecture

The platform is split into separate services, so the part that knows who you are is not the part that handles your conversations, and neither is the part that keeps your subscription and invoices. Each has its own database, and none of them can reach another's. The service that stores your conversations never stores your name or your email address, only an identifier. Everything we run is in the EU. If your organisation needs the product on its own infrastructure, everything except billing runs on your own server. Read about sovereignty.

See details

Four services, each with its own database and its own credentials. Sign-in and your organisation's details live in the control plane. Conversations, files, the audit log and the gateway out to the models live in the data plane. Subscriptions and the invoice ledger live in the accountant, which is never on the path of a request. Sign-in itself runs as a fourth service with its own store.

Within each database the running application connects as a role that cannot alter the schema and cannot switch off row-level security. Schema changes and account deletion run under separate roles that exist for nothing else. The keys that unlock an encrypted audit log exist only in the data plane's environment: the control plane could not read an audit log if it tried.

When the services talk, both sides present a certificate from our own internal certificate authority, and each surface accepts exactly one named peer rather than any certificate that authority ever issued. TLS 1.3 is the floor on those internal connections, not only at the public edge. During a request the gateway asks the control plane for three things: your tier, your policy and your token balance. Your prompt is not among them, and it never leaves the data plane except on its way to the model provider you chose.

Traffic from outside first meets our own front door, which terminates TLS 1.3 and routes by hostname. There is nothing in front of it: no CDN and no third-party proxy sees your traffic in the clear.

Encryption and databases

Your data is encrypted on the way to us, and whenever our own services talk to each other. In the database it is encrypted again: your conversations, the files you upload and the audit log are each locked with a key belonging to your organisation alone. Every encrypted value is tied to the row it lives in, so it cannot be read anywhere else. The key that unlocks them is not in the database, so a copy of the database is not enough to read any of it.

See details

Traffic into the platform is TLS 1.3, certificates renew automatically, and browsers are instructed never to speak plain HTTP to us again. Between our own services, both sides present a certificate from our internal authority, each surface accepts one named peer, and TLS 1.3 is the floor there as well.

At rest the scheme is the same everywhere it applies: AES-256-GCM, one data key per organisation, and every encrypted value bound to its own row. Binding means the organisation and row identifiers are covered by the authentication tag, so a value lifted out of one row and pasted into another fails to decrypt rather than yielding readable data.

The per-organisation keys are themselves stored encrypted, and are unwrapped only in memory by the one service that owns them. The key that unwraps them lives in that service's environment and in no database, so a stolen database copy or a discarded disk yields ciphertext and nothing else. Two such keys are always live, current and retiring, which is what lets a key be replaced and every row re-wrapped without anything becoming unreadable. What this does not defend against is someone who already controls the running service. That is a question of who can reach production, answered under Access.

Isolation between organisations is enforced by Postgres, not by our code remembering to filter. Our internal analytics is the one place a role deliberately reads across organisations, and it is granted column by column: prompts, responses, user identifiers and the wrapped keys are not among the columns it may read, and the conversation tables are not exposed to it at all. The one thing we deliberately leave unencrypted is a conversation's title, because you search on it.

LAYERMECHANISMKEYS
Traffic into the platformTLS 1.3Renewed automatically
Between our servicesMutual TLS 1.3Our own internal CA, one named peer
Conversations and filesAES-256-GCMOne key per organisation
Audit logAES-256-GCMOne key per organisation
Disks and databasesDisk encryptionHeld by our hosting provider

Access, yours and ours

Only the people you invite get into your organisation, and inside it your conversations are yours alone. A colleague who comes across a link to one of them is told it does not exist, not that they are not allowed. What each person can do follows the role you gave them, and two-factor is yours to turn on. Your audit log records what was said but keeps the author sealed: administrators can read a conversation, and putting a name to one takes two of them. On our side, no part of running the platform involves reading what you send.

See details

Access comes down to four questions: how you sign in, what separates organisations and colleagues, who can read the audit log, and what we can reach ourselves.

Your organisation

You sign in through our identity service with OpenID Connect. The credential lasts fifteen minutes and is re-checked on every request; staying signed in works by quietly exchanging it for a new one, and if an old one is replayed, every session for that account is revoked rather than the replay being served. Two-factor is available on every plan and you enrol it yourself. Your role, member, administrator or owner, travels inside the credential, so it is evaluated on every request rather than looked up and trusted.

Between organisations, and inside one

The separation between organisations is a rule in Postgres, and the role our software connects as is not permitted to switch it off: code that forgot to filter returns nothing rather than someone else's rows. Your conversations are scoped a second time, to you personally. Asking for a colleague's conversation returns “not found” rather than “not allowed”, and so does a file inside it, refused before the storage is touched. “Not allowed” would confirm the conversation exists.

The audit log

It exists for organisations on a team plan, and only administrators can open it. An administrator can read the conversations it records: that is what an audit log is for, and it is what makes it possible to check whether a conversation contains someone's personal data before deciding whether anyone needs to be identified at all. What an administrator cannot do alone is put a name to one. One administrator asks and records a written reason, which cannot be edited afterwards; a second, never the same person, enforced in our code and by a constraint in the database, agrees. The request lapses after a day. Once agreed, the requester has two hours to open it, and opening it starts a fifteen-minute window rather than a standing right. Every reveal is recorded in the same transaction that performs it, so there is no sequence of events in which someone learns who wrote something and no trace exists. Our software can add to that trail and has no permission to delete from it.

Our access

We do not read your data as part of running the platform, and that is arranged rather than promised. Your prompts and responses are not written to our operational logs: we keep a test that plants a unique string in a prompt and fails the build if it ever surfaces in a log line. They are not in our error reports, where the reporting client never attaches request bodies, never infers who a user is, and runs a scrubber over everything it does send. And they are not in our internal analytics, which is granted access column by column and cannot see the conversation tables at all.

Where our access does extend

A small number of people can reach the machine the platform runs on, because someone has to deploy it, restore it and fix it. Anyone who can do that can read the keys the services use. The two-person rule above is a control on your administrators; it is not a control on us. What stands in its place is that the group is small, that no routine task requires touching customer content, and that the paths which could expose it are the ones we test to prove they do not. If that is not sufficient for your organisation, it is the reason the whole platform can run on your own infrastructure instead.

CONTROLAVAILABILITYNOTES
Sign-in with OpenID ConnectAll plansThe credential lasts fifteen minutes and is re-checked on every request
Two-factor with app, security key or one-time codesAll plansYou enrol it yourself inside the product
Separation between organisationsAll plansEnforced by the database, not by the code
Your conversations inside your organisationAll plansYours alone; a colleague is told the conversation does not exist
Naming the author of a conversation in the audit logRequires two peopleOne administrator asks with a written reason, another agrees, and the grant expires

What leaves the platform

Your conversation goes to the model you chose and to nothing else, apart from two checks we run ourselves. Every message you send and every answer you get back is screened against our acceptable-use rules by a small model hosted in the EU. That check is ours, it applies on every plan, and no setting turns it off. From Pro upwards, redaction masks personal data before a message leaves us and puts the real values back in the reply, and the masking runs on our own machines, so nothing is handed to a third party in order to hide it. Providers receive the words, not the person. Nothing you send trains a model, ours or anyone else's.

See details

Below is the whole of it: what a provider receives, the two checks we run ourselves, what the tools reach for, and who we count as a sub-processor.

What a provider receives

The conversation, the files attached to it, the instructions we add ourselves, anything you have asked us to remember, and the list of tools the model may call. It carries nothing that identifies you: no user or organisation identifier, no email address, and neither your browser nor your IP address. The connection is made by our own server, authenticated with our own account at that provider. The provider sees a conversation; it does not see whose it is.

The acceptable-use check

Before a message is forwarded, and again before an answer reaches you, the text is classified by Mistral Small. Mistral is in France, and this is the one place where content is sent somewhere you did not choose. It is a classifier and not an answer: it returns a verdict and a category, and the text arrives wrapped so that a message cannot talk its way out of being classified. The request-side check runs after redaction and the answer-side check runs before the real values are restored, so where redaction is on, neither pass sees personal data. The check cannot be switched off by anyone, not by you, not by your administrator, and not by excluding Mistral from the models your organisation allows, because the classifier is resolved outside your model settings. It fails closed: if it times out or returns something we cannot parse, the message is refused rather than forwarded unchecked. A blocked prompt is a prompt no provider ever saw.

Redaction

Available from Pro upwards and on by default where available. The detection engine runs in a container of ours, reached over the same mutual TLS as everything else internal, because no third party should be involved in finding your personal data, which would rather defeat the purpose. It reads Danish and English, including CPR numbers, and it masks every message in the request under one shared mapping, so a name introduced twenty turns ago is still masked today. The first time it finds something, the request pauses and shows you what is about to be hidden. You can keep a value in the clear, and that choice is recorded in the audit log; an administrator can switch that ability off entirely. The reply comes back with the real values restored, so you read your own words and the provider never saw them.

Files and attachments

Files are covered by conversion rather than by hope. A PDF, a Word document, a spreadsheet or a PowerPoint deck is turned into plain text on our own machines before any of this runs, with no help from any outside service, which would put the contents beyond redaction's reach, and where redaction is on, the provider is sent that redacted text in place of the file. Your original document never leaves the platform. What you can attach is a closed list: those four formats, ordinary text and code files, and images in the four common web formats. Anything else is refused at upload rather than passed along as something we cannot read. Two honest limits. Images are not masked: there is no text to extract from a photograph, we do not run character recognition over one, and an image sent to a model that reads images travels as the picture itself. And a document with no text in it, a scan or a photographed contract, would otherwise pass as “nothing to redact” when in truth nothing was ever read; those stop and ask you before they are sent.

Tools

When web search is enabled, the search phrase the model composes goes to Brave in the United States, and our own server, not your browser, fetches the pages behind the results, so no website learns anything about you. Page text is treated as hostile input: it reaches the model sealed as data, and there is no tool that lets the model name a page of its own to fetch. If a search phrase still carries a mask, the search is refused rather than sent. Image generation goes to OpenAI whichever model you are chatting with, because the image model is a separate service behind our own tool.

What the other side keeps

With Anthropic we use their five-minute prompt cache, so the beginning of a long conversation is held on their side for a few minutes at a time. That is the only provider-side retention we deliberately switch on. Everything else is subject to the provider's own API terms, which is why the list below names both the company and where it sits.

Training

We do not train models. Not on your conversations, not on your files, not on your audit log; there is no path in the system that does it, and no dataset assembled from your content. For the providers we forward to, we use each one's business API rather than a consumer product, and we select providers whose API terms state that inputs and outputs are not used to train their models. That is a contractual assurance from them rather than something we can enforce in code. What we can do, and do, is publish where each provider sits and let your administrators decide which of them your organisation may use at all. That choice is enforced at the gateway, not merely hidden in the menu: a model your organisation has not allowed is refused even if it is asked for directly.

What goes where
GOES OUTTO WHOMWHEN
Your message and the conversation so farThe provider of the model you choseEvery request
Files you attachThe same providerWhen the model can read them
Your message, and the answer, for an acceptable-use verdictMistral, in the EUEvery request and every answer, on every plan
A search phraseBrave, in the United StatesOnly when web search is enabled and used
An image promptOpenAIOnly when image generation is used
Your name, email or account identifierNobodyNever sent with a request
Sub-processors
SUB-PROCESSORPURPOSELOCATION
HetznerRunning the platformEU
AnthropicModel inference, where your policy permitsUnited States
OpenAIModel inference and image generation, where your policy permitsUnited States
GoogleModel inference, where your policy permitsUnited States
xAIModel inference, where your policy permitsUnited States
Mistral AIModel inference where your policy permits, and the acceptable-use check on every request and every answerEU
Alibaba Cloud (Qwen)Model inference via the international endpoint, where your policy permitsSingapore
Moonshot AIModel inference, where your policy permitsChina
BraveWeb search, and reading the pages behind the results, when the tool is enabledUnited States
PostmarkSign-in, verification and invitation emailsUnited States
SentryError reports from our own systemsUnited States
StripePayments and invoicingUnited States
ProtonOur own business emailSwitzerland

Retention and erasure

We keep what the product needs to work, and then it goes. A conversation you delete is deleted at once, there is no recycle bin and nothing to restore afterwards. Your organisation's audit history has a fixed lifetime set by your plan, and it is erased automatically when that lifetime is up. If one person asks to be erased, an administrator can have their records destroyed rather than blanked. And if you close the account, everything is destroyed across every system that holds it, after fourteen days in which you can change your mind.

See details

Four kinds of erasure, each with its own rule: the one you perform, the one that happens on its own, the one that covers a single person, and the one that closes everything.

Deleting a conversation

An immediate, permanent delete of the conversation and everything hanging off it: every message, every earlier version of a message, every record of an attachment. There is no “deleted” flag that leaves the rows sitting there. One thing does survive, deliberately: if your organisation is on a plan with an audit log, the record of that exchange stays in the audit log until its own lifetime runs out. That is what an audit log is for, it would be worth very little if the person being audited could delete it, and it is bounded by the window below rather than kept indefinitely.

How long the audit log lives

Ninety days on Basic, a hundred and eighty on Pro, a year on Max, Business and Enterprise, and on Enterprise a different window can be agreed and is then enforced for your organisation specifically. The same window governs the analytics we derive from it, so the usage figures your administrators see cannot outlive the records they came from. Erasure runs in two phases a day apart: the first marks what is due and can be undone, the second removes the whole row, not merely the encrypted text, the row. If your plan moves to a shorter window the shorter window applies from that moment; if it moves to a longer one, what has already gone does not come back.

Erasing one person

When somebody exercises their right to be forgotten, an administrator files the request with a reason, and what follows is a destruction rather than a blanking: their records in the audit trail and in the analytics derived from it are removed, including any the retention worker had marked but not yet reached. The order is deliberate: the most sensitive material, the prompts and responses, goes first, so an interruption partway through leaves the least behind rather than the most. Two different database roles are involved and neither can do the other's job: the running application may only add a request, and the worker may only stamp its own bookkeeping and delete.

Closing the account

Only an owner can start it, and if your organisation is a team, closing it destroys everyone's data, so it takes an explicit typed confirmation rather than a single click. You cancel your subscription first, we will not cancel it on your behalf, because that is your relationship with the payment provider, not ours. Then fourteen days pass, during which you can call it off, and the banner in the product tells you the date. When the day arrives the subscription is checked once more: if you have started paying again in the meantime, the deletion cancels itself rather than destroying a workspace someone is being billed for. Then everything goes: your conversations, your files, your audit history, the things the assistant had saved to remember you, your sign-in identity, and finally your organisation. Afterwards the same email address can sign up again and gets an entirely new, empty organisation. One record survives on purpose: that a deletion happened, with the person it referred to stripped out of it.

DATADEFAULTCONFIGURABLEON CLOSURE
ConversationsUntil you delete themNoDeleted
Files you uploadUntil you delete the conversationNoDeleted
Audit log, and the analytics derived from it90 days on Basic, 180 on Pro, 365 on Max, Business and EnterpriseYes, by agreement on EnterpriseDeleted
What the assistant has saved to remember youUntil you delete itNoDeleted
Account and organisation detailsAs long as the account is activeNoDeleted
Invoices and accounting recordsFive years after the end of the calendar year they relate toNoKept, required by Danish bookkeeping law

Security testing

The boundaries described on this page are held in place by tests that fail the build, not by anyone remembering they exist. Thousands of automated tests run before any change can merge, and the security ones are written backwards: they check that the wrong certificate is refused, that a query without an organisation attached returns nothing, that a log that is supposed to be append-only cannot be edited. Anything touching sign-in, the separation between organisations, payments or the gateway gets a second pass that looks for a way in rather than a way it works.

See details

The tests, the boundaries checked mechanically, the attacker's pass, and what we build on.

The tests are the enforcement

Every change runs the full suite for each service it touches, against a real database that has first been through the same role, grant and schema scripts production runs on its first boot. That detail is the point: the isolation properties on this page are properties of database roles and grants, so a suite that ran against a permissive test database would prove nothing. The security tests are 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 set on the connection returns nothing rather than everything, including when the value is set to the empty string, which is the version of that bug that actually happens. 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.

Boundaries checked mechanically, not remembered

Several claims on this page are architectural: the control plane cannot reach the data plane's code, billing is reachable through one door, the self-hosted edge configuration is identical to the managed one. Each is a check that runs before a commit is made and fails on the pattern that would break it, because an architectural rule that lives only in a document decays in a year. The public front door's configuration is not deployed and hoped for either: tests assert that TLS 1.3 is pinned, that HSTS is set with preload, that server banners are stripped, that the web server's own administrative interface is disabled, and that request logs pass through the scrubbing filter before they are written.

Adversarial review before anything ships

Any change that touches authentication, the separation between organisations, inter-service authentication, billing or the gateway gets a dedicated review pass over the diff, on top of ordinary review and the test suite. It is an attacker's brief rather than a checklist: a finding counts only if it comes with a concrete path from untrusted input to the weakness. It looks for a specific set of failures: a route that lost its authentication dependency, 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 what was done about them, and a critical or high finding stops the change until it is closed.

What we build on

Every dependency is locked to an exact version and content hash, and every container image we build starts from a base image pinned by cryptographic digest rather than by a moving tag, so an image rebuilt tomorrow is the image we tested today, and cannot be changed underneath us by someone else's release.

Where you stand legally

For the GDPR we are your processor and you are the controller; the terms are in the data processing agreement, and every sub-processor is named above. Card data never reaches our systems, Stripe holds it, and no card number touches a database of ours.

Reporting something you find

Write to security@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 from the same place we do. The status page is driven by checks that run outside our infrastructure, so it does not depend on the thing it is reporting on, and incidents open by themselves rather than waiting for someone to write them up. If a breach ever touches your data, you hear from us within 48 hours of us knowing, early enough that your own 72-hour clock to the authorities is still yours to run. The whole machine is imaged every night. And when we investigate a problem, we work from a reference number attached to the request, not from reading what you wrote. status.ourai.dk shows the state of each part of the platform.

See details

The status page, what it deliberately does not show, how we investigate without reading your conversations, and what backups cover.

The status page

status.ourai.dk lists the website, the app, the admin surface and sign-in, each checked every three minutes from outside our network by an independent monitoring service. Two consecutive failures are required before a component is marked down, so a container restarting during a deploy does not cry wolf, and a real outage still shows inside about six minutes. Incidents open and close automatically from the state of those checks: nobody writes the narrative by hand, which is the only way a status page can be trusted to reflect the present rather than someone's availability to update it. Planned work long enough to trip the checks gets a maintenance window opened in advance. Because the checks run outside our infrastructure, an outage of ours cannot take the status page with it.

What the page deliberately does not show

Behind the public components sit heartbeat checks on every background worker: the loops that write billing records, sweep expired holds, run retention and drain queues. Each pings a monitor on a schedule, and the 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 working while everything still answers. Those checks are private because they name internal parts, and a public list of which internal component is currently sick is a map for somebody who means harm.

How we investigate without reading your conversations

Every request through every service carries a short reference id. It appears in the app if a message fails, in the response headers, on every log line for that request, and on the error report if one is raised. That id is what we work from. Diagnostics do not carry your prompts or the model's replies, and before anything leaves our systems it passes through one shared set of redaction patterns for tokens, keys and identifiers, applied by both the log writer and the error reporter from a single definition, so the two cannot drift apart. If you report a problem and quote the reference, we can trace the whole path through four services without asking you what you were writing about.

Uptime

Our standard terms do not commit to an uptime percentage, so no figure is quoted here, a number on a marketing page that is absent from the contract is worth nothing. Organisations that need a contractual service level can have one as part of an enterprise agreement.

When something goes wrong with your data

If a personal-data breach affects your organisation, we notify you without undue delay and in any event within 48 hours of becoming aware of it, whether the breach happened at us or at one of our sub-processors, with whatever is known at the time, supplemented as we learn more. The 48 hours is in the data processing agreement, and it is deliberate: under the GDPR the 72-hour deadline to the supervisory authority is yours, as the controller, and it starts when we tell you. Ours has to be shorter than yours or it is not worth having. For an incident that affects the platform without touching your data, you hear from us within five business days.

Backups

The entire machine is imaged every night and the last seven images are kept, within 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 whole server, not one organisation and not one point in time: 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. Organisations that need point-in-time or per-organisation restore should raise it as part of an enterprise agreement. If you run the platform on your own infrastructure, the backups are yours to take, on your schedule, under your control, as is everything else on that box.

Not covered here?