Skip to content

Account Management API

Status: Implemented Last Updated: January 10, 2026 Location: app/api/account_routes.py

Overview

The Account Management API provides endpoints for users to manage their account lifecycle, including account deletion with a 30-day grace period and GDPR-compliant data export functionality.

Endpoints

Account Status

GET /account/status
Authorization: Bearer <token>

Returns the current account status including deletion state.

Response:

{
  "user_id": "uuid",
  "phone": "+15551234567",
  "name": "John Doe",
  "email": "john@example.com",
  "created_at": "2026-01-01T00:00:00Z",
  "subscription_status": "active",
  "deletion_requested": false,
  "deletion_scheduled_for": null
}


Request Account Deletion

DELETE /account
Authorization: Bearer <token>
Content-Type: application/json

Requests account deletion with a 30-day grace period. The user can cancel during this period.

Request Body:

{
  "confirmation": "DELETE MY ACCOUNT"
}

Response (200 OK):

{
  "message": "Account scheduled for deletion",
  "deletion_date": "2026-02-10",
  "can_cancel_until": "2026-02-10"
}

Error Responses: - 400 INVALID_CONFIRMATION - Confirmation phrase doesn't match - 400 ALREADY_SCHEDULED - Account already scheduled for deletion

Side Effects: - Sets deletion_requested_at and deletion_scheduled_for on User - Cancels active Stripe subscription (cancel at period end) - Background job will permanently delete after 30 days

Rate Limit: 3 requests per hour


Cancel Account Deletion

POST /account/cancel-deletion
Authorization: Bearer <token>

Cancels a pending account deletion within the 30-day grace period.

Response (200 OK):

{
  "message": "Deletion cancelled",
  "status": "active"
}

Error Responses: - 400 NO_DELETION_PENDING - No deletion request exists - 400 GRACE_PERIOD_EXPIRED - Cannot cancel after 30 days

Side Effects: - Clears deletion_requested_at and deletion_scheduled_for - Reactivates subscription if it was set to cancel


Request Data Export

POST /account/export
Authorization: Bearer <token>

Requests a full data export for GDPR compliance. Export is processed in the background.

Response (200 OK):

{
  "job_id": "uuid",
  "status": "queued",
  "message": "Export queued. Check status with GET /account/export/{job_id}"
}

Rate Limit: 2 requests per day


Get Export Status

GET /account/export/{job_id}
Authorization: Bearer <token>

Checks the status of a data export job.

Response (Processing):

{
  "job_id": "uuid",
  "status": "processing",
  "download_url": null,
  "expires_at": null,
  "error": null
}

Response (Ready):

{
  "job_id": "uuid",
  "status": "ready",
  "download_url": "/account/export/{job_id}/download",
  "expires_at": "2026-01-17T00:00:00Z",
  "error": null
}

Status Values: - queued - Job is waiting to be processed - processing - Job is currently running - ready - Export is complete and available for download - failed - Export failed (see error field) - expired - Download link has expired


Download Export

GET /account/export/{job_id}/download
Authorization: Bearer <token>

Downloads the exported data as a JSON file.

Response: - Content-Type: application/json - Content-Disposition: attachment; filename="archety_export_{job_id}.json"

Export Contents:

