appblocklist (Rust)
A blocking Rust client for the AppBlockList App Lookup API. You hand it a bundle ID, an App Store ID or an Android package name; it hands back a serde-ready App struct with the categories an MDM policy needs.
| Crate | appblocklist 1.0.0 |
| Edition | 2021 |
| Dependencies | ureq, serde, serde_json |
| Async runtime | none required |
| Endpoint | GET https://www.appblocklist.com/api/v1/app?id=<ID> |
| Source | https://github.com/explainableaixai/appblocklist-rust |
The service behind it is AppBlockList, a catalogue of iPhone, iPad and Android apps sorted into 58 filtering categories for device management.
Contents
- Install
- First lookup
- The
Appstruct - Handling
Error - Example: a policy gate CLI
- Example: generating a restrictions payload
- Example: calling it from async code
- Design notes
- Why category data
- FAQ
- Links and Related packages
Install
or by hand in Cargo.toml:
[]
= "1.0"
You need an API key. The App Lookup API costs $99 per month for 100,000 lookups, with higher volumes quoted on request. Keep the key out of your source tree:
First lookup
use Client;
TikTok - Videos, Shop & LIVE (12+)
1. Social Networking
2. Streaming Media
Client::new("key") works too, if you load secrets from somewhere other than the environment. Both constructors give a client with a 30 second timeout, two retries for temporary failures and the User-Agent appblocklist-rust/1.0.0 (+https://www.appblocklist.com).
Accepted IDs
com.zhiliaoapp.musically: an iOS bundle ID835599320orid835599320: an App Store ID, prefix optionalcom.openai.chatgpt: an Android package name
The client trims the ID and rejects empty strings, IDs with spaces inside and anything longer than 200 characters before it sends a request.
The App struct
Field names match the JSON keys, so serde_json::to_string(&app) gives you the same document the API returned. That makes it easy to cache answers on disk or forward them to another service.
Helper methods:
| Method | Returns | Use |
|---|---|---|
primary_category() |
Option<&str> |
The first ranked category |
in_category(&["A", "B"]) |
bool |
Any-of match, ASCII case-insensitive |
on_google_play() |
bool |
An Android package name is known |
block_ids() |
Vec<&str> |
Bundle ID and Android twin, de-duplicated |
Client methods:
| Method | Purpose |
|---|---|
Client::new(key) / Client::from_env() |
Construct |
.with_base_url(url) |
Point at a mock server |
.with_agent(agent) |
Bring your own ureq::Agent (proxy, TLS roots, timeouts) |
.with_retries(n, wait) |
Retry policy for 5xx and transport errors |
.lookup(id) |
One app |
.lookup_many(ids, pause) |
Sequential lookups, duplicates skipped |
Free functions: validate_id(&str) and parse_response(status, body). The second is what lookup uses internally, exposed so you can test your own code against recorded answers.
Handling Error
A 404 is not an error. It returns Ok(App { found: false, .. }), with the API's explanation in message. Everything else that can fail is one enum:
| Variant | HTTP | Cause | is_temporary() |
is_fatal() |
|---|---|---|---|---|
InvalidId(msg) |
400 | ID missing or not a recognised format | no | no |
Unauthorized(msg) |
401 | Missing or invalid key | no | yes |
QuotaExceeded { message, monthly_lookups } |
403 | Monthly lookups used up | no | yes |
Server { status, message } |
500 | Temporary error | yes | no |
Unexpected { status, message } |
other | Undocumented status | no | no |
Transport(msg) |
none | DNS, TLS, timeout | yes | no |
Decode(msg) |
200 | Body was not the expected JSON | no | no |
MissingApiKey |
none | No key configured | no | yes |
Temporary errors are retried inside lookup before you see them. A typical match:
use ;
Error implements std::error::Error, so it works with ?, Box<dyn Error>, anyhow and thiserror wrappers.
Example: a policy gate CLI
The crate ships a complete command line tool in examples/abl.rs. It shows the patterns most users need: reading IDs from arguments or stdin, applying a policy, and reporting through exit codes so that shell scripts and CI jobs can react.
| Exit code | Meaning |
|---|---|
| 0 | Every app is within policy |
| 1 | At least one app breaks policy |
| 2 | Usage or configuration error |
| 3 | Monthly lookups used up |
The heart of the check command is one function:
/// "12+" -> 12
And the loop that feeds it uses lookup_many, which sends requests one after another, skips repeated IDs and stops early on a bad key or an empty quota:
let = client.lookup_many;
if let Some = fatal
for in &results
Put it in a pre-merge job for the repository that holds your approved app list:
policy-check:
script:
- cargo run --release --example abl -- check --block "Social Networking" --max-age 12 - < mdm/approved-apps.txt
For a list of IDs to seed approved.txt, the free iOS bundle ID finder and Android package lookup save you digging through store pages.
Example: generating a restrictions payload
Supervised iPhones and iPads accept a Restrictions payload (com.apple.applicationaccess) whose blockedAppBundleIDs key lists apps that may not launch. The keys are documented in Apple's device management reference. The abl mobileconfig subcommand prints that array for every app that breaks policy:
blockedAppBundleIDs
com.zhiliaoapp.musically
If you prefer to build the list in your own code, the struct makes it a filter and a map:
use Client;
use Duration;
Paste the result into a profile, or let your MDM import it. The site has a walkthrough for building a .mobileconfig app restriction profile by hand, and guides for Intune and Jamf. On Android, the same android_package values go into the app blocking policy your EMM applies through Android Enterprise.
Example: calling it from async code
The client is blocking on purpose: it keeps the dependency tree small and works in build scripts, CLIs and threads without a runtime. In a Tokio service, move the call onto the blocking pool:
use ;
use Arc;
async
Client is Clone and Send + Sync; clones share the same connection pool. Keep one per process. Because the service asks for one request at a time per key, put a tokio::sync::Semaphore with one permit in front of it if many tasks share the client:
let gate = new;
let _permit = gate.acquire.await.unwrap;
let app = lookup_async.await?;
Design notes
- No surprises on 404. Unknown apps are a normal outcome in any inventory, so they are data, not errors.
- Sequential by default.
lookup_manynever fans out. That is what the service asks for, and it keeps cost predictable: only 200 answers count as lookups. - Header auth. The key goes in
X-API-Key. The API also accepts anapi_keyquery parameter, but headers stay out of access logs. - Lenient parsing.
app_store_idis a string in the API; the struct also accepts a number. Anullcategory list becomes an emptyVec. - Offline tests.
tests/parse_samples.rsreplays every documented answer throughparse_response. Runcargo testwith no network and no key.
Why category data
Most app policies boil down to a few sentences: no social media on student devices, no streaming on shared kiosks, nothing rated 17+ for younger grades. The hard part is the mapping from those sentences to thousands of bundle IDs that change every week.
The lookup does that mapping. Its database covers 1,874,397 iPhone and iPad apps, links about 800,000 of them to their Android twin and tracks 3.9 million Google Play package names. Each record carries ranked filtering categories from the same 58-category scheme that is applied to 120 million web domains. If your network side already uses a categorized domain feed, the app side can reuse the policy word for word.
Schools in the US have a legal reason to care: the Children's Internet Protection Act ties E-rate funding to filtering. The CIPA-compliant web filtering service covers browsers; this crate covers what is installed on the device.
Files instead of calls
You do not always need the API:
- The MDM app blocklist feed is refreshed every month and ships in six formats, including a
.mobileconfigprofile and Intune and Jamf import files. Schools and businesses pay $99/month or $990/year per organization; districts and IT providers pay $249/month or $2,490/year and get the API with 100,000 lookups a month included. - The Mobile App Database is a licensed file with 12 columns per app: $999 one-time for the top 100,000 apps or $1,999 one-time for the top 500,000.
All plans are on the pricing page.
FAQ
Why is there no async client?
Because most callers do not need one, and a blocking client built on ureq adds no runtime to your binary. Wrapping it in spawn_blocking (shown above) is a few lines.
How do I test code that uses this crate without spending lookups?
Use parse_response(status, body) with recorded JSON, or start a local HTTP server in your test and point the client at it with .with_base_url("http://127.0.0.1:PORT/api/v1").
Does a 404 count against my monthly quota?
No. Only 200 answers are counted. Errors such as 400 and 401 are free as well.
What happens when the quota runs out?
You get Error::QuotaExceeded, with monthly_lookups set when the API includes the count. lookup_many stops at that point and returns what it already collected.
Can I send an Android package and get the iOS bundle ID back?
Yes, when the app exists on both stores. The answer includes bundle_id, app_store_id and android_package, so one lookup covers a mixed fleet. A package that is on Google Play but not yet categorized comes back with found: false and android_package set.
Where can I see which apps are typically blocked?
Start with the curated pages for AI chat and assistant apps, VPN and proxy apps and the guide to apps schools block on iPads.
What format is the response?
A single JSON object (RFC 8259) per request.
Links
- AppBlockList: https://www.appblocklist.com
- API reference: https://www.appblocklist.com/app-categorization-api.php
- This crate on GitHub: https://github.com/explainableaixai/appblocklist-rust
- Product repository on GitLab: https://gitlab.com/url-classifications/appblocklist
- Questions and bugs: https://www.appblocklist.com/contact.php, info@alpha-quantum.com
Related packages
The same API, other languages:
- Go: appblocklist-go
- Java: appblocklist-java
- .NET: appblocklist-dotnet
- Node.js: appblocklist (npm)
- Python: appblocklist (PyPI)
- npm and PyPI source: explainableaixai/appblocklist
Other services from Alpha Quantum: AI tools blocklist, URL categorization database, website categorization API.
License
MIT, Copyright (c) 2026 Alpha Quantum. See LICENSE.