we-promise/sure · error · Provider::Mercury::MercuryError

unauthorized

unauthorized

Error message

Invalid API token

What it means

Raised by Provider::Mercury#handle_response when the Mercury banking API returns HTTP 401. The body is passed to parse_error_message, and "Invalid API token" is the fallback when the body is not parseable JSON or carries no recognized errorCode/message — i.e. Mercury rejected the Bearer token without a specific explanation. The token sent is the one given to Provider::Mercury.new(token) via the Authorization header.

Source

Thrown at app/models/provider/mercury.rb:120

    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)
      errors = parsed[:errors] || {}

      case errors[:errorCode]
      when "ipNotWhitelisted"

View on GitHub (pinned to e69894adb9)

Solutions

  1. Verify the token stored for the Mercury item is present and identical to the active token in the Mercury dashboard (Settings > API tokens), then re-save it.
  2. Check the token format: Mercury expects the full token string; parse_error_message's sibling case warns the 'secret-token:' prefix must be kept, so do not strip or truncate it.
  3. Confirm the token was not revoked — if it was, create a new token and update the stored credential.
  4. If the token is IP-whitelisted, confirm the outbound IP matches; otherwise you would normally see the ipNotWhitelisted message instead, which also arrives as 401.
  5. Reproduce with curl: curl -H 'Authorization: Bearer <token>' https://api.mercury.com/api/v1/accounts to see the raw 401 body.

Example fix

# before
token = ENV["MERCURY_TOKEN"]&.strip # may be nil, 401 at runtime
provider = Provider::Mercury.new(token)
provider.get_accounts

# after
raise ArgumentError, "MERCURY_TOKEN is blank" if ENV["MERCURY_TOKEN"].blank?
token = ENV["MERCURY_TOKEN"].strip
provider = Provider::Mercury.new(token)
begin
  provider.get_accounts
rescue Provider::Mercury::MercuryError => e
  raise if e.error_type != :unauthorized
  # flag the stored token for re-entry instead of retrying
  Item.mark_token_invalid!(e)
end
Defensive patterns

Strategy: try-catch

Validate before calling

# before constructing the client
token = item.settings["token"].to_s.strip
if token.blank?
  raise ArgumentError, "Mercury token missing for item #{item.id}"
end
provider = Provider::Mercury.new(token)

Try / catch

begin
  provider.get_accounts
rescue Provider::Mercury::MercuryError => e
  if e.error_type == :unauthorized
    # mark credential for re-entry; never auto-retry with the same token
    item.credentials.mark_invalid!
  else
    raise
  end
end

Prevention

When it happens

Trigger: Any HTTParty GET (get_accounts, get_account, get_account_transactions) with a token that is empty/nil ('Bearer ' header), revoked or deleted in the Mercury dashboard, copied with whitespace or a wrong value, or a token whose 'secret-token:' prefix/format is wrong but in a way Mercury reports without a structured errorCode. Also hit when base_url points at a different Mercury environment that does not know the token.

Common situations: Token rotated in the Mercury dashboard but the app still stores the old one in the provider settings/credentials; ENV var not loaded in a new deploy so token is nil; token pasted with quotes or trailing newline; test suite hitting production API with a fixture token; Mercury token created for a different organization.

Related errors


AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21). Data as JSON: /api/errors/684434990ff8a772. Report an issue: GitHub.