we-promise/sure · error · ActiveRecord::RecordNotFound

record_not_found

record_not_found

Error message

The requested resource was not found

What it means

Raised by Import::Preflight#preflight_account (app/models/import/preflight.rb:175) and rendered as HTTP 404 {error: 'record_not_found'} by Api::V1::BaseController#handle_not_found. It fires when the account_id sent to an import preflight request is not a UUID at all, or when it is a UUID that does not belong to an account in the authenticated family, because family.accounts.find is family-scoped. The valid_uuid? guard runs first so Postgres never receives a malformed uuid and throws its own 'invalid input syntax for type uuid' error.

Source

Thrown at app/models/import/preflight.rb:175

            filename: filename,
            content_type: content_type,
            content: content,
            parsed_rows_count: parsed_rows_count,
            csv_headers: csv_headers,
            missing_required_headers: missing_required_headers,
            errors: errors,
            warnings: warnings
          )
        }
      )
    end

    def import_config_params
      params.slice(*CONFIG_PARAM_KEYS)
    end

    def preflight_account
      raise ActiveRecord::RecordNotFound unless Api::V1::BaseController.valid_uuid?(params[:account_id])

      family.accounts.find(params[:account_id])
    end

    def csv_upload_attributes
      if params[:file].present?
        csv_file_upload_attributes(params[:file])
      elsif params[:raw_file_content].present?
        csv_raw_content_attributes(params[:raw_file_content].to_s)
      end
    end

    def csv_file_upload_attributes(file)
      raise_response csv_file_too_large_response if file.size > Import.max_csv_size
      raise_response invalid_csv_file_type_response unless Import::ALLOWED_CSV_MIME_TYPES.include?(file.content_type)

      [
        file.read,

View on GitHub (pinned to e69894adb9)

Solutions

  1. Fetch GET /api/v1/accounts with the same API key and use the id field of an account in that family
  2. Omit account_id entirely when the preflight does not need account scoping
  3. Strip whitespace/brackets and validate UUID format client-side before sending
  4. If the account was deleted, recreate it or pick another account before importing

Example fix

# before
curl -H 'X-Api-Key: k' 'https://app/api/v1/imports/preflight?account_id=ACC-1042'
# → 404 record_not_found

# after
ID=$(curl -s -H 'X-Api-Key: k' https://app/api/v1/accounts | jq -r '.data[0].id')
curl -H 'X-Api-Key: k' "https://app/api/v1/imports/preflight?account_id=$ID"
Defensive patterns

Strategy: validation

Validate before calling

UUID_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i
account_id = account_id.to_s.strip.gsub(/[{}]/, '')
raise ArgumentError, 'account_id must be a UUID' unless account_id.match?(UUID_RE)
ids = api.get('/api/v1/accounts').body['data'].map { |a| a['id'] }
raise ArgumentError, 'account_id not in this family' unless ids.include?(account_id)

Type guard

def valid_uuid?(value)
  value.is_a?(String) && value.match?(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i)
end

Prevention

When it happens

Trigger: Calling the imports preflight endpoint with params[:account_id] blank; a slug or account number like 'ACC-1042'; a bracketed/URL-encoded UUID such as %7B...%7D; or a valid UUID of an account owned by a different family or already deleted.

Common situations: Copy-pasting an account number from the web UI instead of the UUID from GET /api/v1/accounts; using an API key issued for a different family or self-hosted instance; a stale account_id cached client-side after the account was deleted; HTTP clients that URL-encode braces around UUIDs.

Related errors


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