App categorization and MDM app blocklists for iOS and Android. This repository is the home of the open-source AppBlockList clients: it documents the App Lookup API, the six blocklist feed formats and the official libraries for ten languages.
AppBlockList answers one question for device administrators and developers: what kind of app is this? Send an iOS bundle ID, an App Store ID or an Android package name and get back the app's ranked filtering categories, age rating, genre, developer and both store identifiers.
| Filtering categories | 58, ranked per app, shared with 120 million categorized web domains |
| Apps in the Mobile App Database | 1,874,397 iPhone and iPad apps |
| Apps with a known Android twin | about 800,000 |
| Google Play package names tracked | 3.9 million |
| App Store genres | 26 |
| Feed refresh | every month |
| Feed formats | 6 |
| Client libraries | Node.js, Python, Go, Rust, Java, .NET, PHP, Ruby, Dart, Elixir |
| License of the clients | MIT |
| Product | Who it is for | What you get |
|---|---|---|
| App Lookup API | developers, MDM integrators | one JSON record per app, on demand |
| MDM App Blocklist Feed | school and business IT teams | ready-to-import block lists in six formats, refreshed monthly |
| Mobile App Database | analysts, security vendors | a licensed file with 12 columns per app |
The rest of this README covers the API first, then the feed formats, then the client libraries and worked examples.
GET https://www.appblocklist.com/api/v1/app?id=<ID>&api_key=<KEY>
The key may be sent as a header instead, which keeps it out of server logs:
GET https://www.appblocklist.com/api/v1/app?id=<ID>
X-API-Key: <KEY>
id accepts:
- an iOS bundle ID, for example
com.zhiliaoapp.musically - an App Store ID, as digits (
835599320) or with theidprefix (id835599320) - an Android package name, for example
com.openai.chatgpt
The maximum length is 200 characters.
{
"found": true,
"name": "TikTok - Videos, Shop & LIVE",
"developer": "TikTok Ltd.",
"bundle_id": "com.zhiliaoapp.musically",
"app_store_id": "835599320",
"android_package": "com.zhiliaoapp.musically",
"app_store_genre": "Entertainment",
"age_rating": "12+",
"filtering_categories": ["Social Networking", "Streaming Media"],
"developer_domain": "tiktok.com",
"iab_category": "Technology & Computing > Computing > Internet > Social Networking"
}| Field | Type | Description |
|---|---|---|
found |
boolean | true when the app is categorized |
name |
string | App Store name |
developer |
string | publisher |
bundle_id |
string | iOS bundle identifier |
app_store_id |
string | numeric App Store ID, returned as a string |
android_package |
string | Google Play twin when the app is on both stores, otherwise "" |
app_store_genre |
string | App Store genre |
age_rating |
string | 4+, 9+, 12+ or 17+ |
filtering_categories |
string[] | one or more of 58 categories, most descriptive first |
developer_domain |
string | publisher's web domain |
iab_category |
string | IAB content taxonomy path |
The body is plain JSON as defined by RFC 8259, so any language can read it without a client library.
| Code | Body | What to do |
|---|---|---|
| 200 | the record above | use it; one lookup is counted |
| 400 | {"error": ...} |
the id is missing or is not a bundle ID, App Store ID or package name |
| 401 | {"error": ...} |
send a valid api_key or X-API-Key |
| 403 | {"error": ..., "monthly_lookups": ...} |
the key has used its monthly lookups |
| 404 | {"found": false, "error": "App not found."} |
unknown app, treat as data |
| 404 | {"found": false, "android_package": "...", "error": "Listed on Google Play, not yet in the categorized database."} |
real Play app, queue for review |
| 500 | {"error": ...} |
temporary, retry with backoff |
- One ID per request.
- Send requests one after another; avoid parallel bursts.
- Only a 200 answer counts as a lookup.
All official clients follow these rules: they never fan out requests and they treat a 404 as a normal result with found = false rather than an exception.
Each app gets one or more categories, ranked. A few examples of the names you will see in filtering_categories:
| Group | Example categories |
|---|---|
| Social and communication | Social Networking, Email and Messaging, Dating, Forums / Message Boards, Community Sites |
| Entertainment | Gaming, Streaming Media, Arts & Entertainment, Podcasts |
| Risk | Gambling, Adult, VPN / Proxy / Anonymizers, Remote Access Tools, Illegal Drugs, Weapons, Violence |
| Work and study | Education/Reference, Business, Web-based Applications, Developer Tools / Code Hosting, Webinars/E-learning |
| Money | Financial Services, Online Banking, Cryptocurrency, Shopping |
| Emerging | AI / LLM Tools |
These are the same category names applied to 120 million domains in the web filtering database. If your DNS filter blocks Gambling, the matching app rule uses the identical string.
Subscribers to App Blocklists for MDM receive a monthly feed covering all 58 categories. Every category is delivered in each of these formats:
| # | Format | File type | Where it goes |
|---|---|---|---|
| 1 | iOS bundle ID list | plain text, one ID per line | any MDM that accepts a pasted list of bundle IDs |
| 2 | Android package list | plain text, one package per line | Android Enterprise managed Play policies, Google Admin |
| 3 | Configuration profile | .mobileconfig, Restrictions payload com.apple.applicationaccess with blockedAppBundleIDs |
Apple Configurator, any Apple MDM |
| 4 | Intune import | Intune import files | Microsoft Intune app restriction policies |
| 5 | Jamf import | Jamf import files | Jamf Pro and Jamf School restrictions |
| 6 | Full CSV | name, developer, both IDs, genre, age rating, categories | spreadsheets, SIEM, custom tooling |
Districts and IT providers get separate files per school or client. Guides for each console: Intune, Jamf, Google Admin and mobileconfig profiles.
Every client is MIT licensed, sends appblocklist-<lang>/1.0.0 (+https://www.appblocklist.com) as its User-Agent, reads the key from APPBLOCKLIST_API_KEY when none is passed, exposes lookup(id) and raises typed errors for 400, 401, 403 and 500.
| Language | Install | Repository |
|---|---|---|
| Node.js | npm install appblocklist |
npm package |
| Python | pip install appblocklist |
PyPI project |
| Go | go get github.com/explainableaixai/appblocklist-go |
appblocklist-go |
| Rust | cargo add appblocklist |
appblocklist-rust |
| Java | Maven com.alphaquantum:appblocklist:1.0.0 |
appblocklist-java |
| .NET | dotnet add package AppBlockList |
appblocklist-dotnet |
| PHP | composer require alphaquantum/appblocklist |
appblocklist-php |
| Ruby | gem install appblocklist |
appblocklist-ruby |
| Dart | dart pub add appblocklist |
appblocklist-dart |
| Elixir | {:appblocklist, "~> 1.0"} in mix.exs |
appblocklist-elixir |
Each repository has its own README with the exact install line, a method table and tests that run without network access.
curl -s -H "X-API-Key: $APPBLOCKLIST_API_KEY" \
"https://www.appblocklist.com/api/v1/app?id=id835599320"Show only what matters for a policy decision with jq:
curl -s -H "X-API-Key: $APPBLOCKLIST_API_KEY" \
"https://www.appblocklist.com/api/v1/app?id=com.openai.chatgpt" \
| jq '{found, name, age_rating, filtering_categories}'Print the HTTP status alongside the body, useful when debugging keys:
curl -s -o /tmp/app.json -w '%{http_code}\n' \
-H "X-API-Key: $APPBLOCKLIST_API_KEY" \
"https://www.appblocklist.com/api/v1/app?id=com.example.unknown"
cat /tmp/app.jsonThis script reads one ID per line, respects the one-at-a-time rule, retries on 500 and stops on 401 or 403.
#!/usr/bin/env bash
# categorize.sh inventory.txt > categorized.csv
set -euo pipefail
: "${APPBLOCKLIST_API_KEY:?set APPBLOCKLIST_API_KEY}"
API="https://www.appblocklist.com/api/v1/app"
echo "id,found,name,bundle_id,android_package,age_rating,categories"
sort -u "$1" | while read -r id; do
[ -z "$id" ] && continue
for attempt in 1 2 3; do
code=$(curl -s -o /tmp/abl.json -w '%{http_code}' -G "$API" \
--data-urlencode "id=$id" -H "X-API-Key: $APPBLOCKLIST_API_KEY")
[ "$code" != "500" ] && break
sleep $((attempt * 2))
done
case "$code" in
200|404)
jq -r --arg id "$id" '[$id, .found, .name // "", .bundle_id // "", .android_package // "",
.age_rating // "", ((.filtering_categories // []) | join("; "))] | @csv' /tmp/abl.json ;;
401|403)
echo "stopping: HTTP $code $(jq -r .error /tmp/abl.json)" >&2; exit 1 ;;
*)
echo "\"$id\",error,,,,,HTTP $code" ;;
esac
sleep 0.2
done# Every app in a blocked category, split by platform
BLOCK='Gambling|Adult|Dating|VPN / Proxy / Anonymizers'
grep -E "$BLOCK" categorized.csv | csvcut -c bundle_id | tail -n +2 | grep -v '^$' > blocked-ios.txt
grep -E "$BLOCK" categorized.csv | csvcut -c android_package | tail -n +2 | grep -v '^$' > blocked-android.txt
wc -l blocked-*.txt(csvcut comes from csvkit; any CSV tool works.)
import os
import time
import requests
API = "https://www.appblocklist.com/api/v1/app"
HEADERS = {"X-API-Key": os.environ["APPBLOCKLIST_API_KEY"]}
def lookup(app_id):
for attempt in range(3):
r = requests.get(API, params={"id": app_id}, headers=HEADERS, timeout=15)
if r.status_code != 500:
break
time.sleep(2 * (attempt + 1))
if r.status_code in (200, 404):
return r.json()
raise RuntimeError("HTTP {}: {}".format(r.status_code, r.json().get("error")))
for app_id in ["com.zhiliaoapp.musically", "com.openai.chatgpt", "835599320"]:
rec = lookup(app_id)
print(app_id, rec.get("found"), rec.get("filtering_categories"))
time.sleep(0.2)from appblocklist import AppBlockList, AppResult
client = AppBlockList() # APPBLOCKLIST_API_KEY
STUDENT_POLICY = {"Gambling", "Adult", "Dating", "Social Networking", "VPN / Proxy / Anonymizers"}
inventory = open("inventory.txt").read().split()
results = client.lookup_many(inventory, pause=0.2)
for r in results:
if isinstance(r, AppResult) and r.found:
verdict = "BLOCK" if set(r.filtering_categories) & STUDENT_POLICY or r.age_rating == "17+" else "allow"
print("{:<6} {:<40} {}".format(verdict, r.name[:40], r.primary_category))android_blocklist = sorted({
r.android_package
for r in results
if isinstance(r, AppResult) and r.found and r.android_package
and set(r.filtering_categories) & STUDENT_POLICY
})
open("blocked-android.txt", "w").write("\n".join(android_blocklist) + "\n")Because each record carries both identifiers, a single iPad inventory yields the matching Android policy for every app that exists on both stores.
- Apps and websites in one taxonomy. The 58 categories are shared with the domain side, so a "block Gambling" policy means the same thing on the network and on the device.
- Both platforms per record. iOS and Android identifiers arrive together for about 800,000 apps.
- Policy-ready fields. Ranked categories, age rating and genre cover the questions school and business policies usually ask.
- Files or API, your choice. Use the monthly feed when you want lists, the API when you want decisions in your own code, and the database when you want everything offline.
- Predictable pricing. Plans are per organization, never per device.
| Product | Price |
|---|---|
| App Lookup API | $99 per month for 100,000 lookups; higher volumes quoted |
| App Blocklists for MDM, School or Business | $99/month or $990/year per organization |
| App Blocklists for MDM, Districts and IT Providers | $249/month or $2,490/year, unlimited schools or client organizations, includes the API with 100,000 lookups a month |
| Mobile App Database, top 100,000 apps | $999 one-time |
| Mobile App Database, top 500,000 apps | $1,999 one-time |
Details: appblocklist.com/pricing.php.
The bundle ID lookup tool and the package name lookup tool on the website show categories for individual apps in the browser.
android_package is filled only when the same app is published on Google Play. Apple-only apps return "".
As data. "This app is not categorized" is a normal answer in an inventory loop, so every official client returns a result with found = false instead of throwing.
Both work. The list is ranked, so filtering_categories[0] is the most descriptive label; matching against any entry catches apps that span several categories, such as a game with a social feed.
Yes. Application allow and deny lists for managed mobile devices are discussed in NIST SP 800-124 Rev. 2. On Android the policy mechanism is described in the Android Enterprise documentation.
Many subscribers are schools. US schools and libraries that take E-rate discounts follow the Children's Internet Protection Act, and an app policy is a natural companion to the web filter they already run. A starter list is published at apps to block on school iPads.
Yes. Open an issue or pull request in the language repository. For questions about data or plans, use the contact page.
- Website: https://www.appblocklist.com/
- AI apps list: https://www.appblocklist.com/lists/ai-apps-list.php
- VPN and proxy apps list: https://www.appblocklist.com/lists/vpn-proxy-apps-list.php
- GitLab mirror: https://gitlab.com/url-classifications/appblocklist
- Email: info@alpha-quantum.com
| Package | Where |
|---|---|
| appblocklist (Node.js) | https://www.npmjs.com/package/appblocklist |
| appblocklist (Python) | https://pypi.org/project/appblocklist/ |
| appblocklist-go, -rust, -java, -dotnet, -php, -ruby, -dart, -elixir | https://github.com/explainableaixai |
Sister products from Alpha Quantum for the network layer:
- Generative AI blocklist feed for blocking AI tool domains
- Web filter for CIPA compliance in schools and libraries
- Domain filtering database with 120 million categorized domains
- URL categorization data
- Categorization API for websites
The client code in this repository and in every appblocklist-* repository is released under the MIT License. Copyright (c) 2026 Alpha Quantum. See LICENSE.