Docs menuCleanup

Cleanup

Every test run creates real accounts in your app. Cleanup (we also call it teardown) deletes them when the run ends, from every system you connect, and gives you a receipt for each.

Guide

Cleanup is included on every plan, including Free, and no plan limit ever stops it. You connect each system once per project; after that, every run in that project cleans up after itself.

Why it’s safe to point at production

Deleting from a live database is only reasonable if it can’t hit the wrong record. Here is what stops it, layer by layer, each one assuming the others might fail:

  1. An exact list, never a pattern. Each run keeps the list of addresses it created. Cleanup sends that list, and a record is deleted only if its email is exactly one of them. No LIKE, no “all of @flowproof.dev”, no wildcard. A real customer’s email is never on the list.
  2. Nobody else can add to the list. Addresses are created only by a session. An email arriving from outside never adds one, the dashboard can’t write one, and the database re-checks that every address belongs to its run.
  3. “Delete” must be said out loud. Every request says "mode": "preview" or "mode": "delete". A request missing the word can’t delete anything, so a dropped field fails safe.
  4. Receipts are checked. If a system reports touching a record outside the list, or changing anything during a preview, Flowproof treats its receipt as untrusted and switches that connection off.
  5. Off until you arm it. A new connection can’t delete anything until it passes a connection test and a dry run, and you arm it (below).

More in Security.

Systems you can connect

SystemHow it worksRemoves
WordPress / WooCommerceFlowproof sends your site a signed request; your site deletes and replies. Flowproof never holds your credentials.Users, their WooCommerce orders and comments
Supabase / PostgresRecommended: an Edge Function in your own project receives a signed request and deletes; Flowproof never holds your database password. Or give Flowproof a connection string.Rows in the tables you name, and the login in auth.users
StripeFlowproof holds a limited credential, encrypted, and does the deleting itself.Customers (which ends their subscriptions)
Webhook (any stack)Flowproof sends your site a signed request; your site deletes and replies. Flowproof never holds your credentials.Whatever your endpoint deletes

You can connect several systems to one project (a WordPress site and its Stripe account, say). Each gets its own receipt.

Arming: create, test, preview, arm

A connection goes through four steps before it may delete. The point is to find a wrong password, a missing permission or a misbehaving endpoint while nothing is at stake, not in the middle of a real cleanup.

  1. Create

    You add the connection. It starts switched off.

  2. Test

    Flowproof connects and checks it has the permissions it needs (for Supabase, that it can see every row; for Stripe, the key’s scopes). Nothing is changed.

  3. Preview

    A dry run of a cleanup for a made-up test run. The system answers with what it would delete, and Flowproof checks that answer: a system that changes anything during a preview is switched off.

  4. Arm

    You allow it to delete. Arming is refused unless the test and the preview both passed in the last hour. From then on, every run in the project deletes its test users there when it ends.

You can disable a connection at any time (it keeps its settings and stops deleting until armed again) or remove it (its stored credentials are deleted). The Projects page of your dashboard shows each connection’s state.

How to connect a system today

The project has to exist first: it’s created the first time your agent runs a test under that name, so run one test before you connect anything.

Each step is one HTTPS request to mcp.flowproof.dev, authorised with your API key. You can run them in a terminal, or hand this page to your coding agent and ask it to do it with you. First, load your key without it landing in your shell history:

Terminal
read -rs FLOWPROOF_API_KEY   # paste your fp_ key, press Enter (nothing is shown)
export FLOWPROOF_API_KEY

Then create the connection with the command on its page (WordPress / WooCommerce, Supabase / Postgres, Stripe or Webhook (any stack)). The reply includes the connection’s id. Use it in place of ADAPTER_ID and replace acme with your project name:

Test the connection
curl -s -X POST https://mcp.flowproof.dev/api/projects/acme/adapters/ADAPTER_ID/test \
  -H "Authorization: Bearer $FLOWPROOF_API_KEY"
Preview (changes nothing)
curl -s -X POST https://mcp.flowproof.dev/api/projects/acme/adapters/ADAPTER_ID/preview \
  -H "Authorization: Bearer $FLOWPROOF_API_KEY"
Arm (allow deletes)
curl -s -X POST https://mcp.flowproof.dev/api/projects/acme/adapters/ADAPTER_ID/arm \
  -H "Authorization: Bearer $FLOWPROOF_API_KEY"

A test answers {"ok": true, ...} or says what’s wrong; a preview answers {"ok": true, "receipt": {...}}, and for a new connection its receipt usually lists addresses as not_found, which is expected.

List your connections, or switch one off or remove it:

List
curl -s https://mcp.flowproof.dev/api/projects/acme/adapters \
  -H "Authorization: Bearer $FLOWPROOF_API_KEY"
Disable
curl -s -X POST https://mcp.flowproof.dev/api/projects/acme/adapters/ADAPTER_ID/disable \
  -H "Authorization: Bearer $FLOWPROOF_API_KEY"
Remove (deletes its stored credentials)
curl -s -X DELETE https://mcp.flowproof.dev/api/projects/acme/adapters/ADAPTER_ID \
  -H "Authorization: Bearer $FLOWPROOF_API_KEY"

Every endpoint is listed in the API reference.

Receipts

Each system returns an itemised receipt: one line per record, with what happened to it and which address it matched.

ActionMeaning
deletedRemoved.
would_deleteA preview or dry run: this would be removed. Nothing was changed.
anonymizedKept, but stripped of the test address (a webhook may do this for records it must keep). Counts as removed.
not_foundNo record uses this address here. Normal when a flow never reached this system.
skippedDeliberately left alone, with a reason (for example, a WordPress administrator account, or Stripe payments to refund by hand).

Receipts appear under Cleanup receipts on each run’s page. They are kept permanently and can’t be edited or deleted, by anyone, including us: if you ever need to show what Flowproof deleted, the record is there.

Apps with audit or consent logs

Some tables are meant to be permanent: an audit trail, a record of who agreed to which terms, a legal or consent log, often protected so rows can only be added, never changed or removed. A test account leaves rows there too. Don’t delete them, and never switch off the protection to make cleanup work.

  • Exempt or tag Flowproof addresses instead. Every test address ends in flowproof.dev, so those rows are easy to tell apart and filter out of reports. Leave them where they are.
  • Let your app decide. With your own endpoint, your app deletes its own test users by its own rules: the account, its data, and nothing your records must keep. Reply anonymized for a record you keep but strip of the test address, or skipped with a reason for one you leave alone.
  • Never weaken an append-only table, trigger or policy to delete test rows, even briefly. A leftover test row costs nothing; a gap in an audit log can cost a great deal.

When cleanup runs

  • When your agent ends the run.
  • When a run is abandoned: once its session runs out (24 hours by default), Flowproof closes it and cleans up anyway.
  • Never for a system that isn’t armed. Test users in a system you haven’t connected stay there, and the run’s page shows that no system was asked to delete anything.