Soft-Deleted Record Still Returned in API
When a user record is soft-deleted — its deleted_at column is set to a non-null timestamp — it continues to appear in GET /api/users list responses. The database query does not include a WHERE deleted_at IS NULL filter, so soft-deleted records are indistinguishable from active ones in API responses.
MediumBeginnerAPI testingManual testingDatabase testing
// UNDERSTAND
// Symptoms
- A user with ID 201 that was deleted via DELETE /api/users/201 still appears in the response body of GET /api/users
- The deleted record's data is returned in search results and auto-complete suggestions
- Attempting to interact with the returned record (e.g. adding it to a team) succeeds even though the account no longer exists
- The total count returned by the API includes deleted records, causing the displayed count to be higher than the actual active record count
// Root Cause
- The ORM query or raw SQL for list and search endpoints omits the WHERE deleted_at IS NULL filter (or the equivalent WHERE is_deleted = false). Soft-deletion sets the flag in the database but the read path never inspects it.
- A global default scope that would automatically exclude deleted records is either absent from the ORM model configuration or was explicitly disabled for this query and never restored.
// Where It Appears
- User management and team member list endpoints that use soft-deletion
- Product or content catalogues where items are archived rather than hard-deleted
- Search and autocomplete endpoints that query the same table without the deletion filter
- Reporting endpoints that count or aggregate records including soft-deleted rows
// REPRODUCE & TEST
// How to Reproduce
- 01Create a user record with POST /api/users with body { "name": "Test User", "email": "testuser@example.com" }; note the returned user ID (e.g. 201)
- 02Soft-delete the record with DELETE /api/users/201; confirm the response is 200 OK or 204 No Content
- 03Send GET /api/users and scan the response for a record with id: 201 or email: 'testuser@example.com'
- 04If user 201 appears in the list, the soft-deleted record is still being returned
// Test Data Needed
- Permission to create and delete user records via the API
- The user ID returned from the creation step (201) to search for in the list response
// Manual Testing Ideas
- Create a record, delete it via the UI, then check the list view and API response to confirm it is no longer present
- Search by the deleted record's name or email and confirm no results are returned
- Check whether the API's total count field reflects the deletion — a count that includes deleted records suggests the filter is missing from count queries too
- Query a search or autocomplete endpoint with the deleted record's name to confirm it is excluded from those results as well
- Check related join queries: if soft-deleted users are still returned in team-member lists or assignment dropdowns, the filter is missing from multiple places
// API Testing Ideas
- Send POST /api/users with { "name": "Test User", "email": "testuser@example.com" }; record the returned id (201)
- Send DELETE /api/users/201; assert the response is 200 OK or 204 No Content
- Send GET /api/users and assert no record with id 201 or email 'testuser@example.com' appears in the response
- Send GET /api/users?search=testuser@example.com and assert an empty result set is returned
- Send GET /api/users/201 directly and assert the response is 404 Not Found — not 200 with the deleted record
// Automation Idea
Send POST /api/users to create a test user; capture the returned id (201). Send DELETE /api/users/201. Send GET /api/users and assert that no item in the response array has id equal to 201. Also send GET /api/users/201 and assert the status is 404. Both assertions confirm the deleted record is excluded from all read paths.
// Expected Result
After DELETE /api/users/201, the record does not appear in GET /api/users list responses, search results, or direct GET /api/users/201 lookups.
// Actual Result (Example)
After DELETE /api/users/201 returns 204 No Content, GET /api/users includes a record with id: 201, name: 'Test User', and email: 'testuser@example.com'. The soft-deleted user is returned as an active record.
// REPORT IT
Example Bug Report
- Title
- Soft-deleted user ID 201 still appears in GET /api/users after DELETE /api/users/201
- Severity
- Medium
- Environment
- Staging environment Postman Admin bearer token Endpoint: GET /api/users
- Steps to Reproduce
- 01Send POST /api/users with { "name": "Test User", "email": "testuser@example.com" }; note the returned id (201)
- 02Send DELETE /api/users/201; confirm the response is 204 No Content
- 03Send GET /api/users and search the response for id 201
- Expected Result
- No record with id 201 appears in the GET /api/users response.
- Actual Result
- GET /api/users returns a record with id: 201, name: 'Test User', email: 'testuser@example.com'. The deleted record is present in the active user list.
- Impact
- Deleted users appear in team-member lists, assignment dropdowns, and audit reports. Applications that trust the list API to reflect current state may act on stale data — for example, sending notifications to deleted users or including their data in exports.