we-promise/sure · error · Provider::Mercury::MercuryError
bad_request
bad_request
Error message
Bad request to Mercury API: #{response.body} What it means
HTTP 400 branch of Provider::Mercury#handle_response (app/models/provider/mercury.rb:114-116). Thrown as MercuryError(:bad_request) whenever Mercury answers with 400; the raw response body is embedded in the error message and logged ('Mercury API: Bad request - ...'). For Mercury this typically means the query string built at lines 63-82 violates API constraints: invalid parameter values or types.
Source
Thrown at app/models/provider/mercury.rb:116
end
private
def auth_headers
{
"Authorization" => "Bearer #{token}",
"Content-Type" => "application/json",
"Accept" => "application/json"
}
end
def handle_response(response)
case response.code
when 200
JSON.parse(response.body, symbolize_names: true)
when 400
Rails.logger.error "Mercury API: Bad request - #{response.body}"
raise MercuryError.new("Bad request to Mercury API: #{response.body}", :bad_request)
when 401
# Parse the error response for more specific messages
error_message = parse_error_message(response.body)
raise MercuryError.new(error_message, :unauthorized)
when 403
raise MercuryError.new("Access forbidden - check your API token permissions", :access_forbidden)
when 404
raise MercuryError.new("Resource not found", :not_found)
when 429
raise MercuryError.new("Rate limit exceeded. Please try again later.", :rate_limited)
else
Rails.logger.error "Mercury API: Unexpected response - Code: #{response.code}, Body: #{response.body}"
raise MercuryError.new("Failed to fetch data: #{response.code} #{response.message} - #{response.body}", :fetch_failed)
end
end
def parse_error_message(body)
parsed = JSON.parse(body, symbolize_names: true)View on GitHub (pinned to e69894adb9)
Solutions
- Read the embedded body in the error message — Mercury states the exact offending parameter.
- Clamp pagination params: limit between 1 and 500, offset >= 0, before calling.
- Validate the account_id format (Mercury account UUIDs) and refresh via get_accounts if suspect.
- Ensure start_date <= end_date and both are real dates.
Example fix
// before client.get_account_transactions(acct_id, offset: params[:offset], limit: params[:limit]) // after limit = [[params[:limit].to_i, 1].max, 500].min offset = [params[:offset].to_i, 0].max client.get_account_transactions(acct_id.to_s, offset: offset, limit: limit)
Defensive patterns
Strategy: validation
Validate before calling
limit = [[limit.to_i, 1].max, 500].min # Mercury max page size offset = [offset.to_i, 0].max raise ArgumentError, 'start after end' if from && to && from > to
Type guard
def bad_request?(e) e.is_a?(Provider::Mercury::MercuryError) && e.error_type == :bad_request end
Try / catch
begin
client.get_account_transactions(acct_id, offset: offset, limit: limit)
rescue Provider::Mercury::MercuryError => e
raise unless e.error_type == :bad_request
DebugLogEntry.capture(category: 'mercury', level: 'error',
message: e.message, provider_key: 'mercury')
skip_account(acct_id)
end Prevention
- Clamp limit to 1..500 and offset to >= 0 in one shared helper used by every caller.
- Validate account ids against get_accounts output before detail calls.
- Never retry 400s — log the embedded body and fix the parameter.
When it happens
Trigger: get_account_transactions with limit exceeding Mercury's maximum page size (500), limit/offset zero or negative, dates that survive #to_date but are rejected server-side, or a malformed account UUID in the path; any GET /accounts or /account/{id} call when Mercury changes request validation rules.
Common situations: Hard-coded limit above the API maximum after a provider policy change; offset math bugs producing negatives during pagination; account ids imported from another provider or truncated; date boundaries like start > end.
Related errors
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/3531e61604cf2bf2.
Report an issue: GitHub.