a method to submit application for new account
            
        :param customer_ip: string with ip address of applicant
        :param first_name: string with first name of applicant
        :param last_name: string with last name of applicant
        :param tax_id: string with tax id number of applicant
        :param date_of_birth: string with ISO format of date of birth of applicant
        :param address_line_1: string with first line of street address of applicant
        :param city_name: string with name of city of address of applicant
        :param state_code: string with code for the state of address of applicant
        :param postal_code: string with postal code of address of applicant
        :param phone_number: string with phone number and area code of applicant
        :param email_address: string with email address of applicant
        :param citizenship_country: string with ISO 3166 alpha-3 country code of citizenship of applicant
        :param employment_status: string with employment status of applicant
        :param product_id: integer with id of account product to apply for
        :param funding_amount: float with amount of dollars to initially fund account
        :param account_number: string with pre-existing bank account number of applicant
        :param routing_number: string with aba routing number for bank of pre-existing account of applicant
        :param backup_withholding: [optional] boolean to indicate backup withholding on accounts of applicant
        :param phone_type: [optional] string with type of phone of applicant
        :param accept_tcpa: boolean to accept to be contacted by citizen one marketing on their phone number 
        :param accept_terms: boolean to accept the terms and conditions associated with new account
        :param address_line_2: [optional] string with second line of address of applicant
        :param middle_name: [optional] string with middle name of applicant
        :param tax_id_type: string with type of tax id of applicant
        :param secondary_citizenship_country: [optional] string with ISO 3166 alpha-3 country code of secondary citizenship
        :param job_title: [optional] string with job title of applicant
        :param annual_income: [optional] integer with dollar value of annual income of applicant 
        :param cd_term: [optional] string with term for the cd account product to apply for
        :param funding_type: string with funding method selected by the applicant to fund new account
        :param account_owner: string with role of applicant who owns pre-existing bank account
        :param secondary_application: dictionary with applicant fields of secondary account holder
        :return: dictionary with successful response details in ['json'] key

        response details:
        { 
            "error": "",
            "code": 200,
            "method": "GET",
            "url": "https://...",
            "headers": { 
                "Location": "https://www.capitalone.com"
            },
            "json": { 
                "applicationId": "adfasdf812381asdf",
                "applicationStatus": "Approved",
                "applicationStatusDescription": "The application is approved and account is created",
                "bankABANumber": "031176110",
                "accountNumber": "12345678909876"
            }
        }

