├── 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 |
--------------------------------------------------------------------------------