we-promise/sure · error · Provider::Coinstats::Error

CoinStats did not return a portfolioId

Error message

CoinStats did not return a portfolioId

What it means

After provider.connect_portfolio_exchange succeeds, Brex-style ExchangeLinker expects a portfolioId in the response payload. If payload[:portfolioId] is blank it raises Provider::Coinstats::Error: the CoinStats API call worked but the response body did not contain the id needed to persist the exchange link (exchange_portfolio_id, institution fields).

Source

Thrown at app/models/coinstats_item/exchange_linker.rb:33

  def link
    return Result.new(success?: false, created_count: 0, errors: [ "Exchange is required" ]) if connection_id.blank?
    return Result.new(success?: false, created_count: 0, errors: [ "Exchange credentials are required" ]) if connection_fields.blank?

    created_count = 0
    exchange = fetch_exchange_definition
    validate_required_fields!(exchange)

    response = provider.connect_portfolio_exchange(
      connection_id: connection_id,
      connection_fields: connection_fields,
      name: name.presence || default_portfolio_name(exchange)
    )

    return Result.new(success?: false, created_count: 0, errors: [ response.error.message ]) unless response.success?

    payload = response.data.with_indifferent_access
    portfolio_id = payload[:portfolioId]
    raise Provider::Coinstats::Error, "CoinStats did not return a portfolioId" if portfolio_id.blank?

    coins = provider.list_portfolio_coins(portfolio_id: portfolio_id)

    ActiveRecord::Base.transaction do
      coinstats_item.update!(
        exchange_connection_id: connection_id,
        exchange_portfolio_id: portfolio_id,
        institution_id: connection_id,
        institution_name: exchange[:name],
        raw_institution_payload: exchange
      )

      if coins.nil?
        Rails.logger.warn "CoinstatsItem::ExchangeLinker - Initial portfolio coin fetch missing for item #{coinstats_item.id} portfolio #{portfolio_id}; deferring local account creation to background sync"
      else
        coinstats_account = exchange_portfolio_account_manager.upsert_account!(
          coins_data: coins,
          portfolio_id: portfolio_id,

View on GitHub (pinned to e69894adb9)

Solutions

  1. Reproduce connect_portfolio_exchange in console and inspect the raw payload to see the actual response shape
  2. Check CoinStats API changelog/version differences and update the Provider::Coinstats response mapping if the field was renamed
  3. Fix incomplete test stubs/cassettes to include portfolioId
  4. Capture a DebugLogEntry with the payload so support can triage recurring exchanges
Defensive patterns

Strategy: try-catch

Try / catch

rescue Provider::Coinstats::Error around the exchange-link call; report a link-failed result with the message and keep the item unlinked (the surrounding ActiveRecord transaction already rolls back the partial link)

Prevention

When it happens

Trigger: connect_portfolio_exchange(connection_id:, connection_fields:, name:) returns success but the JSON body lacks or empties portfolioId — a CoinStats API schema/version change, a renamed field, or a stubbed/mocked response in tests that omits it.

Common situations: CoinStats deploys a breaking response change; VCR cassettes or fixtures stubbing the connect endpoint with an incomplete payload; connecting an exchange type whose response legitimately omits portfolioId.

Related errors


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