Java SDK
The official Java client for the FoPost API, on the JDK's own HTTP client.
com.fopost:fopost-java is the official Java client. It needs Java 17 or newer; HTTP goes through the JDK's own client, so the only dependency is Jackson.
<dependency>
<groupId>com.fopost</groupId>
<artifactId>fopost-java</artifactId>
<version>0.1.0</version>
</dependency>implementation("com.fopost:fopost-java:0.1.0")This is a 0.x release. The surface is still settling and a minor version may break something. Pin an exact version if that matters to you.
Published on Maven Central. Source and issues: github.com/fopost/fopost-java. MIT licensed.
Quick start
import com.fopost.sdk.FoPost;
import com.fopost.sdk.model.*;
import com.fopost.sdk.param.*;
FoPost client = FoPost.create("fp_..."); // or set FOPOST_API_KEY
Workspace workspace = client.workspaces().list().get(0);
List<Account> accounts = client.accounts().list(workspace.id());
Post post = client.posts().create(
CreatePostParams.of(workspace.id())
.content("Hello from Java")
.accounts(accounts.stream().map(Account::id).toList()));
client.posts().publish(post.id());publish returns once delivery is queued, not once it is live. Read posts().deliveries() or subscribe to webhooks for the result.
Threads and scheduling
A post is one or more content blocks. One block is a plain update; several make a thread:
client.posts().create(CreatePostParams.of(workspace.id())
.accounts(accountId)
.content("First post in the thread")
.block(ContentBlockInput.text("Second one, with an image")
.media(MediaItem.of("image", "chart.png", "https://.../chart.png"))));status is draft or scheduled, and a scheduled post needs a time. To send something out now, create it and call publish.
client.posts().create(CreatePostParams.of(workspace.id())
.accounts(accountId)
.content("Scheduled with the SDK")
.schedule(Instant.parse("2026-09-01T10:00:00Z")));Before publishing, preflight reports the per-account blockers and advisory signals without sending anything, and publish with dryRun rehearses the whole thing:
PreflightResult check = client.posts().preflight(post.id());
if (!check.isReady()) {
check.accounts().forEach(a -> System.out.println(a.platform() + ": " + a.issues()));
}Pagination
list returns one page and iterates over its items. autoPaginate walks every page for you, fetching each one as you read it:
Page<Post> page = client.posts().list(
PostListParams.create().workspaceId(workspace.id()).status(PostStatus.PUBLISHED).perPage(50));
System.out.println(page.meta().total() + " published posts");
for (Post post : client.posts().autoPaginate(PostListParams.create().workspaceId(workspace.id()))) {
System.out.println(post.id());
}
long failed = client.posts().stream(PostListParams.create().workspaceId(workspace.id()))
.filter(p -> PostStatus.FAILED.equals(p.status()))
.count();Media
Upload once, then attach the returned file to a content block:
UploadedMedia file = client.media().upload(workspace.id(), Path.of("chart.png")).get(0);
client.posts().create(CreatePostParams.of(workspace.id())
.accounts(accountId)
.block(ContentBlockInput.text("Numbers are in").media(file.toMediaItem())));Webhooks
The signing secret is returned by the create call and never shown again, so store it then.
Webhook hook = client.webhooks().create(
workspace.id(),
"https://example.com/hooks/fopost",
List.of(WebhookEvents.POST_PUBLISHED, WebhookEvents.DELIVERY_FAILED));
System.out.println(hook.secret());
client.webhooks().test(hook.id());What is on the client
| Namespace | Methods |
|---|---|
posts() | list, autoPaginate, stream, get, create, update, delete, duplicate, publish, retry, cancel, preflight, deliveries, publishRuns, analytics, bulkShift, bulkLabel, bulkDelete, validateImport, commitImport, rollbackImport |
workspaces() | list, get, create, update, delete, analytics |
accounts() | list, get, create, delete, healthSummary, health, togglePrimary, validate, refreshToken, analytics, communities() |
labels() | list, get, create, update, delete |
webhooks() | list, create, update, delete, test |
analytics() | overview, timeSeries, topPosts, labels, postsTable, postingStreak, demographics, collect |
automations() | list, get, create, update, delete, toggle, runs, run, trigger, stats |
media() | list, upload, delete |
ai() | credits, generateCaption, rewrite, repurposeUrl |
accounts().communities() covers the X communities an account can post into: list, sync, search, add, remove.
For an endpoint the SDK does not wrap yet, request sends an authenticated call and hands back the decoded body:
JsonNode body = client.request("GET", "/v1/analytics/overview", null, Map.of("days", 30));On ai(), only credits and generateCaption work with an API key today.
rewrite and repurposeUrl reach endpoints that require a dashboard session
and will answer 401. Use the composer for those until a later release.
Configuration
FoPost client = FoPost.builder()
.apiKey("fp_...") // or FOPOST_API_KEY
.baseUrl("https://api.fopost.com") // override for a dev server
.timeout(Duration.ofSeconds(30))
.maxRetries(3) // total attempts on a 429
.transport(myTransport) // bring your own HTTP stack
.build();Clients are immutable and safe to share across threads. A 429 is retried automatically, waiting for the interval the API asks for in Retry-After (capped at 60 seconds). maxRetries counts total attempts, so the default of 3 means two retries.
Errors
Every non-2xx response throws FoPostException or one of its subclasses, carrying the API's status, code, and message. They are unchecked, so nothing forces a try you did not want.
| Status | Exception |
|---|---|
| 400, 422 | ValidationException |
| 401 | AuthenticationException |
| 402 | PaymentRequiredException |
| 403 | PermissionDeniedException |
| 404 | NotFoundException |
| 429 | RateLimitException |
| anything else | FoPostException |
try {
client.posts().publish(postId);
} catch (PaymentRequiredException e) {
System.out.println("Upgrade at " + e.upgradeUrl());
} catch (RateLimitException e) {
System.out.println("Rate limited, retry in " + e.retryAfter());
} catch (FoPostException e) {
System.out.println("API " + e.status() + " (" + e.code() + "): " + e.getMessage());
}A 403 where isSubscriptionRequired() is true means the workspace has no active subscription; read endpoints keep working without one.
Next
Related documentation
- SDKs & Integrations
Official FoPost clients for TypeScript, Python, PHP, Ruby, Go, Rust, Java, .NET, Swift, Kotlin, Dart, and Elixir, framework integrations from Laravel to Next.js, and tooling for the CLI, CI, Terraform, and the automation platforms.
- SDKs Overview
Every official FoPost client, what it covers, and how to pick one.
- TypeScript SDK
The official TypeScript and Node.js client for the FoPost API.
- Python SDK
The official Python client for the FoPost API.
- PHP SDK
The official PHP client for the FoPost API, with no framework and no HTTP library.
- Ruby SDK
The official Ruby client for the FoPost API, with no runtime dependencies.
- Go SDK
The official Go client for the FoPost API.
- Rust SDK
The official Rust client for the FoPost API, async and built on reqwest.