Java SDK for the Novada API — proxy management, scraping, and wallet endpoints.
Standard library HTTP client (java.net.http.HttpClient) plus Jackson for JSON, Java 17+.
Maven:
<dependency>
<groupId>com.novada</groupId>
<artifactId>novada-java</artifactId>
<version>0.1.0</version>
</dependency>Gradle:
implementation("com.novada:novada-java:0.1.0")import com.novada.NovadaClient;
import com.novada.proxy.Product;
import com.novada.proxy.model.ListWhitelistParams;
import com.novada.proxy.model.WhitelistList;
public class QuickStart {
public static void main(String[] args) {
NovadaClient client = NovadaClient.of("YOUR_API_KEY"); // or "" to read NOVADA_API_KEY
WhitelistList list =
client.proxy().whitelist().list(ListWhitelistParams.builder(Product.RESIDENTIAL).build());
System.out.printf("whitelist total=%d%n", list.total());
}
}Novada serves requests from three hosts. All are configurable and default to production:
| Purpose | Default | Used by |
|---|---|---|
| General | https://api-m.novada.com |
Every /v1/* endpoint (proxy, wallet, and the scraper area/balance/unit queries) |
| Web Unblocker | https://webunlocker.novada.com |
scraper().unblocker().scrape(...), or scraper().doRequest(...) with Target.WEB_UNBLOCKER |
| Scraper API | https://scraper.novada.com |
scraper().doRequest(...) / scraper().api().* with Target.SCRAPER_API |
Only the scrape POST /request calls go to the Web Unblocker / Scraper API hosts; everything else
(/v1/*) uses the general host.
NovadaClient client =
NovadaClient.builder("API_KEY")
.baseUrl("https://api-m.novada.com")
.webUnblockerUrl("https://webunlocker.novada.com")
.scraperUrl("https://scraper.novada.com")
.timeout(Duration.ofSeconds(30))
.maxRetries(2)
.build();The Bearer API key is injected on every request. Retries apply only to network errors and HTTP 429/5xx — never to a non-zero business code.
// Sub-accounts
client.proxy().account().create(
CreateAccountParams.builder()
.product(Product.RESIDENTIAL).account("account11").password("pass11").status(1).build());
client.proxy().account().list(ListAccountParams.builder(Product.RESIDENTIAL).build());
// Whitelist
client.proxy().whitelist().add(new AddWhitelistParams(Product.RESIDENTIAL, "10.10.10.1", "test"));
client.proxy().whitelist().list(ListWhitelistParams.builder(Product.RESIDENTIAL).build());
// Residential areas & traffic
client.proxy().residential().countries();
client.proxy().residential().balance();
client.proxy().residential().consumeLog(
new TimeRange("2025-01-01 00:00:00", "2025-01-31 23:59:59"));
// Static ISP / dedicated datacenter
client.proxy().staticIsp().open(
new OpenStaticIspParams("normal", "hk:1|us-va:2", "week", 3));
client.proxy().dedicatedDc().list(ListStaticParams.builder().build());Sub-services: account(), whitelist(), residential(), mobile(), rotatingIsp(),
rotatingDc(), staticIsp(), dedicatedDc(), unlimited(), prohibitDomain(). Required
parameters are validated client-side and reported as a ValidationException before any request is
sent.
// Strongly typed (auto-selects the Scraper API host)
ScrapeResponse res =
client.scraper().api().youtube().videoPost(
new YouTubeVideoParams("https://www.youtube.com/watch?v=HAwTwmzgNc4"));
// Google Search (SerpApi) — typed params, structured result (data().json() is the raw result array)
GoogleSearchResult gs =
client.scraper().api().google().search(
GoogleSearchParams.builder("apple").country("us").build());
System.out.println(gs.code() + " " + gs.costTime() + " " + gs.data().json());
// Generic driver — any scraper_id, choose the host explicitly
ScrapeResponse raw =
client.scraper().doRequest(
ScrapeRequest.builder()
.target(Target.SCRAPER_API)
.scraperName("youtube.com")
.scraperId("youtube_video-post_explore")
.params(List.of(Map.of("url", "https://www.youtube.com/watch?v=HAwTwmzgNc4")))
.returnErrors(true)
.build());
System.out.println(raw.raw()); // raw scrape result (JSON/CSV/XLSX, depending on scraper)
// Web Unblocker — typed scrape; returns a structured result, not raw text
UnblockerResult unb =
client.scraper().unblocker().scrape(
UnblockerParams.builder("https://www.google.com").country("us").build());
System.out.println(unb.code() + " " + unb.html().length() + " " + unb.useBalance());
// Query endpoints on the general host
client.scraper().universal().balance(); // /v1/capture/get_balance
client.scraper().universal().unit(); // /v1/capture/unit
client.scraper().unblocker().countries(); // /v1/proxy/unblocker_area
client.scraper().browser().countries(); // /v1/proxy/browser_areascraper().doRequest(...) marshals params to JSON, places it in the scraper_params form field,
URL-encodes the body, and routes to the host selected by target. Scrape responses are returned
raw because their format varies by scraper. scraper().unblocker().scrape(...) and
scraper().api().google().search(...) are exceptions: they decode the envelope into a structured
result.
client.wallet().balance();
client.wallet().usageRecord(new UsageRecordParams(1, 20));Management endpoints return a uniform envelope {code, data, msg, timestamp}; only code == 0
is success. A non-zero code or a non-2xx HTTP status becomes an ApiException.
import com.novada.exception.ApiException;
import com.novada.exception.AuthException;
import com.novada.exception.RateLimitException;
import com.novada.exception.ValidationException;
try {
client.proxy().whitelist().list(ListWhitelistParams.builder(Product.RESIDENTIAL).build());
} catch (AuthException e) { // HTTP 401/403
System.err.println("invalid API key");
} catch (RateLimitException e) { // HTTP 429
System.err.println("rate limited");
} catch (ApiException e) { // any other non-zero business code / HTTP error
System.err.println("business error code=" + e.code() + ": " + e.getMessage());
} catch (ValidationException e) { // required parameter missing, caught before the request is sent
System.err.println(e.method() + " missing: " + e.fields());
}Runnable examples live in examples/: proxy,
scraper, wallet.
Set NOVADA_API_KEY and run e.g.:
mvn -q compile
javac -cp target/classes -d target/examples examples/proxy/ProxyExample.java
java -cp target/classes:target/examples com.novada.examples.proxy.ProxyExamplemvn spotless:apply # format (google-java-format)
mvn spotless:check verify # format check + build + testsIntegration tests that hit the live API are gated on the NOVADA_API_KEY environment variable
(skipped via Assumptions.assumeTrue when absent).
The POM ships a release profile that signs artifacts with GPG and publishes via the Central
Publishing Portal. Publishing is autoPublish=true, so once
deploy succeeds the artifact goes public on Maven Central automatically — there is no manual
confirmation step.
.github/workflows/release.yml runs on every published GitHub Release and publishes to Maven
Central for you. To cut a release:
# 1. Bump <version> in pom.xml (e.g. 0.1.0 -> 0.2.0) and commit it to main.
git commit -am "Release 0.2.0"
git push
# 2. Tag and create a GitHub Release from that commit. The tag MUST be v<pom.xml version>.
gh release create v0.2.0 --title v0.2.0 --generate-notesThe workflow checks out the v0.2.0 tag, verifies the tag version matches <version> in
pom.xml (fails fast otherwise), then runs mvn -Prelease clean deploy. Within a few minutes the
new version is live on Maven Central.
One-time repo setup required before the workflow can run — see below.
| Secret | Value |
|---|---|
CENTRAL_TOKEN_USERNAME |
Username half of a Central Portal user token (central.sonatype.com → Account → Generate User Token) |
CENTRAL_TOKEN_PASSWORD |
Password half of the same user token |
GPG_PRIVATE_KEY |
Armored GPG private key: gpg --export-secret-keys --armor <KEY_ID> |
GPG_PASSPHRASE |
Passphrase for that GPG key |
Also required, done once, outside this repo:
- A namespace on Central Portal for
com.novada(orio.github.NovadaLabsifnovada.comDNS ownership isn't verifiable) — Central Portal → Namespaces → Add. - The GPG public key published to a keyserver so Central can verify signatures:
gpg --keyserver keyserver.ubuntu.com --send-keys <KEY_ID>.
mvn -Prelease -B clean deployThis requires, in ~/.m2/settings.xml:
<servers>
<server>
<id>central</id>
<username>YOUR_CENTRAL_PORTAL_TOKEN_USERNAME</username>
<password>YOUR_CENTRAL_PORTAL_TOKEN_PASSWORD</password>
</server>
</servers>and a configured GPG signing key (gpg --gen-key, then either rely on the agent or set
gpg.passphrase via -Dgpg.passphrase=... / settings.xml). The default mvn verify build (no
profile) does not sign or publish anything — sources and javadoc jars are always attached, but
signing/publishing only run under -Prelease.