Official Allow2 Parental Freedom Service SDK for Python -- for web services with user accounts (Django sites, Flask apps, FastAPI, SaaS, forums, etc.).
This is a Service SDK -- it runs on your web server, not on a child's device. Following industry standard practice (Stripe, Firebase, Auth0), Allow2 maintains separate Device and Service SDKs. It handles OAuth2 pairing, permission checking, all 3 request types, voice codes, and feedback via the Allow2 Service API.
| Package | allow2-service |
| Targets | Python 3.9+ |
| Dependencies | requests>=2.28 |
| Language | Python (type hints, dataclasses) |
- Python 3.9 or later
requests-- HTTP requests
pip install allow2-service-
Register your application at developer.allow2.com and note your
client_idandclient_secret. -
Create an
Allow2Clientand wire up the OAuth2 flow:
from allow2_service import Allow2Client
from allow2_service.storage import FileTokenStorage
from allow2_service.cache import FileCache
allow2 = Allow2Client(
client_id="YOUR_SERVICE_TOKEN",
client_secret="YOUR_SERVICE_SECRET",
token_storage=FileTokenStorage("/var/lib/allow2/tokens.json"),
cache=FileCache("/tmp/allow2-cache"),
)
# Step 1: Redirect user to Allow2 for pairing
authorize_url = allow2.get_authorize_url(
user_id=current_user_id,
redirect_uri="https://yourapp.com/allow2/callback",
state=csrf_token,
)
# redirect(authorize_url)
# Step 2: Handle the callback
tokens = allow2.exchange_code(
user_id=current_user_id,
code=request.args["code"],
redirect_uri="https://yourapp.com/allow2/callback",
)
# Step 3: Check permissions on every request
result = allow2.check(current_user_id, [1, 3]) # Internet + Gaming
if not result.allowed:
# Show block page
pass
else:
remaining = result.get_remaining_seconds(1)
# Proceed normally, optionally showing countdownThe Service API links a specific user account on your site to exactly one Allow2 child. There is no child selector -- the identity is established at OAuth2 pairing time when the parent selects which child this account belongs to.
This means:
- Each user account on your site maps to one Allow2 child
- The parent performs pairing once per account, selecting the child
- All subsequent permission checks for that account apply to that child automatically
- Parent/admin accounts can be excluded from checking entirely
Redirect the user to Allow2 so their parent can pair the account:
import secrets
state = secrets.token_hex(16)
session["allow2_state"] = state
authorize_url = allow2.get_authorize_url(
user_id=current_user_id,
redirect_uri="https://yourapp.com/callback",
state=state,
)
# redirect(authorize_url)Handle the OAuth2 callback:
if request.args["state"] != session["allow2_state"]:
raise ValueError("Invalid state parameter")
tokens = allow2.exchange_code(
user_id=current_user_id,
code=request.args["code"],
redirect_uri="https://yourapp.com/callback",
)
# Tokens are stored automatically via the configured TokenStorageTokens expire. The SDK handles refresh automatically -- when you call check() or any other method that requires authentication, the SDK detects expired tokens and refreshes them transparently, persisting the updated tokens via your TokenStorageInterface.
Check permissions on every page load or API request:
# Simple format -- flat list of activity IDs (auto-expanded with log=True)
result = allow2.check(user_id, [1, 3, 8]) # Internet + Gaming + Screen Time
# Full format -- explicit log flags
result = allow2.check(user_id, [
{"id": 1, "log": True}, # Internet
{"id": 8, "log": True}, # Screen Time
], timezone="Australia/Sydney")
if not result.allowed:
print(f"Blocked! Day type: {result.today_day_type.name}")
for activity in result.activities:
if activity.banned:
print(f"{activity.name} is banned")
elif not activity.time_block_allowed:
print(f"{activity.name} outside allowed hours")
else:
remaining = result.get_remaining_seconds(1)
# Optionally show countdown in the UI# Returns True only if ALL specified activities are allowed
allowed = allow2.is_allowed(user_id, [1, 3])The SDK caches check results internally using your configured CacheInterface. The default TTL is 60 seconds and can be overridden via the constructor:
allow2 = Allow2Client(
client_id="YOUR_TOKEN",
client_secret="YOUR_SECRET",
token_storage=storage,
cache=cache,
cache_ttl=30, # cache for 30 seconds
)Children can request changes directly from your site. There are three types of request, and the philosophy is simple: the child drives the configuration, the parent just approves or denies.
request_result = allow2.request_more_time(
user_id=user_id,
activity_id=3, # Gaming
minutes=30,
message="Almost done with this level!",
)
print(f"Request ID: {request_result.request_id}")
# Poll for parent response
status = allow2.get_request_status(request_result.request_id, request_result.status_secret)
if status == "approved":
print("Approved!")
elif status == "denied":
print("Request denied.")
else:
print("Still waiting...")request_result = allow2.request_day_type_change(
user_id=user_id,
day_type_id=2, # Weekend
message="We have a day off school today.",
)request_result = allow2.request_ban_lift(
user_id=user_id,
activity_id=6, # Social Media
message="I finished all my homework. Can the ban be lifted?",
)Even though the child is on a website (online), the parent may have no internet -- perhaps they are at work with no signal, or their phone is flat. Voice codes let the parent approve a request by reading a short numeric code over the phone or in person.
from allow2_service.models import RequestType
pair = allow2.generate_voice_challenge(
secret=pairing_secret,
type=RequestType.MORE_TIME,
activity_id=3, # Gaming
minutes=30, # in 5-min increments
)
print(f"Challenge code: {pair.challenge}")
print("Read this to your parent. Ask them for the response code.")is_valid = allow2.verify_voice_response(
secret=pairing_secret,
challenge=pair.challenge,
response=parent_response_code,
)
if is_valid:
print("Approved! Extra time granted.")
else:
print("Invalid code. Please try again.")The codes use HMAC-SHA256 challenge-response, date-bound (expires at midnight). The format is compact enough to read over a phone call: a spaced challenge and a 6-digit response.
Let users submit bug reports and feature requests directly to you, the developer:
from allow2_service.models import FeedbackCategory
# Submit feedback -- returns the discussion ID
discussion_id = allow2.submit_feedback(
user_id=user_id,
category=FeedbackCategory.BUG,
message="The block page appears even when I have time remaining.",
)
# Load feedback threads
threads = allow2.load_feedback(user_id)
# Reply to a thread
allow2.reply_to_feedback(user_id, discussion_id, "This happens every Tuesday.")| Module | Purpose |
|---|---|
| Allow2Client | Main entry point, orchestrates all operations |
| OAuth2Manager | OAuth2 authorize, code exchange, token refresh |
| PermissionChecker | Permission checks with caching |
| RequestManager | All 3 request types with temp token + status polling |
| VoiceCode | HMAC-SHA256 challenge-response for offline approval |
| FeedbackManager | Submit, load, and reply to feedback threads |
| Model | Purpose |
|---|---|
CheckResult |
Parsed permission check response with per-activity status |
Activity |
Single activity's allowed/blocked state and remaining time |
DayType |
Current and upcoming day type information |
OAuthTokens |
Access token, refresh token, expiry |
RequestResult |
Request ID, status secret, and status with helper methods |
VoiceCodePair |
Challenge and expected response pair |
RequestType |
Enum: MORE_TIME, DAY_TYPE_CHANGE, BAN_LIFT |
FeedbackCategory |
Enum: BUG, FEATURE_REQUEST, NOT_WORKING, OTHER |
| Exception | When |
|---|---|
Allow2Error |
Base exception for all SDK errors |
ApiError |
HTTP or API-level errors |
TokenExpiredError |
Token refresh failed (re-pairing needed) |
UnpairedError |
No valid tokens for this user (401/403 from API) |
The SDK persists OAuth2 tokens automatically via the TokenStorageInterface you provide at construction. Two built-in adapters are included.
from allow2_service.storage import FileTokenStorage
token_storage = FileTokenStorage("/var/lib/allow2/tokens.json")from allow2_service.storage import MemoryTokenStorage
token_storage = MemoryTokenStorage()Implement TokenStorageInterface to integrate with your framework:
from allow2_service.token_storage import TokenStorageInterface
from allow2_service.models import OAuthTokens
class DjangoTokenStorage(TokenStorageInterface):
def store(self, user_id: str, tokens: OAuthTokens) -> None:
Allow2Token.objects.update_or_create(
user_id=user_id,
defaults={
"access_token": tokens.access_token,
"refresh_token": tokens.refresh_token,
"expires_at": tokens.expires_at,
},
)
def retrieve(self, user_id: str) -> OAuthTokens | None:
try:
record = Allow2Token.objects.get(user_id=user_id)
except Allow2Token.DoesNotExist:
return None
return OAuthTokens(
access_token=record.access_token,
refresh_token=record.refresh_token,
expires_at=record.expires_at,
)
def delete(self, user_id: str) -> None:
Allow2Token.objects.filter(user_id=user_id).delete()
def exists(self, user_id: str) -> bool:
return Allow2Token.objects.filter(user_id=user_id).exists()The Allow2 Service API follows a 7-step lifecycle, adapted for server-side web applications:
-
Pairing (one-time) -- OAuth2 flow redirects to Allow2 where the parent selects which child this account belongs to. Tokens are stored per user in your database.
-
Child Identification (automatic) -- one account = one child. The child's identity is established at pairing time and encoded in the OAuth2 tokens. No child selector is needed.
-
Parent Access -- admin or parent accounts on your site are simply excluded from Allow2 checking. No special Allow2 flow is needed.
-
Permission Checks (continuous) -- check on every page load or API request, server-side. Pass
log=Trueto record usage. The response includes remaining time, daily limits, time blocks, day types, and bans. -
Warnings & Countdowns -- the server calculates remaining time from the check result. Your frontend displays countdowns and warnings as appropriate (e.g. "5 minutes remaining").
-
Requests -- child requests changes (more time, day type change, ban lift) from your site. The parent approves or denies from their Allow2 app. For parents without internet, voice codes provide offline approval.
-
Feedback -- bug reports and feature requests are sent directly to you, the developer, via the Allow2 feedback system. This gives you a built-in support channel without building one yourself.
"Offline" for a web app sounds contradictory -- but the approval channel can be offline even when the child's browser is online.
The child is on your website (online), but their parent's phone may have no signal. Voice codes solve this:
- Child clicks "Request More Time" on your site
- Your server generates a spaced challenge code
- Child reads the code to their parent (phone call, in person)
- Parent enters it into their Allow2 app (works offline) and reads back the 6-digit response
- Child enters the response on your site
- Your server verifies the HMAC-SHA256 response and grants the time
If the Allow2 API is temporarily unreachable:
- Cache the last check result -- continue enforcing the last known permissions for a short grace period
- Deny by default -- after the grace period, block access to prevent bypass
- Queue requests -- store request attempts and replay them when connectivity resumes
Copyright 2017-2026 Allow2 Pty Ltd. All rights reserved.
See LICENSE for details.