{"record":{"id":"4e85076b5ef40fb8","repo":"gotify/server","slug":"username-already-exists","errorCode":null,"errorMessage":"username already exists","messagePattern":"username already exists","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"api/user.go","lineNumber":245,"sourceCode":"\t\t\t\treturn\n\t\t\t}\n\t\t\tif internal.Admin {\n\t\t\t\tctx.AbortWithError(status, errors.New(\"you are not allowed to create an admin user\"))\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tif existingUser == nil {\n\t\t\tif success := successOrAbort(ctx, 500, a.DB.CreateUser(internal)); !success {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err := a.UserChangeNotifier.fireUserAdded(internal.ID); err != nil {\n\t\t\t\tctx.AbortWithError(500, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tctx.JSON(200, toExternalUser(internal))\n\t\t} else {\n\t\t\tctx.AbortWithError(400, errors.New(\"username already exists\"))\n\t\t}\n\t}\n}\n\n// GetUserByID returns the user by id\n// swagger:operation GET /user/{id} user getUser\n//\n// Get a user.\n//\n// Requires elevated authentication.\n//\n//\t---\n//\tconsumes: [application/json]\n//\tproduces: [application/json]\n//\tsecurity: [clientTokenAuthorizationHeader: [], clientTokenHeader: [], clientTokenQuery: [], basicAuth: []]\n//\tparameters:\n//\t- name: id\n//\t  in: path","sourceCodeStart":227,"sourceCodeEnd":263,"githubUrl":"https://github.com/gotify/server/blob/14bfc256276775c425f988d621dccfe705de18ac/api/user.go#L227-L263","documentation":"Returned by CreateUser with HTTP 400 when a user with the requested username already exists in the database (existingUser != nil in the create branch). The API treats usernames as unique and refuses to create a duplicate, unlike the update path which renames safely.","triggerScenarios":"POST /api/users with a username that collides with an existing user (case as stored); retrying a creation that actually succeeded earlier; registration form submitted twice; LDAP/sync jobs re-creating users that already exist.","commonSituations":"Idempotency-unaware retry logic after a network timeout; test fixtures not cleaning up users between runs; users provisioned by two systems (manual + SSO sync) racing; case-sensitivity mismatch between the client and the DB collation.","solutions":["Pick a different username, or delete/rename the existing user first","Check existence before creating (GET the user by name/id) and update instead of create","Make retry logic idempotent: on 400 'username already exists', treat prior create as succeeded","Ensure test/setup scripts clean up created users"],"exampleFix":"// before\nawait api.createUser({ username: 'bob' }); // 400 if bob exists\n// after\nconst existing = await api.getUserByName('bob').catch(() => null);\nif (!existing) await api.createUser({ username: 'bob' });","handlingStrategy":"validation","validationCode":"const existing = await findUserByName(username); // admin lookup or list+filter\nif (existing) {\n  throw new Error(`username \"${username}\" already exists`);\n}","typeGuard":"function isUsernameFree(users, name) {\n  return !users.some(u => u.username === name);\n}","tryCatchPattern":"try {\n  await api.createUser({ username, ...rest });\n} catch (e) {\n  if (e.status === 400 && /username already exists/.test(e.message)) { promptForDifferentUsername(); }\n  else { throw e; }\n}","preventionTips":["Check name availability in the UI before submit","Make creation retries idempotent (a 400 duplicate after a timeout means it likely succeeded)","Clean up test fixtures in teardown","Standardize username casing before sending to avoid collation surprises"],"tags":["http","conflict","duplicate","users"],"backgroundTag":"duplicate-resource-conflict","analyzedSha":"14bfc256276775c425f988d621dccfe705de18ac","analyzedAt":"2026-09-05T12:52:36.781Z","contentChangedAt":"2026-09-05T12:52:36.781Z","schemaVersion":2},"datasetVersion":"2026-09-12T17:17:11.597Z"}