POST /api/v2/users Returns 409 Conflict ("User already exists") Despite No Existing Record – JIT Password Reset Flow Failing

Hi Auth0 team, I’ve hit a critical blocker in my Just-In-Time (JIT) migration lab that I believe requires a backend review. I’ve included exhaustive logs and reproduction details below.**

Tenant:** dev-cnhsg0roe6qny5na
Region: US
Environment: Production-like lab setup (custom DB connection using Azure-based legacy backend APIs)
Impact: Change Password → JIT user creation blocked, halting full migration flow

Context

I’m building a Just-In-Time (JIT) migration flow for Azure AD B2C migration into Auth0.
The project has two core components:

  1. JIT Sign-In: When a user logs in, Auth0 validates against my legacy API (LocalAccountSignIn) and creates the user dynamically in Auth0.

  2. JIT Forgot Password / Change Password: When a currently non auth0 user triggers a password reset, Auth0 calls the same legacy API (LocalAccountUserExists), and if the user is not yet in Auth0, it attempts to create them using the Management API (POST /api/v2/users).

What Works

  • JIT Sign-In: Fully functional.
    -Auth0 successfully calls my legacy endpoint, validates credentials, and creates users on-the-fly.
    -Users appear correctly under the expected connection (b2c-migrated-users-v2).
    -All environment variables and secrets are correctly configured.

Example log excerpt: LOGIN SUCCESS user_id=ray.garg@zappsec.com migration_status=completed

  • The login script creates users without going through POST /api/v2/users

  • The change password script is the only place where POST /api/v2/users is called (for JIT creation during reset).

In my current design:

  • JIT Sign-In (Login script):
    Uses the legacy LocalAccountSignIn API and returns a user profile directly to Auth0. This path does not use the Management API POST /api/v2/users. It works consistently.

  • JIT Forgot Password / Change Password (Change Password script):
    Uses LocalAccountUserExists and then calls POST /api/v2/users (Management API) only when users-by-email returns empty.
    This is the path that fails with 409 The user already exists.

What Fails

Forgot Password / Change Password flow fails consistently with:

POST /api/v2/users response status=409
{
“statusCode”: 409,
“error”: “Conflict”,
“message”: “The user already exists.”,
“errorCode”: “auth0_idp_error”
}

This occurs even when:

  • /api/v2/users-by-email returns empty ([])

  • No users exist under this connection (b2c-migrated-users-v2)

  • The user does not exist in any other connection

  • I verified this using multiple advanced API searches (see below)

What I Have Already Tried

1. Fresh Connection Creation

  • Created an entirely new custom DB connection (b2c-migrated-users-v2)

  • Copied verified scripts for login, create, get user, and change password

  • Recreated all environment variables with identical key names

  • Ensured context object was enabled in all scripts
    -JIT Sign-In on b2c-migrated-users and b2c-migrated-users-v2 works reliably → confirms config + secrets + legacy APIs are correct.
    -Only the (Forgot Password) Change Password → Management API create branch fails with 409.

2. Verified Application & M2M Toggles

  • Web App toggle → Enabled for b2c-migrated-users-v2

  • M2M App toggle → Enabled for b2c-migrated-users-v2

  • On the M2M app, Management API → Authorized with following scopes:

read:users
update:users
delete:users
create:users
update:users_app_metadata
create:user_tickets
read:connections
read:logs

3. cURL Verification (Direct Management API)

Generated M2M token:

curl --request POST \
  --url https://dev-cnhsg0roe6qny5na.us.auth0.com/oauth/token \
  --header 'content-type: application/json' \
  --data '{
    "client_id": "<client id here >",
    "client_secret": "<client secret here >",
    "audience": "https://<tenant>.us.auth0.com/api/v2/",
    "grant_type": "client_credentials"
  }'

Validated no duplicate users:

# Search by email
curl --request GET \
  --url "https://dev-cnhsg0roe6qny5na.us.auth0.com/api/v2/users?q=email%3A\"rgarg2%40ucsc.edu\"&search_engine=v3" \
  --header "authorization: Bearer <access_token>"
# → []

# Search by connection prefix
curl --request GET \
  --url "https://dev-cnhsg0roe6qny5na.us.auth0.com/api/v2/users?q=user_id%3A\"auth0%7Cb2c-migrated-users*\"&search_engine=v3" \
  --header "authorization: Bearer <access_token>"
# → []

Both queries confirm there are no visible users with this email or connection prefix.

4. Confirmed Legacy API Responses

LocalAccountUserExists returns expected JSON with success=true and valid legacy attributes.
Response sample:

{
“success”: true,
“user”: {
“user_id”: “355f33ba-d08b-4170-9ca5-0f68cf43b7c8”,
“email”: “rgarg2@ucsc.edu”,
“email_verified”: true,
“name”: “Ray (UCSC JIT test) Garg”,
“app_metadata”: { “migration_status”: “completed” }
}
}

