{"record":{"id":"9844ad8ce26dbfe4","repo":"HeyPuter/puter","slug":"bad-request-9844ad","errorCode":"bad_request","errorMessage":"Exactly one of `app` and `origin` is required","messagePattern":"Exactly one of `app` and `origin` is required","errorType":"exception","errorClass":"HttpError","httpStatus":400,"severity":"error","filePath":"src/backend/controllers/feedback/AppFeedbackController.ts","lineNumber":77,"sourceCode":"     * app accepts feedback, plus its canonical title/name for display. Reveals\n     * nothing that `puter.apps.get` doesn't already.\n     */\n    @Get('/target', {\n        subdomain: 'api',\n        requireUserActor: true,\n        requireVerified: true,\n        rateLimit: {\n            scope: 'app-feedback-target',\n            limit: 60,\n            window: 60_000,\n            key: 'user',\n        },\n    })\n    async target(req: Request, res: Response): Promise<void> {\n        const app = readTargetParam(req.query.app);\n        const origin = readTargetParam(req.query.origin);\n        if (!app === !origin) {\n            throw new HttpError(\n                400,\n                'Exactly one of `app` and `origin` is required',\n                { legacyCode: 'bad_request' },\n            );\n        }\n\n        const service = this.services.appFeedback as AppFeedbackService;\n        res.json(await service.getTarget({ app, origin }));\n    }\n\n    /**\n     * POST /app-feedback — store one feedback message and email the app's\n     * developer. Strict limits: the route limits below are the cheap first\n     * line; AppFeedbackService enforces durable per-user/per-app daily caps\n     * from the database (the route limiter fails open, the DB caps don't).\n     */\n    @Post('/', {\n        subdomain: 'api',","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/HeyPuter/puter/blob/908ec23eda38526170322c3edf71ba45ecb1ca95/src/backend/controllers/feedback/AppFeedbackController.ts#L59-L95","documentation":"Thrown by GET /app-feedback/target when the XOR check `!app === !origin` fails — i.e. both app and origin are provided, or neither is. The endpoint takes exactly one target identifier: an app uid/name OR an origin URL. readTargetParam also returns undefined for values over 2048 chars or non-strings, so an over-long origin looks like 'not provided'.","triggerScenarios":"GET /app-feedback/target?app=X&origin=Y (both), or /app-feedback/target with neither; an origin longer than 2048 chars (TARGET_PARAM_MAX_LENGTH) that readTargetParam silently drops to undefined, making an otherwise-present origin count as missing.","commonSituations":"Dialog that fills both fields from context; client always sending origin as a fallback alongside app; an origin with a huge tracking suffix exceeding the 2048 cap and being silently ignored.","solutions":["Send exactly one of ?app=<app> or ?origin=<origin>, not both, not neither.","Ensure origin (if used) is a real URL under 2048 characters — longer values are dropped by readTargetParam and treated as absent.","In client code, branch: pick app when you have an app id, otherwise origin, never concatenate."],"exampleFix":"// before\nfetch('/app-feedback/target?app=' + appId + '&origin=' + origin)\n// after\nfetch('/app-feedback/target?' + (appId ? 'app=' + appId : 'origin=' + origin))","handlingStrategy":"validation","validationCode":"const params = appId ? { app: appId } : (origin ? { origin } : null);\nif (!params) throw new Error('need app or origin');\n// origin must be <= 2048 chars\nif ('origin' in params && params.origin.length > 2048) throw new Error('origin too long');","typeGuard":"const hasOneTarget = (app?: string, origin?: string): boolean =>\n  !!app !== !!origin; // exactly one","tryCatchPattern":null,"preventionTips":["Send exactly one of app or origin.","Keep origin under 2048 chars or readTargetParam drops it.","Build the query string from a single chosen field."],"tags":["validation","feedback","bad-request","xor"],"backgroundTag":null,"analyzedSha":"908ec23eda38526170322c3edf71ba45ecb1ca95","analyzedAt":"2026-08-12T20:53:15.911Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}