{"record":{"id":"d89bd4e318a4b482","repo":"koala73/worldmonitor","slug":"http-status-body","errorCode":null,"errorMessage":"HTTP #{status}: #{body}","messagePattern":"HTTP #(.+?): #(.+?)","errorType":"exception","errorClass":"APIError","httpStatus":null,"severity":"error","filePath":"sdk/ruby/lib/worldmonitor.rb","lineNumber":128,"sourceCode":"\n    # List MCP resources (public).\n    def list_resources\n      rpc(\"resources/list\")\n    end\n\n    # GET a raw REST path (host-relative, e.g. \"/api/health\").\n    def get(path, params = {})\n      raise ArgumentError, \"get() needs a host-relative API path starting with '/'\" unless path.start_with?(\"/\")\n\n      url = base_url + path\n      query = stringify_keys(params)\n      url += \"?#{URI.encode_www_form(query.map { |k, v| [k, stringify_value(v)] })}\" unless query.empty?\n      status, content_type, body = @transport.call(\n        { url: url, method: \"GET\", headers: headers(accept: \"application/json\") },\n        timeout\n      )\n      value = self.class.parse_body(body, content_type)\n      raise APIError.new(status, value) unless (200..299).cover?(status)\n\n      value\n    end\n\n    # API status / health check.\n    def health\n      get(\"/api/health\")\n    end\n\n    # -- curated helpers over the highest-traffic MCP tools -------------------\n    # Every other tool is reachable via call_tool(), so this table stays small\n    # and mirrors the npm CLI's curated commands one-to-one.\n\n    # Live global situation brief.\n    def world_brief(args = {})\n      call_tool(\"get_world_brief\", args)\n    end\n","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/sdk/ruby/lib/worldmonitor.rb#L110-L146","documentation":"Raised as WorldMonitor::APIError by Client#get (line 128) when a REST GET returns a status outside the 200..299 range. The request completed at the transport layer but the server rejected or failed it. The message formats the status, the parsed body (truncated to 300 chars via Client.truncate), and an appended AUTH_HINT when status == 401 (line 56). APIError exposes .status and .body readers.","triggerScenarios":"Calling client.get('/api/health'), client.get('/api/bootstrap'), or any host-relative REST path whose response is 401 (no/invalid X-WorldMonitor-Key), 404 (unknown path or wrong base_url), 429 (rate limited), or 500/502/503 (gateway failure). Also fires when WORLDMONITOR_BASE_URL is overridden to a host serving a non-success status for the path.","commonSituations":"Self-hosted or preview deployment where /api/health is not routed (404). A WORLDMONITOR_BASE_URL override that is mistyped or carries an extra path segment. API key missing in the deployed environment (401 on key-gated endpoints). Burst traffic tripping the rate limiter (429). Transient 5xx during a deploy window.","solutions":["Inspect e.status and e.body: 401 → set api_key: or WORLDMONITOR_API_KEY; 404 → verify base_url and path; 429 → back off; 5xx → retry with jitter.","Print client.base_url to confirm it is https://api.worldmonitor.app unless self-hosting.","Prefer client.health over ad-hoc paths so the path stays correct across SDK versions.","Wrap the call in rescue WorldMonitor::APIError and branch on status instead of letting any non-2xx propagate."],"exampleFix":"# before\ndata = client.get('/api/bootstrap')\n# after\nbegin\n  data = client.get('/api/bootstrap')\nrescue WorldMonitor::APIError => e\n  raise 'Set WORLDMONITOR_API_KEY' if e.status == 401\n  raise if (200..299).cover?(e.status)\n  sleep 2 ** retry and retry if e.status == 429 || e.status >= 500\n  raise\nend","handlingStrategy":"try-catch","validationCode":"# No request can be pre-validated for a server-side 4xx/5xx, but you can\n# gate the call on a known-good base_url:\nraise 'base_url must be https' unless client.base_url.start_with?('https://')","typeGuard":"def api_error?(exc)\n  exc.is_a?(WorldMonitor::APIError)\nend\n\ndef retriable?(exc)\n  exc.is_a?(WorldMonitor::APIError) && [429, 500, 502, 503, 504].include?(exc.status)\nend","tryCatchPattern":"begin\n  data = client.get('/api/bootstrap')\nrescue WorldMonitor::APIError => e\n  case e.status\n  when 401 then raise 'Set WORLDMONITOR_API_KEY'\n  when 429, 500..504 then sleep(2 ** retry_count) and retry if retry_count < 3\n  else raise\n  end\nrescue WorldMonitor::Error\n  raise\nend","preventionTips":["Inspect e.status and e.body to classify the failure rather than re-raising blindly.","Keep base_url on the production default unless self-hosting.","Use client.health instead of hand-typed REST paths.","Set WORLDMONITOR_API_KEY in every environment that calls key-gated REST endpoints."],"tags":["ruby","rest","http","api-error","sdk"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}