Causeloop provides a data governance layer for operators that need to comply with GDPR and similar privacy regulations. The API exposes endpoints for retention management, GDPR Article 17 erasure requests (RTBF), and tenant data export. Services for executing these operations live in app/services/.
Data governance API
All governance endpoints require the governance:write or governance:read scope. Only users with the admin role have these scopes by default.
Base path: /v1/governance/
| Endpoint | Method | Scope | Description |
|---|
/governance/exports | POST | governance:write | Request a full export of workspace data |
/governance/exports | GET | governance:read | List export requests |
/governance/exports/{id} | GET | governance:read | Get export status and download link |
/governance/rtbf/requests | POST | governance:write | Submit an Article 17 RTBF request |
/governance/rtbf/requests | GET | governance:read | List RTBF requests |
/governance/rtbf/requests/{id} | GET | governance:read | Get RTBF request status |
/governance/rtbf/requests/{id}/approve | POST | governance:write | Approve and execute erasure |
/governance/rtbf/requests/{id}/reject | POST | governance:write | Reject the request with a reason |
/governance/rtbf/requests/{id}/certificate | GET | governance:read | Download the erasure certificate (completed requests only) |
Retention policies
Retention is configured per-workspace in workspace_settings. Each class of data has its own retention window:
| Setting key | Applies to | Default |
|---|
issue_retention_days | issues table | Per workspace config |
activity_retention_days | activity_events table | Per workspace config |
prediction_retention_days | predictions table | Per workspace config |
audit_log_retention_days | audit_log table | Compliance floor: ≥ 365 days |
How retention enforcement works
app/services/retention.py provides purge_expired(database_url), which:
- Fetches all workspace IDs
- For each workspace, reads the retention windows from
workspace_settings
- Sets
app.current_workspace GUC (so RLS applies)
- Deletes rows older than the configured window from each table
# Tables and their retention setting keys
RETENTION_MAP = {
"issues": "issue_retention_days",
"activity_events": "activity_retention_days",
"predictions": "prediction_retention_days",
"audit_log": "audit_log_retention_days",
}
The purge_expired() function is implemented but not yet scheduled. It must be called by an external cron job or scheduled worker. A periodic task that calls purge_expired(DATABASE_URL) on a daily schedule is the recommended integration. Automated scheduling is on the SOC 2 roadmap — see SOC 2 readiness.
Running retention manually:
# From the application environment with DATABASE_URL set
python3 -c "
from app.services.retention import purge_expired
import os
result = purge_expired(os.environ['DATABASE_URL'])
print(result) # {'issues': N, 'activity_events': N, ...}
"
Audit floor: the audit log has a compliance minimum of 365 days. If audit_log_retention_days is set below this, the purge skips audit log rows to prevent premature deletion of evidence.
Right-to-be-forgotten (RTBF — GDPR Article 17)
Causeloop implements a multi-step RTBF workflow with state tracking and an erasure certificate.
RTBF states
received → verifying → approved → processing → completed
↘ rejected
↘ on_hold
Submitting a request
curl -X POST https://api.causeloop.ai/v1/governance/rtbf/requests \
-H "Authorization: Bearer <admin-token>" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"mode": "erasure",
"reason": "GDPR Article 17 request received via email"
}'
The request subject’s email is immediately HMAC-SHA256-hashed into a privacy-preserved subject_ref. The plaintext email is only used to identify the subject during processing and is not retained beyond the request record.
Duplicate guard: if an active RTBF request already exists for the same subject in the same workspace, the API returns 409 Conflict.
SLA: the due_at field is set to 30 days from received_at, corresponding to GDPR’s response deadline.
Approving and executing erasure
curl -X POST https://api.causeloop.ai/v1/governance/rtbf/requests/{id}/approve \
-H "Authorization: Bearer <admin-token>"
On approval, the application calls erase_workspace() from app/services/rtbf.py, which:
- Resolves the workspace’s organization slug
- Executes hard deletes of the subject’s data across tenant tables
- Records a persistent erasure record
erase_workspace() requires DATABASE_URL to be available at runtime. When the application is running against the in-memory store (no DATABASE_URL), the approve endpoint records the approval transition but cannot execute the physical deletion. In production with Postgres, the deletion executes synchronously on approval.
Erasure certificate
Once a request reaches the completed state, the erasure certificate is available:
curl https://api.causeloop.ai/v1/governance/rtbf/requests/{id}/certificate \
-H "Authorization: Bearer <admin-token>"
# → {"url": "...", "issued_at": "2026-06-14T...", "request_id": "..."}
Tenant data export
The export service (app/services/tenant_export.py) assembles a complete snapshot of a workspace’s business data. It runs RLS-scoped so it can only access the requesting workspace’s rows.
What is exported
| Table | Columns exported |
|---|
issues | id, title, body, status, severity, created_at |
patterns | id, name, status, risk_score, created_at |
predictions | id, pattern_id, probability, created_at |
recommendations | id, title, status, created_at |
connectors | id, type, display_name, status, created_at |
audit_log | id, action, actor_id, target_type, target_id, created_at, trace_id |
Intentionally excluded: config_encrypted (connector credentials), secret_encrypted (webhook secrets), and all other secret columns. These are never included in exports.
Requesting an export
curl -X POST https://api.causeloop.ai/v1/gdpr/export-requests \
-H "Authorization: Bearer <admin-token>" \
-H "Content-Type: application/json" \
-d '{"format": "json"}'
The current export implementation returns data inline in the response body. Export delivery to object storage (S3/GCS presigned URLs) with checksums is planned. See SOC 2 readiness for the current status.
Data residency
Causeloop does not enforce data residency at the application layer — residency is determined by where you run the database. For EU data residency:
- Use Neon’s EU region (Frankfurt) when creating your Neon project
- Deploy the API to an EU region (Hetzner Falkenstein/Helsinki, Railway EU, Render Frankfurt)
- Configure your LLM provider’s EU endpoint if available, or use the mock provider
If using Anthropic or OpenAI for AI features, data sent to these providers is subject to their privacy policies and data processing agreements. You are responsible for executing DPAs with sub-processors before processing personal data through them.
Offboarding and full tenant deletion
To permanently delete all data for a tenant (not just an individual), use offboard_client():
SELECT offboard_client('acme', 'purge');
This cascades through the entire tenant data tree. See Client provisioning — offboarding for details.
GDPR compliance summary
| Article | Coverage | Status |
|---|
| Art. 5 — Purpose limitation & minimisation | Defined retention windows; secrets excluded from exports | Partial — purge job not yet scheduled |
| Art. 17 — Right to erasure | RTBF API with state tracking, duplicate guard, erasure certificate | Partial — deletion pipeline requires Postgres at runtime |
| Art. 20 — Data portability | Tenant export API | Partial — inline response; object storage delivery planned |
| Art. 30 — Records of processing | Audit log, governance endpoints | In place |
| Art. 32 — Technical measures | Envelope encryption, RLS, TLS, RBAC | Partial — see SOC 2 gaps |