cipawebfiltering 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cipawebfiltering/__init__.py +25 -0
- cipawebfiltering/client.py +209 -0
- cipawebfiltering-1.0.0.dist-info/LICENSE +21 -0
- cipawebfiltering-1.0.0.dist-info/METADATA +278 -0
- cipawebfiltering-1.0.0.dist-info/RECORD +7 -0
- cipawebfiltering-1.0.0.dist-info/WHEEL +5 -0
- cipawebfiltering-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
cipawebfiltering — Python client for the CIPA Web Filtering API.
|
|
3
|
+
|
|
4
|
+
A lightweight wrapper around the REST API at
|
|
5
|
+
https://www.cipawebfiltering.com that lets school districts, libraries,
|
|
6
|
+
and managed service providers look up, filter, and synchronize a
|
|
7
|
+
120-million-domain content classification database from Python.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .client import (
|
|
11
|
+
CIPAWebFilteringClient,
|
|
12
|
+
CIPAWebFilteringError,
|
|
13
|
+
AuthenticationError,
|
|
14
|
+
RateLimitError,
|
|
15
|
+
NotFoundError,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__version__ = "1.0.0"
|
|
19
|
+
__all__ = [
|
|
20
|
+
"CIPAWebFilteringClient",
|
|
21
|
+
"CIPAWebFilteringError",
|
|
22
|
+
"AuthenticationError",
|
|
23
|
+
"RateLimitError",
|
|
24
|
+
"NotFoundError",
|
|
25
|
+
]
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CIPA Web Filtering API client.
|
|
3
|
+
|
|
4
|
+
Wraps the REST endpoints at https://www.cipawebfiltering.com:
|
|
5
|
+
|
|
6
|
+
GET /api/v1/lookup/{domain} single domain classification
|
|
7
|
+
GET /api/v1/categories list all 57+ content categories
|
|
8
|
+
POST /api/v1/bulk-lookup classify up to 1,000 domains
|
|
9
|
+
GET /api/v1/delta changes since a timestamp
|
|
10
|
+
GET /api/v1/stats database statistics
|
|
11
|
+
|
|
12
|
+
Only the standard library and ``requests`` are required.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import time
|
|
16
|
+
from typing import Dict, Iterator, List, Optional
|
|
17
|
+
|
|
18
|
+
import requests
|
|
19
|
+
|
|
20
|
+
DEFAULT_BASE_URL = "https://cipawebfiltering.com/api/v1"
|
|
21
|
+
DEFAULT_TIMEOUT = 30
|
|
22
|
+
USER_AGENT = "cipawebfiltering-python/1.0.0 (+https://www.cipawebfiltering.com)"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class CIPAWebFilteringError(Exception):
|
|
26
|
+
"""Base exception for all client errors."""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AuthenticationError(CIPAWebFilteringError):
|
|
30
|
+
"""Raised on 401/403 — missing, invalid, expired or revoked API key."""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class RateLimitError(CIPAWebFilteringError):
|
|
34
|
+
"""Raised on 429 — request quota exceeded for the current plan."""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class NotFoundError(CIPAWebFilteringError):
|
|
38
|
+
"""Raised on 404 — resource or endpoint not found."""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class CIPAWebFilteringClient:
|
|
42
|
+
"""
|
|
43
|
+
Client for the CIPA Web Filtering domain classification API.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
api_key: Your API key from the account dashboard.
|
|
47
|
+
base_url: Override the API base URL (useful for testing).
|
|
48
|
+
timeout: Per-request timeout in seconds.
|
|
49
|
+
max_retries: Automatic retries on 429 / 5xx with backoff.
|
|
50
|
+
|
|
51
|
+
Example:
|
|
52
|
+
>>> from cipawebfiltering import CIPAWebFilteringClient
|
|
53
|
+
>>> client = CIPAWebFilteringClient("your_api_key")
|
|
54
|
+
>>> result = client.lookup("example.com")
|
|
55
|
+
>>> result["categories"]
|
|
56
|
+
['Education']
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
api_key: str,
|
|
62
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
63
|
+
timeout: int = DEFAULT_TIMEOUT,
|
|
64
|
+
max_retries: int = 3,
|
|
65
|
+
):
|
|
66
|
+
if not api_key:
|
|
67
|
+
raise ValueError("api_key is required")
|
|
68
|
+
self.api_key = api_key
|
|
69
|
+
self.base_url = base_url.rstrip("/")
|
|
70
|
+
self.timeout = timeout
|
|
71
|
+
self.max_retries = max_retries
|
|
72
|
+
self._session = requests.Session()
|
|
73
|
+
self._session.headers.update(
|
|
74
|
+
{
|
|
75
|
+
"Authorization": f"Bearer {api_key}",
|
|
76
|
+
"Accept": "application/json",
|
|
77
|
+
"Accept-Encoding": "gzip",
|
|
78
|
+
"User-Agent": USER_AGENT,
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
def _request(self, method: str, path: str, **kwargs) -> Dict:
|
|
83
|
+
url = f"{self.base_url}/{path.lstrip('/')}"
|
|
84
|
+
last_exc = None
|
|
85
|
+
for attempt in range(self.max_retries + 1):
|
|
86
|
+
try:
|
|
87
|
+
resp = self._session.request(method, url, timeout=self.timeout, **kwargs)
|
|
88
|
+
except requests.RequestException as exc:
|
|
89
|
+
last_exc = exc
|
|
90
|
+
if attempt < self.max_retries:
|
|
91
|
+
time.sleep(2 ** attempt)
|
|
92
|
+
continue
|
|
93
|
+
raise CIPAWebFilteringError(f"Request to {url} failed: {exc}") from exc
|
|
94
|
+
|
|
95
|
+
if resp.status_code in (200, 201):
|
|
96
|
+
return resp.json()
|
|
97
|
+
if resp.status_code == 401:
|
|
98
|
+
raise AuthenticationError(self._msg(resp, "Invalid or missing API key."))
|
|
99
|
+
if resp.status_code == 403:
|
|
100
|
+
raise AuthenticationError(self._msg(resp, "API key revoked or plan does not include this endpoint."))
|
|
101
|
+
if resp.status_code == 404:
|
|
102
|
+
raise NotFoundError(self._msg(resp, "Resource not found."))
|
|
103
|
+
if resp.status_code == 429:
|
|
104
|
+
if attempt < self.max_retries:
|
|
105
|
+
retry_after = int(resp.headers.get("Retry-After", 2 ** attempt))
|
|
106
|
+
time.sleep(retry_after)
|
|
107
|
+
continue
|
|
108
|
+
raise RateLimitError(self._msg(resp, "Rate limit exceeded."))
|
|
109
|
+
if resp.status_code >= 500:
|
|
110
|
+
if attempt < self.max_retries:
|
|
111
|
+
time.sleep(2 ** attempt)
|
|
112
|
+
continue
|
|
113
|
+
raise CIPAWebFilteringError(self._msg(resp, "Server error."))
|
|
114
|
+
raise CIPAWebFilteringError(self._msg(resp, f"Unexpected status {resp.status_code}."))
|
|
115
|
+
raise CIPAWebFilteringError(f"Request failed after retries: {last_exc}")
|
|
116
|
+
|
|
117
|
+
@staticmethod
|
|
118
|
+
def _msg(resp: requests.Response, default: str) -> str:
|
|
119
|
+
try:
|
|
120
|
+
body = resp.json()
|
|
121
|
+
return body.get("message") or body.get("error") or default
|
|
122
|
+
except ValueError:
|
|
123
|
+
return default
|
|
124
|
+
|
|
125
|
+
def lookup(self, domain: str) -> Dict:
|
|
126
|
+
"""
|
|
127
|
+
Classify a single domain against the CIPA filtering database.
|
|
128
|
+
Returns multi-label categories, block recommendation, and confidence.
|
|
129
|
+
"""
|
|
130
|
+
domain = self._clean_domain(domain)
|
|
131
|
+
return self._request("GET", f"/lookup/{domain}")
|
|
132
|
+
|
|
133
|
+
def is_blocked(self, domain: str, policy: str = "default") -> bool:
|
|
134
|
+
"""Convenience boolean — should this domain be blocked under the given policy?"""
|
|
135
|
+
result = self.lookup(domain)
|
|
136
|
+
return bool(result.get("should_block", False))
|
|
137
|
+
|
|
138
|
+
def bulk_lookup(self, domains: List[str]) -> Dict:
|
|
139
|
+
"""Classify up to 1,000 domains in a single request."""
|
|
140
|
+
if len(domains) > 1000:
|
|
141
|
+
raise ValueError("bulk_lookup accepts at most 1000 domains; use bulk_lookup_all().")
|
|
142
|
+
cleaned = [self._clean_domain(d) for d in domains]
|
|
143
|
+
return self._request("POST", "/bulk-lookup", json={"domains": cleaned})
|
|
144
|
+
|
|
145
|
+
def bulk_lookup_all(self, domains: List[str]) -> List[Dict]:
|
|
146
|
+
"""Classify any number of domains by chunking into 1,000-item requests."""
|
|
147
|
+
results: List[Dict] = []
|
|
148
|
+
for i in range(0, len(domains), 1000):
|
|
149
|
+
chunk = domains[i : i + 1000]
|
|
150
|
+
resp = self.bulk_lookup(chunk)
|
|
151
|
+
results.extend(resp.get("results", resp.get("domains", [])))
|
|
152
|
+
return results
|
|
153
|
+
|
|
154
|
+
def categories(self) -> Dict:
|
|
155
|
+
"""Return the full list of 57+ content categories with descriptions."""
|
|
156
|
+
return self._request("GET", "/categories")
|
|
157
|
+
|
|
158
|
+
def domains(
|
|
159
|
+
self,
|
|
160
|
+
category: Optional[str] = None,
|
|
161
|
+
status: Optional[str] = None,
|
|
162
|
+
cursor: Optional[str] = None,
|
|
163
|
+
) -> Dict:
|
|
164
|
+
"""Return one page of classified domains, optionally filtered by category."""
|
|
165
|
+
params = {}
|
|
166
|
+
if category:
|
|
167
|
+
params["category"] = category
|
|
168
|
+
if status:
|
|
169
|
+
params["status"] = status
|
|
170
|
+
if cursor:
|
|
171
|
+
params["cursor"] = cursor
|
|
172
|
+
return self._request("GET", "/domains", params=params)
|
|
173
|
+
|
|
174
|
+
def iter_domains(self, **filters) -> Iterator[Dict]:
|
|
175
|
+
"""Yield every domain record matching filters, walking cursor-based pagination."""
|
|
176
|
+
cursor = None
|
|
177
|
+
while True:
|
|
178
|
+
page = self.domains(cursor=cursor, **filters)
|
|
179
|
+
for record in page.get("domains", []):
|
|
180
|
+
yield record
|
|
181
|
+
cursor = page.get("next_cursor")
|
|
182
|
+
if not cursor:
|
|
183
|
+
break
|
|
184
|
+
|
|
185
|
+
def delta(self, since: str) -> Dict:
|
|
186
|
+
"""Return additions and removals since an ISO-8601 timestamp."""
|
|
187
|
+
return self._request("GET", "/delta", params={"since": since})
|
|
188
|
+
|
|
189
|
+
def stats(self) -> Dict:
|
|
190
|
+
"""Return database statistics: total domains, category counts, last update."""
|
|
191
|
+
return self._request("GET", "/stats")
|
|
192
|
+
|
|
193
|
+
@staticmethod
|
|
194
|
+
def _clean_domain(domain: str) -> str:
|
|
195
|
+
d = domain.strip().lower()
|
|
196
|
+
for prefix in ("https://", "http://"):
|
|
197
|
+
if d.startswith(prefix):
|
|
198
|
+
d = d[len(prefix):]
|
|
199
|
+
d = d.split("/")[0].split("?")[0]
|
|
200
|
+
return d
|
|
201
|
+
|
|
202
|
+
def close(self) -> None:
|
|
203
|
+
self._session.close()
|
|
204
|
+
|
|
205
|
+
def __enter__(self):
|
|
206
|
+
return self
|
|
207
|
+
|
|
208
|
+
def __exit__(self, *exc):
|
|
209
|
+
self.close()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alpha Quantum
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: cipawebfiltering
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python client for the CIPA Web Filtering API — 120M+ classified domains for K-12 schools, districts, and libraries.
|
|
5
|
+
Home-page: https://www.cipawebfiltering.com
|
|
6
|
+
Author: Alpha Quantum
|
|
7
|
+
Author-email: info@alpha-quantum.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Homepage, https://www.cipawebfiltering.com
|
|
10
|
+
Project-URL: API Documentation, https://www.cipawebfiltering.com/docs/integration-guide.php
|
|
11
|
+
Project-URL: Source, https://www.cipawebfiltering.com
|
|
12
|
+
Keywords: cipa,web filtering,content filtering,dns filtering,k-12,school web filter,domain classification,url categorization,children's internet protection act,safe browsing,firewall edl,acceptable use policy,domain database
|
|
13
|
+
Platform: UNKNOWN
|
|
14
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Intended Audience :: Education
|
|
17
|
+
Classifier: Intended Audience :: Information Technology
|
|
18
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
26
|
+
Classifier: Topic :: Internet :: Proxy Servers
|
|
27
|
+
Classifier: Topic :: Security
|
|
28
|
+
Classifier: Topic :: System :: Networking :: Firewalls
|
|
29
|
+
Requires-Python: >=3.7
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
Requires-Dist: requests>=2.20.0
|
|
32
|
+
|
|
33
|
+
# cipawebfiltering
|
|
34
|
+
|
|
35
|
+
A production-ready Python client for the [CIPA web filtering](https://www.cipawebfiltering.com) domain classification database — 120 million domains classified across 57+ content categories, purpose-built for K-12 school districts, public libraries, and any organization that must comply with the Children's Internet Protection Act. The package wraps the REST API in a small, typed, dependency-light interface so IT administrators, network engineers, and compliance teams can look up, filter, and synchronize domain intelligence directly from Python.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## What is CIPA and why does it matter?
|
|
40
|
+
|
|
41
|
+
The Children's Internet Protection Act (CIPA) is a United States federal law enacted in 2000 that requires schools and libraries receiving E-Rate funding or LSTA grants to implement internet safety policies and technology protection measures. In practice, this means deploying a web filtering solution that blocks access to content that is obscene, contains child sexual abuse material (CSAM), or is harmful to minors. Districts must also adopt and enforce an acceptable-use policy that covers student activity on and off campus, including 1:1 device programs.
|
|
42
|
+
|
|
43
|
+
Compliance is not optional: failure to meet CIPA requirements puts E-Rate funding at risk, which for many districts represents hundreds of thousands of dollars annually. Beyond the legal obligation, schools and libraries have a duty of care to protect minors from harmful material while preserving access to educational resources. Over-blocking legitimate content is almost as damaging as under-blocking, because it frustrates teachers, disrupts lesson plans, and erodes trust in the filtering system.
|
|
44
|
+
|
|
45
|
+
The [CIPA web filtering](https://www.cipawebfiltering.com) database addresses both sides of this challenge. Every domain receives multi-label classification rather than a single verdict, so a domain that hosts both educational and social-media content is tagged with both categories. Policy engines can then make precise blocking decisions per category instead of relying on a blunt allow-or-deny list, dramatically reducing false positives while maintaining full coverage of CIPA-mandated content types.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install cipawebfiltering
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The only runtime dependency is [`requests`](https://requests.readthedocs.io/). Python 3.7 and newer are supported.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Quick start
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from cipawebfiltering import CIPAWebFilteringClient
|
|
63
|
+
|
|
64
|
+
client = CIPAWebFilteringClient("your_api_key_here")
|
|
65
|
+
|
|
66
|
+
# Check a single domain
|
|
67
|
+
result = client.lookup("example-games-site.com")
|
|
68
|
+
print(result["categories"]) # ["Gaming", "Gambling"]
|
|
69
|
+
print(result["should_block"]) # True
|
|
70
|
+
print(result["confidence"]) # 0.97
|
|
71
|
+
|
|
72
|
+
# Convenience boolean for inline policy decisions
|
|
73
|
+
if client.is_blocked("unknown-domain.net"):
|
|
74
|
+
enforce_block("unknown-domain.net")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
An API key is required and can be obtained from the account dashboard at [cipawebfiltering.com](https://www.cipawebfiltering.com) after subscribing to a plan. Keys are passed automatically as a Bearer token on every request.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Configuration
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
client = CIPAWebFilteringClient(
|
|
85
|
+
api_key="your_api_key_here",
|
|
86
|
+
base_url="https://cipawebfiltering.com/api/v1", # override for testing
|
|
87
|
+
timeout=30, # per-request timeout in seconds
|
|
88
|
+
max_retries=3, # automatic backoff on 429 and 5xx
|
|
89
|
+
)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Transient failures — HTTP 429 rate limits and 5xx server errors — are retried automatically with exponential backoff, honoring the `Retry-After` header when present. Authentication errors raise immediately.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## API methods
|
|
97
|
+
|
|
98
|
+
### Single domain lookup
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
result = client.lookup("social-media-site.com")
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Response:**
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"domain": "social-media-site.com",
|
|
109
|
+
"categories": ["Social Media", "User-Generated Content"],
|
|
110
|
+
"should_block": true,
|
|
111
|
+
"confidence": 0.95,
|
|
112
|
+
"last_seen": "2026-07-22T08:00:00Z",
|
|
113
|
+
"dns_active": true
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Pass a bare domain — `example.com`, not `https://example.com/page`. Subdomains resolve to their registrable domain automatically. The response includes multi-label categories, a block recommendation based on your policy tier, and a confidence score. Both found and not-found domains return HTTP 200; check the `should_block` boolean or the `categories` array.
|
|
118
|
+
|
|
119
|
+
### Bulk lookup
|
|
120
|
+
|
|
121
|
+
Classify up to 1,000 domains in a single request:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
report = client.bulk_lookup(["tiktok.com", "khanacademy.org", "steam-community.ru"])
|
|
125
|
+
for row in report["results"]:
|
|
126
|
+
print(row["domain"], row["categories"], row["should_block"])
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
For arbitrarily large inputs, chunking is handled automatically:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
all_results = client.bulk_lookup_all(my_50000_domains)
|
|
133
|
+
blocked = [r for r in all_results if r["should_block"]]
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### List categories
|
|
137
|
+
|
|
138
|
+
Retrieve the full taxonomy of 57+ content categories with descriptions:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
cats = client.categories()
|
|
142
|
+
for cat in cats["categories"]:
|
|
143
|
+
print(cat["name"], "-", cat["description"])
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Categories include CIPA-mandated types such as Adult Content, Violence, Weapons, Drugs, Gambling, and Malware, as well as productivity-relevant categories like Social Media, Streaming, Gaming, and Shopping.
|
|
147
|
+
|
|
148
|
+
### Sync the full database
|
|
149
|
+
|
|
150
|
+
For DNS-level filtering, firewall External Dynamic Lists (EDLs), or local proxy caches, stream the entire classified domain list:
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
with open("blocked_domains.txt", "w") as fh:
|
|
154
|
+
for record in client.iter_domains(category="Adult Content"):
|
|
155
|
+
fh.write(record["domain"] + "\n")
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Then keep it current with daily delta syncs instead of re-downloading everything:
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
changes = client.delta(since="2026-07-21T00:00:00Z")
|
|
162
|
+
for added in changes["added"]:
|
|
163
|
+
local_blocklist.add(added["domain"])
|
|
164
|
+
for removed in changes["removed"]:
|
|
165
|
+
local_blocklist.discard(removed["domain"])
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
This "full load once, delta forever" pattern lets a modest API quota support millions of local lookups, because the actual matching happens in your own DNS resolver, Redis, SQLite, or flat file.
|
|
169
|
+
|
|
170
|
+
### Database statistics
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
stats = client.stats()
|
|
174
|
+
print(stats["total_domains"]) # 120000000+
|
|
175
|
+
print(stats["last_updated"]) # "2026-07-22T06:00:00Z"
|
|
176
|
+
print(stats["new_today"]) # ~300000
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Error handling
|
|
182
|
+
|
|
183
|
+
The client raises a small, specific exception hierarchy:
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
from cipawebfiltering import (
|
|
187
|
+
CIPAWebFilteringError,
|
|
188
|
+
AuthenticationError,
|
|
189
|
+
RateLimitError,
|
|
190
|
+
NotFoundError,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
try:
|
|
194
|
+
result = client.lookup("example.com")
|
|
195
|
+
except AuthenticationError:
|
|
196
|
+
# 401/403 — renew or rotate the key
|
|
197
|
+
...
|
|
198
|
+
except RateLimitError:
|
|
199
|
+
# 429 after retries — back off or upgrade the plan
|
|
200
|
+
...
|
|
201
|
+
except CIPAWebFilteringError:
|
|
202
|
+
# any other API or network failure
|
|
203
|
+
...
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The client is also a context manager, so the underlying HTTP session is cleaned up automatically:
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
with CIPAWebFilteringClient("your_api_key_here") as client:
|
|
210
|
+
print(client.stats())
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Use cases
|
|
216
|
+
|
|
217
|
+
**District-wide filtering:** Apply consistent content policies across every building in a district. Import the database into your DNS resolver or secure web gateway and enforce category-based rules that distinguish between a blocked gaming site and a permitted educational game.
|
|
218
|
+
|
|
219
|
+
**1:1 Chromebook and laptop programs:** Students take devices home, outside the school firewall. Feed the domain list into a DNS-over-HTTPS resolver or endpoint agent to maintain CIPA compliance on and off campus.
|
|
220
|
+
|
|
221
|
+
**Public library internet access:** Libraries must filter content on public terminals while respecting patron privacy and intellectual freedom. Multi-label classification lets librarians allow research resources that a single-category system would wrongly block.
|
|
222
|
+
|
|
223
|
+
**DNS and RPZ filtering:** Export the database as a Response Policy Zone (RPZ) file and load it into BIND, Unbound, or any RPZ-capable resolver. Blocking happens at the DNS layer with zero latency overhead on permitted traffic.
|
|
224
|
+
|
|
225
|
+
**Firewall EDL integration:** Generate External Dynamic Lists for Palo Alto, Fortinet, or Cisco firewalls. The delta sync endpoint keeps the list current without manual intervention.
|
|
226
|
+
|
|
227
|
+
**AI tool governance in schools:** The database includes a dedicated AI Tools subcategory covering 16,000+ domains — chatbots, essay writers, homework solvers, deepfake tools, and voice cloning services. Districts can permit approved AI tutoring tools while blocking services that undermine academic integrity.
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## How the database is built
|
|
232
|
+
|
|
233
|
+
The classification pipeline processes approximately 300,000 newly discovered domains every day. Each domain is fetched, its content extracted and analyzed through a multi-stage machine learning pipeline that assigns one or more of 57+ content categories. Unlike single-label classifiers that force every domain into exactly one bucket, the multi-label approach recognizes that real-world websites often span multiple topics. A domain hosting both educational math content and an unmoderated chat forum receives both the Education and the Chat/Messaging labels, allowing the policy engine to make a nuanced decision rather than defaulting to a blanket block or allow.
|
|
234
|
+
|
|
235
|
+
Every classification is verified against a confidence threshold before entering the production database. Domains whose content changes significantly between crawls are re-evaluated and re-labeled automatically. The result is a living dataset that reflects the current state of the web rather than a static snapshot that grows stale within weeks.
|
|
236
|
+
|
|
237
|
+
All content categories required by CIPA — obscene material, CSAM, and content harmful to minors — are maintained as top-level categories with the highest screening priority. Additional categories cover productivity concerns (Social Media, Streaming, Gaming, Shopping), security threats (Malware, Phishing, Command and Control), and emerging risks (AI Tools, Deepfakes, Cryptocurrency).
|
|
238
|
+
|
|
239
|
+
## Delivery formats
|
|
240
|
+
|
|
241
|
+
The [CIPA web filtering](https://www.cipawebfiltering.com) database is available in multiple formats beyond this Python client:
|
|
242
|
+
|
|
243
|
+
- **REST API** — real-time lookups with millisecond response times
|
|
244
|
+
- **CSV downloads** — daily-refreshed flat files for offline use
|
|
245
|
+
- **DNS / RPZ blocklists** — ready-to-load zone files
|
|
246
|
+
- **PAC files** — browser-level proxy auto-config
|
|
247
|
+
- **Hosts files** — simple domain-to-localhost mapping
|
|
248
|
+
- **Firewall EDLs** — external dynamic lists for next-gen firewalls
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Related services
|
|
253
|
+
|
|
254
|
+
For organizations that need broader domain intelligence beyond CIPA compliance, the following services complement this package:
|
|
255
|
+
|
|
256
|
+
* **[Website Categorization API](https://www.websitecategorizationapi.com):** Real-time URL classification using the IAB taxonomy across 700+ categories, supporting ad-tech, brand safety, and content analytics.
|
|
257
|
+
|
|
258
|
+
* **[AI Tools Blocklist](https://www.aitoolsblocklist.com):** A daily-refreshed database of classified AI-tool domains — chatbots, code assistants, image generators, voice cloners — organized into functional categories for granular acceptable-use policies.
|
|
259
|
+
|
|
260
|
+
* **[Web Filtering Database](https://www.webfilteringdatabase.com):** Enterprise-grade downloadable database of 100 million domains across 59 content categories, designed for DNS-level blocking in firewalls, proxies, and secure web gateways.
|
|
261
|
+
|
|
262
|
+
* **[URL Categorization Database](https://www.urlcategorizationdatabase.com):** Categorized domains at enterprise scale for contextual targeting, brand safety, and large-scale analytics.
|
|
263
|
+
|
|
264
|
+
* **[Phishing Detection API](https://www.phishingdetectionapi.com):** Real-time phishing domain detection powered by a daily-updated database of 390,000+ DNS-verified active phishing domains, built for cybersecurity teams, email providers, and safe browsing implementations.
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Links
|
|
269
|
+
|
|
270
|
+
- Product and documentation: [https://www.cipawebfiltering.com](https://www.cipawebfiltering.com)
|
|
271
|
+
- FCC — Children's Internet Protection Act: [https://www.fcc.gov/consumers/guides/childrens-internet-protection-act](https://www.fcc.gov/consumers/guides/childrens-internet-protection-act)
|
|
272
|
+
- E-Rate program: [https://www.fcc.gov/general/e-rate-program](https://www.fcc.gov/general/e-rate-program)
|
|
273
|
+
|
|
274
|
+
## License
|
|
275
|
+
|
|
276
|
+
MIT
|
|
277
|
+
|
|
278
|
+
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
cipawebfiltering/__init__.py,sha256=4h7EDEi_Hpb1cQ8iM7ecOHn0W6RPtSvsaSxRjwKGbnQ,632
|
|
2
|
+
cipawebfiltering/client.py,sha256=uzcUUlhR74tRpnqMs4e4VSB10VZOF2-r94006ZdBeng,7789
|
|
3
|
+
cipawebfiltering-1.0.0.dist-info/LICENSE,sha256=-kjrwollysEMZkPQkJIq5zyefl9XyPw5egz8knSXiB4,1070
|
|
4
|
+
cipawebfiltering-1.0.0.dist-info/METADATA,sha256=hka-j1tcx8qryr6zsNwJO49XXb96mM5N-3ePUlPIjNc,13747
|
|
5
|
+
cipawebfiltering-1.0.0.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
|
6
|
+
cipawebfiltering-1.0.0.dist-info/top_level.txt,sha256=rSgdN_xGPN4vAGf_jCA6tT31i5-ZXawyFojeBypUsVY,17
|
|
7
|
+
cipawebfiltering-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cipawebfiltering
|