we-promise/sure · error · Provider::Coinbase::AuthenticationError

Unauthorized - check your API key and secret

Error message

Unauthorized - check your API key and secret

What it means

Raised by Provider::Coinbase#handle_response when the Coinbase API returns HTTP 401. The message is extract_error_message(parsed) — which reads parsed.dig("errors", 0, "message") — or the fallback "Unauthorized - check your API key and secret". Coinbase CDP keys fail auth when the key/secret pair is wrong, the key was revoked, or it belongs to a different environment (sandbox vs production).

Solutions

  1. Verify the stored key and secret match an active key in the Coinbase Developer Platform (correct project and environment).
  2. Re-copy both values (watch for trailing newlines/spaces) and update the stored credential, then retry.
  3. Confirm you are hitting the right base URL for the key type (sandbox vs production).
  4. If auth still fails, create a fresh key/secret pair and swap it in.
  5. Handle Provider::Coinbase::AuthenticationError by flagging the connection for re-auth instead of dead-looping retries.

Example fix

# before
provider = Provider::Coinbase.new(api_key: key, api_secret: secret)
balance = provider.get_accounts

# after
begin
  provider = Provider::Coinbase.new(api_key: key.strip, api_secret: secret.strip)
  balance = provider.get_accounts
rescue Provider::Coinbase::AuthenticationError => e
  coinbase_account.update!(status: :reauth_required)
  Rails.logger.warn("Coinbase auth failed: #{e.message}")
end
Defensive patterns

Strategy: try-catch

Validate before calling

raise ArgumentError, "Coinbase credentials incomplete" if api_key.to_s.strip.empty? || api_secret.to_s.strip.empty?

Type guard

def coinbase_auth_error?(err)
  err.is_a?(Provider::Coinbase::AuthenticationError)
end

Try / catch

begin
  accounts = provider.get_accounts
rescue Provider::Coinbase::AuthenticationError => e
  connection.update!(status: :reauth_required)
  Rails.logger.warn("Coinbase auth failed: #{e.message}")
end

Prevention

When it happens

Trigger: Any Coinbase request with a revoked, expired, or mistyped CDP API key/secret; using a sandbox key against api.coinbase.com (or vice versa); whitespace or truncated secrets pasted from the Coinbase developer console; keys deleted when the CDP project was removed.

Common situations: Credential rotation on the Coinbase developer console without updating the stored secret; env vars overridden per-environment (test key in prod); secrets mangled by shell interpolation or YAML formatting; team members revoking old keys not knowing the app still uses them.

Understand the failure class

Related errors


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

Appendix: source

Thrown at app/models/provider/coinbase.rb:201

      "#{message}.#{encoded_signature}"
    end

    def auth_headers(method, path)
      {
        "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)