Developer Resources -- The Allow2 MCP Server provides comprehensive API documentation, integration guides, architecture overviews, and interactive examples. Connect it to your AI coding assistant for the best development experience. Start there.
Official Allow2 Parental Freedom Service SDK for Java -- for web services with user accounts (Spring Boot, Jakarta EE, etc.).
This is a Service SDK -- it runs on your web server, not on a child's device. If you are building a device or product owned by a family (a game, smart device, desktop app, etc.), you need the Device SDK instead.
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.
| Group | com.allow2 |
| Artifact | allow2-service |
| Targets | Java 11+ |
| HTTP | java.net.http.HttpClient (no external deps) |
- Java 11 or later
- No external HTTP dependencies (uses
java.net.http.HttpClient)
implementation 'com.allow2:allow2-service:2.0.0-alpha.1'<dependency>
<groupId>com.allow2</groupId>
<artifactId>allow2-service</artifactId>
<version>2.0.0-alpha.1</version>
</dependency>-
Register your application at developer.allow2.com and note your
clientIdandclientSecret. -
Create an
Allow2Clientand wire up the OAuth2 flow:
import com.allow2.service.Allow2Client;
import com.allow2.service.storage.FileTokenStorage;
import com.allow2.service.cache.MemoryCache;
import com.allow2.service.models.CheckResult;
Allow2Client allow2 = new Allow2Client.Builder("YOUR_SERVICE_TOKEN", "YOUR_SERVICE_SECRET")
.tokenStorage(new FileTokenStorage("/var/lib/allow2/tokens.json"))
.cache(new MemoryCache())
.build();
// Step 1: Redirect user to Allow2 for pairing
String authorizeUrl = allow2.getAuthorizeUrl(
currentUserId,
"https://yourapp.com/allow2/callback",
csrfToken
);
// redirect to authorizeUrl
// Step 2: Handle the callback
allow2.exchangeCode(currentUserId, code, "https://yourapp.com/allow2/callback");
// Step 3: Check permissions on every request
CheckResult result = allow2.check(currentUserId, Arrays.asList(1, 3)); // Internet + Gaming
if (!result.isAllowed()) {
// Show block page
} else {
int remaining = result.getRemainingSeconds(1);
// Proceed normally, optionally showing countdown
}The 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:
String state = UUID.randomUUID().toString();
session.setAttribute("allow2_state", state);
String authorizeUrl = allow2.getAuthorizeUrl(currentUserId, "https://yourapp.com/callback", state);
// redirect to authorizeUrlHandle the OAuth2 callback:
if (!state.equals(session.getAttribute("allow2_state"))) {
throw new SecurityException("Invalid state parameter");
}
OAuthTokens tokens = allow2.exchangeCode(currentUserId, code, "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 -- list of activity IDs (auto-expanded with log: true)
CheckResult result = allow2.check(userId, Arrays.asList(1, 3, 8)); // Internet + Gaming + Screen Time
if (!result.isAllowed()) {
System.out.println("Blocked! Day type: " + result.getTodayDayType().getName());
for (Activity activity : result.getActivities()) {
if (activity.isBanned()) {
System.out.println(activity.getName() + " is banned");
} else if (!activity.isTimeBlockAllowed()) {
System.out.println(activity.getName() + " outside allowed hours");
}
}
} else {
int remaining = result.getRemainingSeconds(1);
// Optionally show countdown in the UI
}// Returns true only if ALL specified activities are allowed
boolean allowed = allow2.isAllowed(userId, Arrays.asList(1, 3));The SDK caches check results internally using your configured CacheInterface. The default TTL is 60 seconds and can be overridden via the builder:
Allow2Client allow2 = new Allow2Client.Builder("YOUR_TOKEN", "YOUR_SECRET")
.tokenStorage(storage)
.cache(cache)
.cacheTtl(30) // cache for 30 seconds
.build();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.
RequestResult request = allow2.requestMoreTime(userId, 3, 30, "Almost done with this level!");
System.out.println("Request ID: " + request.getRequestId());
// Poll for parent response
String status = allow2.getRequestStatus(request.getRequestId(), request.getStatusSecret());
if ("approved".equals(status)) {
System.out.println("Approved!");
} else if ("denied".equals(status)) {
System.out.println("Request denied.");
} else {
System.out.println("Still waiting...");
}RequestResult request = allow2.requestDayTypeChange(userId, 2, "We have a day off school today.");RequestResult request = allow2.requestBanLift(userId, 6, "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.
import com.allow2.service.models.RequestType;
VoiceCodePair pair = allow2.generateVoiceChallenge(
pairingSecret,
RequestType.MORE_TIME,
3, // Gaming
30 // in 5-min increments
);
System.out.println("Challenge code: " + pair.getChallenge());
System.out.println("Read this to your parent. Ask them for the response code.");boolean isValid = allow2.verifyVoiceResponse(pairingSecret, pair.getChallenge(), parentResponseCode);
if (isValid) {
System.out.println("Approved! Extra time granted.");
} else {
System.out.println("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:
import com.allow2.service.models.FeedbackCategory;
// Submit feedback -- returns the discussion ID
String discussionId = allow2.submitFeedback(userId, FeedbackCategory.BUG,
"The block page appears even when I have time remaining.");
// Load feedback threads
List<Map<String, Object>> threads = allow2.loadFeedback(userId);
// Reply to a thread
allow2.replyToFeedback(userId, discussionId, "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 |
|---|---|
Allow2Exception |
Base exception for all SDK errors |
ApiException |
HTTP or API-level errors |
TokenExpiredException |
Token refresh failed (re-pairing needed) |
UnpairedException |
No valid tokens for this user (401/403 from API) |
The SDK persists OAuth2 tokens automatically via the TokenStorageInterface you provide at construction.
import com.allow2.service.storage.MemoryTokenStorage;
TokenStorageInterface tokenStorage = new MemoryTokenStorage();import com.allow2.service.storage.FileTokenStorage;
TokenStorageInterface tokenStorage = new FileTokenStorage("/var/lib/allow2/tokens.json");Implement TokenStorageInterface to integrate with your framework:
import com.allow2.service.TokenStorageInterface;
import com.allow2.service.models.OAuthTokens;
public class JpaTokenStorage implements TokenStorageInterface {
private final TokenRepository repository;
public JpaTokenStorage(TokenRepository repository) {
this.repository = repository;
}
@Override
public void store(String userId, OAuthTokens tokens) {
repository.save(new TokenEntity(userId, tokens.getAccessToken(),
tokens.getRefreshToken(), tokens.getExpiresAt()));
}
@Override
public OAuthTokens retrieve(String userId) {
return repository.findById(userId)
.map(e -> new OAuthTokens(e.getAccessToken(), e.getRefreshToken(), e.getExpiresAt()))
.orElse(null);
}
@Override
public void delete(String userId) {
repository.deleteById(userId);
}
@Override
public boolean exists(String userId) {
return repository.existsById(userId);
}
}Copyright 2017-2026 Allow2 Pty Ltd. All rights reserved.
See LICENSE for details.