mudler/LocalAI · error · Error

HTTP ${response.status}

Error message

HTTP ${response.status}

What it means

In the legacy static-UI talk page (core/http/static/talk.js), the browser POSTs its WebRTC SDP offer to 'v1/realtime/calls' to have the server negotiate a realtime voice session. On a non-ok response it tries to read err.error from the JSON body and falls back to 'HTTP <status>'. Failure here means the realtime voice call could not be established at all — no answer SDP comes back.

Source

Thrown at core/http/static/talk.js:348

        };
        // Timeout after 5s
        setTimeout(resolve, 5000);
      }
    });

    // Send offer to server
    const response = await fetch('v1/realtime/calls', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        sdp: pc.localDescription.sdp,
        model: model,
      }),
    });

    if (!response.ok) {
      const err = await response.json().catch(() => ({ error: 'Unknown error' }));
      throw new Error(err.error || `HTTP ${response.status}`);
    }

    const data = await response.json();

    // Set remote description (server's answer)
    await pc.setRemoteDescription({
      type: 'answer',
      sdp: data.sdp,
    });

    console.log('WebRTC connection established, session:', data.session_id);
  } catch (err) {
    console.error('Connection failed:', err);
    hasError = true;
    setStatus('error', 'Connection failed: ' + err.message);
    disconnect();
  }
}

View on GitHub (pinned to 44413a9d06)

Solutions

  1. Check the JSON error message first (err.error) — the server usually names the missing capability or model
  2. Verify the LocalAI build serves POST /v1/realtime/calls (curl -i -X POST with a dummy SDP) and rebuild with the realtime backend if not
  3. Use a realtime-capable model and confirm it is loaded via /v1/models
  4. Ensure the talk page is served from the same origin (or the proxy forwards v1/realtime/* correctly) and auth headers are present

Example fix

// before
const response = await fetch('v1/realtime/calls', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sdp: pc.localDescription.sdp, model }) });

// after — absolute URL + auth + richer fallback
const response = await fetch(`${window.location.origin}/v1/realtime/calls`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}) },
  body: JSON.stringify({ sdp: pc.localDescription.sdp, model }),
});
Defensive patterns

Strategy: try-catch

Validate before calling

// probe the realtime endpoint before offering the talk button
const probe = await fetch('v1/realtime/calls', { method: 'OPTIONS' }).catch(() => null)
if (!probe || probe.status === 404) showRealtimeUnavailableNotice()

Try / catch

try { const data = await postSdpOffer(pc, model) } catch (err) {
  const detail = err.message.replace(/^HTTP (\d+)$/, 'server rejected the call ($1)')
  showMicStatus(detail)
  pc.close()
}

Prevention

When it happens

Trigger: POSTing the SDP offer when the realtime endpoint is not served (older LocalAI, realtime backend not built/enabled), the requested model lacks realtime/audio capability (400 with an error message), server-side WebRTC stack failure, or auth rejection (401/403) on the endpoint. The relative URL also breaks if the page is served from a different origin without a proxy rewrite.

Common situations: LocalAI build without the realtime backend; model name not matching an audio-realtime model in config; API key required but the talk page not sending it; reverse proxy stripping or misrouting v1/realtime/*;TURN/STUN unrelated — this error is pure HTTP, before ICE.

Related errors


AI-assisted analysis of mudler/LocalAI@44413a9d06 (2026-08-15). Data as JSON: /api/errors/0978686425eb63cb. Report an issue: GitHub.