# Fix Okta SCIM deactivation failing with 404 not found

## TL;DR
A 404 on deactivate means Okta targeted a user the app lacks: deleted there, or the externalId anchor drifted. Do not retry the DELETE. Look them up by userName: if gone, clear the task; if present under a different id, deactivate it and fix the mapping.

## The error
```text
DELETE the SCIM v2 user resource (DELETE) -> 404 Not Found
{"schemas":["urn:ietf:params:scim:api:messages:2.0:Error"],"detail":"User not found","status":"404"}
```

## Use this skill
Agent? Search Vectle for this error:
```bash
curl -s "https://vectle.com/api/v1/search?q=okta%20scim%20deactivate%20user%20failed%20404%20not%20found"
```

## Fix it

### Step 1: Check whether the user exists at the provider

```bash
curl -s "https://yourapp.example.com/scim/v2/Users?filter=userName+eq+%22%5Blogin%20email%5D%22" -H "your auth header bearer value]"
```

Expected: either 0 results (user is gone - skip to step 4) or 1 result with an id to compare against the failed task.

### Step 2: Deactivate the record that actually exists

```bash
curl -s -X PATCH "https://yourapp.example.comthe SCIM v2 user resource [existing user id]" -H "your auth header bearer value]" -H "Content-Type: application/scim+json" -d '{"schemas":["urn:ietf:params:scim:api:messages:2.0:PatchOp"],"Operations":[{"op":"replace","path":"active","value":false}]}'
```

Expected: HTTP 200 and the user flips to inactive.

### Step 3: Find why Okta aimed at the wrong id

```bash
In Okta Admin, open the failed task and note the id Okta sent. Compare it with the provider id from step 1 and the externalId mapping.
```

Expected: you can point at the mismatch - usually externalId mapped to email instead of the stable Okta user id.

### Step 4: Clear the stuck task and fix the mapping

```bash
In the Okta provisioning task queue, confirm or clear the failed deactivate task for this user. Then set externalId to the immutable IdP id (Okta user.id) and re-push one test deactivation with the provisioning log open.
```

Expected: the task clears and the test deactivation returns 200.

### Step 5: Re-run deprovisioning for the affected users

```bash
Unassign the app from the affected users (or re-trigger the deprovisioning) and confirm each task completes instead of 404ing.
```

Expected: tasks complete with success and the users show inactive in the app.

## When this applies

- Okta deactivate or delete tasks fail with 404 / resource not found
- The provisioning queue shows stuck deprovisioning tasks for users who may already be gone

## When it doesn't

- The failure is 401 or 403 (fix credentials or permissions first)
- Creates or updates are failing (different root cause)
- The user exists and deactivation 200s but access remains (check the app's own deactivation semantics)

## Compatibility

SCIM 2.0 per RFC 7644; Okta lifecycle management deprovisioning.

## Variant phrasings

### okta deprovision 404 user not found

Same fix. Deprovisioning is idempotent in intent: if the user is already gone, the goal state is met and the task should be cleared, not retried forever.

### scim delete user 404 already deleted

Soft-deleted users still resolve by id; hard-deleted ones 404 and the task just needs clearing.

## Why it happens

Okta stores the provider-side user id from the original create and reuses it for deactivate. If that record was deleted in the app directly, or the anchor mapping drifted so Okta holds a stale id, the DELETE targets nothing and the provider correctly returns 404. Retrying the same DELETE can never succeed.

## Edge cases

- Apps that soft-delete still return the user on GET but 404 on DELETE; deactivate via PATCH active=false instead
- Unassigning in Okta before the mapping fix just re-queues the same failing task
- Bulk offboarding after an app-side cleanup causes mass 404s; clear them in bulk rather than one by one

## If it still fails

- Capture the exact timestamp, the username, and the full error from the system log before changing anything else.
- Reproduce with a single test user so you are not debugging a crowd.
- If it worked before, diff the config against the last known good, then open a vendor ticket with the timestamp and request id. Never send secrets or private keys.

## Prevention

- Track credential and certificate expiry with alerts, not memory.
- Run a synthetic check per app daily so breakage pages you, not a user.
- Document mappings, URLs, and runbook steps where the next admin will find them.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_lJpTgCvO0ElOO8pjhkIiFg
