Effective: 2026-08-10 Last updated: 2026-08-10
Who this is about
megaloop is operated by AI PWRD Inc., a Delaware corporation ("megaloop", "we"), which is the controller of everything described here. This policy covers the megaloop service at megaloop.dev — the website, the dashboard, the management API, and the inference gateway.
Contact us about anything in this document at support@megaloop.dev. That is the only address, on purpose: at this size a privacy desk that routes you to a second inbox is a queue, not a service.
What megaloop does, so the rest of this makes sense
megaloop is a gateway. You link the AI subscriptions you already pay for — your own accounts, your own credentials — and megaloop gives you one endpoint and one key. When you send a request, megaloop picks one of your linked accounts and forwards the request to that provider using your credential. The answer streams back to you.
Two consequences fall out of that, and they shape everything below:
- We hold your provider credentials. They are encrypted, and how is described in detail below.
- Your prompts pass through us on the way to a provider you chose. Passing through is not the same as storing. What we do and do not keep is the next section, because it is the question that matters most.
The short version
- We do not store your prompts or the responses. Not in the database, not in a log, not on disk.
- We do store one row of metadata per request: which of your accounts served it, the model name, the endpoint path, the HTTP status, how long it took, and the token counts the provider reported for it. Every column of that row is listed in What we do keep instead, which is the authoritative list — the Terms (
/terms) and the Acceptable Use Policy (/acceptable-use) point at it rather than repeat it. - To read those token counts we read a bounded head and tail of the response body, in memory, looking for nothing but the provider's own usage figures. What that does and does not mean is set out under Prompts and completions, first rather than last.
- Your provider credentials are encrypted with AES-256-GCM before they are written, and are decrypted only to serve a request or refresh a token.
- There is no password anywhere in the system, and no IP address column in any table.
- We run no analytics, no tracking, no advertising, and no third-party scripts.
- We have no self-serve account deletion yet. Email us and a human does it. The honest limits of that are spelled out under Deleting your account.
- Several things we store have no retention limit today. They are named in Retention rather than glossed over.
Prompts and completions
This is the part most people are asking about, so here it is precisely.
What we do not keep
We do not store the content of your requests, and we do not store the content of the responses. No column anywhere holds a request body or a response body, and no column holds any header sent on an inference request. Nothing writes one to a log file or to disk.
(One header value is stored, and it is not on the inference path: your browser's User-Agent string is saved on a sign-in session so you can recognise a session before revoking it. It is listed under Your browser session. That is the whole exception, and we name it here rather than let a broader sentence stand.)
Mechanically:
- Your response is handed straight back to you, unchanged. It is the provider's own stream: each chunk is passed along by reference as it arrives, never copied, cloned or teed, and never held back waiting on anything we do. What you receive is byte-identical to what the provider sent, in the same order, and nothing done to a chunk waits on anything else — no disk, no network, no lock, no second chunk — so no byte of a response is held back for us.
- Two bounded windows of it are read, and only for the token counts. The first 4,096 bytes of the first chunk, and a rolling last 16,384 bytes of the body, held in memory while the response passes. Nothing accumulates beyond that window: as the stream runs, earlier chunks are released as soon as what remains still covers it, so an hour-long generation holds no more of itself than the last few seconds of one, and what is held is a reference to chunks the provider already sent and you already have — never a growing copy of the response. Each window is scanned for one thing — the usage figures the provider states about its own response. The tail is where the final figures sit and is what fills the token columns below; it is scanned only after the stream has closed and you already hold every byte. The head carries an early cache-hit count, which goes to our operational log and no further. No part of the body is stored: not in the database, not in a log, not on disk. The two sizes are
PEEK_BYTESandTAIL_BYTESinpackages/gateway/src/handler.ts, and a window is kept only for a provider we have a usage reader for — the self-hosted-endpoint archetype has none, so for it nothing is read at all. - Your request body is read once, in memory, and only one field is read out of it. We parse it to learn which model you asked for, so we can route and record it. It is also modified before it is forwarded — a per-account identifier, and on one provider a marker line above your system prompt. Both are set out in full under The AI providers you linked, because a "we read one field" sentence on its own would leave you thinking your bytes go out as you sent them. Nothing you wrote is removed or reordered, and neither the body you sent nor the body we forward is passed to anything that writes to storage. Both are discarded when the request ends.
- We hold the request bytes in memory for the life of the request. That is deliberate: if the account we picked turns out to be rate-limited, we need the original bytes to retry on the next account. When the request finishes, the bytes go with it.
- When we do rotate to another account, the abandoned response is cancelled, not read.
- Our logger cannot record a prompt even by accident. Its type signature only accepts strings, numbers and booleans as named fields, so a request body, a response body, or a header value is not expressible as an argument. What it writes is an event name plus values like an account id, an HTTP status, a retry count, and a token count.
What we do keep instead
One metadata row per served request, tied to your account. This table is the authoritative list of what a request leaves behind — all sixteen columns of request_events in packages/db/src/schema.ts, with created_at lifted out of last place because it is the one a reader looks for first. The Terms and the Acceptable Use Policy point here instead of restating it, because three partial lists in three documents is how a wrong one survives.
| Recorded | What it is |
|---|---|
id | the row's own identifier |
tenant_id | your megaloop account — every read of the table is scoped to it |
created_at | when the request finished |
account_id | which of your linked accounts served it |
provider | which provider that account belongs to |
model | the model name you asked for |
path | the endpoint path, e.g. /v1/messages |
status_code | the HTTP status the provider returned |
response_class | whether it served, rotated, paused or failed |
latency_ms | how long the whole request took |
overhead_ms | how much of that was us rather than the provider |
input_tokens | prompt tokens the provider reported, apart from the two cache figures |
output_tokens | the tokens it generated |
cache_read_tokens | prompt tokens it served from its own cache |
cache_write_tokens | prompt tokens it wrote into that cache |
resolved_model | the model it says it actually served — the one column still left null, see below |
There is no column in that table for a request body, a response body, a header, or an IP address.
A note on the token counts, because this policy used to say the opposite. Until recently those four columns existed and were never filled, and both this document and the /trust page said every row had them null. That is no longer true: the counts are read from the bounded tail described above and written to the row. They are the provider's own figures about its own response — four integers saying how much was sent and generated, and nothing about what any of it said. We keep them for one purpose: showing you what the same traffic would have cost at that provider's published list price, which is the product's entire claim and cannot be shown without them. A count for which the provider reported nothing stays null rather than becoming a zero, so a total is never inflated by a figure nobody measured.
resolved_model is the one column in the table the serving path does not fill today. It exists for the model a provider says it actually served, which is what a price should key off; the id arrives in the first frame of a response rather than the last, and pricing does not need it, because the alias and the dated id map to the same rate.
model and path are the only two strings derived from your request that leave the request. They are in the table above, and model is also echoed back in the error message when a request is rejected for an unusable model. Nothing else from your request body is persisted or reflected.
Where your prompt does go
To the provider whose account served it. That is the entire point of the product — megaloop forwards your request to api.anthropic.com, to the Codex backend at chatgpt.com, or to your own self-hosted endpoint, depending on which account you linked. Those providers receive the full prompt and produce the full completion, and what they retain is governed by their policy, not ours.
Your request also passes through Cloudflare on the way in. That is explained under Third parties because it is a real disclosure and not a footnote.
What we collect, and why
Your account
| What | Why it exists |
|---|---|
| Email address | It is your login, and the only free-text personal detail we ask you for. |
| Account name | This is your email address. At signup the account's name is set to the address you signed up with. It is not a separate label you chose. |
| Theme preference (light/dark), or none | So the dashboard looks the same on every device you sign in from. |
Your email address is stored in more than one place. A policy that said "one column" would be easier to write and would be wrong. It is written verbatim into five columns:
| Where | What it is |
|---|---|
| Your user record | The address you sign in with |
| Your account's name | Set to the same address at signup |
| Your sign-in identity | The subject of your email login identity |
| Every sign-in code we issued you | Each code row carries the address it was sent to |
| Security audit rows | Some events happen before an account exists, so the address is the only thing identifying them |
All five are covered under Retention and Deleting your account, and two of them do not go when your account is deleted. That is stated there, not hidden here.
There is no password column in this system, in any table. You sign in with a six-digit code emailed to you.
Signing in
| What | Why it exists | Notes |
|---|---|---|
| Your email address, on each code request | To send you the code | Kept on the code row |
| A keyed hash of the code | To check the code you type | HMAC-SHA256 with a server-side secret — never a bare hash, never the code itself |
| Attempt count, used/unused flag | So guessing is countable and a code is single-use | Code expires after 10 minutes; 5 guesses per code |
Your browser session
| What | Why it exists |
|---|---|
| A hash of the session token | So we can check your session without being able to reproduce your token |
| Your browser's user-agent string | So a session is identifiable when you review or revoke it |
| Expiry and revocation timestamps | Sessions last 30 days |
No IP address is recorded — there is no column for one, anywhere. That is a deliberate design decision, not an oversight: behind a proxy there is no trustworthy source for a client IP, and a forensic field filled with a spoofable header is worse than no field.
Your API keys
We store a hash of each key plus its visible prefix, the name you gave it, and when it was last used. A database read yields no usable key. We cannot show you a key again after you create it, because we do not have it.
Your linked provider accounts
| What | Why it exists |
|---|---|
| The name you gave the account | So you can tell your accounts apart |
| Provider and link mode | So we know how to talk to it |
| Base URL, for a self-hosted endpoint | So we know where to send the request |
| Access and refresh tokens, as ciphertext | To make the request on your behalf. See Credentials |
| Health state: last used, pause reason, cooldown expiry and kind | So the pool can route around an account that is rate-limited or failing |
| The provider's overage verdict and when it was observed | So we can tell you what happens past your plan's included usage |
Your subscription headroom
When a provider's response reports how much of your quota is spent, we read that from the response headers only and store the current reading: the window label the provider minted (5h, 7d, and so on), the fraction of it used, when it resets, and when we observed it.
This is a snapshot, not a history. Each new reading overwrites the last, and the rows are deleted when you unlink the account. We keep it because it is what powers "which account has room right now?", which is the product's main question.
Your settings
Routing strategy and per-account weights; notification preferences (a global on/off switch, a utilisation threshold, and five per-event toggles). Notifications default to on at 80%.
Billing
If you subscribe: the customer and subscription id our payment provider gives us, the subscription status, the price id, the renewal date, your plan tier, and your trial end date.
Card details never reach us. Checkout and plan changes happen on the payment provider's own hosted pages; we redirect you there and back. Those columns are written only from a webhook whose signature we verify first, and the account it applies to is resolved from our own stored mapping — never from anything in the request.
Security audit trail
We append a row for security-relevant events: a sign-in code requested, a failed code, a rate-limited code request, an account created, a session created or revoked. The rows for code requests and account creation contain the raw email address, because at that moment there is no account to reference yet. Please read Retention and Deleting your account — this table has real limits we are not going to paper over.
Email delivery records
When we send you a usage alert, we record that we sent it: which event, which account, and when. It exists so we do not email you the same thing twice. If you switch notifications off, the check happens before the record is written, so a tenant with notifications off leaves no trail of what they would have been sent.
What we never collect
- Passwords — there is no column for one.
- IP addresses — there is no column for one.
- Card numbers or any payment instrument data.
- Your prompts or the responses to them.
- Analytics, behavioural tracking, session recording, device fingerprinting, or advertising identifiers. There is no such SDK in this product and no third-party script on any page.
Your provider credentials, and how they are encrypted
A linked account is worth real money to you and can be revoked by a mistake on our side. We treat it that way.
- Encryption: AES-256-GCM. 32-byte key, 12-byte nonce, 16-byte authentication tag.
- No plaintext column exists. The only columns in the table hold ciphertext. There is nothing to read in the clear even with full database access.
- Every secret carries its own nonce inside its own envelope. There is no shared nonce column, because reusing a nonce under one key leaks the XOR of the plaintexts and the authentication subkey, which would let someone forge ciphertext without ever holding the key.
- Each ciphertext is cryptographically bound to your account and to its own row. The authenticated data is your tenant id and the row id. A credential copied into another customer's row fails to decrypt rather than quietly working.
- The key is read from the server environment and is never generated. A generated key would silently make every stored credential unreadable at the next restart, so the service refuses to start without a real one.
- A provider credential is decrypted by exactly two callers in production, both on the serving path: forwarding your request, and refreshing an expiring token. No management endpoint, no dashboard page, and no API response decrypts a provider token. None of them can — the record type used for listing accounts does not carry the ciphertext at all.
- One other sealed value is opened by a management route, and it is not a credential. When you link an account by browser approval, the one-time proof-of-possession verifier for that link is sealed with the same key and the same binding, and is opened once when you complete the link. It is single-use, expires in ten minutes, and is not a token for your provider account. We name it so the sentence above stays exactly true rather than approximately true.
- Token refresh is single-flight, serialised under a database lock per account, because two simultaneous refreshes can get your real upstream account revoked.
- A failed decryption tells you nothing about why. The error is uniform on purpose.
Be clear-eyed about one thing: anyone who can read the service's environment can decrypt stored credentials. There is one key for all customers. There is no hardware security module, no per-customer key, and no split-knowledge scheme. That is the honest state of key custody today, and it is also stated publicly on our disclosure page.
Third parties
These are everyone who receives data, and what each actually gets.
The AI providers you linked
They receive your full prompt and produce your full completion. They also receive:
- An OAuth bearer token or key for your own account with them.
- Most of the headers your client sent — plus headers megaloop adds, and minus the ones it removes. Both directions are set out below, because "the headers you sent, minus a few" materially understates what we do.
- A stable pseudonymous identifier per linked account. So that each of your accounts looks like one consistent client installation rather than a stream of strangers, we derive a session id and a device id from an HMAC of your tenant id and account id, keyed with a server-side secret. They are stable for a given account and cannot be reversed to the key.
What megaloop removes. Your megaloop credential (it is replaced by your own provider token), forwarding and caller headers (x-forwarded-*, x-caller-id) so a provider does not learn your network path from us, and the hop-by-hop and encoding headers that would otherwise corrupt a proxied request. On the Codex path we also remove your client's user-agent, originator, version and chatgpt-account-id headers — and put our own in their place, which is the last bullet below.
What megaloop adds — including things you did not send. This is the part a vaguer sentence would hide:
- A session id, always. The account-derived session id is set, not filled in: if your client sent its own session header, ours replaces it. A fresh per-request id is also added every time.
- A pseudonymous user id in the request body, only if you sent none. Unlike the header, this one genuinely defers to your client — if your request body already carries a user identifier, the body is left untouched.
- A synthesised client identity, when your client does not present one. The provider APIs expect the platform block a first-party SDK or CLI sends. When your request carries no such block, megaloop supplies a complete one — platform headers, a direct-access flag, and (only if you sent no
User-Agentof your own) a CLI user-agent string — so the request has the shape the provider's API expects. When your client does present its own block, we touch neither it nor your user-agent — on the Anthropic path. On the Codex path your user-agent is replaced either way; that is the last bullet below. - A fixed first line on your system prompt, on the Anthropic path. Before an Anthropic request is forwarded, megaloop prepends one sentence to the
systemfield of your request body:You are Claude Code, Anthropic's official CLI for Claude.Your own system prompt is kept and follows it, unchanged and in full. Nothing you wrote is deleted, edited, reordered, or moved into the message list, and if yoursystemis a block array your blocks — including their cache settings — are kept as they are, behind one new block holding that sentence. If your client already sends that line first, nothing is added and your bytes are forwarded as they arrived. If you sent nosystemfield at all, megaloop creates one containing only that sentence — the one case where the line is not sitting above a prompt of yours, because there was none. This is not decoration: the upstream refuses premium-model requests (sonnet, opus) on a subscription credential whose system prompt does not begin with that exact line, so without it those models do not serve. The code ispackages/providers/src/anthropic/body-marker.ts, and the line itself is a single constant inpackages/providers/src/anthropic/serving-constants.ts. - A replaced client identity, on the Codex path — unconditionally. Before a Codex request is forwarded, megaloop drops your client's
user-agent,originatorandversionheaders and sets the Codex CLI's own values in their place: originatorcodex_cli_rs, and acodex_cli_rs/<version>user-agent carrying the same version number as theversionheader. This happens on every Codex request, whether or not your client sent any of those headers — unlike the platform block above, it is a replacement and not a fill-in for a bare caller. The backend gates which models an account may reach on that reported client version, so a request wearing your own user-agent can be refused models the same account serves under the CLI's. The code ispackages/providers/src/codex/forward.ts; the values are inpackages/providers/src/codex/constants.ts.
We are telling you this because it is done on your behalf and to your account, and because our Acceptable Use Policy asks you not to misrepresent whose account a request is made on. That rule used to read "who is making a request", which would have forbidden you from doing what the last two bullets show megaloop itself does; it was narrowed for that reason, and the AUP says so under rule
- You should know exactly what megaloop does to your request before you agree to any of it.
Assume your provider treats the last two as outside what it permits. Both exist because the upstream checks for a first-party client and refuses or downgrades a request that does not look like one, and both make your request look like one. megaloop does not represent that either provider allows this, and it cannot indemnify you if they do not — the account and the subscription the request lands on are yours. Our Acceptable Use Policy (/acceptable-use) says the same thing under rule 3.
What a provider retains is their policy, not ours. Read theirs. This is the single largest disclosure in this document and it is inherent to what megaloop is.
Cloudflare — content delivery and tunnel
megaloop.dev is fronted by a Cloudflare Tunnel, which terminates TLS. The inference endpoints route through it. That means Cloudflare sits between you and our gateway for every prompt and every completion, and sees them in plaintext at their edge, for as long as the request is in flight.
We do not have a way to offer the product on this domain without that. We name it here because a "sub-processors" list that mentions email and payments but not the company decrypting the prompt would be misleading. We strip Cloudflare's own headers before forwarding upstream, which protects the provider from learning your network path — it does not remove Cloudflare from the path.
Cloudflare R2 — encrypted backups
Encrypted database backups are stored in Cloudflare R2: hourly incremental, nightly full, with write-ahead log archiving for point-in-time recovery. Backups are encrypted client-side with AES-256 before upload, on top of the AES-256-GCM already applied to every provider credential — so your credentials are double-encrypted in backup.
The bucket is in Cloudflare's Eastern North America region, on the unrestricted jurisdiction rather than an EU-only or government one. Cloudflare publishes the region and not the building, and that region covers eastern Canada as well as the United States, so we state the region and stop there rather than name a country we have not been told.
A backup is kept for up to 14 days. pgBackRest keeps every full backup taken in the last fourteen days, plus the incrementals and write-ahead log segments that depend on them, and expires everything older. Nothing in the storage bucket enforces that: the bucket's only lifecycle rule discards abandoned upload fragments after seven days and deletes no backup at all. The fourteen-day window is kept by pgBackRest's own expiry pass and holds for exactly as long as that keeps running — it is a process we operate, not a guarantee the storage provider makes us. It has also not been exercised yet: the backup repository was created on 2026-08-06 and nothing in it is old enough to have been expired.
Resend — email delivery
Resend delivers two kinds of message and sees the contents of both:
- Your sign-in code. The message body contains the live six-digit code.
- Usage alerts. The message body contains your own account names, utilisation percentages, window labels, and reset times.
Nothing derived from a sent message is logged on our side.
Stripe — payments
Stripe receives our internal account id as metadata and the price id. We do not send your email address to Stripe. Anything else Stripe holds about you, you gave it directly on its own hosted checkout or billing pages, under its policy. Card data never touches megaloop.
Microsoft Azure — hosting
Azure hosts the virtual machines and the managed disk the database sits on. See Where your data lives.
That is the whole list
There is no analytics vendor, no error-reporting service, no APM, no tracing service, no customer data platform, and no advertising network. This was checked against the source, not assumed: the dashboard makes no request to any external host.
Retention
We do not have a retention policy yet. One table is pruned on a schedule; the rest are not. Here is the real state of it, table by table.
| Data | Retention today |
|---|---|
| Email delivery records | 90 days, pruned automatically. This is the only automatic deletion in the system. |
| Request metadata rows | Kept indefinitely. Nothing deletes them on a schedule. They go only if the whole account is deleted. |
| Security audit rows | Kept indefinitely, and they do not go when an account is deleted — see below. |
| Sign-in code rows | Kept indefinitely. A used code is marked used; the row, including the email address on it, is not removed. |
| Sessions | Expire after 30 days and are revoked when you sign out. The row is marked revoked, not deleted. |
| API keys | Revoked, never deleted — deliberately, so an audit can account for every key that ever existed. Only the hash and the prefix were ever stored. |
| In-flight account-link records | Single-use, 10-minute expiry. Expired rows are not swept; they accumulate. |
| Subscription headroom snapshots | Overwritten by each new reading. Deleted when you unlink the account. |
| Linked provider accounts | Hard-deleted the moment you unlink, ciphertext included. |
Two specific admissions, because they are the ones that would make a deletion promise false:
- The security audit table has no foreign key to your account. It was built that way so that events which happen before an account exists — a sign-in code requested by an address that never completes signup — still have somewhere to go. The consequence is that deleting your account does not cascade to it, and those rows contain raw email addresses.
- Sign-in code rows are not scoped to an account at all, for the same reason: they are written before we know who you are. They also hold your email address, and they also do not cascade.
Removing either one is a manual step today. See below.
Your rights, and how to exercise them
Which statutory rights you have depends on where you live and on where we are established. AI PWRD Inc. is established in Delaware, United States, and has no establishment in the EU or the UK. Independent of that, here is what you can actually do right now, and how.
See what we hold
Signed in, you can read back:
- Your identity and settings —
GET /v1/session - Your linked accounts and their health — the dashboard home page, or
GET /v1/accounts - Your subscription headroom readings —
GET /v1/limits - Your usage totals and the individual request metadata rows —
GET /v1/usage,GET /v1/requests - Your API keys, by name and prefix —
GET /v1/keys - Your notification and routing settings —
GET /v1/notifications/preferences,GET /v1/routing
There is no one-click export file today. If you want everything we hold in one bundle, email support@megaloop.dev and we will assemble it.
Correct something
Your email address is your login identifier and cannot be changed in the product today. Email support@megaloop.dev. Account and key names, routing settings, notification settings, and your theme preference are all editable in the dashboard.
Delete specific things yourself
| Action | Where | What actually happens |
|---|---|---|
| Unlink an account | Home → the account's row → Unlink | A real delete. The row and both ciphertext columns are removed. Revoking megaloop's access at the provider is a separate thing you do on their site. |
| Revoke an API key | Home → Keys → Revoke | Stops authenticating on the management API at once. On the inference gateway it can still be accepted for up to 60 seconds — the gateway caches a resolved identity per key rather than reading the database on every request, and it runs as a separate process from the one that handles the revoke. The row is kept on purpose for audit; only a hash and a prefix were ever in it. |
| End a session | Sign out | Revoked server-side. |
| Stop the emails | Notification settings | The global switch silences every alert. It is checked before anything is even recorded. |
Unlinking an account does not erase the request-metadata rows for requests it already served. Those deliberately outlive the account so the history of what ran stays readable to you.
Deleting your account
Be told this plainly: there is no self-serve account deletion today. No button, no endpoint, no script. Email support@megaloop.dev and a human will do it.
When they do:
- What goes: your account record, your user record, your sessions, your API keys, your linked provider accounts and their credentials, your headroom snapshots, your routing settings, your request metadata, your notification preferences, and your email delivery records. All of these cascade from the account row.
- What does not go, unless separately removed by hand: the security audit rows and the sign-in code rows, both of which contain your email address, for the structural reasons in Retention.
We will do that second removal on request too — it is a manual database step, not an automatic one, and we would rather tell you it is manual than imply a button exists. We complete a deletion request within 30 days of confirming it is really you.
A deletion does not reach the backups, and it cannot. Your rows stay in the encrypted backup set until that copy ages out on its own, which takes up to 14 days — the window is described under Third parties. We do not restore a backup in order to delete from it, because restoring one to edit it is a larger risk to every other customer's data than waiting out fourteen days.
Object to email
Sign-in codes are not optional: they are how you log in. Everything else — every usage alert — is controlled by the global switch in your notification settings, and by the five per-event toggles beside it. We send no marketing email.
Complain
If you think we have handled your data badly, email support@megaloop.dev first — it is one person reading it and it will be faster. You are not obliged to come to us first, and nothing here asks you to waive that.
We are a Delaware company with no establishment in the EU or the UK, so there is no lead supervisory authority for us to be regulated by. That does not close the route to your own regulator:
- In the EU, you may complain to the data protection authority of the country you live in, work in, or where you think the problem happened.
- In the UK, that is the Information Commissioner's Office.
- In the United States, your state attorney general takes consumer privacy complaints, and several states also give you a direct route under their own privacy statute.
Where your data lives
- Region: Microsoft Azure,
eastus2(United States). - Application: one virtual machine, running the services as a single unprivileged service user. Secrets, including the encryption key, live in a file with
0600permissions — readable by its owner and nobody else. - Database: PostgreSQL 16 on a separate virtual machine, on a persistent managed disk. It accepts connections on one port, from the application machine's private address only — one firewall rule, one database authentication rule, no public route.
- Ingress: no port on either machine is reachable from the public internet. Your traffic arrives through an outbound-only Cloudflare Tunnel rather than an open listener, and the inference gateway binds to loopback, so it cannot be reached over the network at all — only by the tunnel process on the same machine.
- Encryption at rest: every disk under the service — both machines' system disks and the database's data disk — is encrypted by Azure with a platform-managed key. There is no customer-managed key, no second encryption layer beneath the platform's, and neither machine is a confidential-computing VM. Be clear about what that is worth: platform encryption protects a disk that leaves the datacentre, not a process that can read the running machine. What protects your provider credentials from anyone who reaches the database is the AES-256-GCM applied above it.
- Backups: encrypted, in Cloudflare R2, kept for up to 14 days. Restore has been tested end to end — the most recent drill restored the database and successfully decrypted every stored credential.
If you are in the EU or UK, read this part rather than skim it. Your data is stored and processed in the United States — the one qualification being the backup copy, which sits in a Cloudflare region that spans the eastern United States and eastern Canada, and which we describe as the region because that is all Cloudflare tells us. The AI provider you linked may then process your prompts somewhere else again.
We are a US company with no EU or UK establishment. We operate no standard contractual clauses, we have not certified to any transfer framework, and we have no binding corporate rules — so if you are looking here for the name of a safeguard, there is not one to name. What there is instead: a US service, running in a US region, that you send data to directly. Nothing crosses a border because we moved it; it is where it is because you chose to send it there, and you should make that choice knowing exactly that.
Security
What is actually in place, as opposed to what sounds good:
- Credentials encrypted at rest with AES-256-GCM, bound to your account and row, with a key that is never stored in the database. Detailed above.
- Nothing that authenticates a caller is stored in the clear. Sign-in codes, session tokens, and API keys are all held as hashes. Sign-in code hashes are keyed with a server-side secret, so they are not brute-forceable from the database alone.
- Customer isolation is structural, not a coding convention. It rests on five independent layers: every database method requires an account identifier and will not compile without one; that identifier is always read from the stored key or session row and never from a header, query parameter or body field the client controls; the database schema itself has composite foreign keys that reject a row claiming one account while its parent belongs to another; the encryption binds every ciphertext to its account so a mis-scoped read fails rather than succeeds; and the request router refuses to serve at all if a candidate account does not belong to the caller.
- Nothing on either machine is reachable from the public internet. Ingress is an outbound-only tunnel rather than an open listener; the gateway binds to loopback. The database listens on its own private network only, restricted to the application machine's address by both a firewall rule and a database authentication rule.
- Encrypted off-site backups with a tested restore path.
- The payment webhook verifies its signature before reading any field, and resolves the account from our own stored mapping rather than from the request.
- Sign-in is rate-limited on both axes: codes issued per address, and guesses per code and per address.
What our own staff can see
There is one internal console that reads across all customers, and it is the only thing in the product that does. Constraints on it, enforced in the code rather than by policy:
- It returns aggregates only — counts, timestamps and percentiles. There is no function in it that returns a row, an id, an email address, or a name.
- It reads three tables and names no ciphertext column. It cannot reach a prompt, a completion, a credential, a key, an email address, or any individual account's figures.
- Access is a list of addresses in the server's configuration — not a permission an account could ever be granted.
- Every view is written to the audit log before the read happens.
Separately, and stated plainly: an administrator with shell access to the application machine can read the encryption key and therefore decrypt stored credentials. That is true of most systems of this shape, and we would rather write it down than let the encryption section imply otherwise.
No third-party audit
None of this has been reviewed by anyone outside the project. It is a description of the code, offered so you can check it rather than trust it. Our disclosure page names the file behind each claim for exactly that reason.
Cookies and browser storage
megaloop sets no advertising, analytics, or tracking cookies of any kind. There are two cookies and one local-storage key, and this is all of them.
| Name | Type | What it is for | Lifetime |
|---|---|---|---|
gw_session | Cookie — HttpOnly, Secure, SameSite=Lax | Your signed-in session. Holds the session token. HttpOnly means no script on the page can read it. | 30 days, or until you sign out |
ml_gate | Cookie — HttpOnly, Secure, SameSite=Lax | Pre-launch only. The site is behind a password gate while it is closed; this cookie stops your browser re-prompting on every new tab. It holds an expiry timestamp and a signature over it — no personal data. It disappears with the gate at launch. | 30 days |
ml-theme | Browser local storage | Your light/dark choice, so the page does not flash the wrong colours before it loads. Signed in, the same choice is also stored on your account so it follows you across devices. | Until you clear it |
The session token is never placed in local storage. It is held server-side and handed to the browser only as the HttpOnly cookie above.
There is no cookie banner, and that is a conclusion rather than an omission. Consent is what a banner collects, and none of the three needs any. gw_session is strictly necessary: it is the signed-in session, and without it there is no signed-in state to have. ml_gate is strictly necessary too, for as long as the site is closed, and holds a timestamp and a signature rather than anything about you. ml-theme is browser storage rather than a cookie, and holds one word — light or dark. None of them tracks you across sites, profiles you, feeds an advertiser, or is readable by anyone but us. There is no tracking or advertising cookie to disclose, so a banner would have nothing to ask you about that this table has not already told you.
Children
megaloop is a developer tool, sold on a paid subscription, and is not directed at children. It is not intended for anyone under 18, and we do not knowingly collect personal data from anyone under that age. Eighteen is not a separate rule invented here: section 4 of the Terms (/terms) requires you to be at least 18 to hold an account at all, and this is the same floor stated from the data side. If you believe a child has given us personal data, email support@megaloop.dev and we will delete the account.
Changes to this policy
This policy describes the behaviour of a specific version of the software. When the code changes, the policy changes with it — that is a project rule, not an aspiration: the disclosure page carries an instruction in its own source that says if you change what the code does, you change the page in the same commit or it becomes a lie.
- For a material change — a new category of data, a new third party receiving your data, a change to the prompt-and-completion answer above — we will email you at your account address before it takes effect, and update the date at the top.
- For a correction or clarification, we will update the date at the top.
Old versions are recoverable from the repository history.
Contact
support@megaloop.dev
AI PWRD Inc., a Delaware corporation. Everything in this policy goes to that address — an access request, a deletion request, a correction, or a complaint.
For the technical, file-by-file version of the disclosures in this policy, see the "What we store" page at /trust. It names the source file behind each claim so you can check it.