we-promise/sure · error · Provider::Sophtron::Error
invalid_response
invalid_response
Error message
Invalid Sophtron response format
What it means
Raised by Provider::Sophtron's extract_array_response helper when the parsed response is neither an Array nor a Hash containing any of the expected keys (it normalizes with with_indifferent_access and looks up each candidate key). It is a shape guard: Sophtron returned 2xx JSON, but in a form the caller's key list does not cover - typically a schema change or an error object wearing a success status. The full parsed payload is attached as details.
Source
Thrown at app/models/provider/sophtron.rb:323
:post,
"/UserInstitution/GetUserInstitutionAccounts",
body: { UserInstitutionID: user_institution_id }
)
extract_array_response(parsed, :accounts, :Accounts)
end
def extract_array_response(parsed, *keys)
return parsed if parsed.is_a?(Array)
return [] if parsed.respond_to?(:empty?) && parsed.empty?
if parsed.respond_to?(:with_indifferent_access)
parsed = parsed.with_indifferent_access
keys.each do |key|
return Array(parsed[key]) if parsed.key?(key)
end
end
raise Error.new("Invalid Sophtron response format", :invalid_response, details: parsed)
end
def request(method, api_path, body: nil, parse_json: true)
options = { headers: auth_headers(method: method, api_path: api_path) }
options[:body] = body.to_json if body
response = self.class.public_send(method, "#{base_url}#{api_path}", options)
handle_response(response, parse_json: parse_json)
rescue Error
raise
rescue SocketError, Net::OpenTimeout, Net::ReadTimeout => e
raise Error.new("Sophtron request failed: #{e.message}", :request_failed)
rescue StandardError => e
raise Error.new("Sophtron request failed: #{e.message}", :request_failed)
end
def auth_headers(method:, api_path:)
{View on GitHub (pinned to e69894adb9)
Solutions
- Inspect err details / logs - the parsed payload shows exactly which keys arrived; if one is 'error', chase the upstream message
- Reproduce the raw call with curl using the same auth header to see the true envelope
- If Sophtron renamed the field, add the new key to the *keys argument of the extract_array_response call site
- Keep the previous data snapshot on this failure instead of treating it as an empty result
Example fix
# before arr = extract_array_response(parsed, :jobs) # after - accept the renamed envelope from a newer Sophtron API arr = extract_array_response(parsed, :jobs, :data, :results)
Defensive patterns
Strategy: type-guard
Type guard
def sophtron_array_envelope?(parsed, *keys)
return true if parsed.is_a?(Array)
return false unless parsed.is_a?(Hash)
keys.any? { |k| parsed.key?(k.to_s) || parsed.key?(k.to_sym) }
end Try / catch
begin
items = client.get_institutions # passes through extract_array_response
rescue Provider::Sophtron::Error => e
if e.error_type == :invalid_response
log_payload_shape(e.message) # details holds the parsed payload
keep_previous_data # never downgrade to empty
else
raise
end
end Prevention
- Never convert this error into an empty array - it means the shape was unexpected, not that data is empty
- Keep VCR/recorded cassettes of each Sophtron endpoint envelope so schema changes break tests first
- Extend the key list in extract_array_response call sites when adopting new endpoints
When it happens
Trigger: A Sophtron endpoint normally returning {"jobs": [...]} or {"data": [...]} instead returns {"error": ...}, an envelope with a new key name after an API update, or a nested structure the key list was never taught (e.g. institution-specific responses).
Common situations: Sophtron ships an API revision renaming response fields; a middleware returns a JSON error object with HTTP 200; a new institution's data shape differs from existing ones; the key list wasn't extended when a new endpoint was wired through this helper.
Related errors
- SnapTrade positions response has no results array (keys: #{r
- invalid_access_key
- request_failed
- bad_request
- unauthorized
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/cff48f87c1a512fb.
Report an issue: GitHub.