{"record":{"id":"283dee904a75fa9c","repo":"langfuse/langfuse","slug":"invalid-json-in-filter-parameter","errorCode":null,"errorMessage":"Invalid JSON in filter parameter","messagePattern":"Invalid JSON in filter parameter","errorType":"validation","errorClass":"InvalidRequestError","httpStatus":400,"severity":"error","filePath":"packages/shared/src/features/scores/interfaces/api/shared.ts","lineNumber":69,"sourceCode":"    .transform((str) => str.split(\",\").map((id) => id.trim())) // Split the comma-separated string\n    .refine((arr) => arr.every((id) => typeof id === \"string\"), {\n      message: \"Each score ID must be a string\",\n    })\n    .nullish(),\n  fields: commaSeparatedEnumArray(SCORE_FIELD_GROUPS, null, {\n    unknownValues: \"filter\",\n  }),\n  filter: z\n    .string()\n    .optional()\n    .transform((str) => {\n      if (!str) return undefined;\n      try {\n        const parsed = JSON.parse(str);\n        return parsed;\n      } catch (e) {\n        if (e instanceof InvalidRequestError) throw e;\n        throw new InvalidRequestError(\"Invalid JSON in filter parameter\");\n      }\n    })\n    .pipe(z.array(singleFilter).optional()),\n});\n\n// POST /scores\n// Roughly mirrors ScoreBody in `packages/shared/src/server/ingestion/types.ts`;\n// keep them in sync for fields that cross both surfaces.\nexport const PostScoresBody = applyScoreValidation(\n  PostScoreBodyFoundationSchema.extend({\n    source: PublicApiCreateScoreSourceDomain.default(ScoreSourceEnum.API),\n  }).and(\n    z.discriminatedUnion(\"dataType\", [\n      z.object({\n        value: z.number(),\n        dataType: z.literal(\"NUMERIC\"),\n        configId: z.string().nullish(),\n      }),","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/langfuse/langfuse/blob/59d92c7cf365150d10b753b5a0d1708902a2ed60/packages/shared/src/features/scores/interfaces/api/shared.ts#L51-L87","documentation":"Thrown by the scores API filter parameter parser when the filter query-string value is present but is not valid JSON. The string is JSON.parse'd inside a zod transform; a parse failure (other than a re-thrown InvalidRequestError) is converted to this message.","triggerScenarios":"GET /api/public/scores?filter=environment%3Dproduction or any non-JSON filter string like filter=abc instead of filter=[{\"type\":\"stringOptions\",...}].","commonSituations":"Passing key=value style filters instead of a JSON array; broken URL encoding of quotes/brackets; trailing commas in hand-written JSON.","solutions":["URL-encode a proper JSON array for filter, e.g. filter=%5B%7B%22type%22...%5D","Validate/craft the JSON with JSON.stringify on the client rather than by hand","Omit the filter parameter entirely if no filtering is needed (undefined is allowed)"],"exampleFix":"// before\nGET /api/public/scores?filter=environment=production\n// after\nGET /api/public/scores?filter=%5B%7B%22column%22%3A%22environment%22%2C%22type%22%3A%22stringOptions%22%2C%22operator%22%3A%22%3D%22%2C%22value%22%3A%22production%22%7D%5D","handlingStrategy":"validation","validationCode":"let filter: unknown;\nif (rawFilterParam) { try { filter = JSON.parse(rawFilterParam); } catch { return badRequest('filter must be a JSON array'); } }","typeGuard":"const isValidFilterJson = (s: string) => { try { const v = JSON.parse(s); return Array.isArray(v); } catch { return false; } };","tryCatchPattern":"catch (e) { if (e instanceof InvalidRequestError && e.message === 'Invalid JSON in filter parameter') { /* re-encode filter with encodeURIComponent(JSON.stringify(arr)) */ } throw e; }","preventionTips":["Always encodeURIComponent(JSON.stringify(filterArray)) when building the URL","Never send key=value syntax in the filter param"],"tags":["scores-api","filter","json","query-string"],"backgroundTag":"invalid-json-input","analyzedSha":"59d92c7cf365150d10b753b5a0d1708902a2ed60","analyzedAt":"2026-08-27T22:22:00.402Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}