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
- Reproduce connect_portfolio_exchange in console and inspect the raw payload to see the actual response shape
- Check CoinStats API changelog/version differences and update the Provider::Coinstats response mapping if the field was renamed
- Fix incomplete test stubs/cassettes to include portfolioId
- 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
- Pin VCR cassettes/stubs for connect_portfolio_exchange to real payloads that include portfolioId
- Wrap provider response parsing so missing expected fields surface as a Result failure with payload diagnostics rather than deep exceptions
- Log the raw payload (DebugLogEntry) when the field is missing to catch CoinStats schema changes early
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
- No data returned from search endpoint
- No time series data returned for symbol #{av_symbol}
- Binance credentials not configured
- IndexaCapital provider is not configured
- No metadata returned for symbol #{av_symbol}
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/be89bbc6a3ff4fb7.
Report an issue: GitHub.