{"record":{"id":"d27c1b64007cc7ca","repo":"we-promise/sure","slug":"trading-212-api-error-status-response-code","errorCode":null,"errorMessage":"Trading 212 API error (status #{response.code})","messagePattern":"Trading 212 API error \\(status #(.+?)\\)","errorType":"exception","errorClass":"Provider::Trading212::ApiError","httpStatus":null,"severity":"error","filePath":"app/models/provider/trading212.rb","lineNumber":139,"sourceCode":"\n    def extract_cursor(next_page_path)\n      uri = URI.parse(\"https://placeholder#{next_page_path}\")\n      params = URI.decode_www_form(uri.query.to_s).to_h\n      params[\"cursor\"]\n    rescue URI::InvalidURIError\n      nil\n    end\n\n    def handle_response(response)\n      case response.code\n      when 200, 201\n        response.parsed_response\n      when 401, 403\n        raise AuthenticationError, \"Trading 212 authentication failed (#{response.code}). Check your API key.\"\n      when 429\n        raise RateLimitError, \"Trading 212 rate limit exceeded. Please wait before retrying.\"\n      else\n        raise ApiError.new(\n          \"Trading 212 API error (status #{response.code})\",\n          status_code: response.code,\n          response_body: response.body\n        )\n      end\n    end\n\n    def with_retries(label, max_retries: 3)\n      attempt = 0\n      begin\n        attempt += 1\n        yield\n      rescue *RETRYABLE_ERRORS => e\n        raise if attempt >= max_retries\n        delay = [ 2**attempt, 30 ].min\n        DebugLogEntry.capture(\n          category: \"sync\",\n          level: \"warn\",","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/trading212.rb#L121-L157","documentation":"Provider::Trading212 raises ApiError for any HTTP status not otherwise mapped in handle_response - i.e. everything except 200/201 (success), 401/403 (AuthenticationError) and 429 (RateLimitError). The error carries status_code and response_body accessors so callers can branch on the exact status.","triggerScenarios":"Calling /equity/account/summary, /equity/positions, /equity/history/* endpoints against the wrong environment (live key against demo.trading212.com returns 404/403-shaped failures that land here); Trading212 5xx outages; 404 for endpoints disabled on the account's API plan; malformed query params producing 400/422.","commonSituations":"Environment mismatch: api_key issued for the live account used with environment: \"demo\" or vice versa; Trading212 API version bump moving paths under /v0; equities API enabled but the specific history endpoint not enabled for the key; broker-side maintenance windows.","solutions":["Inspect e.status_code and e.response_body first - the fix depends entirely on the status","If 404: verify the endpoint path and that environment (\"live\" vs \"demo\") matches where the key was created","If 5xx: retry later with backoff; Trading212 equities API has frequent transient failures","If 400/422: log response_body for the exact validation message Trading212 returns and fix the request params","Confirm the account has 'API access' enabled in Trading212 settings for the endpoints being called"],"exampleFix":"# before\nsummary = client.fetch_account_summary\n\n# after\nbegin\n  summary = client.fetch_account_summary\nrescue Provider::Trading212::ApiError => e\n  Rails.logger.warn(\"T212 #{e.status_code}: #{e.response_body}\")\n  raise if e.status_code.to_i / 100 == 4 # client/config problem - do not retry\n  sleep(30) && retry if (retries += 1) < 3 # 5xx - transient\n  raise\nend","handlingStrategy":"try-catch","validationCode":"# Confirm environment matches the key before first call\nexpected = item.environment == \"demo\" ? \"demo.trading212.com\" : \"live.trading212.com\"\nraise ArgumentError, \"environment/base_uri mismatch\" unless base_uri.include?(expected)","typeGuard":"def t212_api_error?(err)\n  err.is_a?(Provider::Trading212::ApiError)\nend\n\ndef retryable?(err)\n  err.status_code.to_i / 100 == 5\nend","tryCatchPattern":"retries = 0\nbegin\n  client.fetch_positions\nrescue Provider::Trading212::ApiError => e\n  retry if (e.status_code.to_i / 100) == 5 && (retries += 1) < 3 && sleep(2**retries * 5)\n  log_t212_failure(e.status_code, e.response_body)\n  raise\nend","preventionTips":["Branch on e.status_code - never blanket-retry ApiError","Persist api environment (live/demo) with the key so they never drift apart","Log response_body - Trading212 includes actionable reasons there"],"tags":["trading212","broker-api","http-5xx","catch-all","equities"],"backgroundTag":"upstream-server-error","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}