{"record":{"id":"061e75dca268593c","repo":"mastra-ai/mastra","slug":"invalid-audit-portal-response","errorCode":null,"errorMessage":"Invalid audit portal response","messagePattern":"Invalid audit portal response","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mastracode/factory-ui/src/ui/domains/factory/services/audit.ts","lineNumber":138,"sourceCode":"  });\n  if (!res.ok) return throwRequestError(res);\n\n  const data: unknown = await res.json();\n  if (!isAuditEventPage(data)) throw new Error('Invalid audit event response');\n  return data;\n}\n\nexport async function fetchAuditPortalLink(baseUrl: string): Promise<string | null> {\n  const res = await fetch(`${baseUrl}/web/audit/portal-link`, {\n    headers: { Accept: 'application/json' },\n    credentials: 'include',\n  });\n  if (res.status === 404) return null;\n  if (!res.ok) return throwRequestError(res);\n\n  const data: unknown = await res.json();\n  if (typeof data !== 'object' || data === null || !('url' in data) || typeof data.url !== 'string') {\n    throw new Error('Invalid audit portal response');\n  }\n  return data.url;\n}\n","sourceCodeStart":120,"sourceCodeEnd":142,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory-ui/src/ui/domains/factory/services/audit.ts#L120-L142","documentation":"fetchAuditPortalLink reads /web/audit/portal-link; a 200 response must be an object containing a string url field, otherwise it throws 'Invalid audit portal response'. 404 is treated as 'no portal configured' (returns null), so this error specifically means the portal endpoint succeeded but returned an unusable body.","triggerScenarios":"GET /web/audit/portal-link responds 200 with JSON lacking a string url (e.g. {link:...}, null, or non-JSON that coincidentally parsed) — typically a schema drift between server and client.","commonSituations":"Backend renamed url to portalUrl or nested it ({portal:{url}}); an empty 200 body from a misconfigured handler; version skew during rollout.","solutions":["Inspect the 200 body and align it with the expected { url: string } shape (fix server or client).","Redeploy client and server together to keep the portal-link contract in sync.","Check for middleware transforming the response (e.g. stripping fields).","Note 404 is expected when no portal exists — only mismatched 200 bodies produce this error."],"exampleFix":"// before (server)\nreturn Response.json({ portalUrl });\n// after (server)\nreturn Response.json({ url: portalUrl });","handlingStrategy":"type-guard","validationCode":"const res = await fetch(`${baseUrl}/web/audit/portal-link`);\nif (res.ok && res.status !== 404) {\n  const body: unknown = await res.json();\n  if (typeof body !== 'object' || body === null || !('url' in (body as any)) || typeof (body as any).url !== 'string') throw new Error('Invalid audit portal response');\n}","typeGuard":"function isPortalLink(v: unknown): v is { url: string } {\n  return typeof v === 'object' && v !== null && typeof (v as Record<string, unknown>).url === 'string';\n}","tryCatchPattern":"try {\n  const url = await fetchAuditPortalLink(baseUrl);\n} catch (e) {\n  if (e instanceof Error && e.message === 'Invalid audit portal response') {\n    console.error('Portal-link contract drift');\n    hidePortalLink(); // degrade gracefully\n  }\n}","preventionTips":["Keep the { url: string } contract in a shared type between client and server.","Treat 404 as the legitimate 'no portal' case in tests.","Test the portal-link handler against the client's validation logic."],"tags":["schema-validation","http","audit","api-contract"],"backgroundTag":"schema-validation-failed","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}