5. Full Change Password Log Trace

CHANGE-PW DEBUG: user not in Auth0 → calling LEGACY_EXIST
CHANGE-PW LEGACY EXIST status=200 body={…}
CHANGE-PW DEBUG: BRANCH=CREATE JIT localUserId=355f33ba-d08b…
CHANGE-PW DEBUG: POST /api/v2/users connection=b2c-migrated-users-v2
CHANGE-PW DEBUG: POST /users response status=409 body={“statusCode”:409,“error”:“Conflict”,“message”:“The user already exists.”,“errorCode”:“auth0_idp_error”}

Key Observation

  • /users-by-email consistently returns zero results.

  • /users creation call fails with 409 Conflict.

  • This occurs even on a brand-new connection, suggesting the duplication check may be tenant-wide or cached, possibly at the normalized_email index layer.

What I Suspect (not confirmed)

At first I suspected a stale identity index or tombstone for rishug123@yahoo.com left behind by a previously deleted user in another connection.
However, this 409 behavior now occurs for every email I test in the JIT Forgot Password flow:

  • rgarg2@ucsc.edu

  • rishug123@yahoo.com

  • ray.garg@zappsec.com

For each of these:

  • GET /api/v2/users-by-email returns []

  • Search by user_id prefix for auth0|b2c-migrated-users* returns []

That makes me wonder if there is:

  • Some tenant-wide uniqueness constraint or

  • Some soft-deleted / cached entry that is not visible via /api/v2/users but still blocks POST /api/v2/users.

Request for Engineering Assistance

Please investigate at the tenant-level identity store for any residual or orphaned entries associated with:

  • email: rgarg2@ucsc.edu or rishug123@yahoo.com or ray.garg@zappsec.com. I’m not certain this is the root cause, but given the behavior, I’d really appreciate a deeper backend check to rule this out.

  • previous connection: b2c-migrated-users

  • current connection: b2c-migrated-users-v2

If a stale entry exists in the internal store, please purge or release it so I can test creation again.

If not, I’d like confirmation of:

  1. Whether Auth0 enforces tenant-wide uniqueness on email across multiple custom DB connections. Even if it does, i have made sure that the emails im testing with dont exist in the user directory before i try the jit forgot password flow.

  2. Whether the /users endpoint may still reference cached soft-deleted entries.

  3. Any recommended remediation beyond recreating connections (which I’ve already done).

Why This Matters

This issue blocks our JIT password-reset onboarding flow, a key stage in our Auth0 adoption.
Without resolution, no migrated user can complete their reset and activation process.
The JIT login portion works flawlessly, so we know the code path and APIs are configured correctly, this is purely a user creation conflict preventing our production go-live.

Expected Behavior

If /api/v2/users-by-email returns [], POST /api/v2/users should succeed in creating a new user under the specified connection.

Request Summary

Please assist in:

  • Verifying and clearing any orphaned identity index for rgarg2@ucsc.edu/rishug123@yahoo.com/ray.garg@zappsec.com

  • Confirming whether Auth0 imposes a hidden tenant-wide uniqueness constraint between deleted records and new connection user creation

  • Providing any mitigation or workaround that avoids the duplicate user creation error that comes from testing the jit forgot password flow

Key questions for the Auth0 team

  1. Under what conditions can POST /api/v2/users return 409 The user already exists when:

    • GET /api/v2/users-by-email returns [], and

    • No users appear for the user_id prefix tied to this connection?

  2. Are there internal uniqueness constraints (e.g. normalized email + connection) or soft-deleted records that are not visible via /api/v2/users but still block creation?

  3. Is there any tenant-level cache or index that might need to be rebuilt or cleared by Auth0 engineering when many test users have been created/deleted across multiple custom DB connections?

  4. Is there any recommended alternative pattern for:

    • JIT creation during Change Password,

    • When the user is still only in the legacy system, and

    • users-by-email returns no visible Auth0 record?

Thank you in advance.
I’ve spent several days reproducing this issue methodically across multiple connections and configurations, and I’m confident it’s not a misconfiguration or script-level bug. At this point I’ve exhausted what I can see from the configuration and scripts, and I’m leaning strongly toward this being an internal uniqueness/indexing issue rather than a script-level bug.
At this point, I’m requesting a backend-level review of the tenant’s identity index and duplicate enforcement logic.

P.S If needed, I can securely share screenshots via DM or email.

Hi again @ray4

In regards to the issue that you are experiencing, can you confirm that you Create script does not attempt to create the user in your Azure AD database as well besides creating the user in Auth0?

Usually, the Create script is used to create users in your current datastore whenever a user signs up through Auth0, not to create Auth0 users during a migration, that is what the Login script would be for.

