{"record":{"id":"f90fcca6c76c4aa1","repo":"we-promise/sure","slug":"rate-limited","errorCode":"rate_limited","errorMessage":"Brex rate limit exceeded. Please try again later.","messagePattern":"Brex rate limit exceeded\\. Please try again later\\.","errorType":"http","errorClass":"Provider::Brex::BrexError","httpStatus":429,"severity":"warning","filePath":"app/models/provider/brex.rb","lineNumber":224,"sourceCode":"\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\n    def parse_json(body)\n      return {} if body.blank?\n\n      JSON.parse(body, symbolize_names: true)\n    end\n\n    def rfc3339_start_date(start_date)\n      time =\n        case start_date\n        when Time\n          start_date\n        when DateTime","sourceCodeStart":206,"sourceCodeEnd":242,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/brex.rb#L206-L242","documentation":"Raised by Provider::Brex#handle_response on HTTP 429: Brex's rate limit was exceeded and the request was not processed. It is transient by design — the same call succeeds after waiting — and get_paginated's tight loop (limit 1000, up to MAX_PAGES 25 pages per resource) can trip it when syncing many accounts. http_status 429 and trace_id are attached.","triggerScenarios":"Several get_paginated loops running concurrently for the same token; frequent scheduled syncs (e.g. every minute) stacking up; a burst of page requests within one large transaction backfill exceeding the per-minute quota.","commonSituations":"Parallel background jobs syncing multiple Brex connections simultaneously, cron schedules drifting together, initial historical backfills that page heavily.","solutions":["Retry the failed call after an exponential backoff (start ~seconds, not milliseconds) — the request was not processed, so retry is safe","Serialize or stagger Brex sync jobs per token so concurrent paginated loops don't stack","Lengthen the sync interval or reduce the number of accounts synced per run","If persistent, check the Brex dashboard for the token's quota tier and request a raise"],"exampleFix":"# before\nclient.get_cash_transactions(account_id, start_date: from)\n\n# after\nattempts = 0\nbegin\n  client.get_cash_transactions(account_id, start_date: from)\nrescue Provider::Brex::BrexError => e\n  raise unless e.error_type == :rate_limited && (attempts += 1) <= 5\n  sleep((2**attempts) + rand(2))\n  retry\nend","handlingStrategy":"retry","validationCode":"# Cheap client-side throttle before a sync burst\nclass BrexRateLimiter\n  def initialize(min_interval: 0.5) = (@min_interval = min_interval)\n  def throttle! = (now = Process.clock_gettime(Process::CLOCK_MONOTONIC); sleep(@min_interval - (now - @last)) if @last && (now - @last) < @min_interval; @last = Process.clock_gettime(Process::CLOCK_MONOTONIC))\nend","typeGuard":"def brex_rate_limited?(error)\n  error.is_a?(Provider::Brex::BrexError) && error.error_type == :rate_limited\nend","tryCatchPattern":"attempts = 0\nbegin\n  client.get_accounts\nrescue Provider::Brex::BrexError => e\n  raise unless e.error_type == :rate_limited && (attempts += 1) <= 5\n  sleep((2**attempts) + rand(2)) # exponential + jitter\n  retry\nend","preventionTips":["Serialize Brex sync jobs per token; stagger schedules with jitter","Space paginated requests with a small client-side interval","Cap backfill windows so get_paginated does fewer pages per run","Alert only when 429s persist after backoff, not on single occurrences"],"tags":["brex","http-429","rate-limit","throttling"],"backgroundTag":"http-429-rate-limited","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}