Drop-in Java client for the fernan.club REST API. Async-first, JDK 17+, zero dependencies beyond Gson, safe to embed in mods, launchers, or standalone JVM apps.
Unofficial. This project is community tooling. It is not affiliated with, endorsed by, or sponsored by fernan.club. The maintainer receives no compensation from fernan.club, does not operate any part of the upstream service, and does not vouch for the goods or services it delivers. Read the full DISCLAIMER before using.
- One
FernanCliententry point exposing five typed services (user,store,refunds,referrals,health). - All endpoints return
CompletableFuture<T>. - One
FernanExceptionwith a typedErrorTypeenum covering every API error mode (auth, banned, validation, not-found, conflict, cooldown, rate-limit, server error). - Explicit
ReferralChoicetype — the library never silently auto-applies a referral code; the caller decides per call. - Optional
X-Integrationpartner-attribution signal for client devs who want fernan.club to know who's driving traffic.
repositories {
maven { url = uri("https://jitpack.io") }
}
dependencies {
implementation("com.github.trqlmao:club.fernan.api:0.3.0")
}repositories {
maven { url 'https://jitpack.io' }
}
dependencies {
implementation 'com.github.trqlmao:club.fernan.api:0.3.0'
}<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependency>
<groupId>com.github.trqlmao</groupId>
<artifactId>club.fernan.api</artifactId>
<version>0.3.0</version>
</dependency>This library is intentionally structured so AI coding assistants (Claude Code, Copilot, Cursor, Cline, etc.) can wire it into your project in a single pass. If you're driving an agent, point it at:
CLAUDE.md— full integration guide written for AI consumption. Covers patterns, pitfalls, and threading.llms.txt— structured index of every doc and example, per the llmstxt.org convention. Suitable for an agent to ingest as canonical context.examples/— three canonical integration patterns (minimal, alt-manager, error handling).
The surface area an agent has to learn is deliberately small: one entry point
(FernanClient), five typed services, one exception (FernanException) with
a typed ErrorType enum, and an explicit ReferralChoice value type that
prevents an agent (or human) from silently auto-applying a referral code.
import club.fernan.api.FernanClient;
import club.fernan.api.model.referral.ReferralChoice;
FernanClient client = FernanClient.builder()
.apiKey(System.getenv("FERNAN_KEY"))
.userAgent("my-app/1.0")
.integration("my-app")
.build();
client.user().me()
.thenAccept(me -> System.out.println("balance: " + me.balance()));
client.store().purchase(1, 5, ReferralChoice.none())
.thenAccept(this::onPurchase)
.exceptionally(t -> { onFailure(t); return null; });
// ... later, on app exit:
client.shutdown();Don't use
.join()outside CLI/script contexts. It blocks the calling thread (catastrophic on a render thread, Netty event loop, or game tick) and wraps exceptions inCompletionException. Chain with.thenAccept/.exceptionallyinstead. SeeCLAUDE.mdfor the threading model.
| Service | Endpoints |
|---|---|
client.user() |
me, apiKey, regenerateApiKey, redeemKey |
client.store() |
stock, cooldowns, purchase, purchases, purchaseDetail, validateReferral |
client.refunds() |
create, cancel, list, get |
client.referrals() |
create, list, stats, toggle, delete (MediaPlus+) |
client.health() |
get, simple |
See examples/ for self-contained snippets:
MinimalExample.java— smallest possible "build a client, fetch user, print balance".AltManagerExample.java— canonical flow for clients with an alt-storage layer: list stock → prompt for referral → purchase → decode credentials → register with a host-appAltStore.ErrorHandlingExample.java— recovering from rate limits, cooldowns, and insufficient-balance failures.
The library never silently applies a referral code. Every purchase requires an
explicit ReferralChoice:
// Apply a code the user selected:
client.store().purchase(productId, qty, ReferralChoice.of("creator123"));
// User explicitly declined / no preference:
client.store().purchase(productId, qty, ReferralChoice.none());If you surface referrals to end users, prompt them to choose rather than auto-defaulting to your own code.
import club.fernan.api.exception.ErrorType;
import club.fernan.api.exception.FernanException;
client.store().purchase(1, 100, ReferralChoice.none())
.exceptionally(t -> {
FernanException e = (FernanException) t.getCause();
switch (e.type()) {
case INSUFFICIENT_BALANCE -> notifyTopUp();
case COOLDOWN -> scheduleRetry(e.cooldownEndsAt());
case RATE_LIMITED -> backoff(e.retryAfter());
case AUTHENTICATION -> promptRelogin();
default -> log(e);
}
return null;
});import club.fernan.api.locale.FernanLocale;
client.store().stock(FernanLocale.JA).join();Supported: EN, ES, DE, JA, ZH, TW.
Identify your application to fernan.club without claiming referral revenue:
FernanClient.builder()
.apiKey(key)
.integration("my-app")
.build();This sends X-Integration: my-app on every request.
- Next minor — wire
UserService.preferredReferral()/preferredReferral(String)once the upstream endpoints are finalized. - Next minor — reconcile the
X-Integrationheader with whatever final shape the upstream lands on. - Future — pluggable retry policy with exponential backoff for transient 5xx + 429 responses.
See CHANGELOG.md for released changes.
- Java 17 or newer (compiled with
--release 17; verified on 17, 21, and 25). - One transitive dependency: Gson 2.11+.
- Discussions for questions and integration recipes.
- Issues for bugs and feature requests (read CONTRIBUTING.md first).
- Security policy for vulnerability reports.
- Disclaimer — unaffiliated, unendorsed, AS-IS.
MIT.