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
- 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.
- 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.
- Confirm the token was not revoked — if it was, create a new token and update the stored credential.
- 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.
- 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
- Presence/format-check the token before building the provider instead of learning about a blank token from a 401.
- Store the token exactly as Mercury issues it, including the 'secret-token:' prefix shown in the sibling error branch.
- When rotating tokens in the Mercury dashboard, update the stored credential in the same change that removes the old one.
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.