├── tests ├── __init__.py ├── test_identify_identifier.py ├── test_lookups.py └── test_enhance_metadata.py ├── deviceidentifier ├── cli │ ├── __init__.py │ ├── cdma_meid.py │ ├── apple_idfa.py │ ├── apple_udid.py │ ├── gsma_iccid.py │ ├── gsma_imei.py │ ├── apple_model.py │ ├── apple_serial.py │ ├── identify_identifier.py │ ├── apple_anumber.py │ ├── apple_identifier.py │ ├── gsma_tac.py │ ├── apple_internal_name.py │ └── enhance_metadata.py ├── util │ ├── __init__.py │ ├── local.py │ └── exceptions.py ├── __init__.py └── api.py ├── requirements.txt ├── Makefile ├── requirements-local.txt ├── .gitignore ├── MANIFEST.in ├── tox.ini ├── setup.py ├── LICENSE └── README.md /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /deviceidentifier/cli/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /deviceidentifier/util/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.32.0 2 | -------------------------------------------------------------------------------- /deviceidentifier/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.0.5' 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | init: 2 | pip install -r requirements.txt 3 | 4 | init-dev: 5 | pip install -r requirements-local.txt 6 | 7 | test: 8 | py.test tests 9 | 10 | .PHONY: init test 11 | -------------------------------------------------------------------------------- /requirements-local.txt: -------------------------------------------------------------------------------- 1 | -r requirements.txt 2 | 3 | # Tests 4 | mock==2.0.0 5 | pytest==3.0.5 6 | pytest-mock==1.5 7 | pytest-cov==2.4.0 8 | tox==2.3.2 9 | 10 | # Packaging 11 | pypandoc==1.3.3 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **.pyc 2 | .cache 3 | .DS_Store 4 | 5 | # Ignore distribution side-products 6 | dist/ 7 | build/ 8 | deviceidentifier.egg-info/ 9 | 10 | # Ignore test and checker outputs 11 | .tox 12 | .coverage 13 | coverage.xml 14 | reports/ 15 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # Include the readme file 2 | include README.md 3 | include requirements.txt 4 | 5 | # Include all code files 6 | recursive-include deviceidentifier * 7 | 8 | # Exclude all bytecode 9 | global-exclude *.pyc 10 | global-exclude *.pyo 11 | -------------------------------------------------------------------------------- /deviceidentifier/util/local.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | 3 | 4 | def get_local_serial(): 5 | ''' Retrieves the serial number from the executing host. 6 | For example, 'C02NT43PFY14' 7 | ''' 8 | return [x for x in [subprocess.Popen("system_profiler SPHardwareDataType |grep -v tray |awk '/Serial/ {print $4}'", shell=True, stdout=subprocess.PIPE).communicate()[0].strip()] if x] 9 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | include = 4 | deviceidentifier/* 5 | 6 | [tox] 7 | envlist = py27 8 | skipsdist = True 9 | 10 | [testenv] 11 | setenv = 12 | PYTHONPATH={toxinidir}:{toxinidir} 13 | commands = py.test tests/ -q --junitxml=reports/junit.xml --cov apps/ --cov-report xml 14 | install_command = pip install -U {opts} {packages} 15 | 16 | [testenv:py27] 17 | deps = 18 | setuptools 19 | -r{toxinidir}/requirements-local.txt 20 | -------------------------------------------------------------------------------- /deviceidentifier/cli/cdma_meid.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from deviceidentifier import api 3 | 4 | import sys, json 5 | 6 | 7 | def main(): 8 | # Sample showing how to look up data from a CDMA MEID. 9 | 10 | if len(sys.argv) < 2: 11 | print 'Usage: provide a CDMA MEID for a breakdown of data on it.' 12 | exit(-1) 13 | 14 | print json.dumps( 15 | api.lookup( api.TYPE_CDMA_MEID, sys.argv[1:][0] ), 16 | indent=4, sort_keys=True, ensure_ascii=False, encoding='utf8' 17 | ) 18 | 19 | if __name__ == '__main__': 20 | main() 21 | -------------------------------------------------------------------------------- /deviceidentifier/cli/apple_idfa.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from deviceidentifier import api 3 | 4 | import sys, json 5 | 6 | 7 | def main(): 8 | # Sample showing how to look up data from an Apple IDFA. 9 | 10 | if len(sys.argv) < 2: 11 | print 'Usage: provide an Apple IDFA for a breakdown of data on it.' 12 | exit(-1) 13 | 14 | print json.dumps( 15 | api.lookup( api.TYPE_APPLE_IDFA, sys.argv[1:][0] ), 16 | indent=4, sort_keys=True, ensure_ascii=False, encoding='utf8' 17 | ) 18 | 19 | if __name__ == '__main__': 20 | main() 21 | -------------------------------------------------------------------------------- /deviceidentifier/cli/apple_udid.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from deviceidentifier import api 3 | 4 | import sys, json 5 | 6 | 7 | def main(): 8 | # Sample showing how to look up data from an Apple UDID. 9 | 10 | if len(sys.argv) < 2: 11 | print 'Usage: provide an Apple UDID for a breakdown of data on it.' 12 | exit(-1) 13 | 14 | print json.dumps( 15 | api.lookup( api.TYPE_APPLE_UDID, sys.argv[1:][0] ), 16 | indent=4, sort_keys=True, ensure_ascii=False, encoding='utf8' 17 | ) 18 | 19 | if __name__ == '__main__': 20 | main() 21 | -------------------------------------------------------------------------------- /deviceidentifier/cli/gsma_iccid.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from deviceidentifier import api 3 | 4 | import sys, json 5 | 6 | 7 | def main(): 8 | # Sample showing how to look up data from a GSMA ICCID. 9 | 10 | if len(sys.argv) < 2: 11 | print 'Usage: provide a GSMA ICCID for a breakdown of data on it.' 12 | exit(-1) 13 | 14 | print json.dumps( 15 | api.lookup( api.TYPE_GSMA_ICCID, sys.argv[1:][0] ), 16 | indent=4, sort_keys=True, ensure_ascii=False, encoding='utf8' 17 | ) 18 | 19 | if __name__ == '__main__': 20 | main() 21 | -------------------------------------------------------------------------------- /deviceidentifier/cli/gsma_imei.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from deviceidentifier import api 3 | 4 | import sys, json 5 | 6 | 7 | def main(): 8 | # Sample showing how to look up data from a GSMA IMEI. 9 | 10 | if len(sys.argv) < 2: 11 | print 'Usage: provide a GSMA IMEI for a breakdown of data on it.' 12 | exit(-1) 13 | 14 | print json.dumps( 15 | api.lookup( api.TYPE_GSMA_IMEI, sys.argv[1:][0] ), 16 | indent=4, sort_keys=True, ensure_ascii=False, encoding='utf8' 17 | ) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /deviceidentifier/cli/apple_model.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from deviceidentifier import api 3 | 4 | import sys, json 5 | 6 | 7 | def main(): 8 | # Sample showing how to look up data from an Apple model. 9 | 10 | if len(sys.argv) < 2: 11 | print 'Usage: provide an Apple model for a breakdown of data on it.' 12 | exit(-1) 13 | 14 | print json.dumps( 15 | api.lookup( api.TYPE_APPLE_MODEL, sys.argv[1:][0] ), 16 | indent=4, sort_keys=True, ensure_ascii=False, encoding='utf8' 17 | ) 18 | 19 | if __name__ == '__main__': 20 | main() 21 | -------------------------------------------------------------------------------- /deviceidentifier/cli/apple_serial.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from deviceidentifier import api 3 | 4 | import sys, json 5 | 6 | 7 | def main(): 8 | # Sample showing how to look up data from an Apple serial. 9 | 10 | if len(sys.argv) < 2: 11 | print 'Usage: provide an Apple serial for a breakdown of data on it.' 12 | exit(-1) 13 | 14 | print json.dumps( 15 | api.lookup( api.TYPE_APPLE_SERIAL, sys.argv[1:][0] ), 16 | indent=4, sort_keys=True, ensure_ascii=False, encoding='utf8' 17 | ) 18 | 19 | if __name__ == '__main__': 20 | main() 21 | -------------------------------------------------------------------------------- /deviceidentifier/cli/identify_identifier.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from deviceidentifier import api 3 | 4 | import sys, json 5 | 6 | 7 | def main(): 8 | # Sample showing how to identify an unknown identifier. 9 | 10 | if len(sys.argv) < 2: 11 | print 'Usage: provide an unknown identifier for a list of potential identities.' 12 | exit(-1) 13 | 14 | print json.dumps( 15 | api.identify_identifier( sys.argv[1:][0] ), 16 | indent=4, sort_keys=True, ensure_ascii=False, encoding='utf8' 17 | ) 18 | 19 | if __name__ == '__main__': 20 | main() 21 | -------------------------------------------------------------------------------- /deviceidentifier/cli/apple_anumber.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from deviceidentifier import api 3 | 4 | import sys, json 5 | 6 | 7 | def main(): 8 | # Sample showing how to look up data from an Apple "A" number. 9 | 10 | if len(sys.argv) < 2: 11 | print 'Usage: provide an Apple "A" number for a breakdown of data on it.' 12 | exit(-1) 13 | 14 | print json.dumps( 15 | api.lookup( api.TYPE_APPLE_ANUMBER, sys.argv[1:][0] ), 16 | indent=4, sort_keys=True, ensure_ascii=False, encoding='utf8' 17 | ) 18 | 19 | if __name__ == '__main__': 20 | main() 21 | -------------------------------------------------------------------------------- /deviceidentifier/cli/apple_identifier.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from deviceidentifier import api 3 | 4 | import sys, json 5 | 6 | 7 | def main(): 8 | # Sample showing how to look up data from an Apple identifier. 9 | 10 | if len(sys.argv) < 2: 11 | print 'Usage: provide an Apple identifier for a breakdown of data on it.' 12 | exit(-1) 13 | 14 | print json.dumps( 15 | api.lookup( api.TYPE_APPLE_IDENTIFIER, sys.argv[1:][0] ), 16 | indent=4, sort_keys=True, ensure_ascii=False, encoding='utf8' 17 | ) 18 | 19 | if __name__ == '__main__': 20 | main() 21 | -------------------------------------------------------------------------------- /deviceidentifier/cli/gsma_tac.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from deviceidentifier import api 3 | 4 | import sys, json 5 | 6 | 7 | def main(): 8 | # Sample showing how to look up data from a GSMA type allocation code. 9 | 10 | if len(sys.argv) < 2: 11 | print 'Usage: provide a GSMA type allocation code for a breakdown of data on it.' 12 | exit(-1) 13 | 14 | print json.dumps( 15 | api.lookup( api.TYPE_GSMA_TAC, sys.argv[1:][0] ), 16 | indent=4, sort_keys=True, ensure_ascii=False, encoding='utf8' 17 | ) 18 | 19 | if __name__ == '__main__': 20 | main() 21 | -------------------------------------------------------------------------------- /deviceidentifier/cli/apple_internal_name.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from deviceidentifier import api 3 | 4 | import sys, json 5 | 6 | 7 | def main(): 8 | # Sample showing how to look up data from an Apple internal name. 9 | 10 | if len(sys.argv) < 2: 11 | print 'Usage: provide an Apple internal name for a breakdown of data on it.' 12 | exit(-1) 13 | 14 | print json.dumps( 15 | api.lookup( api.TYPE_APPLE_INTERNAL_NAME, sys.argv[1:][0] ), 16 | indent=4, sort_keys=True, ensure_ascii=False, encoding='utf8' 17 | ) 18 | 19 | if __name__ == '__main__': 20 | main() 21 | -------------------------------------------------------------------------------- /tests/test_identify_identifier.py: -------------------------------------------------------------------------------- 1 | from deviceidentifier.util.exceptions import InvalidTokenError, ExpiredTokenError 2 | from deviceidentifier import api 3 | 4 | import pytest 5 | 6 | 7 | class TestIdentifierIdentifiers(object): 8 | 9 | def test_auth(self): 10 | ''' Will the API fail if we provide the wrong token? ''' 11 | with pytest.raises(InvalidTokenError) as exc_info: 12 | api.identify_identifier( 'iPhone5,3', token='bad_token', ) 13 | with pytest.raises(ExpiredTokenError) as exc_info: 14 | api.identify_identifier( 'iPhone5,3', token='yneektgqk98bercl4w5a92wb7kkxe3rh', ) 15 | 16 | def test_calls(self): 17 | api.identify_identifier( 'C8QH6T96DPNG', ) 18 | -------------------------------------------------------------------------------- /deviceidentifier/cli/enhance_metadata.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from deviceidentifier import api 3 | 4 | import sys, json 5 | 6 | 7 | def main(): 8 | # Look at what we can get with the identifiers from an iTunes backup. 9 | 10 | if len(sys.argv) != 6: 11 | print 'Usage: provide an identifier, a serial number, an internal name, an IMEI and an ICCID' 12 | print 'Try: iPhone5,3 C8QH6T96DPNG N92AP 013554006297015 8965880812100011146' 13 | exit(-1) 14 | 15 | identifierCode = sys.argv[1:][0] 16 | serialNumber = sys.argv[1:][1] 17 | internalName = sys.argv[1:][2] 18 | imeiCode = sys.argv[1:][3] 19 | iccidCode = sys.argv[1:][4] 20 | 21 | print json.dumps( 22 | api.enhance_metadata( 23 | apple_identifier=identifierCode, 24 | apple_serial=serialNumber, 25 | gsma_imei=imeiCode, 26 | gsma_iccid=iccidCode, 27 | ), 28 | indent=4, sort_keys=True, ensure_ascii=False, encoding='utf8' 29 | ) 30 | 31 | if __name__ == '__main__': 32 | main() 33 | -------------------------------------------------------------------------------- /tests/test_lookups.py: -------------------------------------------------------------------------------- 1 | from deviceidentifier.util.exceptions import InvalidTokenError, ExpiredTokenError, InvalidIdentifierError 2 | from deviceidentifier import api 3 | 4 | import pytest 5 | 6 | 7 | class TestLookups(object): 8 | 9 | def test_auth(self): 10 | ''' Will the API fail if we provide the wrong token? ''' 11 | with pytest.raises(InvalidTokenError) as exc_info: 12 | api.lookup( api.TYPE_APPLE_IDENTIFIER, 'iPhone5,3', token='bad_token', ) 13 | with pytest.raises(ExpiredTokenError) as exc_info: 14 | api.lookup( api.TYPE_APPLE_IDENTIFIER, 'iPhone5,3', token='yneektgqk98bercl4w5a92wb7kkxe3rh', ) 15 | 16 | def test_misformed(self): 17 | with pytest.raises(InvalidIdentifierError) as exc_info: 18 | api.lookup( api.TYPE_APPLE_SERIAL, 'XXBADSERIALXX', ) 19 | 20 | def test_calls(self): 21 | api.lookup( api.TYPE_APPLE_ANUMBER, 'A1784', ) 22 | api.lookup( api.TYPE_APPLE_IDENTIFIER, 'iPhone5,3', ) 23 | api.lookup( api.TYPE_APPLE_IDFA, '002ebf12-a125-5ddf-a739-67c3c5d20177', ) 24 | api.lookup( api.TYPE_APPLE_INTERNAL_NAME, 'N92AP', ) 25 | api.lookup( api.TYPE_APPLE_MODEL, 'MC605FD/A', ) 26 | api.lookup( api.TYPE_APPLE_SERIAL, 'C8QH6T96DPNG', ) 27 | api.lookup( api.TYPE_APPLE_UDID, 'db72cb76a00cb81675f19907d4ac2b298628d83c', ) 28 | api.lookup( api.TYPE_CDMA_MEID, '354403064522046', ) 29 | api.lookup( api.TYPE_GSMA_ICCID, '8965880812100011146', ) 30 | api.lookup( api.TYPE_GSMA_IMEI, '013554006297015', ) 31 | api.lookup( api.TYPE_GSMA_TAC, '01326300', ) 32 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | from deviceidentifier import __version__ 4 | 5 | PACKAGE_NAME = 'deviceidentifier' 6 | 7 | 8 | def markdown2rst(path): 9 | try: 10 | import pypandoc 11 | return pypandoc.convert(path, 'rst', 'md') 12 | except ImportError: 13 | with open(path, 'r') as f: 14 | return f.read() 15 | 16 | 17 | def extract_requirements(path): 18 | with open(path) as fp: 19 | return [x.strip() for x in fp.read().split('\n') if not x.startswith('#')] 20 | 21 | setup( 22 | name=PACKAGE_NAME, 23 | 24 | version=__version__, 25 | 26 | description="Utils to manipulate and learn from assorted device identifier formats via Reincubate's DeviceIdentifier API.", 27 | long_description=markdown2rst('README.md'), 28 | 29 | url='https://github.com/reincubate/deviceidentifier-py', 30 | 31 | author='Reincubate', 32 | author_email='enterprise@reincubate.com', 33 | 34 | license='Proprietary', 35 | 36 | packages=[PACKAGE_NAME, ], 37 | package_data={PACKAGE_NAME: [PACKAGE_NAME + '/*']}, 38 | include_package_data=True, 39 | 40 | classifiers=['Development Status :: 5 - Production/Stable', 41 | 'Environment :: Console', 42 | 'Intended Audience :: Developers', 43 | 'License :: OSI Approved :: GNU General Public License (GPL)', 44 | 'Natural Language :: English', 45 | 'Operating System :: OS Independent', 46 | 'Programming Language :: Python :: 2.6', 47 | 'Programming Language :: Python :: 2.7', 48 | 'Topic :: Utilities'], 49 | 50 | install_requires=extract_requirements('requirements.txt'), 51 | ) 52 | -------------------------------------------------------------------------------- /deviceidentifier/util/exceptions.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | # Valid error types 4 | ERROR_INVALID_IDENTIFIER = 'invalid_identifier' 5 | ERROR_INVALID_TOKEN = 'invalid_token' 6 | ERROR_EXPIRED_TOKEN = 'expired_token' 7 | ERROR_BAD_REQUEST = 'bad_request' 8 | ERROR_UNHANDLED_ERROR = 'unhandled_error' 9 | 10 | # You shouldn't see these 11 | ERROR_PERMISSION_DENIED = 'permission_denied' 12 | ERROR_PAGE_NOT_FOUND = 'page_not_found' 13 | 14 | 15 | class InvalidIdentifierError(Exception): 16 | def __init__(self, message): 17 | super(InvalidIdentifierError, self).__init__(message) 18 | self.message = message 19 | 20 | class InvalidTokenError(Exception): 21 | def __init__(self, message): 22 | super(InvalidTokenError, self).__init__(message) 23 | self.message = message 24 | 25 | class ExpiredTokenError(Exception): 26 | def __init__(self, message): 27 | super(ExpiredTokenError, self).__init__(message) 28 | self.message = message 29 | 30 | class BadRequestError(Exception): 31 | def __init__(self, message): 32 | super(BadRequestError, self).__init__(message) 33 | self.message = message 34 | 35 | class UnhandledError(Exception): 36 | def __init__(self, message): 37 | super(UnhandledError, self).__init__(message) 38 | self.message = message 39 | 40 | ERROR_MAPPING = { 41 | ERROR_INVALID_IDENTIFIER: InvalidIdentifierError, 42 | ERROR_INVALID_TOKEN: InvalidTokenError, 43 | ERROR_EXPIRED_TOKEN: ExpiredTokenError, 44 | ERROR_BAD_REQUEST: BadRequestError, 45 | ERROR_UNHANDLED_ERROR: UnhandledError, 46 | } 47 | 48 | def get_error( error_json, status_code ): 49 | error_json = json.loads( error_json ) 50 | 51 | type, message = error_json['type'], error_json['message'] 52 | 53 | if type not in ERROR_MAPPING.keys(): # Uh-oh 54 | return Exception( '%s (HTTP %s)' % ( message, status_code ) ) 55 | 56 | return ERROR_MAPPING[type](message) 57 | -------------------------------------------------------------------------------- /tests/test_enhance_metadata.py: -------------------------------------------------------------------------------- 1 | from deviceidentifier.util.exceptions import InvalidTokenError, ExpiredTokenError, InvalidIdentifierError, BadRequestError 2 | from deviceidentifier import api 3 | 4 | import pytest 5 | 6 | 7 | class TestEnhanceMetadata(object): 8 | 9 | def test_auth(self): 10 | ''' Will the API fail if we provide the wrong token? ''' 11 | with pytest.raises(InvalidTokenError) as exc_info: 12 | api.enhance_metadata( apple_identifier='iPhone5,3', token='bad_token', ) 13 | with pytest.raises(ExpiredTokenError) as exc_info: 14 | api.enhance_metadata( apple_identifier='iPhone5,3', token='yneektgqk98bercl4w5a92wb7kkxe3rh', ) 15 | 16 | def test_misformed(self): 17 | with pytest.raises(InvalidIdentifierError) as exc_info: 18 | api.enhance_metadata( apple_serial='XXBADSERIALXX', ) 19 | 20 | with pytest.raises(BadRequestError) as exc_info: 21 | api.enhance_metadata( apple_serial=None, ) 22 | 23 | def test_calls(self): 24 | api.enhance_metadata( 25 | apple_anumber='A1784', apple_identifier='iPhone5,3', apple_idfa='002ebf12-a125-5ddf-a739-67c3c5d20177', 26 | apple_internal_name='N92AP', apple_model='MC605FD/A', apple_serial='C8QH6T96DPNG', 27 | apple_udid='db72cb76a00cb81675f19907d4ac2b298628d83c', 28 | cdma_meid='354403064522046', 29 | gsma_imei='013554006297015', gsma_iccid='8965880812100011146', gsma_tac='01326300', 30 | ) 31 | 32 | api.enhance_metadata( 33 | apple_anumber='A1784', apple_identifier='iPhone5,3', apple_idfa='002ebf12-a125-5ddf-a739-67c3c5d20177', 34 | apple_internal_name='N92AP', apple_model='MC605FD/A', apple_serial='C8QH6T96DPNG', 35 | apple_udid='db72cb76a00cb81675f19907d4ac2b298628d83c', 36 | cdma_meid='354403064522046', 37 | gsma_imei='013554006297015', gsma_iccid='8965880812100011146', gsma_tac='01326300', 38 | additional={ 39 | 'deviceClass': 'iPhone', 40 | } 41 | ) 42 | -------------------------------------------------------------------------------- /deviceidentifier/api.py: -------------------------------------------------------------------------------- 1 | from deviceidentifier.util.exceptions import get_error, BadRequestError, InvalidIdentifierError 2 | 3 | import requests, json, logging, os 4 | 5 | # Get an instance of a logger 6 | logger = logging.getLogger( 'deviceidentifier' ) 7 | 8 | # Valid identifier types 9 | TYPE_APPLE_ANUMBER = 'apple-anumbers' 10 | TYPE_APPLE_IDENTIFIER = 'apple-identifiers' 11 | TYPE_APPLE_IDFA = 'apple-idfas' 12 | TYPE_APPLE_INTERNAL_NAME = 'apple-internal-names' 13 | TYPE_APPLE_MODEL = 'apple-models' 14 | TYPE_APPLE_SERIAL = 'apple-serials' 15 | TYPE_APPLE_UDID = 'apple-udids' 16 | TYPE_CDMA_MEID = 'cdma-meids' 17 | TYPE_GSMA_ICCID = 'gsma-iccids' 18 | TYPE_GSMA_IMEI = 'gsma-imeis' 19 | TYPE_GSMA_TAC = 'gsma-tacs' 20 | 21 | VALID_TYPES = [ 22 | TYPE_APPLE_ANUMBER, TYPE_APPLE_IDENTIFIER, TYPE_APPLE_IDFA, 23 | TYPE_APPLE_INTERNAL_NAME, TYPE_APPLE_MODEL, TYPE_APPLE_SERIAL, 24 | TYPE_APPLE_UDID, 25 | TYPE_CDMA_MEID, 26 | TYPE_GSMA_ICCID, TYPE_GSMA_IMEI, TYPE_GSMA_TAC, 27 | ] 28 | 29 | TOKEN_ENV = os.environ.get( 'RI_DEVID_TOKEN', None ) 30 | 31 | 32 | def lookup( type, identifier, token=None, ): 33 | if not token and TOKEN_ENV: 34 | token = TOKEN_ENV 35 | 36 | if not token: 37 | logger.warn( 'Token not specified, relying on anonymous access which may be limited or rate-limited' ) 38 | headers = {} 39 | else: 40 | headers = { 'Authorization': 'Token %s' % token, } 41 | 42 | if type not in VALID_TYPES: 43 | raise InvalidIdentifierError( '"%s" is not a valid identifier type: see VALID_TYPES' % type ) 44 | 45 | response = requests.get( 46 | 'https://di-api.reincubate.com/v1/%s/%s/' % ( type, identifier ), 47 | headers=headers, 48 | ) 49 | 50 | if response.status_code != 200: 51 | raise get_error( response.content, response.status_code ) 52 | 53 | return json.loads( response.content ) 54 | 55 | def identify_identifier( identifier, token=None, ): 56 | if not token and TOKEN_ENV: 57 | token = TOKEN_ENV 58 | 59 | if not token: 60 | logger.warn( 'Token not specified, relying on anonymous access which may be limited or rate-limited' ) 61 | headers = {} 62 | else: 63 | headers = { 'Authorization': 'Token %s' % token, } 64 | 65 | response = requests.get( 66 | 'https://di-api.reincubate.com/v1/identify-identifier/%s/' % identifier, 67 | headers=headers, 68 | ) 69 | 70 | if response.status_code != 200: 71 | raise get_error( response.content, response.status_code ) 72 | 73 | return json.loads( response.content ) 74 | 75 | def enhance_metadata( apple_anumber=None, apple_identifier=None, apple_idfa=None, 76 | apple_internal_name=None, apple_model=None, apple_serial=None, 77 | apple_udid=None, 78 | cdma_meid=None, 79 | gsma_imei=None, gsma_iccid=None, gsma_tac=None, 80 | additional={}, 81 | token=None, ): 82 | if not token and TOKEN_ENV: 83 | token = TOKEN_ENV 84 | 85 | if not token: 86 | logger.warn( 'Token not specified, relying on anonymous access which may be limited or rate-limited' ) 87 | headers = {} 88 | else: 89 | headers = { 'Authorization': 'Token %s' % token, } 90 | 91 | built_query = { 'identifiers': {}, } 92 | 93 | if apple_anumber: 94 | built_query['identifiers']['apple_anumber'] = apple_anumber 95 | if apple_identifier: 96 | built_query['identifiers']['apple_identifier'] = apple_identifier 97 | if apple_idfa: 98 | built_query['identifiers']['apple_idfa'] = apple_idfa 99 | if apple_internal_name: 100 | built_query['identifiers']['apple_internal_name'] = apple_internal_name 101 | if apple_model: 102 | built_query['identifiers']['apple_model'] = apple_model 103 | if apple_serial: 104 | built_query['identifiers']['apple_serial'] = apple_serial 105 | if apple_udid: 106 | built_query['identifiers']['apple_udid'] = apple_udid 107 | 108 | if cdma_meid: 109 | built_query['identifiers']['cdma_meid'] = cdma_meid 110 | 111 | if gsma_imei: 112 | built_query['identifiers']['gsma_imei'] = gsma_imei 113 | if gsma_iccid: 114 | built_query['identifiers']['gsma_iccid'] = gsma_iccid 115 | if gsma_tac: 116 | built_query['identifiers']['gsma_tac'] = gsma_tac 117 | 118 | if additional: 119 | built_query['identifiers']['additional'] = additional 120 | 121 | if len( built_query['identifiers'].keys() ) == 0: 122 | raise BadRequestError( 'No identifiers passed to query' ) 123 | 124 | response = requests.post( 125 | 'https://di-api.reincubate.com/v1/enhance-metadata/', 126 | headers=headers, 127 | data=json.dumps( built_query ), 128 | ) 129 | 130 | if response.status_code != 200: 131 | raise get_error( response.content, response.status_code ) 132 | 133 | return json.loads( response.content ) 134 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | 167 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DeviceIdentifier API Python library 2 | 3 | Utils to manipulate and learn from assorted device identifier formats via Reincubate's [DeviceIdentifier API](https://www.reincubate.com/deviceidentifier-api/). 4 | 5 | Technical documentation is available on [Reincubate's site](https://docs.reincubate.com/deviceidentifier/?utm_source=github&utm_medium=deviceidentifier-py&utm_campaign=deviceidentifier). 6 | 7 | ## Getting started 8 | 9 | Try these: 10 | 11 | ```bash 12 | $ pip install deviceidentifier 13 | ``` 14 | 15 | Calls to the API through this client then become easy: 16 | 17 | ```python 18 | from deviceidentifier import api 19 | 20 | # Look up an Apple serial number 21 | api.lookup( api.TYPE_APPLE_SERIAL, 'C8QH6T96DPNG' ) 22 | 23 | # Identify the type of an identifier 24 | api.identify_identifier( 'iPhone5,3' ) 25 | 26 | # Triangulate a bunch of data from a collection of identifiers 27 | api.enhance_metadata( 28 | apple_identifier='iPhone5,3', 29 | apple_serial='C8QH6T96DPNG', 30 | gsma_imei='013554006297015', 31 | gsma_iccid='8965880812100011146' 32 | ) 33 | ``` 34 | 35 | The API supports anonymous access, and provides limited, rate-limited data when doing so. Tokens can be obtained by contacting [Reincubate](mailto:enterprise@reincubate.com), and either by setting an environment variable: 36 | 37 | ```bash 38 | $ export RI_DEVID_TOKEN='api-authentication-token' 39 | ``` 40 | 41 | Or by passing the token value directly into the code: 42 | 43 | ```python 44 | from deviceidentifier import api 45 | 46 | # Look up an Apple serial number 47 | api.lookup( api.TYPE_APPLE_SERIAL, 'api-authenticaton-token', 'C8QH6T96DPNG' ) 48 | ``` 49 | 50 | ## Using the command-line interface 51 | 52 | ### Apple 53 | 54 | #### Apple serial numbers: legacy (80s & 90s), old (early 2000s) and post-2010 formats 55 | 56 | ```bash 57 | $ python -m deviceidentifier.cli.apple_serial C8QH6T96DPNG 58 | ``` 59 | 60 | ```json 61 | { 62 | "anonymised": "C8QH6•••DPNG", 63 | "configurationCode": { 64 | "code": "DPNG", 65 | "image": { 66 | "height": 120, 67 | "url": "https://di-api.reincubate.com/resource-159c9e87a3d6bbf5075bb030fa2925a0/", 68 | "width": 120 69 | }, 70 | "skuHint": "iPhone 4 CDMA (8GB)" 71 | }, 72 | "coverageUrl": "https://checkcoverage.apple.com/gb/en?sn=C8QH6T96DPNG", 73 | "id": "C8QH6T96DPNG", 74 | "manufacturing": { 75 | "city": "", 76 | "company": "", 77 | "country": "China", 78 | "date": "2012-02-05", 79 | "flag": "🇨🇳", 80 | "id": "C8Q" 81 | }, 82 | "serialType": "2010", 83 | "uniqueId": { 84 | "productionNo": 31524, 85 | "value": "T96" 86 | } 87 | } 88 | ``` 89 | 90 | #### Apple model numbers 91 | 92 | ```bash 93 | $ python -m deviceidentifier.cli.apple_model MC605FD/A 94 | ``` 95 | ```json 96 | { 97 | "anonymised": "C605", 98 | "appleIdentifier": { 99 | "id": "iPhone3,1", 100 | "image": { 101 | "height": 330, 102 | "url": "https://di-api.reincubate.com/resource-26b007e1007180a28e272036775a48a0/", 103 | "width": 450 104 | }, 105 | "product": { 106 | "line": "iPhone", 107 | "sku": "iPhone 4" 108 | }, 109 | "variant": "GSM" 110 | }, 111 | "id": "C605", 112 | "region": { 113 | "flags": "🇦🇹🇱🇮🇨🇭", 114 | "name": "Austria, Liechtenstein, Switzerland" 115 | }, 116 | "specification": { 117 | "case_size": "", 118 | "colour": "Black", 119 | "material": "", 120 | "storage": "32 GB" 121 | }, 122 | "type": "Retail" 123 | } 124 | ``` 125 | 126 | #### Apple identifiers 127 | 128 | ```bash 129 | $ python -m deviceidentifier.cli.apple_identifier iPhone5,3 130 | ``` 131 | ```json 132 | { 133 | "id": "iPhone5,3", 134 | "image": { 135 | "height": 330, 136 | "url": "https://di-api.reincubate.com/resource-775ac9634280be8d7dfb0b75b4727f69/", 137 | "width": 450 138 | }, 139 | "product": { 140 | "line": "iPhone", 141 | "sku": "iPhone 5c" 142 | }, 143 | "variant": "GSM + CDMA" 144 | } 145 | ``` 146 | 147 | #### Apple internal names 148 | 149 | ```bash 150 | $ python -m deviceidentifier.cli.apple_internal_name N92AP 151 | ``` 152 | ```json 153 | { 154 | "appleIdentifier": { 155 | "id": "iPhone3,3", 156 | "image": { 157 | "height": 330, 158 | "url": "https://di-api.reincubate.com/resource-1e7820cb714e3d477534f291c0f87e83/", 159 | "width": 450 160 | }, 161 | "product": { 162 | "line": "iPhone", 163 | "sku": "iPhone 4" 164 | }, 165 | "variant": "CDMA" 166 | } 167 | } 168 | ``` 169 | 170 | #### Apple IDFA / IDFV 171 | 172 | ```bash 173 | $ python -m deviceidentifier.cli.apple_idfa 002ebf12-a125-5ddf-a739-67c3c5d20177 174 | ``` 175 | ```json 176 | { 177 | "anonymised": "••••••••-••••-••••-••••-••••••••••••", 178 | "formatted": "002ebf12-a125-5ddf-a739-67c3c5d20177" 179 | } 180 | ``` 181 | 182 | #### Apple UDIDs 183 | 184 | ```bash 185 | $ python -m deviceidentifier.cli.apple_udid db72cb76a00cb81675f19907d4ac2b298628d83c 186 | ``` 187 | ```json 188 | { 189 | "anonymised": "••••••••••••••••••••••••••••••••••••••••", 190 | "compromised": false, 191 | "formatted": "db72cb76a00cb81675f19907d4ac2b298628d83c" 192 | } 193 | ``` 194 | 195 | #### Apple "A" numbers 196 | 197 | ```bash 198 | python -m deviceidentifier.cli.apple_anumber A1784 199 | ``` 200 | ```json 201 | { 202 | "appleIdentifier": { 203 | "id": "iPhone9,4", 204 | "image": { 205 | "height": 330, 206 | "url": "https://di-api.reincubate.com/resource-d8c14fc2a4dfcf27d5a217fb5e4c0cc4/", 207 | "width": 450 208 | }, 209 | "product": { 210 | "line": "iPhone", 211 | "sku": "iPhone 7 Plus" 212 | }, 213 | "variant": "GSM" 214 | } 215 | } 216 | ``` 217 | 218 | ### CDMA 219 | 220 | #### Mobile Equipment Identifier (MEIDs) 221 | 222 | ```bash 223 | $ python -m deviceidentifier.cli.cdma_meid 354403064522046 224 | ``` 225 | 226 | ```json 227 | { 228 | "anonymised": "35440306••••••6", 229 | "checksum": "6", 230 | "id": "354403064522046", 231 | "manufacturer": "440306", 232 | "pESN": "808D1904", 233 | "regionCode": { 234 | "code": "35", 235 | "group": "Comreg", 236 | "origin": "Ireland" 237 | }, 238 | "serial": "452204" 239 | } 240 | ``` 241 | 242 | ### GSMA 243 | 244 | #### IMEIs (enriched with data from Apple's GSX service for clients with access) 245 | 246 | ```bash 247 | $ python -m deviceidentifier.cli.gsma_imei 013554006297015 248 | ``` 249 | 250 | ```json 251 | { 252 | "anonymised": "01355400••••••5", 253 | "checksum": "5", 254 | "gsmaTac": { 255 | "appleModel": { 256 | "anonymised": "D298", 257 | "appleIdentifier": { 258 | "id": "iPhone5,2", 259 | "image": { 260 | "height": 330, 261 | "url": "https://di-api.reincubate.com/resource-c2aac9e5e3695fca1090633a4ea1b60d/", 262 | "width": 450 263 | }, 264 | "product": { 265 | "line": "iPhone", 266 | "sku": "iPhone 5" 267 | }, 268 | "variant": "CDMA + LTE" 269 | }, 270 | "id": "D298", 271 | "region": { 272 | "flags": null, 273 | "name": null 274 | }, 275 | "specification": { 276 | "case_size": "", 277 | "colour": "White", 278 | "material": "", 279 | "storage": "16 GB" 280 | }, 281 | "type": "Retail" 282 | }, 283 | "id": "01355400", 284 | "manufacturer": "Apple", 285 | "product": { 286 | "line": "iPhone", 287 | "sku": "iPhone 5" 288 | } 289 | }, 290 | "gsx": { 291 | "appleSerial": { 292 | "anonymised": "F2TK4•••DTWF", 293 | "configurationCode": { 294 | "code": "DTWF", 295 | "image": { 296 | "height": 120, 297 | "url": "https://di-api.reincubate.com/resource-4cb3c6fe7c62f327cd11712196c221b0/", 298 | "width": 120 299 | }, 300 | "skuHint": "iPhone 5 (GSM, CDMA)" 301 | }, 302 | "coverageUrl": "https://checkcoverage.apple.com/gb/en?sn=F2TK4TZ7DTWF", 303 | "id": "F2TK4TZ7DTWF", 304 | "manufacturing": { 305 | "city": "Zhengzhou", 306 | "company": "Foxconn", 307 | "country": "China", 308 | "date": "2013-01-22", 309 | "flag": "🇨🇳", 310 | "id": "F2T" 311 | }, 312 | "serialType": "2010", 313 | "uniqueId": { 314 | "productionNo": 32341, 315 | "value": "TZ7" 316 | } 317 | }, 318 | "sale": { 319 | "estimatedPurchaseDate": "2013-04-10", 320 | "initialCarrier": "Sweden Tele2.", 321 | "realPurchaseDate": "2013-04-10", 322 | "saleRegion": "Sweden", 323 | "saleRegionFlag": "🇸🇪", 324 | "seller": "TELE2 SVERIGE AB" 325 | }, 326 | "skuHint": "IPHONE 5", 327 | "specifications": [ 328 | "WHITE", 329 | "16GB", 330 | "GSM" 331 | ], 332 | "status": { 333 | "appleId": null, 334 | "coverage": "Out Of Warranty (No Coverage)", 335 | "sim": "Locked" 336 | } 337 | }, 338 | "id": "013554006297015", 339 | "reportingBodyIdentifier": { 340 | "code": "01", 341 | "group": "PTCRB", 342 | "origin": "United States" 343 | }, 344 | "serial": "629701", 345 | "svn": null, 346 | "type": "IMEI" 347 | } 348 | ``` 349 | 350 | #### Type allocation codes (TAC) 351 | 352 | ```bash 353 | $ python -m deviceidentifier.cli.gsma_tac 01326300 354 | ``` 355 | ```json 356 | { 357 | "appleModel": { 358 | "anonymised": "D198", 359 | "appleIdentifier": { 360 | "id": "iPhone3,1", 361 | "image": { 362 | "height": 330, 363 | "url": "https://di-api.reincubate.com/resource-26b007e1007180a28e272036775a48a0/", 364 | "width": 450 365 | }, 366 | "product": { 367 | "line": "iPhone", 368 | "sku": "iPhone 4" 369 | }, 370 | "variant": "GSM" 371 | }, 372 | "id": "D198", 373 | "region": { 374 | "flags": "🇮🇳", 375 | "name": "India" 376 | }, 377 | "specification": { 378 | "case_size": null, 379 | "colour": "White", 380 | "material": null, 381 | "storage": "8 GB" 382 | }, 383 | "type": "Retail" 384 | }, 385 | "id": "01326300", 386 | "manufacturer": "Apple", 387 | "product": { 388 | "line": "iPhone", 389 | "sku": "iPhone 4" 390 | } 391 | } 392 | ``` 393 | 394 | #### ICCIDs 395 | 396 | ```bash 397 | $ python -m deviceidentifier.cli.gsma_iccid 8965880812100011146 398 | ``` 399 | 400 | ```json 401 | { 402 | "anonymised": "896588••••••••••••6", 403 | "atiiccid": null, 404 | "checksum": "6", 405 | "issuer": { 406 | "code": "88", 407 | "country": { 408 | "code": "65", 409 | "flag": "🇸🇬", 410 | "name": "Singapore" 411 | }, 412 | "name": null 413 | }, 414 | "majorIndustry": { 415 | "code": "89", 416 | "industry": "Telecommunications administrations and private operating agencies", 417 | "type": "Healthcare, telecommunications and other future industry assignments" 418 | }, 419 | "month": "08", 420 | "simNumber": "001114", 421 | "switch": "10", 422 | "year": "12" 423 | } 424 | ``` 425 | 426 | ### Identifying an identifier 427 | 428 | ```bash 429 | $ python -m deviceidentifier.cli.identify_identifier iPhone5,3 430 | ``` 431 | 432 | ```json 433 | { 434 | "iPhone5,3": [ 435 | "apple_identifier" 436 | ] 437 | } 438 | ``` 439 | 440 | ## Troubleshooting 441 | 442 | See the [support & service status](https://docs.reincubate.com/ricloud/status/?utm_source=github&utm_medium=deviceidentifier-py&utm_campaign=deviceidentifier) page. 443 | 444 | ## Need more functionality? 445 | 446 | Reincubate's vision is to provide data access, extraction and recovery technology for all app platforms - be they mobile, desktop, web, appliance or in-vehicle. 447 | 448 | The company was founded in 2008 and was first to market with both iOS and iCloud data extraction technology. With over half a decade's experience helping law enforcement and security organisations access iOS data, Reincubate has licensed software to government, child protection and corporate clients around the world. 449 | 450 | The company can help users with: 451 | 452 | * iCloud access and data recovery 453 | * Recovery of data deleted from SQLite databases 454 | * Bulk iOS data recovery 455 | * Forensic examination of iOS data 456 | * Passcode, password, keybag and keychain analysis 457 | * Custom iOS app data extraction 458 | * Advanced PList, TypedStream and Mbdb manipulation 459 | 460 | Contact [Reincubate](https://www.reincubate.com/?utm_source=github&utm_medium=deviceidentifier-py&utm_campaign=deviceidentifier) for more information. 461 | 462 | ## Terms & license 463 | 464 | See the `LICENSE` file for details on this implementation's license. Users must not use the API in any way that is unlawful, illegal, fraudulent or harmful; or in connection with any unlawful, illegal, fraudulent or harmful purpose or activity. 465 | --------------------------------------------------------------------------------