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

  1. Inspect the 200 body and align it with the expected { url: string } shape (fix server or client).
  2. Redeploy client and server together to keep the portal-link contract in sync.
  3. Check for middleware transforming the response (e.g. stripping fields).
  4. 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

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


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/061e75dca268593c. Report an issue: GitHub.