{"record":{"id":"1439a4628356359e","repo":"we-promise/sure","slug":"rate-limited-1439a4","errorCode":"rate_limited","errorMessage":"Sophtron rate limit exceeded. Please try again later.","messagePattern":"Sophtron rate limit exceeded\\. Please try again later\\.","errorType":"exception","errorClass":"Provider::Sophtron::Error","httpStatus":429,"severity":"warning","filePath":"app/models/provider/sophtron.rb","lineNumber":365,"sourceCode":"\n    def handle_response(response, parse_json: true)\n      body = response.body.to_s\n\n      case response.code.to_i\n      when 200, 201, 204\n        return {} if body.strip.blank?\n\n        parse_json ? JSON.parse(body, symbolize_names: true) : parse_optional_json(body)\n      when 400\n        raise Error.new(\"Bad request to Sophtron API: #{body}\", :bad_request, details: body)\n      when 401\n        raise Error.new(\"Invalid Sophtron User ID or Access Key\", :unauthorized, details: body)\n      when 403\n        raise Error.new(\"Access forbidden by Sophtron\", :access_forbidden, details: body)\n      when 404\n        raise Error.new(\"Sophtron resource not found\", :not_found, details: body)\n      when 429\n        raise Error.new(\"Sophtron rate limit exceeded. Please try again later.\", :rate_limited, details: body)\n      else\n        raise Error.new(\n          \"Sophtron API request failed: #{response.code} #{response.message} - #{body}\",\n          :fetch_failed,\n          details: body\n        )\n      end\n    rescue JSON::ParserError => e\n      raise Error.new(\"Invalid JSON response from Sophtron API: #{e.message}\", :invalid_response, details: body)\n    end\n\n    def parse_optional_json(body)\n      JSON.parse(body, symbolize_names: true)\n    rescue JSON::ParserError\n      body\n    end\n\n    def normalize_base_url(value)","sourceCodeStart":347,"sourceCodeEnd":383,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/sophtron.rb#L347-L383","documentation":"Provider::Sophtron raises this Error with error_type=:rate_limited when Sophtron responds HTTP 429. Sophtron throttles per API user; the message advises retrying later, and the response body is attached as details.","triggerScenarios":"Bursty job polling (hammering get_job every second while a bank login completes); syncing many SophtronItems in parallel from one scheduled job; re-importing transaction history for several accounts simultaneously under a single Sophtron API user.","commonSituations":"A background cron/Sidekiq schedule that fan-outs imports without jitter; adding several institutions right after another; production and staging sharing one Sophtron API user so combined traffic trips the quota.","solutions":["Exponentially back off and retry on :rate_limited (e.g. 30s, 2m, 8m) instead of failing the sync","Serialize or add jitter to SophtronItem imports so concurrent jobs do not overlap","Increase the polling interval for job status (Sophtron jobs take tens of seconds; poll every 5-10s not every 1s)","If quota is chronically hit, request a rate-limit increase from Sophtron or use separate API users per environment"],"exampleFix":"# before\nresult = provider.get_accounts(customer_id)\n\n# after\nresult = with_backoff { provider.get_accounts(customer_id) }\n\ndef with_backoff(attempts: 4)\n  yield\nrescue Provider::Sophtron::Error => e\n  raise unless e.error_type == :rate_limited && attempts > 1\n  sleep(2 ** (5 - attempts))\n  retry if (attempts -= 1) > 0\n  raise\nend","handlingStrategy":"retry","validationCode":"# Rate limits cannot be pre-validated; pre-throttle instead\nSyncThrottler.acquire(:sophtron) # token bucket, 1 req / interval, before each provider call","typeGuard":"def sophtron_rate_limited?(err)\n  err.is_a?(Provider::Sophtron::Error) && e.error_type == :rate_limited\nend","tryCatchPattern":"retries = 0\nbegin\n  provider.get_transactions(account_id)\nrescue Provider::Sophtron::Error => e\n  retry if e.error_type == :rate_limited && (retries += 1) <= 4 && sleep(2**retries * 15)\n  raise\nend","preventionTips":["Serialize SophtronItem imports (queue with concurrency 1) instead of parallel fan-out","Poll job status at 5-10s intervals, not 1s","Add jitter to scheduled syncs so many items do not start on the same tick"],"tags":["sophtron","http-429","rate-limit","throttling","backoff"],"backgroundTag":"http-429-rate-limit","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}