{"record":{"id":"d7888ecd94d7584e","repo":"gotify/server","slug":"cannot-delete-internal-application","errorCode":null,"errorMessage":"cannot delete internal application","messagePattern":"cannot delete internal application","errorType":"http","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"api/application.go","lineNumber":197,"sourceCode":"//\t    schema:\n//\t        $ref: \"#/definitions/Error\"\n//\t  403:\n//\t    description: Forbidden\n//\t    schema:\n//\t        $ref: \"#/definitions/Error\"\n//\t  404:\n//\t    description: Not Found\n//\t    schema:\n//\t        $ref: \"#/definitions/Error\"\nfunc (a *ApplicationAPI) DeleteApplication(ctx *gin.Context) {\n\twithID(ctx, \"id\", func(id uint) {\n\t\tapp, err := a.DB.GetApplicationByID(id)\n\t\tif success := successOrAbort(ctx, 500, err); !success {\n\t\t\treturn\n\t\t}\n\t\tif app != nil && app.UserID == auth.GetUserID(ctx) {\n\t\t\tif app.Internal {\n\t\t\t\tctx.AbortWithError(400, errors.New(\"cannot delete internal application\"))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif success := successOrAbort(ctx, 500, a.DB.DeleteApplicationByID(id)); !success {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif app.Image != \"\" {\n\t\t\t\tos.Remove(a.ImageDir + app.Image)\n\t\t\t}\n\t\t} else {\n\t\t\tctx.AbortWithError(404, fmt.Errorf(\"app with id %d doesn't exists\", id))\n\t\t}\n\t})\n}\n\n// UpdateApplication updates an application info by its id.\n// swagger:operation PUT /application/{id} application updateApplication\n//\n// Update an application.","sourceCodeStart":179,"sourceCodeEnd":215,"githubUrl":"https://github.com/gotify/server/blob/14bfc256276775c425f988d621dccfe705de18ac/api/application.go#L179-L215","documentation":"This 400 error is returned by the DELETE /applications/:id endpoint when the application being deleted is marked as internal. Internal applications are system-managed records that the API forbids users from deleting, regardless of ownership. The check runs only after confirming the requester owns the application (app.UserID == auth.GetUserID(ctx)), so this error means 'you own it, but it is protected'.","triggerScenarios":"Calling DELETE on an application whose DB record has internal=true. Typically the app was created by seed data, an admin, or a system bootstrap routine rather than by the user.","commonSituations":"Developers trying to clean up seed/demo applications; environments where an internal app got assigned to a user's account; scripts iterating over all owned applications and deleting each one, hitting the protected internal entry.","solutions":["Pick a different (non-internal) application to delete, or leave the internal one in place","If the record truly must go, flip internal=false in the database (UPDATE applications SET internal=false WHERE id=...) and retry as an admin","Filter internal applications out client-side before issuing DELETE calls (e.g. skip apps where internal is true)","Patch the handler/seed so internal apps are not owned by regular users"],"exampleFix":"// before\napps.forEach(a => deleteApplication(a.id));\n// after\napps.filter(a => !a.internal).forEach(a => deleteApplication(a.id));","handlingStrategy":"validation","validationCode":"if (app.internal) { throw new SkipError('internal applications cannot be deleted'); }\nawait api.delete(`/applications/${app.id}`);","typeGuard":"function isDeletable(app) { return app != null && app.userid === currentUser.id && !app.internal; }","tryCatchPattern":null,"preventionTips":["Filter out internal applications in list views before offering delete actions","Treat 400 'cannot delete internal application' as an expected skip, not a bug","Never mark user-facing records as internal unless they are meant to be immutable","When cleaning up, check the internal flag from GET responses first"],"tags":["http-400","authorization","rest-api","go","gin"],"backgroundTag":"protected-resource-delete-rejected","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"}