{"record":{"id":"2bfe7ddc2ea9bfe2","repo":"we-promise/sure","slug":"bad-request","errorCode":"bad_request","errorMessage":"Bad request to Brex API","messagePattern":"Bad request to Brex API","errorType":"http","errorClass":"Provider::Brex::BrexError","httpStatus":400,"severity":"error","filePath":"app/models/provider/brex.rb","lineNumber":212,"sourceCode":"    end\n\n    def auth_headers\n      {\n        \"Authorization\" => \"Bearer #{token}\",\n        \"Content-Type\" => \"application/json\",\n        \"Accept\" => \"application/json\"\n      }\n    end\n\n    def handle_response(response, path:)\n      trace_id = brex_trace_id(response)\n\n      case response.code\n      when 200\n        parse_json(response.body)\n      when 400\n        Rails.logger.error \"Brex API: bad request for #{path} trace_id=#{trace_id}\"\n        raise BrexError.new(\"Bad request to Brex API\", :bad_request, http_status: 400, trace_id: trace_id)\n      when 401\n        Rails.logger.warn \"Brex API: unauthorized for #{path} trace_id=#{trace_id}\"\n        raise BrexError.new(\"Invalid Brex API token or account permissions\", :unauthorized, http_status: 401, trace_id: trace_id)\n      when 403\n        Rails.logger.warn \"Brex API: access forbidden for #{path} trace_id=#{trace_id}\"\n        raise BrexError.new(\"Access forbidden - check Brex API token scopes\", :access_forbidden, http_status: 403, trace_id: trace_id)\n      when 404\n        Rails.logger.warn \"Brex API: resource not found for #{path} trace_id=#{trace_id}\"\n        raise BrexError.new(\"Brex resource not found\", :not_found, http_status: 404, trace_id: trace_id)\n      when 429\n        Rails.logger.warn \"Brex API: rate limited for #{path} trace_id=#{trace_id}\"\n        raise BrexError.new(\"Brex rate limit exceeded. Please try again later.\", :rate_limited, http_status: 429, trace_id: trace_id)\n      else\n        Rails.logger.error \"Brex API: unexpected response code=#{response.code} path=#{path} trace_id=#{trace_id}\"\n        raise BrexError.new(\"Failed to fetch data from Brex API: HTTP #{response.code}\", :fetch_failed, http_status: response.code, trace_id: trace_id)\n      end\n    end\n","sourceCodeStart":194,"sourceCodeEnd":230,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/brex.rb#L194-L230","documentation":"Raised by Provider::Brex#handle_response when the Brex API answers HTTP 400 to any request. It means Brex considered the request malformed — invalid query parameters, a bad cursor, an unsupported limit, or a wrongly formatted date — not an auth or outage problem. The error carries http_status 400 and the response's X-Brex-Trace-Id as trace_id, which Brex support can use to locate the rejected request.","triggerScenarios":"get_paginated sends a stale or replayed cursor param; get_cash_transactions/get_primary_card_transactions pass a posted_at_start that rfc3339_start_date formats unexpectedly; page_params include an unsupported limit value; or the path itself is malformed (wrong account id encoding).","commonSituations":"Persisting pagination cursors between sync runs and replaying them after they expired, passing user-supplied date strings straight into start_date, and Brex API version changes that rename or restrict query params.","solutions":["Capture error.trace_id from the raised BrexError and check the Rails log line 'Brex API: bad request for <path>' to identify which call was rejected","Confirm callers only pass Date/Time/parseable values as start_date so rfc3339_start_date emits a clean RFC3339 timestamp","Stop persisting pagination cursors across processes — get_paginated already loops internally within one call","If the cause is unclear, reproduce the exact request with curl and escalate to Brex support with the trace_id"],"exampleFix":"# before\nclient.get_cash_transactions(account_id, start_date: params[:from]) # raw string from user\n\n# after\nfrom = Date.parse(params[:from]) rescue nil\nclient.get_cash_transactions(account_id, start_date: from) # nil skips posted_at_start","handlingStrategy":"validation","validationCode":"def valid_brex_start_date?(value)\n  return true if value.nil? || value.is_a?(Date) || value.is_a?(Time)\n  Time.zone.parse(value.to_s).present?\nrescue ArgumentError, TypeError\n  false\nend","typeGuard":"def brex_bad_request?(error)\n  error.is_a?(Provider::Brex::BrexError) && error.error_type == :bad_request\nend","tryCatchPattern":"begin\n  client.get_cash_transactions(id, start_date: from)\nrescue Provider::Brex::BrexError => e\n  raise unless e.error_type == :bad_request\n  Rails.logger.error(\"Brex 400 trace_id=#{e.trace_id} — do not retry, fix params\")\n  raise\nend","preventionTips":["Only pass Date/Time objects or pre-validated strings as start_date","Never persist pagination cursors across processes or runs","Log error.trace_id with every 400 so Brex support can trace rejected requests","Add contract tests around rfc3339_start_date so date formatting regressions surface before hitting the API"],"tags":["brex","http-400","api","pagination"],"backgroundTag":"http-400-bad-request","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}