{"record":{"id":"d90b941faf69cb3e","repo":"we-promise/sure","slug":"rate-limited-d90b94","errorCode":"rate_limited","errorMessage":"Rate limit exceeded","messagePattern":"Rate limit exceeded","errorType":"exception","errorClass":"Provider::Redbark::RateLimitError","httpStatus":429,"severity":"warning","filePath":"app/models/provider/redbark.rb","lineNumber":248,"sourceCode":"    # Redbark error envelope: { error: { message, code, details } }\n    # Error messages carry the parsed provider message only, never the raw\n    # response body - callers log and re-log these strings.\n    def handle_response(response)\n      case response.code\n      when 200, 201\n        JSON.parse(response.body, symbolize_names: true)\n      when 400\n        raise Error.new(\"Bad request: #{error_message_from(response)}\", :bad_request)\n      when 401\n        raise AuthenticationError.new(\"Invalid API key\", :unauthorized)\n      when 403\n        raise AuthenticationError.new(\"Access forbidden - your Redbark plan may not include API access\", :access_forbidden)\n      when 404\n        raise Error.new(\"Resource not found\", :not_found)\n      when 410\n        raise Error.new(\"Endpoint requires an accountId: #{error_message_from(response)}\", :bad_request)\n      when 429\n        raise RateLimitError.new(\"Rate limit exceeded\", :rate_limited)\n      when 500..599\n        raise ServerError.new(\"Redbark server error (#{response.code})\", :server_error)\n      else\n        raise Error.new(\"Unexpected response #{response.code}: #{error_message_from(response)}\", :unknown)\n      end\n    end\n\n    def error_message_from(response)\n      parsed = JSON.parse(response.body)\n      parsed.dig(\"error\", \"message\") || \"no error message provided\"\n    rescue JSON::ParserError\n      \"unparseable error response\"\n    end\nend\n","sourceCodeStart":230,"sourceCodeEnd":263,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/redbark.rb#L230-L263","documentation":"Raised by Provider::Redbark's handle_response on HTTP 429 as RateLimitError. Unlike most clients, Redbark's with_retries explicitly includes RateLimitError in its retry rescue list, so a 429 is retried up to 3 times with exponential backoff+jitter before this error surfaces to the caller.","triggerScenarios":"Burst-syncing many accounts in parallel, multiple Sidekiq jobs sharing one API key, or hammering /transactions across a large import — enough sustained 429s to exhaust the internal retries.","commonSituations":"Background sync jobs overlapping at schedule boundaries; one API key shared across staging and production; retry storms amplifying request volume.","solutions":["Reduce sync concurrency (fewer parallel jobs per API key)","Stagger job schedules so bulk account syncs don't align","Back off at the caller level when this surfaces — the library already retried 3 times with backoff","Check the Redbark dashboard for the key's rate tier if limits are consistently tight"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":"def redbark_rate_limited?(error)\n  error.is_a?(Provider::Redbark::RateLimitError)\nend","tryCatchPattern":"begin\n  redbark.get_transactions(connection_id: cid, account_id: aid, start_date: from, end_date: to)\nrescue Provider::Redbark::RateLimitError\n  RedbarkSyncJob.perform_in(jittered_delay(10.minutes), account_id: aid) # library already retried 3x\nend","preventionTips":["Cap concurrent Redbark jobs per API key (e.g. good_job/queue concurrency limits)","Stagger scheduled syncs so accounts don't all fire at :00","Reserve immediate retries for the library; callers should use longer scheduled backoff"],"tags":["redbark","http-429","rate-limit","retry","throttling"],"backgroundTag":"http-429-rate-limited","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}