paperclipai/paperclip · error · ApiError

Announcement request failed

Error message

Announcement request failed

What it means

The announcements API wrapper performs fetch to /api/announcements/* with same-origin credentials. Any non-OK HTTP response (404, 401, 500, etc.) is converted to ApiError("Announcement request failed", status, null). It is raised from request(), used by current(), list, and dismiss().

Solutions

  1. Check error.status to branch: 401 → re-authenticate/refresh session; 404 → treat announcement as gone and clear local state; 5xx → retry later.
  2. Verify the API server is reachable at /api and the announcements routes are registered.
  3. On dismiss failures, keep the local dismissal optimistic and reconcile server-side on next load.
  4. If behind a reverse proxy, confirm /api/announcements/* is routed correctly.

Example fix

// before
if (!response.ok) throw new ApiError("Announcement request failed", response.status, null);
// after
if (!response.ok) {
  const body = await response.text().catch(() => null);
  throw new ApiError(`Announcement request failed (${response.status})`, response.status, body ? { body } : null);
}
Defensive patterns

Strategy: try-catch

Validate before calling

const res = await fetch("/api/health", { credentials: "same-origin" });
if (!res.ok) throw new Error("API unreachable — announcements will fail");

Type guard

function isAnnouncementAuthError(e: unknown): e is ApiError {
  return e instanceof ApiError && e.status === 401;
}

Try / catch

try {
  const announcement = await announcementsApi.current(signal);
} catch (e) {
  if (e instanceof ApiError) {
    if (e.status === 401) redirectToLogin();
    else if (e.status === 404) clearLocalAnnouncementState();
    else scheduleRetry();
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Any fetch to /api/announcements/current, /api/announcements/:id/dismiss, etc., where response.ok is false — e.g. session expired (401), unknown announcement id on dismiss (404), or server error (500).

Common situations: User session cookie expired so the announcements endpoint returns 401; dismissing an announcement already removed server-side (404); API server down or middleware failing (5xx); base path changed behind a proxy so /api/announcements 404s.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/ba877629cee56b34. Report an issue: GitHub.

Appendix: source

Thrown at ui/src/api/announcements.ts:8

import { announcementSchema, type Announcement } from "@paperclipai/shared";
import { ApiError } from "./client";

async function request(path: string, init: RequestInit) {
  const response = await fetch(`/api/announcements/${path}`, {
    credentials: "same-origin", cache: "no-store", ...init,
  });
  if (!response.ok) throw new ApiError("Announcement request failed", response.status, null);
  return response;
}

export const announcementsApi = {
  // Deliberately not coalesced by URL across account changes.
  async current(signal: AbortSignal): Promise<Announcement | null> {
    const response = await request("current", { signal });
    const payload = await response.json();
    return payload === null ? null : announcementSchema.parse(payload);
  },
  async dismiss(id: string, companyId: string, signal: AbortSignal) {
    await request(`${encodeURIComponent(id)}/dismiss`, {
      method: "POST", signal, headers: { "Content-Type": "application/json" }, body: JSON.stringify({ companyId }),
    });
  },
};

View on GitHub (pinned to 3f1d897a7c)