koala73/worldmonitor · error · APIError

HTTP %d: %s%s

Error message

HTTP %d: %s%s

What it means

Raised as APIError by Client.get() when a REST GET returns a non-2xx HTTP status (status < 200 or status >= 300, line 205-206). The request reached the server and completed at the transport layer, but the server rejected it or failed. The message formats the numeric status, the parsed body (truncated to 300 chars via _truncate), and an appended AUTH_HINT when status == 401 (line 62). APIError exposes .status and .body for programmatic inspection.

Source

Thrown at sdk/python/src/worldmonitor_sdk/__init__.py:206

        """List MCP resources (public)."""
        return self._rpc("resources/list")

    def get(self, path, params=None, **kwargs):
        """GET a raw REST path (host-relative, e.g. ``/api/health``)."""
        if not path.startswith("/"):
            raise ValueError("get() needs a host-relative API path starting with '/'")
        query = dict(params or {})
        query.update(kwargs)
        url = self.base_url + path
        if query:
            url += "?" + urllib.parse.urlencode({k: _stringify(v) for k, v in query.items()})
        status, content_type, text = self._transport(
            {"url": url, "method": "GET", "headers": self._headers(accept="application/json")},
            self.timeout,
        )
        value = parse_body(text, content_type)
        if status < 200 or status >= 300:
            raise APIError(status, value)
        return value

    def health(self):
        """API status / health check."""
        return self.get("/api/health")

    # -- curated helpers over the highest-traffic MCP tools ----------------
    # Every other tool is reachable via call_tool(), so this table stays
    # small and mirrors the npm CLI's curated commands one-to-one.

    def world_brief(self, **args):
        """Live global situation brief."""
        return self.call_tool("get_world_brief", args)

    def country_brief(self, country_code, **args):
        """AI strategic brief for a country (ISO 3166-1 alpha-2 code)."""
        return self.call_tool("get_country_brief", args, country_code=country_code)

View on GitHub (pinned to ffec79ac33)

Solutions

  1. Inspect e.status and e.body: a 401 means set api_key= or WORLDMONITOR_API_KEY; a 404 means verify base_url and the path; a 429 means back off; a 5xx means retry with jitter.
  2. Confirm base_url: print client.base_url — it should be https://api.worldmonitor.app unless self-hosting.
  3. For health checks, prefer client.health() over ad-hoc paths so the path stays correct across versions.
  4. Wrap the call in try/except APIError and branch on status rather than letting any non-2xx crash the process.

Example fix

# before
data = client.get('/api/bootstrap')
# after
from worldmonitor_sdk import APIError
try:
    data = client.get('/api/bootstrap')
except APIError as e:
    if e.status == 401:
        raise RuntimeError('Set WORLDMONITOR_API_KEY') from e
    if e.status == 429 or e.status >= 500:
        time.sleep(2 ** retry); retry += 1; continue
    raise
Defensive patterns

Strategy: try-catch

Validate before calling

# No request can be pre-validated for a server-side 4xx/5xx, but you can
# gate the call on a known-good base_url:
assert client.base_url.startswith('https://'), 'base_url must be https'

Type guard

from worldmonitor_sdk import APIError, WorldMonitorError

def is_api_error(exc: Exception) -> bool:
    return isinstance(exc, APIError)

def is_retriable(exc: Exception) -> bool:
    return isinstance(exc, APIError) and exc.status in (429, 500, 502, 503, 504)

Try / catch

from worldmonitor_sdk import APIError, WorldMonitorError

try:
    data = client.get('/api/bootstrap')
except APIError as e:
    if e.status == 401:
        ...  # surface auth instructions, do not retry
    elif e.status in (429, 502, 503, 504):
        ...  # back off and retry
    else:
        raise
except WorldMonitorError:
    raise

Prevention

When it happens

Trigger: Calling client.get('/api/health'), client.get('/api/bootstrap'), or any host-relative REST path whose server response is 401 (no/invalid X-WorldMonitor-Key), 404 (unknown path or wrong base_url), 429 (rate limited), 500/502/503 (upstream gateway failure), or a Cloudflare edge block returning a non-2xx code. Also fires when WORLDMONITOR_BASE_URL is overridden to a host that serves a non-success status for the given path.

Common situations: Self-hosted or Vercel-preview deployment where /api/health or /api/bootstrap is not routed, returning 404. A copy-pasted WORLDMONITOR_BASE_URL that drops the subdomain or adds a trailing path segment. API key not set in CI (401 on key-gated REST endpoints). Burst traffic hitting the shared rate limiter (429). Transient 5xx during a Vercel deploy.

Related errors


AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12). Data as JSON: /api/errors/e1e9d22161032445. Report an issue: GitHub.