{"record":{"id":"9c8bcbb2a6f705c3","repo":"we-promise/sure","slug":"configuration-error","errorCode":"configuration_error","errorMessage":"Up access token is required","messagePattern":"Up access token is required","errorType":"exception","errorClass":"Provider::Up::UpError","httpStatus":null,"severity":"error","filePath":"app/models/provider/up.rb","lineNumber":20,"sourceCode":"  include HTTParty\n  extend SslConfigurable\n\n  DEFAULT_BASE_URL = \"https://api.up.com.au/api/v1\".freeze\n  DEFAULT_PAGE_SIZE = 100\n  # Host that authenticated requests (bearer token) may be sent to. Absolute URLs\n  # taken from API responses (links.next) are validated against this.\n  ALLOWED_HOST = URI.parse(DEFAULT_BASE_URL).host.freeze\n\n  headers \"User-Agent\" => \"Sure Finance Up Client\"\n  default_options.merge!({ timeout: 120 }.merge(httparty_ssl_options))\n\n  attr_reader :access_token\n\n  # Build a client with the family's Up personal access token. Raises if blank.\n  def initialize(access_token)\n    @access_token = access_token.to_s.strip\n\n    raise UpError.new(\"Up access token is required\", :configuration_error) if @access_token.blank?\n  end\n\n  # GET /util/ping - validates the personal access token.\n  # Returns the parsed payload (contains meta.id / meta.statusEmoji) or raises UpError.\n  def ping\n    get(\"util/ping\")\n  end\n\n  # GET /accounts - returns an array of flattened account hashes.\n  # Each hash: { id:, displayName:, accountType:, ownershipType:, balance: {...}, createdAt: }\n  def get_accounts\n    fetch_all_resources(\"accounts\").map { |resource| flatten_account(resource) }\n  end\n\n  # GET /accounts/{id}/transactions - returns an array of flattened transaction hashes.\n  # Both HELD (pending) and SETTLED (posted) transactions are returned; callers derive\n  # pending status from the :status field.\n  def get_account_transactions(account_id:, since: nil, until_date: nil, page_size: DEFAULT_PAGE_SIZE)","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/up.rb#L2-L38","documentation":"Provider::Up's constructor strips the supplied personal access token and immediately raises UpError with error_type=:configuration_error when it is blank. This is a fail-fast guard: the client refuses to make any HTTP call (such as GET /util/ping) without credentials, so the error is always raised locally, not by Up's API.","triggerScenarios":"Provider::Up.new(nil), Provider::Up.new(\"\") or Provider::Up.new(\"   \") - i.e. constructing the client from an UpItem whose stored token is empty; token attribute nil because the record was created without one or encryption misconfiguration returned nil.","commonSituations":"Sync job iterating UpItems including half-configured ones; ActiveRecord Encryption env keys missing/different from those used when the token was written, so the ciphertext cannot be decrypted and the attribute reads blank; UI flow that creates the UpItem before the user pastes the token; tests using factories without the token attribute.","solutions":["Ensure every UpItem persists a non-empty up_access_token before enqueueing syncs (validate presence at creation)","Scope sync jobs to items where the token is present: UpItem.where.not(access_token: nil)","If tokens vanished after a deploy, check ACTIVE_RECORD_ENCRYPTION_* env vars match those used at write time","Call provider.ping after construction to validate the token against Up's API before long imports"],"exampleFix":"# before\nUpItem.find_each { |item| Provider::Up.new(item.access_token).get_accounts }\n\n# after - skip/flag unconfigured items instead of raising\nUpItem.find_each do |item|\n  token = item.access_token.to_s.strip\n  next item.update!(status: \"requires_update\") if token.blank?\n  Provider::Up.new(token).get_accounts\nend","handlingStrategy":"validation","validationCode":"token = item.access_token.to_s.strip\nraise ArgumentError, \"Up access token missing for UpItem #{item.id}\" if token.blank?\nprovider = Provider::Up.new(token)","typeGuard":"def up_configured?(item)\n  item.access_token.to_s.strip.present?\nend","tryCatchPattern":"begin\n  Provider::Up.new(token).ping\nrescue Provider::Up::UpError => e\n  raise if e.error_type != :configuration_error\n  item.update!(status: \"requires_update\") # flag for user re-entry, no HTTP happened\nend","preventionTips":["Validate token presence when the UpItem is created, not only when syncing","Scope sync jobs to items with present credentials","Keep ActiveRecord Encryption env keys stable across deploys or plan a re-encryption migration","ping the token right after save to fail at input time, not sync time"],"tags":["up-bank","configuration","missing-token","fail-fast","access-token"],"backgroundTag":"missing-api-key","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}