Skip to content

NovadaLabs/novada-java

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

novada-java

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+.

Install

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")

Quick start

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());
  }
}

The three base URLs

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.

Proxy

// 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.

Scraper

// 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_area

scraper().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.

Wallet

client.wallet().balance();
client.wallet().usageRecord(new UsageRecordParams(1, 20));

Error handling

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());
}

Examples

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.ProxyExample

Development

mvn spotless:apply   # format (google-java-format)
mvn spotless:check verify   # format check + build + tests

Integration tests that hit the live API are gated on the NOVADA_API_KEY environment variable (skipped via Assumptions.assumeTrue when absent).

Publishing to Maven Central

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.

Automated (preferred): GitHub Release

.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-notes

The 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.

GitHub Secrets

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 (or io.github.NovadaLabs if novada.com DNS 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>.

Manual (local) fallback

mvn -Prelease -B clean deploy

This 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.

License

MIT

About

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+.

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors