ofapisv1
quickstart

OnlyFans API with Java

You can call the OnlyFans API from Java with no third-party HTTP library: ofapis is a plain REST API with Bearer auth, so the JDK's built-in java.net.http.HttpClient (Java 11+) is all you need. Every request goes to https://api.ofapis.com/api/public/v1. This quickstart takes you from token to a sent message.

ofapis is an independent, unofficial API for OnlyFans, not affiliated with OnlyFans. Accounts are connected once in the dashboard; the session is kept alive server-side, so your Java code only handles your ofapis_sk_... token.

1. Get a token

Create a key in the dashboard (start free) and read it from an environment variable, never a checked-in constant. See authentication.

2. Setup

java.net.http is part of the JDK, so no dependency is strictly required. For JSON, add a small parser — Jackson is used below:

<!-- Maven pom.xml -->
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.17.1</version>
</dependency>
// Gradle build.gradle
implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1'

3. Verify the token — GET /me

Build one HttpClient and a helper that attaches the Authorization header to every request:

import java.net.URI;
import java.net.http.*;
import com.fasterxml.jackson.databind.*;

public class Ofapis {
    static final String BASE = "https://api.ofapis.com/api/public/v1";
    static final String TOKEN = System.getenv("OFAPIS_KEY");
    static final HttpClient http = HttpClient.newHttpClient();
    static final ObjectMapper json = new ObjectMapper();

    static HttpRequest.Builder req(String path) {
        return HttpRequest.newBuilder(URI.create(BASE + path))
            .header("Authorization", "Bearer " + TOKEN)
            .header("Content-Type", "application/json");
    }

    public static void main(String[] args) throws Exception {
        var res = http.send(req("/me").GET().build(),
                            HttpResponse.BodyHandlers.ofString());
        if (res.statusCode() != 200)
            throw new RuntimeException("GET /me failed: " + res.statusCode() + " " + res.body());
        JsonNode me = json.readTree(res.body());
        System.out.println("connected as " + me.get("username").asText());
    }
}

4. List chats

var res = http.send(req("/chats?limit=50").GET().build(),
                    HttpResponse.BodyHandlers.ofString());
for (JsonNode c : json.readTree(res.body()).path("data"))
    System.out.println(c.get("id").asText());

5. Send a message

var body = HttpRequest.BodyPublishers.ofString("{\"text\":\"Thanks for subscribing!\"}");
var res  = http.send(req("/chats/123/messages").POST(body).build(),
                     HttpResponse.BodyHandlers.ofString());

switch (res.statusCode()) {
    case 200 -> System.out.println("delivered — 1 credit spent");
    case 401 -> throw new IllegalStateException("bad or revoked token");
    case 429 -> System.err.println("rate limited — back off and retry");
    default  -> throw new RuntimeException("send failed: " + res.statusCode() + " " + res.body());
}

A 200 means delivered and one credit spent. Failed calls — an expired session, a 429, or an upstream 5xx — are never charged, so retrying after a failure is free.

Async and timeouts

For throughput, use http.sendAsync(...) which returns a CompletableFuture, and set a timeout on the client: HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build(). Reuse the single HttpClient across threads — it is safe to share and pools connections.

Agencies: scope by account

Managing several creators? Address each by id under scoped routes, e.g. req("/accounts/42/chats").GET().build(). See agency automation.

FAQ

Do I need an SDK to call the OnlyFans API from Java?

No. java.net.http.HttpClient from the JDK plus any JSON library (Jackson, Gson) is enough. You can also generate a typed client from the OpenAPI spec linked in the docs.

How do I handle rate limits in Java?

Check for HTTP 429 and retry after a short backoff. Plan ceilings run from 60 rpm on Free to 500 rpm on Pro — see pricing.

Does a failed request cost a credit?

No. Billing is metered per successful call: one 2xx equals one credit. Timeouts, 429s and upstream 5xx responses are not charged.

Is HttpClient thread-safe?

Yes. A single HttpClient instance is designed to be shared across threads and reuses connections, so build one and inject it rather than creating one per request.

Next steps

Related: C# · Ruby

Related guides

All guides
Start free — 250 credits, no card
Generate a token and make your first call in minutes.
Get started