---
title: "User Delete"
method: POST
path: "/api/user/delete"
tags: ["settings", "sync", "ai"]
---

# User Delete

`POST /api/user/delete`

## Request

- Base URL: `https://www.oxfordledge.com` (the document declares no server; this is the origin it was published from)
- URL: `https://www.oxfordledge.com/api/user/delete`
- Auth: none declared

GDPR account deletion request (COUNSEL-09 + F11 2026-04-30 PM;
R-RESCIND OWNER 2026-09-25).

Two-phase deletion:
  1. NOW (this handler): mark `deletion_requested_at` THEN set every
     Stripe subscription to cancel AT PERIOD END (mark first since
     2026-09-24: the eventual `.deleted` webhook reads the mark to
     suppress the Developer-plan final overage invoice). A trial holds
     no subscription and runs out on its own.
  2. +30 days (services/automation.py daily cron): HARD-cancel every
     subscription (nothing renews past the window), then cascade-delete
     user_billing / credit_purchases / portfolios / pg_watchlist /
     api_keys and DELETE FROM users.

The 30-day window is GDPR Article 17-aligned and is a real grace
window: `POST /api/settings/cancel-deletion`
(routes_settings_erasure_fastapi.py, which records why it did not exist
before R-RESCIND) rescinds inside it and lifts the period-end cancel.

Failure modes (defense-in-depth)
- Stripe API down: deletion_requested_at is still set so the cron
  eventually processes the user. Stripe failure is logged + Sentry-
  breadcrumbed but does NOT block the request; the purge's hard cancel
  is the backstop.
- No Stripe subscription (free tier / trial): nothing to schedule.

## Response `200`

Successful Response

- UserDataResponse: Loose response_model for user-data routes (export, delete, consent, preferences). Multiple shape variants per action.

---

[API](https://skmtc.dev/oxfordledge/apis/oxford-ledge-api.md) · [All operations](https://skmtc.dev/oxfordledge/apis/oxford-ledge-api/llms.txt) · [OpenAPI document](https://skmtc.dev/oxfordledge/apis/oxford-ledge-api/revisions/e832a1260f0b?raw)