{
  "export_version": "1.0",
  "exported_at": "2026-01-10T00:00:00Z",
  "user_id": "uuid",
  "profile": {
    "phone": "+15551234567",
    "name": "John Doe",
    "pronouns": "he/him",
    "email": "john@example.com",
    "display_name": "Johnny",
    "timezone": "America/Los_Angeles",
    "created_at": "2026-01-01T00:00:00Z",
    "last_active_at": "2026-01-10T00:00:00Z"
  },
  "subscription": {
    "status": "active",
    "plan": "monthly",
    "credit_balance": 500,
    "trial_started_at": null,
    "trial_expires_at": null
  },
  "personality_profile": {
    "openness": 75,
    "conscientiousness": 60,
    "extraversion": 45,
    "agreeableness": 80,
    "neuroticism": 35,
    "personality_summary": {...},
    "top_personality_traits": ["empathetic", "creative"],
    "top_interests": ["music", "travel"],
    "core_values": ["creativity", "connection"],
    "lifestyle_traits": ["outdoorsy"],
    "selected_persona_id": "sage",
    "total_photos_analyzed": 10,
    "last_analysis_at": "2026-01-01T00:00:00Z"
  },
  "traits": [
    {
      "name": "adventurous",
      "category": "personality",
      "confidence": 85,
      "strength": 70,
      "created_at": "2026-01-01T00:00:00Z"
    }
  ],
  "relationships": [
    {
      "persona_id": "sage",
      "trust_score": 75,
      "rapport_score": 80,
      "stage": "friend",
      "updated_at": "2026-01-10T00:00:00Z"
    }
  ],
  "connected_services": [
    {
      "scope": "calendar",
      "connected_at": "2026-01-05T00:00:00Z",
      "expires_at": "2026-01-12T00:00:00Z"
    }
  ],
  "miniapp_sessions": [
    {
      "app_id": "trip_planner",
      "state": "planning",
      "started_at": "2026-01-08T00:00:00Z",
      "ended_at": null,
      "message_count": 15
    }
  ],
  "memories": [
    {
      "id": "mem_123",
      "text": "User loves hiking in the mountains",
      "category": "interests",
      "created_at": "2026-01-02T00:00:00Z"
    }
  ],
  "billing": {
    "customer_id": "cus_xxx",
    "note": "For detailed billing history, use GET /billing/history"
  }
}

Error Responses: - 404 - Job not found or expired - 403 - Not authorized (wrong user) - 400 - Export not ready yet - 410 - Export has expired (after 7 days)


Billing History

Billing history is available through the payment routes:

GET /payment/billing/history?limit=20&starting_after=pi_xxx
Authorization: Bearer <token>

Response:

{
  "transactions": [
    {
      "id": "pi_xxx",
      "date": "2026-01-01T00:00:00Z",
      "amount": 500,
      "currency": "usd",
      "description": "Trial",
      "status": "succeeded",
      "invoice_url": "https://...",
      "receipt_url": "https://..."
    }
  ],
  "has_more": false,
  "total_count": 1
}


Database Schema

The following columns support account deletion:

-- In users table
deletion_requested_at TIMESTAMPTZ  -- When deletion was requested
deletion_scheduled_for TIMESTAMPTZ  -- When permanent deletion will occur

-- Index for finding accounts to delete
CREATE INDEX idx_users_deletion_scheduled
  ON users(deletion_scheduled_for)
  WHERE deletion_scheduled_for IS NOT NULL;

Background Jobs

Account Hard Delete Job

A background job (not yet implemented) should: 1. Run daily 2. Find users where deletion_scheduled_for < NOW() 3. Permanently delete: - All memories from Supermemory - All database records (cascade) - Stripe customer data 4. Send confirmation email


Security Considerations

  1. Authentication Required - All endpoints require valid JWT token
  2. Rate Limiting - Deletion (3/hour), Export (2/day)
  3. Confirmation Phrase - Deletion requires exact phrase match
  4. Grace Period - 30 days to recover from accidental deletion
  5. Export Expiry - Download links expire after 7 days
  6. Ownership Verification - Users can only access their own exports

  • US-10.3: Export My Data - GDPR-compliant data export
  • US-10.4: Delete My Account - Account deletion with grace period
  • US-9.2: View Billing History - Transaction history

Implementation Notes

Current Limitations

  1. In-Memory Export Storage - Export jobs are stored in memory. In production, should use Redis or database table for persistence across restarts.

  2. Memories Export - Currently attempts to fetch from Supermemory but may fail if service unavailable. Gracefully degrades.

  3. Hard Delete Job - Background job for permanent deletion after 30 days is not yet implemented.

Future Enhancements

  1. Upload exports to Supabase Storage with signed URLs
  2. Send email notification when export is ready
  3. Implement hard delete background job
  4. Add PDF export format option
  5. Add confirmation email for deletion request