Data Handling

Last reviewed: August 29, 2026

Our Privacy Policy describes what we are permitted to do with your data. This page describes what the software actually does, which is narrower and easier to check.

Every factual statement below is tied by a test to the file that makes it true. When one of those files changes so that a sentence here stops being accurate, the build fails and someone has to rewrite the sentence. Where we have not built something, this page says so rather than describing the intention as though it were shipped.

Numbers, not names

When you connect a system of your own — payments, product analytics, a support inbox — the rule is that we keep the figure and not the record.

An integration may persist a figure it computed. It may never persist a record belonging to your customers. MRR for August, sessions in the last seven days, tickets open by category: those are figures. Your customer's email address, their name, a charge, the text of their ticket: those are records. Records are read when an agent needs them, held for the length of one turn, and dropped.

recordMetrics is the only write path from an integration into our database, and it calls assertMetricSafe on every metric before the insert. That check rejects a metric whose name contains an email address, or the words email, name, phone, address, ssn, card number or customer id — matched against the name with separators stripped, so that customer_name and customerName are both caught.

A build in which a connector writes to leads, support_tickets or expenses does not ship. A test reads the source of every module that touches a stored credential and fails if it writes to a table holding a third party's records, or if it writes to the database by any route other than the metric one. It finds connectors by behaviour rather than by filename, because an earlier version globbed for *-connector.ts, matched nothing after a file was deleted, and reported green while checking an empty list.

No integration connector is live today: the count the guard asserts is zero. The Stripe one was removed and the Google one is not built. We are publishing the rule before anything is running under it, so that you can hold us to it when something is — and so that the day a connector lands, that count changes and this paragraph fails its test.

Credentials for a connected account are encrypted with a key bound to your organization and that provider, so one organization's stored token cannot be replayed under another. Disconnecting soft-deletes the row so the record of who connected what survives; a ciphertext that will not open is treated as a broken connection and you are asked to reconnect.

Checked inlib/integrations/metric-policy.ts·lib/integrations/metrics.ts·lib/integrations/__tests__/integration-guard.test.ts·lib/token-encryption.ts

What your agents can and cannot see

Every agent's tools are built by a factory that takes your organization id, and every query those tools make is filtered by it. There is no global tool that picks up the tenant from ambient state, which is the usual way this goes wrong.

On every route that returns your data, the organization is resolved from your verified session or from a signed service token — not from whatever the request asked for. A few internal routes accept an organization id as a parameter, and those check it against the caller's own membership before answering. In neither case does naming an organization grant access to it.

CrewAI's own agent memory is switched off on all eight agents, because we could not demonstrate from this repository that it is scoped per organization. Nothing in our code configures that framework's storage, so it falls back to a process-wide default — and scheduled work for many customers shares one container. We have not observed a leak. That is not the bar; the bar is being able to show there cannot be one. So it is off, and turning it back on requires per-organization storage plus a test that fails when two organizations share a process.

What an agent does remember is our own agent_memory table, where every row is keyed by organization and every read and write filters on it. Conversation history is injected the same way, from rows bound to your organization.

Checked inservices/crewai-agents/agents/*/agent.py·services/crewai-agents/agents/*/tools.py·services/crewai-agents/db/queries.py·packages/db/schema/agent-memory.ts

Security scans: what the evidence holds

When the scanner reports a finding on a site you own, it keeps the proof. For a security finding we store the request and response that produced it: method, URL, headers, bodies, status and timing. Without that, a finding is an assertion you have no way to check.

Credential headers are redacted by name, not by pattern, because an opaque session cookie looks like any other identifier and no pattern can recognise it. Authorization, Proxy-Authorization, Cookie, Set-Cookie and the x-api-key family are replaced with [redacted] wherever they appear, on the request side and the response side, without looking at the value. The header name is kept, because the fact that a request was authenticated is what makes a finding reproducible. Values in other headers still go through pattern masking, which catches the things that do have a recognisable shape.

Reading your GitHub repository, we never read an issue body out of the response. Titles, labels, comment and reaction counts are what we keep, and none of it is stored. GitHub returns bodies in the payload; we map the fields we want and the rest is discarded with the response. Bodies are where customer emails, stack traces and pasted credentials end up, and demand can be read off titles and labels without them.

Checked inlib/scanners/mask-secret.ts·packages/db/schema/security-evidence.ts·lib/github-app.ts

Retention, export and deletion

The retention window for agent conversations is 365 days, and the job that enforces it is written and tested. Twelve months is derived rather than picked: nothing in the product reads a transcript by age — every read path takes the newest few hundred rows — and the longest cycle the product models is the quarter, so a year covers four of them and nothing needs more.

