we-promise/sure · error · Provider::Coinbase::ApiError
API error: #{response.code}
Error message
API error: #{response.code} What it means
Raised by Provider::Coinbase#handle_response (coinbase.rb:206) as Provider::Coinbase::ApiError for any non-2xx status that is not 401 or 429. The message is Coinbase's own error text (parsed.dig("errors", 0, "message") || parsed["error"] || parsed["message"]) or the literal "API error: #{response.code}" when the body yields nothing parseable.
Source
Thrown at app/models/provider/coinbase.rb:206
"Authorization" => "Bearer #{generate_jwt(method, path)}",
"Content-Type" => "application/json"
}
end
def handle_response(response)
parsed = response.parsed_response
case response.code
when 200..299
parsed.is_a?(Hash) ? parsed : { "data" => parsed }
when 401
error_msg = extract_error_message(parsed) || "Unauthorized - check your API key and secret"
raise AuthenticationError, error_msg
when 429
raise RateLimitError, "Rate limit exceeded"
else
error_msg = extract_error_message(parsed) || "API error: #{response.code}"
raise ApiError, error_msg
end
end
def extract_error_message(parsed)
return parsed if parsed.is_a?(String)
return nil unless parsed.is_a?(Hash)
parsed.dig("errors", 0, "message") || parsed["error"] || parsed["message"]
end
end
View on GitHub (pinned to e69894adb9)
Solutions
- Read the message text: a Coinbase error string points at the exact bad param/resource; "API error: <code>" means the body was unparseable — capture the raw response body to diagnose.
- Fix the offending request parameter/resource id for 4xx errors.
- Retry 5xx with backoff and check the Coinbase status page during suspected incidents.
- Pin/request the correct API version in headers if a behavior change is suspected.
- Isolate per-resource failures so one 4xx does not abort a whole import.
Example fix
# before
begin
txs = provider.get_transactions(account_id: id)
rescue => e
raise # anything kills the sync
end
# after
begin
txs = provider.get_transactions(account_id: id)
rescue Provider::Coinbase::ApiError => e
if /5\d\d/.match?(e.message)
raise RetryableJobError, e.message
else
Rails.logger.error("Coinbase call for #{id} failed: #{e.message}")
end
end Defensive patterns
Strategy: try-catch
Type guard
def coinbase_api_error?(err) err.is_a?(Provider::Coinbase::ApiError) end
Try / catch
begin
txs = provider.get_transactions(account_id: id)
rescue Provider::Coinbase::ApiError => e
if /API error: 5/.match?(e.message)
raise RetryableJobError, e.message
else
Rails.logger.error("Coinbase failure for #{id}: #{e.message}")
end
end Prevention
- When the message is the generic "API error: <code>", capture the raw body — the JSON error path yielded nothing.
- Treat 5xx as retryable and 4xx as permanent in job retry policy.
- Pin the Coinbase API version in request headers to survive behavior drift.
- Scope account ids to the same key that fetched them.
When it happens
Trigger: HTTP 400 for invalid request params (bad pagination cursor, unknown account id, malformed body); HTTP 404 for deleted resources; HTTP 500/503 during Coinbase incidents; HTML/JSON-parse failures from proxies that leave parsed nil so the generic "API error: <code>" shows; version changes where an endpoint moves or changes shape.
Common situations: Coinbase API version drift after an announcement (fields renamed, endpoints deprecated); passing an account id from a different key's scope; deploys behind a corporate proxy that rewrites error bodies; incident-window syncs returning 503s that a job retries too aggressively.
Related errors
- Unauthorized - check your API key and secret
- Rate limit exceeded
- Kraken API request failed: #{response.code}
- fetch_failed
- unknown
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/2d9a2ebfd9537872.
Report an issue: GitHub.