You can try the following to remediate the issue that you are experiencing:

  • Update your JIT Script. Modify the payload in your “Change Password” JIT script where you call the Management API. Add a specific flag in user_metadata to signal that this is a migration event.
const managementApiPayload = {
  connection: 'b2c-migrated-users-v2',
  email: email,
  password: tempPassword, 
  user_metadata: {
    is_jit_migration: true 
  }
};
  • Update your Create script to check for this flag
function create(user, callback) {
  if (user.user_metadata && user.user_metadata.is_jit_migration) {

    console.log('JIT Migration detected: Skipping legacy DB creation for ' + user.email);

    return callback(null); 
  }

Let me know if this works for you, if not, I will be taking a look at your tenant configuration!

Kind Regards,
Nik

Hi @nik.baleca ,

Thank you again for your help. I implemented the is_jit_migration flag exactly as you suggested and updated both the Change Password script and the Create script accordingly.

Here is what I’m observing now:

1. users-by-email consistently returns zero results

Example:

GETBYEMAIL: mgmt.getUsersByEmail status=200 body=[]

2. The Change Password script correctly hydrates the profile and attempts a JIT create

BRANCH=CREATE JIT localUserId=d7f9... email=ray.garg@zappsec.com
profile={
  "connection":"b2c-migrated-users-v2",
  "email":"ray.garg@zappsec.com",
  "email_verified":true,
  "password":"Hello123!",
  "name":"Ray (Auth0Demo) Garg",
  "app_metadata":{"migration_status":"completed"},
  "user_metadata":{"is_jit_migration":true}
}

3. However, the Management API still returns 409 Conflict

POST /api/v2/users → 409
body={"statusCode":409,"error":"Conflict","message":"The user already exists.","errorCode":"auth0_idp_error"}

4. This happens even on completely new emails

I tested with:

  • ray.garg@zappsec.com

  • rishug123@yahoo.com

  • rgarg2@ucsc.edu

All of them:

  • Return [] from /users-by-email

  • Then immediately fail with 409 – The user already exists

Conclusion

This appears to suggest the presence of an orphaned or partially-created identity record inside the b2c-migrated-users-v2 connection that is not visible to /users-by-email.

Would you be able to inspect the tenant and the connection backend to confirm if these identities exist in the internal connection store?

Happy to provide any other logs you need, thank you again for taking the time to help me resolve this.

Best regards,
Ray

Hi @nik.baleca quick follow-up with fresh update + logs.

Status: The JIT forgot-password flow is still failing for me due to an inconsistency between Management API “lookup” and “create”.

Current behavior troubleshooting (still 100% consistent)

  1. From my app, I trigger Forgot Password for <test email> and Auth0 sends the reset email.

  2. I click the reset link, submit new password on Auth0 hosted reset page.

  3. My Custom DB changePassword runs and attempts JIT-create via Management API.

What I’m seeing in logs

  • GETBYEMAIL runs and shows mgmt.getUsersByEmail count=0, then my upstream legacy EXIST API confirms the user exists.

  • In changePassword, context flags are not present in this flow in my tenant:

    • context.protocol=undefined, hasQueryTicket=false, hasBodyTicket=falselooksLikeReset=false
      (I’m not using these flags to block; I’m only logging them since they appear inconsistent here.)
  • Mgmt API lookup returns empty:

    • GET /api/v2/users-by-email?email=<test email here>200 []

      ***I’m using the standard GET /api/v2/users-by-email lookup. If there’s a better-supported way to do the locate attempt of the user (that is currently causing the 409 ghost/soft-deleted/indexed state), I’m happy to try it. If there’s a recommended endpoint/query to locate the record behind the 409 (e.g., soft-deleted/ghost/indexing state), I’m also happy to test it :sweat_smile:

  • But create conflicts:

    • POST /api/v2/users409 Conflict: "The user already exists."
  • I added a belt-and-suspenders retry (re-query users-by-email multiple times after 409), but it stays [], so the fallback cannot proceed.

So from the tenant’s perspective: the user “exists” enough to block creation, but is not returned by users-by-email.

Parallel progress (not blocked)

  • Bulk migration sign-in is working (users land with migration_status=pending, then complete on first login).

  • I’m currently hardening bulk-migrated forgot password (patch existing Auth0 user + update B2C password during the transition window + service bus move to native Auth0 DB).

Plan B for JIT forgot-password (fallback):

If this tenant-level inconsistency can’t be resolved quickly, I’m planning to handle JIT forgot-password via a fully custom reset email + reset UI that updates the password in B2C only; then the user’s next Auth0 login succeeds via ROPC and the existing login-script JIT creation path. Let me know anything to look out for as I approach this, that would be much appreciated.

Question: Is there a known scenario where /api/v2/users-by-email returns [] while /api/v2/users returns 409 for the same email/connection (ghost/soft-deleted user, indexing/caching issue, blocked state, etc.)? If so, what’s the recommended remediation to clear that state?