mastra-ai/mastra · error
Invalid audit portal response
Error message
Invalid audit portal response
What it means
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.
Source
Thrown at mastracode/factory-ui/src/ui/domains/factory/services/audit.ts:138
});
if (!res.ok) return throwRequestError(res);
const data: unknown = await res.json();
if (!isAuditEventPage(data)) throw new Error('Invalid audit event response');
return data;
}
export async function fetchAuditPortalLink(baseUrl: string): Promise<string | null> {
const res = await fetch(`${baseUrl}/web/audit/portal-link`, {
headers: { Accept: 'application/json' },
credentials: 'include',
});
if (res.status === 404) return null;
if (!res.ok) return throwRequestError(res);
const data: unknown = await res.json();
if (typeof data !== 'object' || data === null || !('url' in data) || typeof data.url !== 'string') {
throw new Error('Invalid audit portal response');
}
return data.url;
}
View on GitHub (pinned to 75dd419e61)
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.
Example fix
// before (server)
return Response.json({ portalUrl });
// after (server)
return Response.json({ url: portalUrl }); Defensive patterns
Strategy: type-guard
Validate before calling
const res = await fetch(`${baseUrl}/web/audit/portal-link`);
if (res.ok && res.status !== 404) {
const body: unknown = await res.json();
if (typeof body !== 'object' || body === null || !('url' in (body as any)) || typeof (body as any).url !== 'string') throw new Error('Invalid audit portal response');
} Type guard
function isPortalLink(v: unknown): v is { url: string } {
return typeof v === 'object' && v !== null && typeof (v as Record<string, unknown>).url === 'string';
} Try / catch
try {
const url = await fetchAuditPortalLink(baseUrl);
} catch (e) {
if (e instanceof Error && e.message === 'Invalid audit portal response') {
console.error('Portal-link contract drift');
hidePortalLink(); // degrade gracefully
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: Backend renamed url to portalUrl or nested it ({portal:{url}}); an empty 200 body from a misconfigured handler; version skew during rollout.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Invalid audit event response
- Query parameter "status" must be "draft" or "published"
- Attention read-all response is missing its continuation curs
- Request failed (${res.status}) / server-provided message
- OpenAI Codex device authorization response missing required
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/061e75dca268593c.
Report an issue: GitHub.