gotify/server · error
username already exists
Error message
username already exists
What it means
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.
Source
Thrown at api/user.go:245
return
}
if internal.Admin {
ctx.AbortWithError(status, errors.New("you are not allowed to create an admin user"))
return
}
}
if existingUser == nil {
if success := successOrAbort(ctx, 500, a.DB.CreateUser(internal)); !success {
return
}
if err := a.UserChangeNotifier.fireUserAdded(internal.ID); err != nil {
ctx.AbortWithError(500, err)
return
}
ctx.JSON(200, toExternalUser(internal))
} else {
ctx.AbortWithError(400, errors.New("username already exists"))
}
}
}
// GetUserByID returns the user by id
// swagger:operation GET /user/{id} user getUser
//
// Get a user.
//
// Requires elevated authentication.
//
// ---
// consumes: [application/json]
// produces: [application/json]
// security: [clientTokenAuthorizationHeader: [], clientTokenHeader: [], clientTokenQuery: [], basicAuth: []]
// parameters:
// - name: id
// in: pathView on GitHub (pinned to 14bfc25627)
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
Example fix
// before
await api.createUser({ username: 'bob' }); // 400 if bob exists
// after
const existing = await api.getUserByName('bob').catch(() => null);
if (!existing) await api.createUser({ username: 'bob' }); Defensive patterns
Strategy: validation
Validate before calling
const existing = await findUserByName(username); // admin lookup or list+filter
if (existing) {
throw new Error(`username "${username}" already exists`);
} Type guard
function isUsernameFree(users, name) {
return !users.some(u => u.username === name);
} Try / catch
try {
await api.createUser({ username, ...rest });
} catch (e) {
if (e.status === 400 && /username already exists/.test(e.message)) { promptForDifferentUsername(); }
else { throw e; }
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- cannot delete last admin
- basic auth required
- invalid credentials
- no client auth provided
- you are not allowed to access this api
AI-assisted analysis of gotify/server@14bfc25627 (2026-09-05).
Data as JSON: /api/errors/4e85076b5ef40fb8.
Report an issue: GitHub.