That job runs nightly at 02:00 UTC. It is registered in production as cron-agent-message-retention. Saying so is new: for a while this paragraph read that the job existed but was not scheduled, because the script that registers our cron jobs was broken in two ways and silently skipped it. Both are fixed and the script has now been run. A dated promise no job keeps is worse than an awkward paragraph, so the awkward paragraph stood until the job exists.

You can take a copy of your data at any time from /api/account/export while signed in as the owner of your organization. It streams structured JSON: your apps, your agent conversations, your company wiki, your KPI and expense entries, your OKRs, your briefs and your scan findings. The export leaves out the credentials for your connected accounts, and says so in the file. Handing those back in a plaintext download would turn a privacy feature into a way to drain your connected accounts. If the file had to be shortened for size, its last key names what was cut and at how many rows.

Closing an account marks it closed immediately, detaches its members and takes its app listings down, and schedules the permanent delete for 30 days later. The grace period is there so that a decision made in anger can be reversed, and so the export can still be used. Closure is requested by email today, because there is no button for it in the product yet.

The permanent delete removes the organization row and lets the foreign-key graph cascade, rather than working through a hand-written list of tables that would go stale. A list in code is wrong the first time someone adds a table and nobody notices, and it fails silently: you would be told you were erased while your rows remained. Two records survive it, unlinked from the organization: billing events, for the seven-year tax retention, and audit logs, which the schema declares immutable. The purge is driven by the same scheduler as the retention sweep and runs nightly at 03:00 UTC, registered as cron-account-purge.

Checked inapp/api/cron/agent-message-retention/route.ts·app/api/account/export/route.ts·app/api/account/route.ts·app/api/cron/account-purge/route.ts

Where your data lives

Everything runs in one Google Cloud project, founderr-io-prod, in us-central1. Application containers, the Postgres database, the cache, object storage, the task queues and the secret store are all in that project and that region.

Model inference is the exception: it goes to Vertex AI at the global endpoint, because the Gemini 3.x models we use are served nowhere else. If regional pinning of inference matters to you, say so before you buy, because today we cannot offer it.

Every model call goes to Google. No code path in this product calls OpenAI or Anthropic for inference. Our privacy policy names all three, because our terms reserve the right to route to them. Only one of them is wired.

Checked incloudbuild.yaml·services/crewai-agents/config/llm.py

Sub-processors

These are the third parties that data reaches today. Vendors we have configured but not wired into any customer-facing path are left off deliberately.

  • Google Cloud — hosting, database, cache, object storage, task queues, secrets. Everything the product stores.
  • Google Vertex AI — prompts, the content you give agents, and the context they assemble to answer.
  • Microsoft Clarity — product analytics. Every page of this product, signed out and signed in alike, loads Microsoft Clarity from the root layout, so interactions inside the dashboard reach Microsoft too. Our cookie policy has not caught up with this: it lists Google Analytics and Vercel Analytics, neither of which is wired in the code today. This list is the accurate one.
  • Resend Transactional and agent email is sent through Resend. Recipient addresses and message bodies.
  • Stripe — billing. Card details are entered on Stripe's own hosted checkout and never reach our servers. We hold the subscription state and the customer id.
  • GitHub — only if you install our App. It reads commit history, pull requests, and issue titles and labels (never issue bodies). It also writes, but only one way: approving a code fix pushes a branch and opens a pull request for you to review. It never commits to your default branch, so the merge is still yours.
  • Cloudflare — sits in front of the application, so it sees request metadata for every page you load.
  • CapSolver — used only in the directory-submission flow, to clear a CAPTCHA on the directory's own signup form. CapSolver receives the URL and the site key of the directory page being submitted to, and nothing else. With no key configured it does nothing at all.

Checked inapp/layout.tsx·lib/email.ts·app/api/billing/checkout/route.ts·lib/github-app.ts·services/crewai-agents/agents/casey/captcha_solver.py

What is not done yet

Collected in one place, because a page like this is only worth reading if the gaps are in it.

  • The database migrations behind account deletion are written and reviewed but not yet applied to production. Until they are, a closure request is carried out by hand rather than by the job — the job is scheduled and will run, but the schema it depends on has to land first.
  • There is no screen for closing an account. The endpoint exists; the button does not. Email us and we will do it.
  • No integration connector is running, so the "numbers, not names" rule currently governs nothing. It is enforced by a test that is watching for the first one.
  • The cookie policy is out of date, as described above.
  • We have not been audited by a third party and we hold no SOC 2 or ISO 27001 certification. Everything on this page is our own account of our own code.

Ask us

If something here is not specific enough to answer your question, write to [email protected] and name the file you want us to walk you through. For deletion requests specifically, see Data Deletion. For the legal terms that sit over all of this, see the Privacy Policy.