def account_application(self, customer_ip, first_name, last_name, tax_id, date_of_birth, address_line_1, city_name, state_code, postal_code, phone_number, email_address, citizenship_country, employment_status, product_id, funding_amount, account_number, routing_number, backup_withholding=False, phone_type='mobile', accept_tcpa=False, accept_terms=True, address_line_2='', middle_name='', tax_id_type='SSN', secondary_citizenship_country='', job_title='', annual_income=0, cd_term='', funding_type='fundach', account_owner='primary', secondary_application=None):

        '''
            a method to submit application for new account
            
        :param customer_ip: string with ip address of applicant
        :param first_name: string with first name of applicant
        :param last_name: string with last name of applicant
        :param tax_id: string with tax id number of applicant
        :param date_of_birth: string with ISO format of date of birth of applicant
        :param address_line_1: string with first line of street address of applicant
        :param city_name: string with name of city of address of applicant
        :param state_code: string with code for the state of address of applicant
        :param postal_code: string with postal code of address of applicant
        :param phone_number: string with phone number and area code of applicant
        :param email_address: string with email address of applicant
        :param citizenship_country: string with ISO 3166 alpha-3 country code of citizenship of applicant
        :param employment_status: string with employment status of applicant
        :param product_id: integer with id of account product to apply for
        :param funding_amount: float with amount of dollars to initially fund account
        :param account_number: string with pre-existing bank account number of applicant
        :param routing_number: string with aba routing number for bank of pre-existing account of applicant
        :param backup_withholding: [optional] boolean to indicate backup withholding on accounts of applicant
        :param phone_type: [optional] string with type of phone of applicant
        :param accept_tcpa: boolean to accept to be contacted by citizen one marketing on their phone number 
        :param accept_terms: boolean to accept the terms and conditions associated with new account
        :param address_line_2: [optional] string with second line of address of applicant
        :param middle_name: [optional] string with middle name of applicant
        :param tax_id_type: string with type of tax id of applicant
        :param secondary_citizenship_country: [optional] string with ISO 3166 alpha-3 country code of secondary citizenship
        :param job_title: [optional] string with job title of applicant
        :param annual_income: [optional] integer with dollar value of annual income of applicant 
        :param cd_term: [optional] string with term for the cd account product to apply for
        :param funding_type: string with funding method selected by the applicant to fund new account
        :param account_owner: string with role of applicant who owns pre-existing bank account
        :param secondary_application: dictionary with applicant fields of secondary account holder
        :return: dictionary with successful response details in ['json'] key

        response details:
        { 
            "error": "",
            "code": 200,
            "method": "GET",
            "url": "https://...",
            "headers": { 
                "Location": "https://www.capitalone.com"
            },
            "json": { 
                "applicationId": "adfasdf812381asdf",
                "applicationStatus": "Approved",
                "applicationStatusDescription": "The application is approved and account is created",
                "bankABANumber": "031176110",
                "accountNumber": "12345678909876"
            }
        }
        '''
    
        title = '%s.account_application' % self.__class__.__name__
        
        from copy import deepcopy
        
    # validate general inputs
        input_fields = {
            'customer_ip': customer_ip,
            'product_id': product_id,
            'cd_term': cd_term,
            'funding_type': funding_type,
            'funding_amount': funding_amount,
            'account_number': account_number,
            'routing_number': routing_number,
            'account_owner': account_owner,
            'accept_terms': accept_terms,
            'accept_tcpa': accept_tcpa,
            'secondary_application': secondary_application
        }
        for key, value in input_fields.items():
            object_title = '%s(%s=%s)' % (title, key, str(value))
            self.fields.validate(value, '.%s' % key, object_title)

    # validate applicant fields
        app_fields = {
            'address_line_1': address_line_1,
            'address_line_2': address_line_2,
            'city_name': city_name,
            'state_code': state_code,
            'postal_code': postal_code,
            'first_name': first_name,
            'middle_name': middle_name,
            'last_name': last_name,
            'tax_id_type': tax_id_type,
            'tax_id': tax_id,
            'date_of_birth': date_of_birth,
            'email_address': email_address,
            'backup_withholding': backup_withholding,
            'citizenship_country': citizenship_country,
            'secondary_citizenship_country': secondary_citizenship_country,
            'employment_status': employment_status,
            'job_title': job_title,
            'annual_income': annual_income,
            'phone_number': phone_number,
            'phone_type': phone_type
        }
        for key, value in app_fields.items():
            object_title = '%s(%s=%s)' % (title, key, str(value))
            self.fields.validate(value, '.%s' % key, object_title)
    
    # construct url
        url = self.deposits_endpoint + 'account-application'
    
    # construct method specific errors
        error_map = { 
            404: 'Not Found. No products found for the provided productId.',
            409: 'The application could not be processed due to a business error. Currently, this status is only returned when an existing Capital One customer attempts to open a new account using this API.'
        }

    # construct headers
        headers_kwargs = {
            'Customer-IP-Address': customer_ip
        }

    # construct applicant list
        applicant_list = []
        applicants = [ 'primary' ]
        if secondary_application:
            applicants.append('secondary')
    
    # iterate over applicants
        for applicant in applicants:
        
        # substitute in secondary application fields
            if applicant == 'secondary':
                app_fields = secondary_application
                for key, value in app_fields.items():
                    object_title = '%s(%s=%s)' % (title, key, str(value))
                    self.fields.validate(value, '.%s' % key, object_title)  

        # construct applicant kwargs
            applicant_kwargs = {
                'applicantRole': applicant,
                'firstName': app_fields['first_name'],
                'lastName': app_fields['last_name'],
                'homeAddress': {
                    'addressLine1': app_fields['address_line_1'],
                    'city': app_fields['city_name'],
                    'postalCode': app_fields['postal_code']
                },
                'taxIdType': app_fields['tax_id_type'],
                'emailAddress': app_fields['email_address'],
                'backupWithholding': app_fields['backup_withholding'],
                'employmentStatus': app_fields['employment_status']
            }

        # add optional middle name and second address
            if app_fields['middle_name']:
                applicant_kwargs['middleName'] = app_fields['middle_name']
        
        # add state code
            from labpack.datasets.iso_3166_2_US import compile_map as map_3166_2
            state_codes = map_3166_2()
            if not app_fields['state_code'] in state_codes.keys():
                raise ValueError('%s(state_code=%s) must be a valid 3 letter country code.' % (title, app_fields['state_code']))
            applicant_kwargs['homeAddress']['stateCode'] = app_fields['state_code']
        
        # add tax id
            if len(app_fields['tax_id']) < 10:
                tax_id_temp = app_fields['tax_id']
                tax_string = tax_id_temp[0:3] + '-' + tax_id[3:5] + '-' + tax_id[5:]
            else:
                tax_string = tax_id
            applicant_kwargs['taxId'] = tax_string
        
        # add date of birth
            if not '-' in app_fields['date_of_birth']:
                dob_temp = app_fields['date_of_birth']
                app_fields['date_of_birth'] = dob_temp[0:4] + '-' + dob_temp[4:6] + '-' + dob_temp[6:8]
            if not len(app_fields['date_of_birth']) == 10:
                raise ValueError('%s(date_of_birth=%s) must be in ISO format YYYY-MM-DD or YYYYMMDD' % (title, app_fields['date_of_birth']))
            applicant_kwargs['dateOfBirth'] = app_fields['date_of_birth']
        
        # add citizenship country
            from labpack.datasets.iso_3166 import compile_map as map_3166
            country_codes = map_3166()
            if not app_fields['citizenship_country'] in country_codes.keys():
                raise ValueError('%s(citizenship_country=%s) must be a valid 3 letter country code.' % (title, app_fields['citizenship_country']))
            applicant_kwargs['citizenshipCountry'] = app_fields['citizenship_country']
        
        # add secondary citizenship country
            if app_fields['secondary_citizenship_country']:
                if not app_fields['secondary_citizenship_country'] in country_codes.keys():
                    raise ValueError('%s(secondary_citizenship_country=%s) must be a valid 3 letter country code.' % (title, app_fields['secondary_citizenship_country']))
                applicant_kwargs['secondaryCitizenshipCountry'] = app_fields['secondary_citizenship_country']
        
        # add job title
            if app_fields['job_title']:
                applicant_kwargs['jobTitle'] = app_fields['job_title']
                
        # add annual income field
            if app_fields['annual_income']:
                income_category = 250000
                income_list = [ { 50000: 25000 }, { 100000: 75000 }, { 150000: 125000 }, { 250000: 200000 } ]
                for level in income_list:
                    key, value = next(iter(level.items()))
                    if app_fields['annual_income'] > int(key):
                        continue
                    elif int(key) != 25000:
                        income_category = int(value)
                        break
                applicant_kwargs['annualIncome'] = income_category
    
        # add phone number fields
            phone_kwargs = {
                'phoneNumber': app_fields['phone_number'],
                'acceptedTcpa': app_fields['accept_tcpa']
            }
            if phone_type == 'mobile':
                applicant_kwargs['mobilePhoneNumber'] = phone_kwargs
            elif phone_type == 'home':
                applicant_kwargs['homePhoneNumber'] = phone_kwargs
            elif phone_type == 'work':
                applicant_kwargs['workPhoneNumber'] = phone_kwargs

        # add applicant fields to data kwargs
            applicant_copy = deepcopy(applicant_kwargs)
            applicant_list.append(applicant_copy)
    
    # construct data fields
        data_kwargs = {
            'applicants': applicant_list,
            'productId': str(product_id)
        }
        
    # add cd term
        if product_id == '3500':
            if not cd_term:
                raise IndexError('%s(cd_term=0) must not be empty if product_id=3500')
            else:
    # TODO validate cd terms in account product 
                data_kwargs['cdTerm'] = cd_term
        
    # add funding details
        funding_details = {
            'fundingType': funding_type,
            'fundingAmount': funding_amount,
            'externalAccountDetails': {
                'accountNumber': account_number,
                'bankABANumber': routing_number,
                'accountOwnership': account_owner
            }
        }
        data_kwargs['fundingDetails'] = funding_details
    
    # add terms and conditions
        term_details = {
            'acceptAccountDisclosures': accept_terms,
            'acceptPaperlessAgreement': accept_terms,
            'acceptFraudProtection': accept_terms
        }
        data_kwargs['termsAndConditions'] = term_details
        
    # send request
        details = self._requests(url, method='POST', headers=headers_kwargs, data=data_kwargs, errors=error_map)

        return details