jamiepine/voicebox · error · Error

HTTP error! status: ${response.status}

Error message

HTTP error! status: ${response.status}

What it means

Thrown by the shared `request<T>()` helper inside ApiClient (client.ts:79-97) whenever a JSON endpoint returns a non-2xx HTTP status. The error body is parsed as `{ detail }` (FastAPI convention); `formatErrorDetail` flattens string/array/object detail into a readable message, falling back to `HTTP error! status: <code>` when no detail is present. Because nearly every JSON method on the client routes through this helper, it is the single chokepoint for all REST failures (health, profiles, generations, history, effects, cloud).

Source

Thrown at app/src/lib/api/client.ts:93

    const serverUrl = useServerStore.getState().serverUrl;
    return serverUrl;
  }

  private async request<T>(endpoint: string, options?: RequestInit): Promise<T> {
    const url = `${this.getBaseUrl()}${endpoint}`;
    const response = await fetch(url, {
      ...options,
      headers: {
        'Content-Type': 'application/json',
        ...options?.headers,
      },
    });

    if (!response.ok) {
      const error = await response.json().catch(() => ({
        detail: response.statusText,
      }));
      throw new Error(formatErrorDetail(error.detail, `HTTP error! status: ${response.status}`));
    }

    return response.json();
  }

  // Health
  async getHealth(): Promise<HealthResponse> {
    return this.request<HealthResponse>('/health');
  }

  // Profiles
  async createProfile(data: VoiceProfileCreate): Promise<VoiceProfileResponse> {
    return this.request<VoiceProfileResponse>('/profiles', {
      method: 'POST',
      body: JSON.stringify(data),
    });
  }

View on GitHub (pinned to 51f49dea19)

Solutions

  1. Confirm the backend is running and reachable: open `serverUrl + '/health'` in a browser or curl it; a 200 means the base URL is correct.
  2. Read the thrown message — when `detail` is present it carries the FastAPI validation array joined by ';', which names the exact failing field. Fix the payload field it names.
  3. If the message is the bare fallback `HTTP error! status: 404`, the endpoint does not exist on the backend — update the backend to match the client version or vice versa.
  4. For 500s, check the backend process logs (the Python traceback identifies the server-side cause), not the frontend.
  5. If `serverUrl` is wrong, correct it in the server settings UI which writes `useServerStore.serverUrl`.

Example fix

// before — caller has no status context
await api.createProfile(data);

// after — surface status code for diagnostics
try {
  return await api.createProfile(data);
} catch (e) {
  const m = String(e?.message ?? e);
  throw new Error(`createProfile failed: ${m}`, { cause: e });
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Before any request(), confirm the backend is reachable
async function ensureBackend(baseUrl: string) {
  const r = await fetch(`${baseUrl}/health`);
  if (!r.ok) throw new Error(`Backend unreachable (health ${r.status})`);
  return true;
}

Type guard

// Narrow a thrown value to a usable message
function isApiError(e: unknown): e is Error {
  return e instanceof Error && /HTTP error! status: \d+/.test(e.message);
}

// Extract the numeric status when present
function httpStatusOf(e: unknown): number | null {
  const m = String((e as Error)?.message ?? '').match(/status: (\d+)/);
  return m ? Number(m[1]) : null;
}

Try / catch

try {
  return await api.someMethod(payload);
} catch (e) {
  const status = httpStatusOf(e);
  if (status === 422) showValidationToast((e as Error).message); // FastAPI detail array
  else if (status && status >= 500) showRetryToast('Server error, please retry');
  else throw e; // unknown — rethrow
}

Prevention

When it happens

Trigger: Any `this.request(...)` call — e.g. `getHealth()`, `createProfile()`, `listHistory()`, `getCloudStatus()` — when `getBaseUrl()` (read from `useServerStore.getState().serverUrl`) points at a backend that returns 4xx/5xx. Concretely: 404 when the endpoint path is wrong or the server is a different/older version; 422 when a FastAPI validation error occurs (detail is an array of `{msg,loc,type}`); 500 when the Python backend raises; ECONNREFUSED surfaces before this line as a fetch rejection, but a 502/504 from a proxy reaches here.

Common situations: Backend Voicebox Python server is not running or is on a different port than `serverUrl`; user changed the server URL in settings to a wrong value; backend version mismatch where the frontend expects an endpoint the server does not expose; FastAPI request-body validation failure on a create/update call; GPU/model not loaded causing a 500 during generation; CORS preflight failure manifests as an opaque error but a proxied 5xx reaches this throw.

Related errors


AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12). Data as JSON: /api/errors/437016c8ee24675b. Report an issue: GitHub.