koala73/worldmonitor · error

Embed map frame request failed: ${resp.status}

Error message

Embed map frame request failed: ${resp.status}

What it means

Generic failure path of fetchEmbedMapFrame: any response that is not ok and not 429/5xx (those route to EmbedMapFrameUnavailableError) throws this Error embedding the HTTP status code. It typically indicates a client-side problem such as 400 (bad query), 401/403 (auth), or 404 (unknown layers/frame).

Solutions

  1. Read the status code in the message: 400 → fix query/layerIds; 401/403 → renew or correctly attach the embed key; 404 → the frame/layers no longer exist.
  2. Log the full target URL and headers (redacting secrets) and validate layerIds against the current API.
  3. Regenerate the embed key if it was revoked and update the consumer.
  4. URL-encode all query parameters when building the request.

Example fix

// before
const target = `${base}/frame?layers=${layerIds.join(',')}`;
// after
const target = `${base}/frame?${new URLSearchParams({ layers: layerIds.join(',') })}`;
// and handle 4xx explicitly:
if (!resp.ok && resp.status !== 429 && resp.status < 500) {
  throw new Error(`Embed map frame request failed: ${resp.status} — check layerIds and embed key`);
}
Defensive patterns

Strategy: try-catch

Validate before calling

const target = `${base}/frame?${new URLSearchParams({ layers: layerIds.join(',') })}`;
if (!knownLayerIds.every(id => validLayerIds.has(id))) throw new Error('Unknown layerIds');
if (!embedKey) throw new Error('Embed key missing — create one first');

Try / catch

try {
  const frame = await fetchEmbedMapFrame(params);
} catch (e) {
  const m = /failed: (\d{3})/.exec(e.message);
  if (m) {
    const code = Number(m[1]);
    if (code === 401 || code === 403) promptEmbedKeyRenewal();
    else if (code === 404) refreshLayerCatalog();
    else if (code === 400) console.error('Bad query:', params);
  } else throw e;
}

Prevention

When it happens

Trigger: Embed frame endpoint responds 400/401/403/404 or other 4xx: malformed layerIds or query parameters, missing/invalid embed key or auth header, or a frame id that does not exist.

Common situations: Caller passing layer ids the endpoint does not recognize after a rename; expired or revoked embed key sent in headers; constructing the target URL with unencoded characters; requesting a frame configuration that was deleted.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15). Data as JSON: /api/errors/7241952827606673. Report an issue: GitHub.

Appendix: source

Thrown at src/embed/embed-fetch.ts:186

  let target: string;
  if (grant) {
    headers['X-WorldMonitor-Grant'] = grant.token;
    const url = new URL(EMBED_MAP_FRAME_PATH, origin);
    url.searchParams.set('layers', canonicalizeEmbedLayers(layerIds).join(','));
    target = url.toString();
  } else {
    target = `${origin}${EMBED_MAP_FRAME_PATH}?${publicFrameSearch(layerIds)}`;
  }

  const resp = await globalThis.fetch(target, {
    method: 'GET',
    headers,
    credentials: 'omit',
  });
  if (resp.status === 429 || resp.status >= 500) {
    throw new EmbedMapFrameUnavailableError(frameRetryAfterMs(resp));
  }
  if (!resp.ok) throw new Error(`Embed map frame request failed: ${resp.status}`);
  return await resp.json() as EmbedMapFrameResponse;
}

/**
 * The cacheable query for the free layers among `layerIds`, falling back to
 * the plain uncacheable query when none qualify.
 *
 * Paid layers are dropped from the keyless URL rather than carried into it:
 * a keyless caller cannot receive them either way, and naming them would
 * multiply the shared key space past its seven free subsets. The frame marks
 * them un-ready from their absence in the response, which is the same outcome
 * an explicit `not-entitled` would produce.
 */
function publicFrameSearch(layerIds: readonly EmbedLayerId[]): string {
  const free = getEmbedPanelFreeTier('map');
  const eligible = free
    ? canonicalizeEmbedLayers(layerIds).filter((id) => free.layers.includes(id))
    : [];

View on GitHub (pinned to 7d06c8633d)