Metadata-Version: 2.1
Name: appblocklist
Version: 1.0.0
Summary: Python client for the AppBlockList App Lookup API: categorize iOS and Android apps by bundle ID, App Store ID or package name into 58 filtering categories for MDM app blocklists.
Home-page: https://www.appblocklist.com
Author: Alpha Quantum
Author-email: info@alpha-quantum.com
License: MIT
Project-URL: Homepage, https://www.appblocklist.com
Project-URL: Documentation, https://www.appblocklist.com/app-categorization-api.php
Project-URL: Source, https://github.com/explainableaixai/appblocklist
Project-URL: Tracker, https://www.appblocklist.com/contact.php
Project-URL: GitLab, https://gitlab.com/url-classifications/appblocklist
Project-URL: MDM Blocklist Feed, https://www.appblocklist.com/mdm-app-blocklist-feed.php
Project-URL: Mobile App Database, https://www.appblocklist.com/mobile-app-database.php
Project-URL: Web Filtering Database, https://www.webfilteringdatabase.com
Description: # appblocklist for Python
        
        `appblocklist` is the Python client for the [AppBlockList app categorization API](https://www.appblocklist.com/app-categorization-api.php). Give it an iPhone bundle ID, an App Store ID or a Google Play package name and it returns a typed `AppResult`: name, developer, both store identifiers, genre, age rating, IAB category and up to several ranked filtering categories out of 58.
        
        It is written for the scripts that MDM and IT teams actually run: inventory audits, nightly policy jobs, approval forms and profile generators. The only dependency is `requests`.
        
        ## Contents
        
        1. Install
        2. Five-minute tour
        3. The `AppResult` object
        4. Client reference
        5. Errors and retries
        6. Recipes
        7. Why use it
        8. FAQ
        9. Links and related packages
        
        ---
        
        ## 1. Install
        
        ```bash
        pip install appblocklist
        ```
        
        Python 3.7 or newer. In a project with a virtual environment:
        
        ```bash
        python3 -m venv .venv
        . .venv/bin/activate
        pip install appblocklist
        ```
        
        Pin it in `requirements.txt` as `appblocklist==1.0.0` if you deploy jobs to several machines.
        
        ## 2. Five-minute tour
        
        ```python
        from appblocklist import AppBlockList
        
        client = AppBlockList("YOUR_API_KEY")
        
        app = client.lookup("com.openai.chatgpt")
        print(app.found)                  # True
        print(app.name, "/", app.developer)
        print(app.filtering_categories)   # ranked list, most descriptive first
        print(app.primary_category)
        print(app.age_rating)             # e.g. '12+'
        print(app.bundle_id, app.android_package)
        ```
        
        Leave the key out of the code and set it once in the shell, a systemd unit or your CI secrets:
        
        ```bash
        export APPBLOCKLIST_API_KEY=your_key
        ```
        
        ```python
        client = AppBlockList()  # picks up APPBLOCKLIST_API_KEY
        ```
        
        The same app can be reached through any of its identifiers. These three calls return the same record:
        
        ```python
        client.lookup("com.zhiliaoapp.musically")  # iOS bundle ID
        client.lookup("835599320")                 # App Store ID
        client.lookup("id835599320")               # App Store ID with prefix
        ```
        
        Asking a yes or no question is one line:
        
        ```python
        if client.is_blocked("com.zhiliaoapp.musically", ["Social Networking", "Gambling"]):
            print("blocked by policy")
        ```
        
        ## 3. The `AppResult` object
        
        `AppResult` is a dataclass, so it prints readably, compares by value and converts with `dataclasses.asdict()`.
        
        | Attribute | Type | Example | Notes |
        |---|---|---|---|
        | `query` | `str` | `'id835599320'` | what you passed in, trimmed |
        | `found` | `bool` | `True` | `False` for an app that is not in the categorized database |
        | `name` | `str` | `'TikTok - Videos, Shop & LIVE'` | App Store name |
        | `developer` | `str` | `'TikTok Ltd.'` | publisher |
        | `bundle_id` | `str` | `'com.zhiliaoapp.musically'` | iOS identifier |
        | `app_store_id` | `str` | `'835599320'` | kept as a string, as the API sends it |
        | `android_package` | `str` | `'com.zhiliaoapp.musically'` | Google Play twin, empty when there is none |
        | `app_store_genre` | `str` | `'Entertainment'` | one of 26 genres |
        | `age_rating` | `str` | `'12+'` | `4+`, `9+`, `12+` or `17+` |
        | `filtering_categories` | `list[str]` | `['Social Networking', 'Streaming Media']` | ranked |
        | `developer_domain` | `str` | `'tiktok.com'` | the publisher's website |
        | `iab_category` | `str` | `'Technology & Computing > ...'` | IAB content taxonomy path |
        | `error` | `str or None` | `'App not found.'` | API message for a not-found app |
        | `raw` | `dict` | | the untouched JSON body |
        
        Two helpers live on the object:
        
        - `primary_category`: the first filtering category, or `None`.
        - `in_category(names)`: case-insensitive test against a string or any iterable of names.
        
        ## 4. Client reference
        
        ```python
        AppBlockList(
            api_key=None,          # falls back to APPBLOCKLIST_API_KEY
            timeout=15.0,          # seconds per request
            retries=2,             # extra attempts on HTTP 500 and network errors
            retry_delay=1.0,       # linear backoff base, seconds
            send_key_as="header",  # or "query" to send api_key= in the URL
            base_url="https://www.appblocklist.com/api/v1/app",
            session=None,          # bring your own requests.Session (proxies, adapters)
        )
        ```
        
        | Method | Returns | Lookups counted |
        |---|---|---|
        | `lookup(app_id)` | `AppResult` | 1 when found |
        | `is_blocked(app_id, categories)` | `bool` | 1 when found |
        | `categories(app_id)` | `list[str]` | 1 when found |
        | `lookup_many(app_ids, pause=0.1)` | `list` in input order | 1 per unique found app |
        
        Under the hood every call is a single `GET https://www.appblocklist.com/api/v1/app?id=...` with the key in the `X-API-Key` header and the User-Agent `appblocklist-python/1.0.0 (+https://www.appblocklist.com)`.
        
        `lookup_many` is deliberately sequential. The service asks for one ID per request and no parallel bursts, so the method sleeps `pause` seconds between calls and looks up repeated IDs only once.
        
        ## 5. Errors and retries
        
        ```
        AppBlockListError
        ├── BadRequestError       400
        ├── AuthenticationError   401
        ├── QuotaExceededError    403  (.monthly_lookups)
        └── ServerError           500 and network failures
        ```
        
        | Status | Outcome in Python | Meaning |
        |---|---|---|
        | 200 | `AppResult(found=True)` | categorized app, one lookup counted |
        | 404 | `AppResult(found=False)` | `App not found.` or `Listed on Google Play, not yet in the categorized database.` |
        | 400 | `BadRequestError` | `id` missing or not a recognizable identifier |
        | 401 | `AuthenticationError` | missing or invalid key |
        | 403 | `QuotaExceededError` | monthly lookups used; `exc.monthly_lookups` has the allowance |
        | 500 | `ServerError` after retries | temporary, safe to retry |
        
        A 404 is returned rather than raised because "this app is unknown" is normal data in an inventory loop. Every exception carries `.status`, `.message` and the parsed `.body`.
        
        In `lookup_many`, a 400 or 500 for one ID becomes a dict `{"query", "found": False, "error", "status"}` in the output list. A 401 or 403 is raised immediately, because every later call would fail the same way.
        
        ```python
        from appblocklist import AppBlockList, QuotaExceededError, AppResult
        
        client = AppBlockList()
        try:
            results = client.lookup_many(ids)
        except QuotaExceededError as exc:
            raise SystemExit("Out of lookups for this month: {}".format(exc.monthly_lookups))
        
        failed = [r for r in results if not isinstance(r, AppResult)]
        ```
        
        ## 6. Recipes
        
        ### Recipe A: generate a `.mobileconfig` that blocks apps by category
        
        Apple's Restrictions payload (`com.apple.applicationaccess`) takes a `blockedAppBundleIDs` array, as described in Apple's [Device Management documentation](https://developer.apple.com/documentation/devicemanagement). This script turns a candidate list into a signed-ready profile using only the standard library plus this package.
        
        ```python
        import plistlib
        import sys
        import uuid
        
        from appblocklist import AppBlockList, AppResult
        
        BLOCK = {"Gambling", "Dating", "Adult", "VPN / Proxy / Anonymizers", "Social Networking"}
        
        candidates = [line.strip() for line in open(sys.argv[1]) if line.strip()]
        client = AppBlockList()
        blocked = sorted({
            r.bundle_id
            for r in client.lookup_many(candidates, pause=0.15)
            if isinstance(r, AppResult) and r.found and r.bundle_id and set(r.filtering_categories) & BLOCK
        })
        
        payload = {
            "PayloadType": "com.apple.applicationaccess",
            "PayloadVersion": 1,
            "PayloadIdentifier": "org.example.appblocklist.restrictions",
            "PayloadUUID": str(uuid.uuid4()).upper(),
            "PayloadDisplayName": "Blocked apps",
            "blockedAppBundleIDs": blocked,
        }
        profile = {
            "PayloadType": "Configuration",
            "PayloadVersion": 1,
            "PayloadIdentifier": "org.example.appblocklist",
            "PayloadUUID": str(uuid.uuid4()).upper(),
            "PayloadDisplayName": "AppBlockList restrictions",
            "PayloadContent": [payload],
        }
        with open("blocked-apps.mobileconfig", "wb") as fh:
            plistlib.dump(profile, fh)
        print("{} bundle IDs written".format(len(blocked)))
        ```
        
        If you would rather not maintain this yourself, the [ready-made mobileconfig guide](https://www.appblocklist.com/mdm/mobileconfig-block-apps.php) explains the monthly profile that ships with the blocklist feed.
        
        ### Recipe B: per-device audit of an MDM export
        
        The repository ships `examples/audit_inventory.py`. It reads a CSV with `device` and `app_id` columns, looks up each distinct app once and writes `audit.csv` listing every device that has an app in a blocked category or rated above `12+`.
        
        ```bash
        python examples/audit_inventory.py inventory.csv
        # 37 violations written to audit.csv
        ```
        
        The key trick is grouping by app before calling the API, so a fleet of 2,000 iPads with the same 300 apps costs 300 lookups, not 600,000.
        
        ### Recipe C: app approval form with Flask
        
        ```python
        from flask import Flask, jsonify, request
        from appblocklist import AppBlockList, BadRequestError
        
        app = Flask(__name__)
        client = AppBlockList()
        
        HARD_NO = {"Gambling", "Adult", "Dating", "Illegal Drugs", "Weapons"}
        ASK_A_TEACHER = {"Social Networking", "Gaming", "Streaming Media", "AI / LLM Tools"}
        
        @app.post("/approve")
        def approve():
            try:
                r = client.lookup(request.form.get("app_id", ""))
            except BadRequestError as exc:
                return jsonify(error=exc.message), 400
            if not r.found:
                return jsonify(decision="manual", reason=r.error)
            cats = set(r.filtering_categories)
            if cats & HARD_NO or r.age_rating == "17+":
                decision = "deny"
            elif cats & ASK_A_TEACHER:
                decision = "manual"
            else:
                decision = "approve"
            return jsonify(decision=decision, app=r.name, categories=r.filtering_categories, age=r.age_rating)
        ```
        
        Schools receiving E-rate funds must filter content that is harmful to minors under the [Children's Internet Protection Act](https://www.fcc.gov/consumers/guides/childrens-internet-protection-act). An approval form that records the category and age rating for each decision gives you a paper trail for that policy.
        
        ### Recipe D: weekly cron job that reports new risky apps
        
        ```python
        # /opt/appaudit/weekly.py
        import json
        import pathlib
        from appblocklist import AppBlockList, AppResult
        
        STATE = pathlib.Path("/var/lib/appaudit/seen.json")
        WATCH = {"VPN / Proxy / Anonymizers", "Remote Access Tools", "Cryptocurrency", "AI / LLM Tools"}
        
        seen = set(json.loads(STATE.read_text())) if STATE.exists() else set()
        current = set(pathlib.Path("/var/lib/appaudit/inventory.txt").read_text().split())
        new_ids = sorted(current - seen)
        
        client = AppBlockList()
        for r in client.lookup_many(new_ids):
            if isinstance(r, AppResult) and r.found and set(r.filtering_categories) & WATCH:
                print("{:<45} {:<30} {}".format(r.query, r.name[:30], ", ".join(r.filtering_categories)))
        
        STATE.write_text(json.dumps(sorted(current)))
        ```
        
        ```cron
        0 6 * * 1  APPBLOCKLIST_API_KEY=your_key /opt/appaudit/.venv/bin/python /opt/appaudit/weekly.py | mail -s "New apps to review" it@example.org
        ```
        
        Only IDs that appeared since last week are looked up, which keeps the monthly count low.
        
        ## 7. Why use it
        
        - **One record per app across both stores.** About 800,000 apps carry their Android twin, so an iOS-only inventory still produces an Android package list.
        - **58 filtering categories that match the web side.** The taxonomy is the one applied to 120 million web domains, which means one policy can cover a site and its app.
        - **Age rating alongside categories.** Policies for younger students often hinge on `17+` regardless of category.
        - **Free misses.** Only a 200 answer is counted. Unknown apps, bad IDs and server errors cost nothing.
        - **Typed and testable.** A dataclass result, a small exception tree and an injectable `requests.Session` make it easy to mock in your own tests.
        
        For the full catalogue as a file, the [Mobile App Database](https://www.appblocklist.com/mobile-app-database.php) covers 1,874,397 iPhone and iPad apps in 12 columns. Teams that just want the finished lists can take the [monthly blocklist feed for MDM](https://www.appblocklist.com/mdm-app-blocklist-feed.php) in six formats.
        
        | Plan | Price |
        |---|---|
        | App Lookup API | $99 per month, 100,000 lookups; higher volumes quoted |
        | App Blocklists for MDM (School or Business) | $99/month or $990/year per organization |
        | Districts and IT Providers | $249/month or $2,490/year, unlimited schools or client organizations, API with 100,000 lookups a month included |
        | Mobile App Database | $999 one-time (top 100,000 apps), $1,999 one-time (top 500,000 apps) |
        
        See the [AppBlockList pricing page](https://www.appblocklist.com/pricing.php) for current terms.
        
        ## 8. FAQ
        
        ### How do I mock the client in my own unit tests?
        
        Pass a `mock.Mock(spec=requests.Session)` as `session=` and set `session.get.return_value` to an object with `status_code` and `json()`. The package's own `tests/test_client.py` shows the pattern with no network access.
        
        ### Is it safe to run lookups in threads?
        
        The API asks for requests one after another, so avoid thread pools. `lookup_many` already paces requests, and grouping by unique ID usually cuts the number of calls far more than parallelism would.
        
        ### What happens when an app is only on Google Play?
        
        If the package name is known but not yet categorized, you get `found=False`, an `error` saying it is listed on Google Play, and `android_package` filled in. Treat it as a manual review item.
        
        ### Can I look up an app by its name?
        
        No. The API takes identifiers only. Use the free [bundle ID finder](https://www.appblocklist.com/tools/bundle-id-lookup.php) to turn a name into a bundle ID first.
        
        ### Does categorizing apps help with children's privacy rules?
        
        Category and age rating are inputs to a policy, not legal advice. For services directed at children under 13 in the US, the FTC's [COPPA Rule](https://www.ftc.gov/legal-library/browse/rules/childrens-online-privacy-protection-rule-coppa) is the reference text.
        
        ### Which Python versions are tested?
        
        The package targets Python 3.7 and newer and uses only `requests` plus the standard library.
        
        ### What is MDM, briefly?
        
        Mobile device management is the practice of enrolling phones and tablets in a central console that pushes settings, apps and restrictions. The [Wikipedia article on mobile device management](https://en.wikipedia.org/wiki/Mobile_device_management) is a good neutral overview.
        
        ## 9. Links and related packages
        
        ### Links
        
        - Product: https://www.appblocklist.com/
        - Lookup API page: https://www.appblocklist.com/app-categorization-api.php
        - Google Admin guide: https://www.appblocklist.com/mdm/google-admin-block-apps.php
        - VPN and proxy apps list: https://www.appblocklist.com/lists/vpn-proxy-apps-list.php
        - Package name lookup: https://www.appblocklist.com/tools/package-name-lookup.php
        - GitHub: https://github.com/explainableaixai/appblocklist
        - GitLab: https://gitlab.com/url-classifications/appblocklist
        - Support: https://www.appblocklist.com/contact.php, info@alpha-quantum.com
        
        ### Related packages
        
        Same API, other runtimes:
        
        - [appblocklist on npm](https://www.npmjs.com/package/appblocklist) for Node.js
        - [appblocklist-go](https://github.com/explainableaixai/appblocklist-go)
        - [appblocklist-rust](https://github.com/explainableaixai/appblocklist-rust)
        - [appblocklist-java](https://github.com/explainableaixai/appblocklist-java)
        - [appblocklist-dotnet](https://github.com/explainableaixai/appblocklist-dotnet)
        - [appblocklist-php](https://github.com/explainableaixai/appblocklist-php)
        - [appblocklist-ruby](https://github.com/explainableaixai/appblocklist-ruby)
        - [appblocklist-dart](https://github.com/explainableaixai/appblocklist-dart)
        - [appblocklist-elixir](https://github.com/explainableaixai/appblocklist-elixir)
        
        Other Alpha Quantum data products for the network side:
        
        - The [web filtering database](https://www.webfilteringdatabase.com) of 120 million domains in the same 58 categories
        - [aitoolsblocklist on PyPI](https://pypi.org/project/aitoolsblocklist/) and the [AI blocklist for web filtering](https://www.aitoolsblocklist.com)
        - [CIPA compliant web filter](https://www.cipawebfiltering.com) for schools and libraries
        - [Domain categorization database](https://www.urlcategorizationdatabase.com)
        - [Website classification API](https://www.websitecategorizationapi.com)
        
        ## License
        
        MIT, Copyright (c) 2026 Alpha Quantum.
        
Keywords: mdm,app-blocklist,bundle-id,ios,android,app-categorization,intune,jamf,parental-control,school-devices,mobile-security,app-store,api-client
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: Intended Audience :: Education
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Security
Classifier: Topic :: System :: Systems Administration
Classifier: Topic :: Internet :: WWW/HTTP
Requires-Python: >=3.7
Description-Content-Type: text/markdown
