we-promise/sure · error · AuthenticationError

unauthorized

unauthorized

Error message

Authentication token missing in response

What it means

POST /auth/authenticate returned 200/201 with parseable JSON, but payload[:token] is blank. This only happens in username/document/password mode - when an api_token is configured, token_auth? is true and authenticate! is never called (token = api_token directly). It means authentication 'succeeded' at HTTP level but the response envelope does not contain a usable JWT.

Source

Thrown at app/models/provider/indexa_capital.rb:187

    end

    def token
      @token ||= token_auth? ? @api_token : authenticate!
    end

    def authenticate!
      response = self.class.post(
        "#{base_url}/auth/authenticate",
        headers: base_headers,
        body: {
          username: username,
          document: document,
          password: password
        }.to_json
      )
      payload = handle_response(response)
      jwt = payload[:token]
      raise AuthenticationError.new("Authentication token missing in response", :unauthorized) if jwt.blank?

      jwt
    end

    def handle_response(response)
      case response.code
      when 200, 201
        begin
          JSON.parse(response.body, symbolize_names: true)
        rescue JSON::ParserError => e
          raise Error.new("Invalid JSON in response: #{e.message}", :bad_response)
        end
      when 400
        Rails.logger.error "IndexaCapital API: Bad request - #{response.body}"
        raise Error.new("Bad request: #{response.body}", :bad_request)
      when 401
        raise AuthenticationError.new("Invalid credentials", :unauthorized)
      when 403

View on GitHub (pinned to e69894adb9)

Solutions

  1. Switch to api_token mode: generate a token from the Indexa dashboard/env and pass api_token: - it bypasses authenticate! entirely
  2. Log payload.keys on failure to detect envelope changes immediately
  3. Verify the triple manually (username / document / password) against a curl POST to /auth/authenticate
  4. If the shape changed, update the jwt = payload[:token] extraction to the new field and add a regression test

Example fix

# before
Provider::IndexaCapital.new(username: u, document: d, password: p)

# after
Provider::IndexaCapital.new(api_token: credentials.api_token.presence || begin
  Provider::IndexaCapital.new(username: u, document: d, password: p).tap(&:token)
end)
# simpler: just prefer the pre-generated token
Provider::IndexaCapital.new(api_token: settings.indexa_api_token)
Defensive patterns

Strategy: fallback

Validate before calling

# prefer token mode: it never calls authenticate!, so this error cannot occur
client = if settings.indexa_api_token.present?
  Provider::IndexaCapital.new(api_token: settings.indexa_api_token.to_s.strip)
else
  Provider::IndexaCapital.new(username: u, document: d, password: p)
end

Type guard

def indexa_token_missing?(error)
  error.is_a?(Provider::IndexaCapital::AuthenticationError) &&
    error.message == "Authentication token missing in response"
end

Try / catch

begin
  client.list_accounts
rescue Provider::IndexaCapital::AuthenticationError => e
  raise unless e.message == "Authentication token missing in response"
  raise unless settings.indexa_api_token.present? # fallback available?
  Provider::IndexaCapital.new(api_token: settings.indexa_api_token).list_accounts
end

Prevention

When it happens

Trigger: Indexa changes the auth response shape (token renamed or nested); the account is in a state that returns 200 with an error-ish body but no token (e.g. re-login required, 2FA introduced); an intermediate gateway rewriting the response.

Common situations: Silent API contract changes after Indexa deploys, credentials technically valid but account locked pending re-acceptance of terms, password-mode integrations breaking while token-mode ones keep working.

Understand the failure class

Related errors


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