{"record":{"id":"426ff96c7a5b70d5","repo":"musistudio/claude-code-router","slug":"provider-manifest-must-be-a-json-object","errorCode":null,"errorMessage":"Provider manifest must be a JSON object.","messagePattern":"Provider manifest must be a JSON object\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core/src/contracts/deep-link.ts","lineNumber":182,"sourceCode":"  );\n  return {\n    ...(account ? { account } : {}),\n    ...(apiKey ? { apiKey } : {}),\n    baseUrl,\n    ...(icon ? { icon } : {}),\n    ...(modelDescriptions ? { modelDescriptions } : {}),\n    ...(modelDisplayNames ? { modelDisplayNames } : {}),\n    ...(modelMetadata ? { modelMetadata } : {}),\n    models,\n    ...(name ? { name } : {}),\n    ...(protocol ? { protocol } : {}),\n    ...(source ? { source } : {})\n  };\n}\n\nexport function parseProviderManifestPayload(value: unknown, sourceUrl?: string): ProviderDeepLinkPayload {\n  if (!isRecord(value)) {\n    throw new Error(\"Provider manifest must be a JSON object.\");\n  }\n  const providerValue = isRecord(value.provider)\n    ? value.provider\n    : isRecord(value.ccrProvider)\n      ? value.ccrProvider\n      : value;\n  return parseProviderPayloadFields(new URLSearchParams(), providerValue, sourceUrl);\n}\n\nfunction parseProviderPayloadFields(\n  params: URLSearchParams,\n  payload: Record<string, unknown> | undefined,\n  sourceFallback?: string\n): ProviderDeepLinkPayload {\n  const name = boundedString(\n    firstStringParam(params, [\"name\"]) ??\n      firstPayloadString(payload, [\"name\"]),\n    maxNameLength,","sourceCodeStart":164,"sourceCodeEnd":200,"githubUrl":"https://github.com/musistudio/claude-code-router/blob/99f24806c6a2c660b16e53e95211c517448a6c90/packages/core/src/contracts/deep-link.ts#L164-L200","documentation":"parseProviderManifestPayload expects the fetched manifest document to be a JSON object. If the parsed value is an array, a string, a number, null, or a primitive, isRecord(value) fails and this error is thrown before provider-field extraction.","triggerScenarios":"Calling parseProviderManifestPayload(value) where value is e.g. a JSON array of providers, a bare string, or the JSON.parse result of an error page body.","commonSituations":"Manifest endpoint returning a top-level array or a plain string; server returning an error/HTML body that was JSON.parse'd elsewhere; hand-authored manifest file with the provider nested but the file itself is a list.","solutions":["Make the manifest a top-level JSON object (wrap arrays under a key, e.g. {\"provider\": {...}})","Check what the manifest URL actually returns (curl it) and fix the document shape","If the manifest legitimately uses ccrProvider key, keep it as {\"ccrProvider\": {...}} — still an object"],"exampleFix":"// before\n[{\"name\":\"acme\",\"base_url\":\"https://api.acme.dev\"}]\n// after\n{\"provider\":{\"name\":\"acme\",\"base_url\":\"https://api.acme.dev\"}}","handlingStrategy":"type-guard","validationCode":"const parsed = JSON.parse(text); if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) return rejectManifest();","typeGuard":"const isRecord = (v: unknown): v is Record<string, unknown> =>\n  typeof v === \"object\" && v !== null && !Array.isArray(v);","tryCatchPattern":"try { parseProviderManifestPayload(parsed); } catch (e) { if (e instanceof Error && e.message === \"Provider manifest must be a JSON object.\") return logManifestShape(parsed); throw e; }","preventionTips":["Validate fetched manifest bodies with isRecord before parsing","Return objects (not arrays) from manifest endpoints"],"tags":["manifest","json","validation"],"backgroundTag":"schema-validation-failed","analyzedSha":"99f24806c6a2c660b16e53e95211c517448a6c90","analyzedAt":"2026-08-27T04:11:01.184Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}